From 50fff66412310b79ee67f047c6d4d36177a754db Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 10:53:23 +0100 Subject: [PATCH] Cover channel reload state and pending request ownership --- tools/chatgui/CMakeLists.txt | 6 + tools/chatgui/tests/test_channel_list.cpp | 131 ++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 tools/chatgui/tests/test_channel_list.cpp diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index b2921621..a93101be 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -474,6 +474,12 @@ target_link_libraries(test_channel_delegate PRIVATE ${QT_LIBS} ${QT_TEST_LIB} ut add_test(NAME test_channel_delegate COMMAND test_channel_delegate) set_tests_properties(test_channel_delegate PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 15) +add_executable(test_channel_list tests/test_channel_list.cpp src/channellist.cpp src/channeldelegate.cpp + src/avatars/groupavatarmanager.cpp db/db_manager.cpp transport/gui_bridge_impl.cpp) +target_link_libraries(test_channel_list PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun utun_voice pthread ${CMAKE_DL_LIBS}) +add_test(NAME test_channel_list COMMAND test_channel_list) +set_tests_properties(test_channel_list PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 30) + add_executable(test_single_instance tests/test_single_instance.cpp src/single_instance.cpp) target_link_libraries(test_single_instance PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun pthread ${CMAKE_DL_LIBS}) if(WIN32) diff --git a/tools/chatgui/tests/test_channel_list.cpp b/tools/chatgui/tests/test_channel_list.cpp new file mode 100644 index 00000000..001b6e00 --- /dev/null +++ b/tools/chatgui/tests/test_channel_list.cpp @@ -0,0 +1,131 @@ +/* Настоящие Qt-меню, БД ядра и bridge: обновления списка и отмена его команд. */ +#include "../src/channellist.h" +#include "../src/channeldelegate.h" +#include "../db/db_manager.h" +#include "../transport/gui_bridge.h" +#include +#include +#include +#include +extern "C" { +#include "utun_instance.h" +#include "chat/chat_core.h" +#include "dm/dm_core.h" +#include "mem.h" +#include "debug_config.h" +} + +struct ListCore { + UASYNC* ua = uasync_create(); + UTUN_INSTANCE* inst = nullptr; + DbManager db; + QString channel; + ~ListCore() { + gui_bridge_set_inst(nullptr); gui_bridge_set_uasync(nullptr); + if (ua) uasync_mark_stopped(ua); + db.setDb(nullptr); + if (inst) utun_instance_destroy(inst); + if (ua) uasync_destroy(ua, 0); + } + bool start() { + if (!ua) return false; + const char* config = "[global]\n" + "my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n" + "my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n" + "[chatserver]\ngroup_autoconnect=0\nmember_files_enabled=0\nstorage_autoload=0\n"; + inst = utun_instance_create_from_str(ua, config); + if (!inst || utun_core_start(inst) || chat_service_start(inst)) return false; + chat_core_create_channel_auto(inst, "Group"); + db.setDb(inst->topo_sqlite_db); db.setMyNodeId(inst->node_id); + if (db.getChannels().size() != 1) return false; + channel = db.getChannels()[0].channelId; + const QByteArray peer = QByteArray::fromHex("ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a"); + return dm_start(inst, 7, (const uint8_t*)peer.constData(), (const uint8_t*)peer.constData(), "PM", nullptr) == 0; + } +}; + +class ChannelListTest : public QObject { + Q_OBJECT +private slots: + void initTestCase() { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); } + + void reloadKeepsConnectionState() { + ListCore core; QVERIFY(core.start()); + ChannelList list(&core.db); list.loadChannels(); + auto* view = list.findChild(); QVERIFY(view); + list.setChannelPeersOnline(core.channel, 3); + list.setAutoConnectActive(true); + list.loadChannels(); + QCOMPARE(view->currentIndex().data(ChannelPeersOnlineRole).toInt(), 3); + QVERIFY(view->currentIndex().data(ChannelAutoConnectActiveRole).toBool()); + QSignalSpy selected(&list, &ChannelList::channelSelected); + list.selectChannel(QString()); + QCOMPARE(view->currentIndex().data(ChannelChannelIdRole).toString(), core.channel); + QVERIFY(selected.isEmpty()); + } + + void unreadNotificationIsUnique() { + ListCore core; QVERIFY(core.start()); + ChannelList list(&core.db); list.loadChannels(); + auto* view = list.findChild(); QVERIFY(view); + QSignalSpy changes(view->model(), &QAbstractItemModel::dataChanged); + list.setChannelUnreadCount(core.channel, 7); + QCOMPARE(list.getChannelUnreadCount(core.channel), 7); + QCOMPARE(changes.count(), 1); + list.setChannelUnreadCount(core.channel, 7); + QCOMPARE(changes.count(), 1); + } + + void presenceRequestsAreCancelledWithoutLeaks() { + ListCore core; QVERIFY(core.start()); + ChannelList list(&core.db); + uasync_mark_running(core.ua); + gui_bridge_set_uasync(core.ua); gui_bridge_set_inst(core.inst); + const int allocations = u_get_alloc_count(); + list.refreshDmPresence(); list.refreshDmPresence(); + QTest::qWait(100); + QVERIFY(core.ua->pending_head != nullptr); + gui_bridge_set_inst(nullptr); + QVERIFY(core.ua->pending_head == nullptr); + QCOMPARE(u_get_alloc_count(), allocations); + } + + void menuRejectsChangedDmGeneration() { + ListCore core; QVERIFY(core.start()); + ChannelList list(&core.db); list.loadChannels(); list.resize(300, 400); list.show(); + const auto dm = core.db.getDmConversations()[0]; + list.selectChannel("dm:" + dm.convId); + auto* view = list.findChild(); QVERIFY(view); + QSignalSpy removed(&list, &ChannelList::dmDeleteRequested); + QTimer::singleShot(0, &list, [&] { + auto* menu = qobject_cast(QApplication::activePopupWidget()); QVERIFY(menu); + auto* action = menu->findChild("deleteDmAction"); QVERIFY(action && action->isEnabled()); + view->model()->setData(view->currentIndex(), QVariant::fromValue(dm.generation + 1), ChannelDmGenerationRole); + QTest::keyClick(menu, Qt::Key_Down); QTest::keyClick(menu, Qt::Key_Return); + }); + view->customContextMenuRequested(view->visualRect(view->currentIndex()).center()); + QVERIFY(removed.isEmpty()); + } + + void menuSurvivesOwnerDeletion() { + ListCore core; QVERIFY(core.start()); + QPointer list = new ChannelList(&core.db); + list->loadChannels(); list->resize(300, 400); list->show(); + auto* view = list->findChild(); QVERIFY(view); + const QPoint point = view->visualRect(view->currentIndex()).center(); + QTimer::singleShot(0, [&] { delete list.data(); }); + view->customContextMenuRequested(point); + QVERIFY(!list); + } + + void borrowedDbCanDisappear() { + QPointer db = new DbManager; + ChannelList list(db); delete db.data(); + list.loadChannels(); list.refreshDmPresence(); + QTest::qWait(100); + QCOMPARE(list.findChild()->model()->rowCount(), 0); + } +}; + +QTEST_MAIN(ChannelListTest) +#include "test_channel_list.moc"