diff --git a/tools/chatgui/tests/test_member_files_lifecycle.cpp b/tools/chatgui/tests/test_member_files_lifecycle.cpp index 8747e78f2..f2ec1ad4d 100644 --- a/tools/chatgui/tests/test_member_files_lifecycle.cpp +++ b/tools/chatgui/tests/test_member_files_lifecycle.cpp @@ -13,6 +13,7 @@ #include extern "C" { #include "utun_instance.h" +#include "chat/chat_core.h" #include "member_files/member_files.h" #include "debug_config.h" #include "mem.h" @@ -80,7 +81,8 @@ private slots: gui_bridge_set_member_files_response_cb(nullptr); gui_bridge_set_member_files_transfer_cb(nullptr); } void cleanup() { - activeBatch = nullptr; activeBrowser = nullptr; responses.clear(); transfers.clear(); + activeBatch = nullptr; activeBrowser = nullptr; + QCoreApplication::processEvents(); responses.clear(); transfers.clear(); #ifdef UTUN_TEST_FILE_ALLOC_FAILURES failAllocation = 0; #endif @@ -320,7 +322,7 @@ private slots: #ifdef UTUN_TEST_FILE_ALLOC_FAILURES QCOMPARE(failAllocation, 0); #endif - core.attach(); QVERIFY(download->isEnabled()); + core.attach(); item->setSelected(false); item->setSelected(true); QVERIFY(download->isEnabled()); QTimer::singleShot(0, [&] { auto* dialog = qobject_cast(QApplication::activeModalWidget()); if (dialog) { dialog->selectFile(destination); QMetaObject::invokeMethod(dialog, "accept"); } @@ -331,6 +333,83 @@ private slots: } QCOMPARE(u_get_allocated_count(), before); } + + void realStorageRoundTrip() { + const size_t before = u_get_allocated_count(); + { + QTemporaryDir root; QVERIFY(root.isValid()); + UASYNC* ua = uasync_create(); QVERIFY(ua); UTUN_INSTANCE* instance = nullptr; QTimer poll; + const auto cleanup = qScopeGuard([&] { + activeBatch = nullptr; poll.stop(); gui_bridge_set_inst(nullptr); gui_bridge_set_uasync(nullptr); + uasync_mark_stopped(ua); + if (instance) utun_instance_destroy(instance); + uasync_destroy(ua, 0); QCoreApplication::processEvents(); + }); + const QByteArray config = "[global]\nauto_sockets=no\ntun_enabled=no\n" + "my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n" + "my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n" + "[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\nmember_files_enabled=1\n" + "local_invites_enabled=0\nlocal_broadcasts_enabled=0\nmember_files_path=" + root.path().toUtf8() + + "/storage\n[ntp]\nenabled=no\n"; + instance = utun_instance_create_from_str(ua, config.constData()); QVERIFY(instance); + QCOMPARE(utun_core_start(instance), 0); QCOMPARE(chat_service_start(instance), 0); + const uint64_t group = chat_core_create_channel_auto(instance, "Files"); QVERIFY(group); + const QByteArray channel = QByteArray::number(group); const uint64_t node = instance->node_id; + auto* tags = chat_member_tags_load(instance, channel.constData(), node); QVERIFY(tags); + const int set = chat_member_tags_set(tags, "storage", "yes"); + const int commit = set ? set : chat_member_tags_commit(instance, tags); chat_member_tags_free(tags); QCOMPARE(commit, 0); + chat_event_set_handler(instance, [](UTUN_INSTANCE*, int event, const uint8_t* data, int size) { + gui_bridge_post(event, data, size); + }); + uasync_mark_running(ua); gui_bridge_set_uasync(ua); gui_bridge_set_inst(instance); + connect(&poll, &QTimer::timeout, this, [ua] { uasync_poll(ua, 0); }); poll.start(1); + // Дождаться ответа готового storage, не подменяя асинхронный старт его worker. + bool ready = false; + for (int attempt = 0; attempt < 20 && !ready; attempt++) { + mf_message q{}; q.kind = MF_REQUEST; q.command = MF_QUOTA; q.request_id = 100 + attempt; q.owner = node; + const auto count = responses.size(); QCOMPARE(gui_bridge_member_files_request(group, node, &q), 0); + QTRY_COMPARE_WITH_TIMEOUT(responses.size(), count + 1, 3000); mf_message r{}; + const auto& bytes = responses.last(); + QCOMPARE(mf_decode((const uint8_t*)bytes.constData() + 16, bytes.size() - 16, &r), 0); + QVERIFY(r.status == MF_OK || r.status == MF_BUSY); ready = r.status == MF_OK; + } + QVERIFY(ready); + const QByteArray payload(20000, 'f'); const QString source = root.filePath("source.txt"); + { QFile file(source); QVERIFY(file.open(QIODevice::WriteOnly)); QCOMPARE(file.write(payload), qint64(payload.size())); } + int completed = 0; bool success = false; QString summary; + auto finished = [&](bool ok, const QString& text, const QStringList&) { completed++; success = ok; summary = text; }; + const QString stored = root.filePath("storage/" + QString::number(group) + '/' + QString::number(node) + "/source.txt"); + { + MemberFileBatch batch(group, node, node, MemberFileBatch::Upload, nullptr); activeBatch = &batch; + batch.progress = [](const QString&, int) {}; batch.finished = finished; + batch.upload({source}, {}); QTRY_COMPARE_WITH_TIMEOUT(completed, 1, 5000); QVERIFY2(success, qPrintable(summary)); + } + { QFile file(stored); QVERIFY(file.open(QIODevice::ReadOnly)); QCOMPARE(file.readAll(), payload); } + mf_message q{}; q.kind = MF_REQUEST; q.command = MF_STAT; q.request_id = 1000; q.owner = node; strcpy(q.path, "source.txt"); + const auto count = responses.size(); QCOMPARE(gui_bridge_member_files_request(group, node, &q), 0); + QTRY_COMPARE_WITH_TIMEOUT(responses.size(), count + 1, 3000); mf_message info{}; + const auto bytes = responses.last(); + QCOMPARE(mf_decode((const uint8_t*)bytes.constData() + 16, bytes.size() - 16, &info), 0); QCOMPARE(info.status, uint16_t(MF_OK)); + const QString destination = root.filePath("download.txt"); + const QByteArray version((const char*)info.version, 16); completed = 0; success = false; + { + MemberFileBatch batch(group, node, node, MemberFileBatch::Download, nullptr); activeBatch = &batch; + batch.progress = [](const QString&, int) {}; batch.finished = finished; + batch.start({{"source.txt", destination, version, false}}); + QTRY_COMPARE_WITH_TIMEOUT(completed, 1, 5000); QVERIFY2(success, qPrintable(summary)); + } + { QFile file(destination); QVERIFY(file.open(QIODevice::ReadOnly)); QCOMPARE(file.readAll(), payload); } + completed = 0; success = false; + { + MemberFileBatch batch(group, node, node, MemberFileBatch::Remove, nullptr); activeBatch = &batch; + batch.progress = [](const QString&, int) {}; batch.finished = finished; + batch.start({{"source.txt", {}, version, false}}); + QTRY_COMPARE_WITH_TIMEOUT(completed, 1, 5000); QVERIFY2(success, qPrintable(summary)); + } + QVERIFY(!QFile::exists(stored)); QVERIFY(QFile::exists(source)); QVERIFY(QFile::exists(destination)); + } + QCOMPARE(u_get_allocated_count(), before); + } }; QTEST_MAIN(FileLifecycleTest)