368 lines
20 KiB
C++
368 lines
20 KiB
C++
/* Настоящие Qt-карточка и модель; очередь ядра задерживает ответы для проверки смены выбора. */
|
|
#include "../src/accountlist.h"
|
|
#include "../src/memberlistmodel.h"
|
|
#include "../transport/gui_bridge.h"
|
|
#include "../db/db_manager.h"
|
|
#include <QtTest>
|
|
#include <QListView>
|
|
#include <QAbstractItemModelTester>
|
|
#include <QMenu>
|
|
#include <QScopeGuard>
|
|
#include <memory>
|
|
#include <functional>
|
|
#include <vector>
|
|
extern "C" {
|
|
#include "utun_instance.h"
|
|
#include "chat/chat_core.h"
|
|
#include "chat/chat_member.h"
|
|
#include "member_files/protocol.h"
|
|
#include "debug_config.h"
|
|
#include "mem.h"
|
|
}
|
|
|
|
static UTUN_INSTANCE instance{};
|
|
static std::vector<chat_member_detail_req> requests;
|
|
static gui_member_detail_fn detailCallback;
|
|
static bool ready = true;
|
|
static bool rejectOwned = false;
|
|
static int ownedPosts = 0;
|
|
static std::vector<chat_member_list_req> memberRequests;
|
|
static std::vector<chat_connect_node_req> connectRequests;
|
|
|
|
extern "C" UTUN_INSTANCE* gui_bridge_get_inst() { return &instance; }
|
|
extern "C" int gui_bridge_is_uasync_ready() { return ready; }
|
|
extern "C" void gui_bridge_set_member_detail_cb(gui_member_detail_fn cb) { detailCallback = cb; }
|
|
extern "C" void gui_bridge_set_radio_mute_result_cb(gui_dm_messages_fn) {}
|
|
extern "C" int gui_bridge_toggle_member_radio_mute(const char*, uint64_t) { return 0; }
|
|
extern "C" void gui_bridge_post_uasync_fn(void (*fn)(void*), void* arg) {
|
|
if (fn == chat_core_collect_member_detail_trampoline) requests.push_back(*static_cast<chat_member_detail_req*>(arg));
|
|
if (fn == chat_core_request_member_list_trampoline || fn == chat_core_request_member_rtt_trampoline)
|
|
memberRequests.push_back(*static_cast<chat_member_list_req*>(arg));
|
|
if (fn == chat_core_connect_node_trampoline) connectRequests.push_back(*static_cast<chat_connect_node_req*>(arg));
|
|
u_free(arg);
|
|
}
|
|
extern "C" int gui_bridge_post_uasync_owned_fn(void (*fn)(void*), void* arg) {
|
|
++ownedPosts;
|
|
if (rejectOwned) return -1;
|
|
gui_bridge_post_uasync_fn(fn, arg);
|
|
return 0;
|
|
}
|
|
|
|
static QByteArray row(quint64 node, bool online = true, int up = 5, int rtt = 30) {
|
|
chat_member_display member{};
|
|
member.node_id = node; member.online = online; member.conn_presence = 5; member.conn_up = up;
|
|
member.rtt = rtt; member.nat_type = 0xff; member.flags = CHAT_MEMBER_FLAG_PHONE_KNOWN;
|
|
snprintf(member.name, sizeof(member.name), "peer%llu", (unsigned long long)node);
|
|
QByteArray bytes(CHAT_MEMBER_DISPLAY_SIZE, 0);
|
|
chat_core_serialize_member(&member, reinterpret_cast<uint8_t*>(bytes.data()));
|
|
return bytes;
|
|
}
|
|
|
|
static QByteArray reply(const chat_member_detail_req& req, const QList<quint64>& hops = {}, int result = 0) {
|
|
QByteArray bytes(CHAT_MEMBER_DETAIL_HEADER_SIZE + hops.size() * 8, 0);
|
|
auto* data = reinterpret_cast<uint8_t*>(bytes.data());
|
|
mf_put64(data, req.request_id); mf_put64(data + 8, req.group_id); mf_put64(data + 16, req.node_id);
|
|
data[24] = result; data[25] = result ? 0 : 7; data[28] = hops.size();
|
|
for (int i = 0; i < hops.size(); i++) mf_put64(data + CHAT_MEMBER_DETAIL_HEADER_SIZE + i * 8, hops[i]);
|
|
return bytes;
|
|
}
|
|
|
|
static void deliver(const QByteArray& bytes) {
|
|
if (detailCallback) detailCallback(reinterpret_cast<const uint8_t*>(bytes.constData()), bytes.size());
|
|
}
|
|
|
|
static void fill(AccountList& panel, const QString& channel, const QByteArray& bytes) {
|
|
panel.setChannel(channel);
|
|
panel.onMemberList(channel, bytes.size() / CHAT_MEMBER_DISPLAY_SIZE,
|
|
reinterpret_cast<const uint8_t*>(bytes.constData()), bytes.size());
|
|
}
|
|
|
|
class MemberDetailTest : public QObject {
|
|
Q_OBJECT
|
|
static QString body(AccountList& p) { return p.findChild<QTextEdit*>("memberDetailText")->toPlainText(); }
|
|
static QString header(AccountList& p) { return p.findChild<QLabel*>("memberDetailHeader")->text(); }
|
|
static bool select(AccountList& p, int rowNumber) {
|
|
return QMetaObject::invokeMethod(&p, "onMemberClicked", Qt::DirectConnection, Q_ARG(QModelIndex, p.model()->index(rowNumber)));
|
|
}
|
|
static bool context(AccountList& panel, const QString& actionText, const std::function<void()>& change = {}) {
|
|
const QPoint point = panel.findChild<QListView*>()->visualRect(panel.model()->index(0)).center();
|
|
bool clicked = false;
|
|
QTimer choose, deadline; choose.setSingleShot(true); deadline.setSingleShot(true);
|
|
QObject::connect(&deadline, &QTimer::timeout, &deadline, [] {
|
|
if (auto* menu = qobject_cast<QMenu*>(QApplication::activePopupWidget())) menu->close();
|
|
});
|
|
QObject::connect(&choose, &QTimer::timeout, &choose, [&] {
|
|
auto* menu = qobject_cast<QMenu*>(QApplication::activePopupWidget());
|
|
if (!menu) { QTest::qFail("member context menu was not shown", __FILE__, __LINE__); return; }
|
|
if (change) change();
|
|
if (!menu->isVisible()) return;
|
|
for (auto* action : menu->actions()) if (action->text() == actionText) {
|
|
if (!action->isEnabled()) { menu->close(); return; }
|
|
clicked = true;
|
|
QTest::mouseClick(menu, Qt::LeftButton, Qt::NoModifier, menu->actionGeometry(action).center());
|
|
return;
|
|
}
|
|
menu->close(); QTest::qFail("member context action was not found", __FILE__, __LINE__);
|
|
});
|
|
choose.start(0); deadline.start(1000);
|
|
const bool invoked = QMetaObject::invokeMethod(&panel, "onMemberContextMenu", Qt::DirectConnection, Q_ARG(QPoint, point));
|
|
return invoked && clicked;
|
|
}
|
|
private slots:
|
|
void init() {
|
|
requests.clear(); memberRequests.clear(); connectRequests.clear(); ownedPosts = 0; rejectOwned = false; ready = true;
|
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
|
|
}
|
|
|
|
void flatModelPreservesRowsSnapshotsAndPersistentIndexes() {
|
|
MemberListModel model;
|
|
QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::QtTest);
|
|
model.setChannel("123", false);
|
|
auto bytes = row(7) + row(8) + row(9);
|
|
model.onMemberList("123", 3, reinterpret_cast<const uint8_t*>(bytes.constData()), bytes.size());
|
|
QCOMPARE(model.rowCount(model.index(0)), 0);
|
|
const QPersistentModelIndex last = model.index(2);
|
|
const auto snapshot = model.member(9); QVERIFY(snapshot);
|
|
model.onMemberRemoved("123", 7);
|
|
QCOMPARE(last.row(), 1); QCOMPARE(model.nodeIdAt(last.row()), quint64(9));
|
|
const auto changed = row(9, false, 0, 0xffff);
|
|
model.onMemberUpdated("123", reinterpret_cast<const uint8_t*>(changed.constData()));
|
|
QVERIFY(snapshot->online); QVERIFY(!model.member(9)->online);
|
|
const auto added = row(10);
|
|
model.onMemberUpdated("123", reinterpret_cast<const uint8_t*>(added.constData()));
|
|
QCOMPARE(model.rowCount(), 3); QCOMPARE(model.nodeIdAt(2), quint64(10));
|
|
QVERIFY(!model.member(7)); QCOMPARE(model.nodeIdAt(-1), quint64(0));
|
|
}
|
|
|
|
void snapshotChangedSeesNewChannel() {
|
|
MemberListModel model; model.setChannel("123", false);
|
|
memberRequests.clear();
|
|
connect(&model, &MemberListModel::snapshotChanged, &model, [&] { model.refresh(); });
|
|
model.setChannel("456", false);
|
|
QCOMPARE(memberRequests.size(), size_t(2));
|
|
for (const auto& request : memberRequests) QCOMPARE(QString::fromUtf8(request.ch_id), QString("456"));
|
|
}
|
|
|
|
void losingOwnerPolicyHidesDeletedRowsWithoutCore() {
|
|
MemberListModel model; model.setChannel("123", true);
|
|
auto bytes = row(7) + row(8);
|
|
bytes[CHAT_MEMBER_DISPLAY_SIZE + 10] = CHAT_MEMBER_FLAG_DELETED;
|
|
model.onMemberList("123", 2, reinterpret_cast<const uint8_t*>(bytes.constData()), bytes.size());
|
|
QCOMPARE(model.rowCount(), 2);
|
|
ready = false; model.setChannel("123", false);
|
|
QCOMPARE(model.rowCount(), 1); QVERIFY(!model.member(8));
|
|
}
|
|
|
|
void malformedUpdatesKeepExistingSnapshot() {
|
|
MemberListModel model; model.setChannel("123", false);
|
|
const auto bytes = row(7);
|
|
model.onMemberList("123", 1, reinterpret_cast<const uint8_t*>(bytes.constData()), bytes.size());
|
|
model.onMemberList("123", 2, reinterpret_cast<const uint8_t*>(bytes.constData()), bytes.size());
|
|
model.onMemberList("123", -1, nullptr, -1);
|
|
model.onMemberUpdated("123", nullptr);
|
|
QCOMPARE(model.rowCount(), 1); QCOMPARE(model.nodeIdAt(0), quint64(7));
|
|
}
|
|
|
|
void rejectedMemberRequestsReleaseArguments() {
|
|
const auto before = u_get_allocated_count();
|
|
rejectOwned = true;
|
|
MemberListModel model; model.setChannel("123", false);
|
|
model.refresh(); model.refreshRtt();
|
|
QCOMPARE(ownedPosts, 3);
|
|
QCOMPARE(u_get_allocated_count(), before);
|
|
}
|
|
|
|
void invalidChannelDoesNotReplaceVisibleSnapshotOrSendRequests() {
|
|
MemberListModel model; model.setChannel("123", false);
|
|
const auto bytes = row(7);
|
|
model.onMemberList("123", 1, reinterpret_cast<const uint8_t*>(bytes.constData()), bytes.size());
|
|
memberRequests.clear();
|
|
model.setChannel(QString(64, '1'), false);
|
|
model.setChannel(QString::fromUtf8("123\0suffix", 10), false);
|
|
QCOMPARE(model.rowCount(), 1); QCOMPARE(model.nodeIdAt(0), quint64(7));
|
|
QVERIFY(memberRequests.empty());
|
|
model.refresh(); QCOMPARE(memberRequests.size(), size_t(1));
|
|
QCOMPARE(QString::fromUtf8(memberRequests.back().ch_id), QString("123"));
|
|
}
|
|
|
|
void twoPanelsReceiveTheirRepliesAndSurviveOtherPanelDestruction() {
|
|
AccountList first; first.show(); fill(first, "123", row(7)); QVERIFY(select(first, 0));
|
|
const auto firstRequest = requests.back();
|
|
auto second = std::make_unique<AccountList>(); second->show(); fill(*second, "456", row(8)); QVERIFY(select(*second, 0));
|
|
const auto secondRequest = requests.back();
|
|
deliver(reply(firstRequest, {7, 111})); QVERIFY(body(first).contains("000000000000006f"));
|
|
deliver(reply(secondRequest, {8, 222})); QVERIFY(body(*second).contains("00000000000000de"));
|
|
second.reset();
|
|
QVERIFY(QMetaObject::invokeMethod(&first, "onDetailTimerTick"));
|
|
deliver(reply(requests.back(), {7, 333})); QVERIFY(body(first).contains("000000000000014d"));
|
|
}
|
|
|
|
void rejectedDetailRequestCanRetryImmediately() {
|
|
AccountList panel; panel.show(); fill(panel, "123", row(7));
|
|
rejectOwned = true; QVERIFY(select(panel, 0)); QVERIFY(requests.empty());
|
|
rejectOwned = false; QVERIFY(QMetaObject::invokeMethod(&panel, "onDetailTimerTick"));
|
|
QCOMPARE(requests.size(), size_t(1));
|
|
deliver(reply(requests.back(), {7, 8})); QVERIFY(body(panel).contains("BGP:"));
|
|
}
|
|
|
|
void detailEscapesSocketNamesAndFormatsIpv6Ports() {
|
|
AccountList panel; panel.show(); fill(panel, "123", row(7)); QVERIFY(select(panel, 0));
|
|
auto bytes = reply(requests.back()); bytes.resize(CHAT_MEMBER_DETAIL_HEADER_SIZE + CHAT_MEMBER_DETAIL_LINK_SIZE);
|
|
auto* data = reinterpret_cast<uint8_t*>(bytes.data()); data[29] = 1;
|
|
auto* link = data + CHAT_MEMBER_DETAIL_HEADER_SIZE; memset(link, 0, CHAT_MEMBER_DETAIL_LINK_SIZE);
|
|
link[0] = 1; link[1] = 3; link[2] = 1; link[3] = 1;
|
|
link[5] = 6; link[21] = 1; link[22] = 0x35; link[23] = 5;
|
|
link[24] = 6; link[40] = 2; link[41] = 0xbb; link[42] = 1;
|
|
strcpy(reinterpret_cast<char*>(link + 43), "<sock&>");
|
|
deliver(bytes);
|
|
QVERIFY(body(panel).contains("[::1]:1333")); QVERIFY(body(panel).contains("[::2]:443"));
|
|
QVERIFY(body(panel).contains("<sock&>"));
|
|
}
|
|
|
|
void contextWithoutDatabaseUsesSnapshotAndOwnsConnectRequest() {
|
|
AccountList panel; panel.resize(300, 600); panel.show(); fill(panel, "123", row(7));
|
|
QSignalSpy calls(&panel, &AccountList::callRequested);
|
|
QVERIFY(context(panel, "Call peer")); QCOMPARE(calls.size(), 1);
|
|
QCOMPARE(calls.first()[0].toString(), QString("123")); QCOMPARE(calls.first()[1].toULongLong(), quint64(7));
|
|
QVERIFY(!context(panel, "Rename")); QVERIFY(!context(panel, "Edit"));
|
|
const auto before = u_get_allocated_count();
|
|
rejectOwned = true; QVERIFY(context(panel, "Connect")); QCOMPARE(u_get_allocated_count(), before);
|
|
QVERIFY(connectRequests.empty());
|
|
rejectOwned = false; QVERIFY(context(panel, "Connect")); QCOMPARE(connectRequests.size(), size_t(1));
|
|
QCOMPARE(QString::fromUtf8(connectRequests.back().ch_id), QString("123")); QCOMPARE(connectRequests.back().node_id, uint64_t(7));
|
|
}
|
|
|
|
void contextRevalidatesChannelMemberAndOnlineState() {
|
|
AccountList panel; panel.resize(300, 600); panel.show(); fill(panel, "123", row(7));
|
|
QSignalSpy calls(&panel, &AccountList::callRequested);
|
|
QVERIFY(context(panel, "Call peer", [&] {
|
|
const auto offline = row(7, false);
|
|
panel.onMemberUpdated("123", reinterpret_cast<const uint8_t*>(offline.constData()));
|
|
}));
|
|
QCOMPARE(calls.size(), 0);
|
|
fill(panel, "123", row(7));
|
|
QVERIFY(context(panel, "Call peer", [&] { fill(panel, "456", row(8)); }));
|
|
QCOMPARE(calls.size(), 0);
|
|
fill(panel, "123", row(7));
|
|
QVERIFY(context(panel, "Call peer", [&] { panel.onMemberRemoved("123", 7); }));
|
|
QCOMPARE(calls.size(), 0);
|
|
fill(panel, "123", row(7) + row(8));
|
|
QVERIFY(context(panel, "Call peer", [&] { fill(panel, "123", row(8) + row(7)); }));
|
|
QCOMPARE(calls.size(), 1); QCOMPARE(calls.first()[1].toULongLong(), quint64(7));
|
|
}
|
|
|
|
void destroyingPanelDuringContextMenuIsSafe() {
|
|
auto panel = std::make_unique<AccountList>(); panel->show(); fill(*panel, "123", row(7));
|
|
QPointer<AccountList> weak(panel.get());
|
|
QVERIFY(!context(*panel, "Call peer", [&] { panel.reset(); }));
|
|
QVERIFY(weak.isNull());
|
|
}
|
|
|
|
void destroyingBorrowedDatabaseDisablesEditing() {
|
|
auto database = std::make_unique<DbManager>();
|
|
AccountList panel(database.get()); panel.show(); fill(panel, "123", row(7));
|
|
database.reset();
|
|
QVERIFY(QMetaObject::invokeMethod(&panel, "onMemberDoubleClicked", Qt::DirectConnection,
|
|
Q_ARG(QModelIndex, panel.model()->index(0))));
|
|
QVERIFY(!context(panel, "Edit"));
|
|
}
|
|
|
|
void unavailableCoreDiscardsPendingTokenBeforeRetry() {
|
|
AccountList panel; panel.show(); fill(panel, "123", row(7)); QVERIFY(select(panel, 0));
|
|
const auto old = requests.back();
|
|
ready = false; QVERIFY(QMetaObject::invokeMethod(&panel, "onDetailTimerTick"));
|
|
ready = true; QVERIFY(QMetaObject::invokeMethod(&panel, "onDetailTimerTick"));
|
|
QCOMPARE(requests.size(), size_t(2));
|
|
deliver(reply(old, {111})); QVERIFY(!body(panel).contains("BGP:"));
|
|
deliver(reply(requests.back(), {7, 222})); QVERIFY(body(panel).contains("00000000000000de"));
|
|
}
|
|
|
|
void malformedStoredAddressDoesNotReadOutsideBlob() {
|
|
sqlite3* database = nullptr; QCOMPARE(sqlite3_open(":memory:", &database), SQLITE_OK);
|
|
const auto close = qScopeGuard([&] { sqlite3_close(database); });
|
|
DbManager manager;
|
|
AccountList panel(&manager); panel.show(); fill(panel, "123", row(7));
|
|
const char* schema = "CREATE TABLE nodes(node_id,last_seen_at,created_at);"
|
|
"INSERT INTO nodes VALUES(7,0,0);"
|
|
"CREATE TABLE node_addresses(node_id,family,protocol,address,port,socket_id,addr_type,nat_mapping,nat_inbound,nat_source);"
|
|
"INSERT INTO node_addresses VALUES(7,6,2,x'01',555,1,1,0,0,0);"
|
|
"INSERT INTO node_addresses VALUES(7,4,2,x'7f000001',444,2,1,0,0,0);";
|
|
QCOMPARE(sqlite3_exec(database, schema, nullptr, nullptr, nullptr), SQLITE_OK);
|
|
manager.setDb(database);
|
|
QVERIFY(select(panel, 0));
|
|
QVERIFY(body(panel).contains("127.0.0.1:444"));
|
|
QVERIFY(!body(panel).contains(":555"));
|
|
}
|
|
|
|
void modelOwnsPresenceAndRttAndRefreshPreservesSelection() {
|
|
AccountList panel; panel.resize(600, 800); panel.show();
|
|
fill(panel, "123", row(7, false, 0, 0xffff) + row(8));
|
|
QVERIFY(select(panel, 0)); QCOMPARE(requests.size(), size_t(1));
|
|
deliver(reply(requests.back())); // Физический транспорт UP не делает участника онлайн.
|
|
QVERIFY(header(panel).contains("#9E9E9E")); QVERIFY(body(panel).contains("offline"));
|
|
const auto live = row(7, true, 4, 93);
|
|
panel.onMemberUpdated("123", reinterpret_cast<const uint8_t*>(live.constData()));
|
|
QVERIFY(header(panel).contains("#4CAF50")); QVERIFY(body(panel).contains("rtt=9.3ms"));
|
|
deliver(reply(requests.back(), {7, 9})); QVERIFY(body(panel).contains("0000000000000009"));
|
|
const auto other = row(7, false, 0, 0xffff);
|
|
panel.onMemberUpdated("456", reinterpret_cast<const uint8_t*>(other.constData()));
|
|
QVERIFY(header(panel).contains("#4CAF50"));
|
|
panel.refresh(); QCOMPARE(panel.model()->rowCount(), 2); QVERIFY(body(panel).contains("rtt=9.3ms"));
|
|
const auto reordered = row(8) + live;
|
|
panel.onMemberList("123", 2, reinterpret_cast<const uint8_t*>(reordered.constData()), reordered.size());
|
|
QVERIFY(header(panel).contains("peer7"));
|
|
QCOMPARE(panel.findChild<QListView*>()->currentIndex().row(), 1);
|
|
}
|
|
|
|
void aToBToADiscardsOldResponsesWithoutClearingNewRequest() {
|
|
AccountList panel; panel.show(); fill(panel, "123", row(7) + row(8));
|
|
QVERIFY(select(panel, 0)); const auto first = requests.back();
|
|
QVERIFY(select(panel, 1)); const auto second = requests.back();
|
|
QVERIFY(select(panel, 0)); const auto third = requests.back();
|
|
QVERIFY(first.request_id < second.request_id && second.request_id < third.request_id);
|
|
deliver(reply(first, {111})); deliver(reply(second, {222}));
|
|
QVERIFY(!body(panel).contains("BGP:"));
|
|
auto wrongGroup = third; wrongGroup.group_id = 456;
|
|
deliver(reply(wrongGroup, {333})); QVERIFY(!body(panel).contains("BGP:"));
|
|
deliver(reply(third, {7, 444})); QVERIFY(body(panel).contains("00000000000001bc"));
|
|
const auto pending = requests.size();
|
|
QVERIFY(QMetaObject::invokeMethod(&panel, "onDetailTimerTick")); QCOMPARE(requests.size(), pending + 1);
|
|
const auto oldChannel = requests.back();
|
|
fill(panel, "456", row(7)); QVERIFY(select(panel, 0)); const auto current = requests.back();
|
|
deliver(reply(oldChannel, {555})); QVERIFY(!body(panel).contains("BGP:"));
|
|
deliver(reply(current, {7, 666})); QVERIFY(body(panel).contains("000000000000029a"));
|
|
}
|
|
|
|
void newMemberEventInvalidatesPendingPathEvenWhenFieldsAreEqual() {
|
|
AccountList panel; panel.show(); const auto initial = row(7);
|
|
fill(panel, "123", initial); QVERIFY(select(panel, 0)); const auto first = requests.back();
|
|
panel.onMemberUpdated("123", reinterpret_cast<const uint8_t*>(initial.constData()));
|
|
QCOMPARE(requests.size(), size_t(1));
|
|
deliver(reply(first, {111})); QCOMPARE(requests.size(), size_t(2)); QVERIFY(!body(panel).contains("BGP:"));
|
|
const auto correct = reply(requests.back(), {7, 8});
|
|
deliver(correct.left(correct.size() - 1)); QVERIFY(!body(panel).contains("BGP:"));
|
|
deliver(correct); QVERIFY(body(panel).contains("0000000000000008"));
|
|
panel.onMemberRemoved("123", 7); QVERIFY(!header(panel).contains("peer7"));
|
|
deliver(correct); QVERIFY(!body(panel).contains("BGP:"));
|
|
}
|
|
|
|
void errorTimeoutAndHiddenPanelReleaseOnlyTheirOwnRequests() {
|
|
AccountList panel; panel.show(); fill(panel, "123", row(7)); QVERIFY(select(panel, 0));
|
|
deliver(reply(requests.back(), {}, CHAT_MEMBER_DETAIL_NOT_MEMBER));
|
|
QVERIFY(header(panel).contains("#4CAF50")); // Диагностическая ошибка не меняет live-снимок.
|
|
QVERIFY(QMetaObject::invokeMethod(&panel, "onDetailTimerTick")); const auto expired = requests.back();
|
|
QTRY_VERIFY_WITH_TIMEOUT(requests.back().request_id != expired.request_id, 6500);
|
|
const auto current = requests.back(); deliver(reply(expired, {111}));
|
|
deliver(reply(current, {7, 8})); QVERIFY(body(panel).contains("BGP:"));
|
|
QVERIFY(QMetaObject::invokeMethod(&panel, "onDetailTimerTick")); const auto hidden = requests.back();
|
|
panel.hide(); deliver(reply(hidden, {222})); panel.show();
|
|
QVERIFY(requests.back().request_id > hidden.request_id);
|
|
deliver(reply(hidden, {222})); QVERIFY(!body(panel).contains("00000000000000de"));
|
|
ready = false; panel.setChannel(QString()); deliver(reply(requests.back(), {333}));
|
|
QCOMPARE(panel.model()->rowCount(), 0); QVERIFY(!body(panel).contains("BGP:"));
|
|
}
|
|
};
|
|
|
|
QTEST_MAIN(MemberDetailTest)
|
|
#include "test_member_detail.moc"
|