Skip to content

Commit 8b702b0

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

6 files changed

Lines changed: 200 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: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@
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>
9+
#include <netaddress.h>
910

1011
#include <QAbstractListModel>
1112
#include <QList>
1213

1314
namespace interfaces { class Node; }
1415

16+
struct BanListEntry
17+
{
18+
CSubNet subnet;
19+
CBanEntry ban_entry;
20+
};
21+
1522
class BanListModel : public QAbstractListModel
1623
{
1724
Q_OBJECT
@@ -41,7 +48,7 @@ public Q_SLOTS:
4148

4249
private:
4350
interfaces::Node& m_node;
44-
QList<CCombinedBan> m_ban_list;
51+
QList<BanListEntry> m_ban_list;
4552
};
4653

4754
#endif // BITCOIN_QML_MODELS_BANLISTMODEL_H

test/CMakeLists.txt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,39 @@ 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}/..
2337
${CMAKE_CURRENT_SOURCE_DIR}/../bitcoin/src
38+
${CMAKE_CURRENT_SOURCE_DIR}/../bitcoin/src/univalue/include
2439
)
2540

2641
target_link_libraries(bitcoinqml_unit_tests
@@ -29,6 +44,13 @@ target_link_libraries(bitcoinqml_unit_tests
2944
Qt6::Test
3045
)
3146

47+
if(TARGET GTest::gmock)
48+
target_link_libraries(bitcoinqml_unit_tests PRIVATE GTest::gmock)
49+
if(TARGET bitcoin_common)
50+
target_link_libraries(bitcoinqml_unit_tests PRIVATE bitcoin_common)
51+
endif()
52+
endif()
53+
3254
add_test(NAME bitcoinqml_unit_tests COMMAND bitcoinqml_unit_tests)
3355

3456
add_executable(bitcoinqml_qmltests

test/mocks/mocknode.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_QML_TEST_MOCKS_MOCKNODE_H
77

88
#include <gmock/gmock.h>
9+
#include <net_processing.h>
910
#include <interfaces/handler.h>
1011
#include <interfaces/node.h>
1112

test/test_banlistmodel.cpp

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