Skip to content

Commit c468fe1

Browse files
committed
test: add PeerDetailsModel unit coverage for issue 02
1 parent ec5cf79 commit c468fe1

File tree

5 files changed

+163
-19
lines changed

5 files changed

+163
-19
lines changed

qml/models/peerdetailsmodel.cpp

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,38 @@ PeerDetailsModel::PeerDetailsModel(CNodeCombinedStats* nodeStats, QmlPeerTableMo
2121
connect(parent, &QmlPeerTableModel::dataChanged, this, &PeerDetailsModel::onModelDataChanged);
2222
}
2323

24-
void PeerDetailsModel::onModelRowsRemoved(const QModelIndex& parent, int first, int last)
24+
void PeerDetailsModel::onModelRowsRemoved(const QModelIndex& /* parent */, int first, int last)
2525
{
26-
for (int row = first; row <= last; ++row) {
27-
QModelIndex index = m_model->index(row, 0, parent);
28-
int nodeIdInRow = m_model->data(index, QmlPeerTableModel::NetNodeId).toInt();
29-
if (nodeIdInRow == this->nodeId()) {
30-
if (!m_disconnected) {
31-
m_disconnected = true;
32-
Q_EMIT disconnected();
33-
}
34-
break;
35-
}
26+
if (m_disconnected || m_row < 0) {
27+
return;
28+
}
29+
30+
if (m_row >= first && m_row <= last) {
31+
m_disconnected = true;
32+
Q_EMIT disconnected();
33+
return;
34+
}
35+
36+
if (m_row > last) {
37+
m_row -= (last - first + 1);
3638
}
3739
}
3840

3941
void PeerDetailsModel::onModelDataChanged(const QModelIndex& /* top_left */, const QModelIndex& /* bottom_right */)
4042
{
41-
if (m_model->data(m_model->index(m_row, 0), QmlPeerTableModel::NetNodeId).isNull() ||
42-
m_model->data(m_model->index(m_row, 0), QmlPeerTableModel::NetNodeId).toInt() != nodeId()) {
43-
if (!m_disconnected) {
44-
m_disconnected = true;
45-
Q_EMIT disconnected();
46-
}
43+
if (m_disconnected || m_row < 0) {
44+
return;
45+
}
46+
47+
const QModelIndex row_index = m_model->index(m_row, 0);
48+
const QVariant node_id = m_model->data(row_index, QmlPeerTableModel::NetNodeId);
49+
if (!node_id.isValid() || node_id.toInt() != nodeId()) {
50+
m_disconnected = true;
51+
Q_EMIT disconnected();
4752
return;
4853
}
4954

50-
m_combinedStats = m_model->data(m_model->index(m_row, 0), QmlPeerTableModel::StatsRole).value<CNodeCombinedStats*>();
55+
m_combinedStats = m_model->data(row_index, QmlPeerTableModel::StatsRole).value<CNodeCombinedStats*>();
5156

5257
// Only update when all information is available
5358
if (m_combinedStats && m_combinedStats->fNodeStateStatsAvailable) {

qml/models/peerdetailsmodel.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ private Q_SLOTS:
8585
void onModelDataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right);
8686

8787
private:
88-
int m_row;
88+
int m_row{-1};
8989
CNodeCombinedStats* m_combinedStats;
9090
QmlPeerTableModel* m_model;
9191
bool m_disconnected;

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ add_executable(bitcoinqml_unit_tests
1919
test_sendrecipient.cpp
2020
test_peerstatsutil.cpp
2121
test_peerlistmodel.cpp
22+
test_peerdetailsmodel.cpp
2223
test_peerlistsortproxy.cpp
2324
test_banlistmodel.cpp
2425
test_debuglogactionmodel.cpp

test/test_peerdetailsmodel.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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 <qml/models/peerdetailsmodel.h>
6+
7+
#include <QtTest/QtTest>
8+
9+
#include <util/time.h>
10+
11+
#include <chrono>
12+
#include <memory>
13+
14+
using namespace std::chrono_literals;
15+
16+
class FakePeerActionsForDetails final : public PeerActionsInterface
17+
{
18+
public:
19+
bool getNodesStats(interfaces::Node::NodesStats& stats) override
20+
{
21+
stats = m_stats;
22+
return true;
23+
}
24+
25+
bool disconnectByAddress(const CNetAddr&) override { return false; }
26+
bool disconnectById(NodeId) override { return false; }
27+
bool ban(const CNetAddr&, int64_t) override { return false; }
28+
29+
interfaces::Node::NodesStats m_stats{};
30+
};
31+
32+
class PeerDetailsModelTests : public QObject
33+
{
34+
Q_OBJECT
35+
36+
private Q_SLOTS:
37+
void emitsDataChangedOnlyWhenNodeStateAvailable();
38+
void emitsDisconnectedWhenTrackedPeerRowIsRemoved();
39+
40+
private:
41+
static interfaces::Node::NodesStats BuildPeerStats(NodeId node_id, const std::string& subver, bool state_available)
42+
{
43+
CNodeStats node_stats{};
44+
node_stats.nodeid = node_id;
45+
node_stats.m_addr_name = "127.0.0.1:8333";
46+
node_stats.addrLocal = "127.0.0.1:8334";
47+
node_stats.nVersion = 70016;
48+
node_stats.cleanSubVer = subver;
49+
node_stats.fInbound = false;
50+
node_stats.m_connected = GetTime<std::chrono::seconds>() - 120s;
51+
node_stats.m_last_send = GetTime<std::chrono::seconds>() - 10s;
52+
node_stats.m_last_recv = GetTime<std::chrono::seconds>() - 20s;
53+
node_stats.nSendBytes = 1536;
54+
node_stats.nRecvBytes = 4096;
55+
node_stats.m_conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
56+
node_stats.m_permission_flags = NetPermissionFlags::None;
57+
node_stats.m_last_ping_time = std::chrono::microseconds{42000};
58+
node_stats.m_min_ping_time = std::chrono::microseconds{21000};
59+
60+
CNodeStateStats state_stats{};
61+
state_stats.m_relay_txs = true;
62+
state_stats.m_addr_relay_enabled = true;
63+
state_stats.m_starting_height = 850000;
64+
state_stats.nSyncHeight = 850010;
65+
state_stats.nCommonHeight = 850005;
66+
state_stats.their_services = NODE_NETWORK;
67+
68+
interfaces::Node::NodesStats stats;
69+
stats.emplace_back(node_stats, state_available, state_stats);
70+
return stats;
71+
}
72+
};
73+
74+
void PeerDetailsModelTests::emitsDataChangedOnlyWhenNodeStateAvailable()
75+
{
76+
auto fake_actions = std::make_unique<FakePeerActionsForDetails>();
77+
auto* fake = fake_actions.get();
78+
fake->m_stats = BuildPeerStats(/*node_id=*/42, "/Satoshi:26.1.0/", /*state_available=*/false);
79+
80+
QmlPeerTableModel peer_model{std::move(fake_actions)};
81+
QVERIFY(peer_model.rowCount() == 1);
82+
83+
auto* stats = peer_model.data(peer_model.index(0, 0), QmlPeerTableModel::StatsRole).value<CNodeCombinedStats*>();
84+
QVERIFY(stats != nullptr);
85+
86+
PeerDetailsModel details{stats, &peer_model};
87+
QCOMPARE(details.nodeId(), 42);
88+
QCOMPARE(details.address(), QStringLiteral("127.0.0.1:8333"));
89+
90+
QSignalSpy data_changed_spy{&details, &PeerDetailsModel::dataChanged};
91+
92+
fake->m_stats = BuildPeerStats(/*node_id=*/42, "/Satoshi:27.0.0/", /*state_available=*/false);
93+
peer_model.refresh();
94+
QCOMPARE(data_changed_spy.count(), 0);
95+
96+
fake->m_stats = BuildPeerStats(/*node_id=*/42, "/Satoshi:27.0.0/", /*state_available=*/true);
97+
peer_model.refresh();
98+
99+
QCOMPARE(data_changed_spy.count(), 1);
100+
QCOMPARE(details.userAgent(), QStringLiteral("/Satoshi:27.0.0/"));
101+
}
102+
103+
void PeerDetailsModelTests::emitsDisconnectedWhenTrackedPeerRowIsRemoved()
104+
{
105+
auto fake_actions = std::make_unique<FakePeerActionsForDetails>();
106+
auto* fake = fake_actions.get();
107+
fake->m_stats = BuildPeerStats(/*node_id=*/7, "/Satoshi:26.0.0/", /*state_available=*/true);
108+
109+
QmlPeerTableModel peer_model{std::move(fake_actions)};
110+
QVERIFY(peer_model.rowCount() == 1);
111+
112+
auto* stats = peer_model.data(peer_model.index(0, 0), QmlPeerTableModel::StatsRole).value<CNodeCombinedStats*>();
113+
QVERIFY(stats != nullptr);
114+
115+
PeerDetailsModel details{stats, &peer_model};
116+
QSignalSpy disconnected_spy{&details, &PeerDetailsModel::disconnected};
117+
118+
fake->m_stats.clear();
119+
peer_model.refresh();
120+
121+
QCOMPARE(disconnected_spy.count(), 1);
122+
123+
peer_model.refresh();
124+
QCOMPARE(disconnected_spy.count(), 1);
125+
}
126+
127+
int RunPeerDetailsModelTests(int argc, char* argv[])
128+
{
129+
PeerDetailsModelTests tests;
130+
return QTest::qExec(&tests, argc, argv);
131+
}
132+
133+
#ifndef BITCOINQML_NO_TEST_MAIN
134+
QTEST_MAIN(PeerDetailsModelTests)
135+
#endif
136+
#include "test_peerdetailsmodel.moc"

test/test_unit_tests_main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ int RunQRImageProviderTests(int argc, char* argv[]);
1818
int RunQmlBitcoinUnitsTests(int argc, char* argv[]);
1919
int RunPeerStatsUtilTests(int argc, char* argv[]);
2020
int RunPeerListModelTests(int argc, char* argv[]);
21+
int RunPeerDetailsModelTests(int argc, char* argv[]);
2122
int RunPeerListSortProxyTests(int argc, char* argv[]);
2223
int RunBanListModelTests(int argc, char* argv[]);
2324
int RunDebugLogActionModelTests(int argc, char* argv[]);
@@ -55,6 +56,7 @@ int main(int argc, char* argv[])
5556
status |= RunQmlBitcoinUnitsTests(argc, argv);
5657
status |= RunPeerStatsUtilTests(argc, argv);
5758
status |= RunPeerListModelTests(argc, argv);
59+
status |= RunPeerDetailsModelTests(argc, argv);
5860
status |= RunPeerListSortProxyTests(argc, argv);
5961
status |= RunBanListModelTests(argc, argv);
6062
status |= RunDebugLogActionModelTests(argc, argv);

0 commit comments

Comments
 (0)