Skip to content

Commit ff0e416

Browse files
committed
qml, tests: decouple BanListModel from qt BantableModel
1 parent a4afbe0 commit ff0e416

5 files changed

Lines changed: 196 additions & 5 deletions

File tree

qml/models/banlistmodel.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ int BanListModel::rowCount(const QModelIndex& parent) const
2424
QVariant BanListModel::data(const QModelIndex& index, int role) const
2525
{
2626
if (!index.isValid() || index.row() >= m_ban_list.size()) return {};
27-
const CCombinedBan& entry = m_ban_list.at(index.row());
27+
const BanListEntry& entry = m_ban_list.at(index.row());
2828
switch (static_cast<BanRoles>(role)) {
2929
case BanRoles::AddressRole:
3030
return QString::fromStdString(entry.subnet.ToString());
3131
case BanRoles::BanUntilRole: {
32-
QDateTime dt = QDateTime::fromSecsSinceEpoch(entry.banEntry.nBanUntil);
32+
QDateTime dt = QDateTime::fromSecsSinceEpoch(entry.ban_entry.nBanUntil);
3333
return QLocale::system().toString(dt, QStringLiteral("MMMM d, yyyy h:mm AP"));
3434
}
3535
}

qml/models/banlistmodel.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@
55
#ifndef BITCOIN_QML_MODELS_BANLISTMODEL_H
66
#define BITCOIN_QML_MODELS_BANLISTMODEL_H
77

8-
#include <qt/bantablemodel.h>
8+
#include <net_types.h>
99

1010
#include <QAbstractListModel>
1111
#include <QList>
1212

1313
namespace interfaces { class Node; }
1414

15+
struct BanListEntry
16+
{
17+
CSubNet subnet;
18+
CBanEntry ban_entry;
19+
};
20+
1521
class BanListModel : public QAbstractListModel
1622
{
1723
Q_OBJECT
@@ -41,7 +47,7 @@ public Q_SLOTS:
4147

4248
private:
4349
interfaces::Node& m_node;
44-
QList<CCombinedBan> m_ban_list;
50+
QList<BanListEntry> m_ban_list;
4551
};
4652

4753
#endif // BITCOIN_QML_MODELS_BANLISTMODEL_H

test/CMakeLists.txt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,34 @@ cmake_minimum_required(VERSION 3.22)
33
# Tests for the QML/Qt6 app
44

55
find_package(Qt 6.2 MODULE REQUIRED COMPONENTS Core Test Qml Quick QuickTest)
6+
find_package(GTest QUIET)
67

7-
add_executable(bitcoinqml_unit_tests
8+
set(bitcoinqml_unit_tests_sources
89
test_unit_tests_main.cpp
910
test_bitcoinamount.cpp
1011
test_qmlbitcoinunits.cpp
1112
${CMAKE_CURRENT_SOURCE_DIR}/../qml/bitcoinamount.cpp
1213
${CMAKE_CURRENT_SOURCE_DIR}/../qml/bitcoinunits.cpp
1314
)
1415

16+
if(TARGET GTest::gmock)
17+
list(APPEND bitcoinqml_unit_tests_sources
18+
test_banlistmodel.cpp
19+
${CMAKE_CURRENT_SOURCE_DIR}/../qml/models/banlistmodel.cpp
20+
)
21+
endif()
22+
23+
add_executable(bitcoinqml_unit_tests ${bitcoinqml_unit_tests_sources})
24+
1525
target_compile_definitions(bitcoinqml_unit_tests
1626
PRIVATE
1727
BITCOINQML_NO_TEST_MAIN
1828
)
1929

30+
if(TARGET GTest::gmock)
31+
target_compile_definitions(bitcoinqml_unit_tests PRIVATE BITCOINQML_HAVE_GMOCK)
32+
endif()
33+
2034
target_include_directories(bitcoinqml_unit_tests
2135
PRIVATE
2236
${CMAKE_CURRENT_SOURCE_DIR}/..
@@ -29,6 +43,10 @@ target_link_libraries(bitcoinqml_unit_tests
2943
Qt6::Test
3044
)
3145

46+
if(TARGET GTest::gmock)
47+
target_link_libraries(bitcoinqml_unit_tests PRIVATE GTest::gmock)
48+
endif()
49+
3250
add_test(NAME bitcoinqml_unit_tests COMMAND bitcoinqml_unit_tests)
3351

3452
add_executable(bitcoinqml_qmltests

test/test_banlistmodel.cpp

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (c) 2026 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <QtTest/QtTest>
6+
7+
#include <QSet>
8+
9+
#include <qml/models/banlistmodel.h>
10+
#include <test/mocks/mocknode.h>
11+
#include <util/translation.h>
12+
13+
#include <gmock/gmock.h>
14+
#include <netbase.h>
15+
16+
#include <map>
17+
#include <optional>
18+
#include <stdexcept>
19+
#include <utility>
20+
21+
const TranslateFn G_TRANSLATION_FUN{nullptr};
22+
23+
namespace {
24+
CSubNet ParseSubnet(const std::string& subnet)
25+
{
26+
const std::optional<CSubNet> parsed = LookupSubNet(subnet, /*allow_lookup=*/false);
27+
if (!parsed.has_value()) {
28+
throw std::runtime_error("failed to parse subnet test fixture");
29+
}
30+
return *parsed;
31+
}
32+
33+
CBanEntry MakeBanEntry(int64_t ban_until)
34+
{
35+
CBanEntry entry;
36+
entry.nBanUntil = ban_until;
37+
return entry;
38+
}
39+
} // namespace
40+
41+
class BanListModelTests : public QObject
42+
{
43+
Q_OBJECT
44+
45+
private Q_SLOTS:
46+
void refreshPopulatesRolesAndRows();
47+
void unbanAtTargetsSelectedSubnet();
48+
void unbanAtIgnoresInvalidRows();
49+
};
50+
51+
void BanListModelTests::refreshPopulatesRolesAndRows()
52+
{
53+
using ::testing::_;
54+
using ::testing::DoAll;
55+
using ::testing::NiceMock;
56+
using ::testing::Return;
57+
using ::testing::SetArgReferee;
58+
59+
banmap_t banned;
60+
const CSubNet subnet_a = ParseSubnet("10.0.0.0/8");
61+
const CSubNet subnet_b = ParseSubnet("127.0.0.1/32");
62+
banned.emplace(subnet_a, MakeBanEntry(1'900'000'000));
63+
banned.emplace(subnet_b, MakeBanEntry(2'000'000'000));
64+
65+
NiceMock<MockNode> node;
66+
EXPECT_CALL(node, getBanned(_))
67+
.Times(1)
68+
.WillOnce(DoAll(SetArgReferee<0>(banned), Return(true)));
69+
70+
BanListModel model{node, nullptr};
71+
QSignalSpy count_spy(&model, &BanListModel::countChanged);
72+
73+
model.refresh();
74+
75+
QCOMPARE(model.count(), 2);
76+
QCOMPARE(model.rowCount(), 2);
77+
QCOMPARE(model.rowCount(model.index(0, 0)), 0);
78+
QCOMPARE(count_spy.count(), 1);
79+
80+
const auto roles = model.roleNames();
81+
QCOMPARE(roles.value(static_cast<int>(BanListModel::BanRoles::AddressRole)), QByteArray{"address"});
82+
QCOMPARE(roles.value(static_cast<int>(BanListModel::BanRoles::BanUntilRole)), QByteArray{"banUntil"});
83+
84+
QSet<QString> addresses;
85+
for (int row = 0; row < model.rowCount(); ++row) {
86+
const QModelIndex index = model.index(row, 0);
87+
QVERIFY(index.isValid());
88+
89+
const QString address = model.data(index, static_cast<int>(BanListModel::BanRoles::AddressRole)).toString();
90+
QVERIFY(!address.isEmpty());
91+
addresses.insert(address);
92+
93+
const QString ban_until = model.data(index, static_cast<int>(BanListModel::BanRoles::BanUntilRole)).toString();
94+
QVERIFY(!ban_until.isEmpty());
95+
}
96+
97+
QVERIFY(addresses.contains(QString::fromStdString(subnet_a.ToString())));
98+
QVERIFY(addresses.contains(QString::fromStdString(subnet_b.ToString())));
99+
}
100+
101+
void BanListModelTests::unbanAtTargetsSelectedSubnet()
102+
{
103+
using ::testing::_;
104+
using ::testing::DoAll;
105+
using ::testing::NiceMock;
106+
using ::testing::Return;
107+
using ::testing::SetArgReferee;
108+
using ::testing::Truly;
109+
110+
banmap_t banned;
111+
const CSubNet subnet = ParseSubnet("10.0.0.0/8");
112+
banned.emplace(subnet, MakeBanEntry(1'900'000'000));
113+
114+
NiceMock<MockNode> node;
115+
EXPECT_CALL(node, getBanned(_))
116+
.Times(1)
117+
.WillOnce(DoAll(SetArgReferee<0>(banned), Return(true)));
118+
119+
BanListModel model{node, nullptr};
120+
model.refresh();
121+
122+
EXPECT_CALL(node, unban(Truly([&](const CSubNet& value) {
123+
return value.ToString() == subnet.ToString();
124+
}))).Times(1).WillOnce(Return(true));
125+
model.unbanAt(0);
126+
}
127+
128+
void BanListModelTests::unbanAtIgnoresInvalidRows()
129+
{
130+
using ::testing::_;
131+
using ::testing::DoAll;
132+
using ::testing::NiceMock;
133+
using ::testing::Return;
134+
using ::testing::SetArgReferee;
135+
136+
banmap_t banned;
137+
banned.emplace(ParseSubnet("10.0.0.0/8"), MakeBanEntry(1'900'000'000));
138+
139+
NiceMock<MockNode> node;
140+
EXPECT_CALL(node, getBanned(_))
141+
.Times(1)
142+
.WillOnce(DoAll(SetArgReferee<0>(banned), Return(true)));
143+
144+
BanListModel model{node, nullptr};
145+
model.refresh();
146+
147+
EXPECT_CALL(node, unban(_)).Times(0);
148+
model.unbanAt(-1);
149+
model.unbanAt(42);
150+
}
151+
152+
int RunBanListModelTests(int argc, char* argv[])
153+
{
154+
BanListModelTests tests;
155+
return QTest::qExec(&tests, argc, argv);
156+
}
157+
158+
#ifndef BITCOINQML_NO_TEST_MAIN
159+
QTEST_MAIN(BanListModelTests)
160+
#endif
161+
#include "test_banlistmodel.moc"

test/test_unit_tests_main.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
int RunBitcoinAmountTests(int argc, char* argv[]);
88
int RunQmlBitcoinUnitsTests(int argc, char* argv[]);
9+
#ifdef BITCOINQML_HAVE_GMOCK
10+
int RunBanListModelTests(int argc, char* argv[]);
11+
#endif
912

1013
int main(int argc, char* argv[])
1114
{
@@ -14,6 +17,9 @@ int main(int argc, char* argv[])
1417
int status = 0;
1518
status |= RunBitcoinAmountTests(argc, argv);
1619
status |= RunQmlBitcoinUnitsTests(argc, argv);
20+
#ifdef BITCOINQML_HAVE_GMOCK
21+
status |= RunBanListModelTests(argc, argv);
22+
#endif
1723

1824
return status;
1925
}

0 commit comments

Comments
 (0)