/* Реальный браузер Qt с захватом bridge-команд: выбор подписанного storage через * NAT, права чужого UID и отбрасывание старого LIST после смены каталога. */ #include "../src/member_files/browser.h" #include "../db/db_manager.h" #include "../transport/gui_bridge.h" #include #include #include #include #include #include extern "C" { #include "member_files/member_files.h" #include "mem.h" } static mf_gui_request captured; static mf_gui_request quotaCaptured; extern "C" int gui_bridge_is_uasync_ready(void) { return 1; } extern "C" UTUN_INSTANCE* gui_bridge_get_inst(void) { return nullptr; } extern "C" void gui_bridge_post_uasync_fn(void (*function)(void*), void* arg) { if (function == member_files_gui_request) { auto* request = (mf_gui_request*)arg; if (request->message.command == MF_QUOTA) quotaCaptured = *request; else captured = *request; } u_free(arg); } class BrowserTest : public QObject { Q_OBJECT private slots: void rolePermissionsAndLateResponse() { sqlite3* sql = nullptr; QCOMPARE(sqlite3_open(":memory:", &sql), SQLITE_OK); QCOMPARE(sqlite3_exec(sql, "CREATE TABLE accounts(node_id INTEGER,display_name TEXT);" "INSERT INTO accounts VALUES(11,'Author'),(22,'Reader'),(33,'Server');" "CREATE TABLE peers_42(node_id INTEGER,storage INTEGER,adm_tags TEXT,adm_tags_sig BLOB,deleted INTEGER,source INTEGER);" "INSERT INTO peers_42 VALUES(11,0,'{}',NULL,0,0),(22,0,'{}',NULL,0,0)," "(33,0,'{\"storage\":\"yes\"}',zeroblob(64),0,0);", nullptr, nullptr, nullptr), SQLITE_OK); DbManager db; db.setDb(sql); db.setMyNodeId(22); { MemberFileBrowser browser(&db); browser.setChannel("42", 11); auto selectors = browser.findChildren(); QCOMPARE(selectors.size(), 2); QCOMPARE(selectors[0]->count(), 1); QCOMPARE(selectors[0]->currentData().toULongLong(), 33ULL); QCOMPARE(captured.message.owner, 11ULL); QCOMPARE(captured.message.command, (uint8_t)MF_LIST); QCOMPARE(quotaCaptured.message.owner, 11ULL); QCOMPARE(quotaCaptured.message.command, (uint8_t)MF_QUOTA); auto* quota = browser.findChild("memberFilesQuota"); QVERIFY(quota); mf_gui_request previousQuota = quotaCaptured; mf_message stats = quotaCaptured.message; stats.kind = MF_RESPONSE; stats.data_len = MF_QUOTA_BYTES; mf_put64(stats.data, 2 * 1024 * 1024); mf_put64(stats.data + 8, 8 * 1024 * 1024); mf_put64(stats.data + 16, 1024 * 1024); mf_put64(stats.data + 24, 42); mf_put64(stats.data + 32, 7); uint8_t quotaPacket[16 + MF_WIRE_MAX]; mf_put64(quotaPacket, 42); mf_put64(quotaPacket + 8, 33); int quotaLength = mf_encode(&stats, quotaPacket + 16, MF_WIRE_MAX); browser.onResponse(quotaPacket, quotaLength + 16); QCOMPARE(quota->text(), QString("Storage: %1 used / %2 total · Files: 42 · Directories: 7 · Reserved for uploads: %3") .arg(QLocale().formattedDataSize(2 * 1024 * 1024, 2), QLocale().formattedDataSize(8 * 1024 * 1024, 2), QLocale().formattedDataSize(1024 * 1024, 2))); QVERIFY(quota->toolTip().contains("2097152")); mf_gui_request previous = captured; mf_message r = previous.message; r.kind = MF_RESPONSE; r.type = MF_DIRECTORY; const char name[] = "readme.txt"; unsigned n = sizeof(name) - 1; r.data[0] = (uint8_t)n; r.data[2] = MF_FILE; mf_put64(r.data + 3, 123); memcpy(r.data + MF_ENTRY_HEADER, name, n); r.data_len = MF_ENTRY_HEADER + n; uint8_t packet[16 + MF_WIRE_MAX]; mf_put64(packet, 42); mf_put64(packet + 8, 33); int length = mf_encode(&r, packet + 16, MF_WIRE_MAX); browser.onResponse(packet, length + 16); auto* table = browser.findChild(); QCOMPARE(table->topLevelItemCount(), 1); table->setCurrentItem(table->topLevelItem(0)); for (auto* button : browser.findChildren()) { if (button->text().startsWith("Upload") || button->text().startsWith("Rename") || button->text() == "Delete") QVERIFY(!button->isEnabled()); if (button->text().startsWith("Download")) QVERIFY(button->isEnabled()); } int own = selectors[1]->findData(QVariant::fromValue(22)); QVERIFY(own >= 0); selectors[1]->setCurrentIndex(own); QCOMPARE(captured.message.owner, 22ULL); QCOMPARE(table->topLevelItemCount(), 0); QCOMPARE(quotaCaptured.message.owner, 22ULL); QVERIFY(quotaCaptured.message.request_id != previousQuota.message.request_id); browser.onResponse(quotaPacket, quotaLength + 16); QCOMPARE(quota->text(), QString("Loading quota…")); stats = quotaCaptured.message; stats.kind = MF_RESPONSE; stats.data_len = MF_QUOTA_BYTES; mf_put64(stats.data, 3 * 1024 * 1024); mf_put64(stats.data + 8, 8 * 1024 * 1024); mf_put64(stats.data + 24, 12); mf_put64(stats.data + 32, 3); quotaLength = mf_encode(&stats, quotaPacket + 16, MF_WIRE_MAX); browser.onResponse(quotaPacket, quotaLength + 16); QString ownQuota = QString("Storage: %1 used / %2 total · Files: 12 · Directories: 3") .arg(QLocale().formattedDataSize(3 * 1024 * 1024, 2), QLocale().formattedDataSize(8 * 1024 * 1024, 2)); QCOMPARE(quota->text(), ownQuota); browser.onResponse(packet, length + 16); QCOMPARE(table->topLevelItemCount(), 0); for (auto* button : browser.findChildren()) if (button->text().startsWith("Upload")) QVERIFY(button->isEnabled()); mf_message busy = captured.message; busy.kind = MF_RESPONSE; busy.status = MF_BUSY; length = mf_encode(&busy, packet + 16, MF_WIRE_MAX); browser.onResponse(packet, length + 16); QTRY_VERIFY_WITH_TIMEOUT(captured.message.request_id != busy.request_id, 1500); QCOMPARE(captured.message.owner, 22ULL); selectors[1]->setCurrentIndex(0); QCOMPARE(quota->text(), QString("Select a member to see storage quota")); browser.onResponse(quotaPacket, quotaLength + 16); QCOMPARE(quota->text(), QString("Select a member to see storage quota")); } db.setDb(nullptr); QCOMPARE(sqlite3_close(sql), SQLITE_OK); } }; QTEST_MAIN(BrowserTest) #include "test_member_files_browser.moc"