From b4a933945948daa7905a7dfbc834ebdc634ba748 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 10 Oct 2026 10:59:58 +0200 Subject: [PATCH] Test recursive removal against real disk storage --- .../tests/test_member_files_browser.cpp | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tools/chatgui/tests/test_member_files_browser.cpp b/tools/chatgui/tests/test_member_files_browser.cpp index f301636e..a4adea3a 100644 --- a/tools/chatgui/tests/test_member_files_browser.cpp +++ b/tools/chatgui/tests/test_member_files_browser.cpp @@ -29,6 +29,7 @@ #include extern "C" { #include "member_files/member_files.h" +#include "member_files/storage.h" #include "mem.h" #include "debug_config.h" } @@ -261,6 +262,87 @@ private slots: requestStarted = {}; transferStarted = {}; } + void recursiveDeletion_data() { + QTest::addColumn("scenario"); + for (const auto* scenario : {"complete", "cancel-scan", "cancel-remove", "changed-file", "new-child", "changed-page"}) + QTest::newRow(scenario) << QString::fromLatin1(scenario); + } + + void recursiveDeletion() { + QFETCH(QString, scenario); + QTemporaryDir root; QVERIFY(root.isValid()); + QString user = root.path() + "/42/22"; + QVERIFY(QDir().mkpath(user + "/folder/nested/empty")); QVERIFY(QDir().mkpath(user + "/other")); + auto write = [&](const QString& path, const QByteArray& contents) { + QFile file(user + '/' + path); QVERIFY(file.open(QIODevice::WriteOnly)); QCOMPARE(file.write(contents), qint64(contents.size())); + }; + // Длинные имена вынуждают настоящий storage выдавать несколько страниц LIST. + for (int i = 0; i < 40; i++) write("folder/" + QString::number(i) + QString(220, 'x'), "payload"); + write("folder/nested/file.txt", "nested"); write("loose.txt", "selected"); write("keep.txt", "unselected"); + auto* storage = mf_storage_create(root.path().toUtf8().constData(), 1000000, 200000); + QVERIFY(storage); auto destroy = qScopeGuard([&] { mf_storage_destroy(storage); }); + QCOMPARE(mf_storage_initialize(storage), int(MF_OK)); + QList entries; + for (const auto* path : {"folder", "loose.txt", "other"}) { + mf_message q{}, r{}; q.kind = MF_REQUEST; q.command = MF_STAT; q.owner = 22; q.request_id = entries.size() + 1; + strcpy(q.path, path); mf_storage_execute(storage, 42, 22, &q, &r); QCOMPARE(r.status, uint16_t(MF_OK)); + entries.append({QString::fromUtf8(path), {}, QByteArray((const char*)r.version, 16), r.type == MF_DIRECTORY}); + } + MemberFileBatch batch(42, 33, 22, MemberFileBatch::Remove, nullptr); + bool done = false, success = false, injected = false; QString summary, changed; + int pages = 0, removed = 0, requests = 0, rootStats = 0; + batch.progress = [](const QString&, int) {}; + batch.finished = [&](bool ok, const QString& status, const QStringList&) { done = true; success = ok; summary = status; }; + auto reset = qScopeGuard([] { requestStarted = {}; }); + requestStarted = [&](mf_gui_request request) { + requests++; + QTimer::singleShot(0, &batch, [&, request] { + const auto& q = request.message; QString path = QString::fromUtf8(q.path); + if (q.command == MF_STAT && path == "folder") { + rootStats++; + if (scenario == "new-child" && rootStats == 2) { write("folder/arrived.txt", "new arrival"); injected = true; } + } + if (q.command == MF_LIST) { + QCOMPARE(removed, 0); pages++; + QVERIFY(!mf_zero(q.version, 16)); + if (scenario == "changed-page" && q.cursor) { write("folder/arrived.txt", "new arrival"); injected = true; } + } + if (q.command == MF_REMOVE_PATH) { + QVERIFY(pages >= 4); // folder: две страницы, nested и empty: по одной. + if (scenario == "changed-file" && !injected) { changed = path; write(path, "changed contents"); injected = true; } + } + mf_message response{}; mf_storage_execute(storage, request.group, request.message.owner, &q, &response); + if (q.command == MF_REMOVE_PATH && !response.status) { + removed++; QVERIFY(!QFileInfo::exists(user + '/' + path)); + } + if (scenario == "cancel-scan" && response.more) { + batch.cancel(); QVERIFY(!batch.response(response)); return; + } + QVERIFY(batch.response(response)); + if (scenario == "cancel-remove" && removed == 1) { batch.cancel(); QVERIFY(!batch.response(response)); } + }); + }; + batch.start(entries); QTRY_VERIFY(done); + int stopped = requests; QCoreApplication::processEvents(); QCOMPARE(requests, stopped); + QFile keep(user + "/keep.txt"); QVERIFY(keep.open(QIODevice::ReadOnly)); QCOMPARE(keep.readAll(), QByteArray("unselected")); + if (scenario == "complete") { + QVERIFY2(success, qPrintable(summary)); QCOMPARE(removed, 46); + QVERIFY(!QFileInfo::exists(user + "/folder")); QVERIFY(!QFileInfo::exists(user + "/other")); + QVERIFY(!QFileInfo::exists(user + "/loose.txt")); QVERIFY(summary.contains("Completed: 46")); + } else { + QVERIFY(!success); QVERIFY(QFileInfo::exists(user + "/folder")); + if (scenario == "cancel-scan" || scenario == "changed-page" || scenario == "changed-file") QCOMPARE(removed, 0); + if (scenario == "cancel-remove") { QCOMPARE(removed, 1); QVERIFY(summary.contains("Completed: 1")); } + if (scenario.startsWith("cancel-")) QVERIFY(summary.contains("Deletion cancelled")); + if (scenario == "changed-file") { QVERIFY(injected); QVERIFY(QFileInfo::exists(user + '/' + changed)); } + if (scenario == "new-child") { + QVERIFY(injected); QVERIFY(QFileInfo::exists(user + "/folder/arrived.txt")); + QVERIFY(summary.contains(QString::fromUtf8(mf_status_text(MF_DIRECTORY_NOT_EMPTY)))); + } + if (scenario == "changed-page") QVERIFY(summary.contains(QString::fromUtf8(mf_status_text(MF_STALE)))); + } + } + void deleteAndParentShortcuts() { sqlite3* sql = nullptr; QCOMPARE(sqlite3_open(":memory:", &sql), SQLITE_OK); QCOMPARE(sqlite3_exec(sql, "CREATE TABLE accounts(node_id INTEGER,display_name TEXT);"