diff --git a/tools/chatgui/tests/test_member_files_browser.cpp b/tools/chatgui/tests/test_member_files_browser.cpp index 8138aaef..3d173ebe 100644 --- a/tools/chatgui/tests/test_member_files_browser.cpp +++ b/tools/chatgui/tests/test_member_files_browser.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include extern "C" { #include "member_files/member_files.h" @@ -104,6 +105,107 @@ private slots: } void init() { transfers.clear(); cancelled.clear(); ready = true; transferStarted = {}; requestStarted = {}; } + void selectionAndRecursiveClipboard() { + QTemporaryDir cache; QVERIFY(cache.isValid()); QByteArray oldCache = qgetenv("XDG_CACHE_HOME"); + qputenv("XDG_CACHE_HOME", cache.path().toUtf8()); + auto restore = qScopeGuard([oldCache] { qputenv("XDG_CACHE_HOME", oldCache); QApplication::clipboard()->clear(); }); + 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(22,'Me'),(33,'Server');" + "CREATE TABLE peers_42(node_id INTEGER,adm_tags TEXT,adm_tags_sig BLOB,deleted INTEGER,source INTEGER);" + "INSERT INTO peers_42 VALUES(22,'{}',NULL,0,0),(33,'{\"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", 22); browser.show(); browser.activateWindow(); + auto* tree = browser.findChild(); + for (const auto& name : {"folder", "loose.txt", "skip.txt", "last.txt"}) { + auto* item = new QTreeWidgetItem(tree, {name}); item->setData(0, Qt::UserRole, name); + item->setData(0, Qt::UserRole + 1, QString(name) == "folder" ? MF_DIRECTORY : MF_FILE); + item->setData(0, Qt::UserRole + 2, QByteArray(16, '\7')); + } + tree->setFocus(); QTRY_VERIFY(tree->hasFocus()); + auto click = [tree](int row, Qt::KeyboardModifiers modifiers) { + QTest::mouseClick(tree->viewport(), Qt::LeftButton, modifiers, tree->visualItemRect(tree->topLevelItem(row)).center()); + }; + click(0, Qt::NoModifier); click(2, Qt::ShiftModifier); QCOMPARE(tree->selectedItems().size(), 3); + click(1, Qt::ControlModifier); QCOMPARE(tree->selectedItems().size(), 2); + QTest::keyClick(tree, Qt::Key_A, Qt::ControlModifier); QCOMPARE(tree->selectedItems().size(), 4); + click(0, Qt::NoModifier); click(1, Qt::ShiftModifier); QCOMPARE(tree->selectedItems().size(), 2); + QTimer::singleShot(0, [] { if (auto* menu = qobject_cast(QApplication::activePopupWidget())) menu->close(); }); + tree->customContextMenuRequested(tree->visualItemRect(tree->topLevelItem(1)).center()); + QCOMPARE(tree->selectedItems().size(), 2); + QMap directories{{"folder", {"empty", "nested", "inside.txt"}}, {"folder/empty", {}}, + {"folder/nested", {"deep.txt"}}}; + int pages = 0, verified = 0; + requestStarted = [&](mf_gui_request request) { + QTimer::singleShot(0, &browser, [&, request] { + mf_message r = request.message; r.kind = MF_RESPONSE; r.status = MF_OK; r.data_len = 0; + QString path = QString::fromUtf8(r.path); r.type = directories.contains(path) ? MF_DIRECTORY : MF_FILE; + memset(r.version, 7, 16); + if (r.command == MF_LIST) { + pages++; auto names = directories.value(path); + if (r.cursor < quint64(names.size())) { + QByteArray name = names[int(r.cursor)].toUtf8(); QString child = path + '/' + QString::fromUtf8(name); + r.data[0] = uint8_t(name.size()); r.data[2] = directories.contains(child) ? MF_DIRECTORY : MF_FILE; + memset(r.data + 19, 7, 16); memcpy(r.data + MF_ENTRY_HEADER, name.constData(), name.size()); + r.data_len = MF_ENTRY_HEADER + name.size(); r.cursor++; r.more = r.cursor < quint64(names.size()); + } + } else if (r.command == MF_STAT) verified++; + else return; + uint8_t bytes[16 + MF_WIRE_MAX]; mf_put64(bytes, request.group); mf_put64(bytes + 8, request.server); + int size = mf_encode(&r, bytes + 16, MF_WIRE_MAX); QVERIFY(size > 0); browser.onResponse(bytes, size + 16); + }); + }; + transferStarted = [&](mf_transfer_request request) { + QTimer::singleShot(0, &browser, [&, request] { + QFile file(QString::fromUtf8(request.local)); QVERIFY(file.open(QIODevice::WriteOnly)); + QByteArray bytes(request.remote); QCOMPARE(file.write(bytes), qint64(bytes.size())); file.close(); complete(browser, request); + }); + }; + auto* copy = browser.findChild("memberFilesCopy"); copy->trigger(); + QTRY_VERIFY(copy->isEnabled()); QCOMPARE(pages, 5); QCOMPARE(verified, 3); QCOMPARE(transfers.size(), 3); + auto urls = QApplication::clipboard()->mimeData()->urls(); QCOMPARE(urls.size(), 2); + QString folder = urls[0].toLocalFile(); QVERIFY(QFileInfo(folder + "/empty").isDir()); + QFile deep(folder + "/nested/deep.txt"); QVERIFY(deep.open(QIODevice::ReadOnly)); + QCOMPARE(deep.readAll(), QByteArray("folder/nested/deep.txt")); + QVERIFY(QFileInfo(urls[1].toLocalFile()).isFile()); + requestStarted = {}; transferStarted = {}; + } + db.setDb(nullptr); QCOMPARE(sqlite3_close(sql), SQLITE_OK); + } + + void recursiveUploadAndPreflight() { + QTemporaryDir root; QVERIFY(root.isValid()); QVERIFY(QDir(root.path()).mkpath("folder/empty")); + QVERIFY(QDir(root.path()).mkpath("folder/nested")); + QString source = root.filePath("folder/nested/file.txt"); + { QFile file(source); QVERIFY(file.open(QIODevice::WriteOnly)); QCOMPARE(file.write("data"), 4LL); } + MemberFileBatch batch(42, 33, 22, MemberFileBatch::Upload, nullptr); + bool done = false, success = false; QString summary; QStringList created; + batch.progress = [](const QString&, int) {}; + batch.conflict = [](const QString&, bool) { return int(QMessageBox::Cancel); }; + batch.finished = [&](bool ok, const QString& status, const QStringList&) { done = true; success = ok; summary = status; }; + requestStarted = [&](mf_gui_request request) { + QTimer::singleShot(0, &batch, [&, request] { + mf_message response = request.message; response.kind = MF_RESPONSE; + response.status = response.command == MF_STAT ? MF_NOT_FOUND : MF_OK; + if (response.command == MF_MKDIR) created.append(QString::fromUtf8(response.path)); + QVERIFY(batch.response(response)); + }); + }; + transferStarted = [&](mf_transfer_request request) { + QCOMPARE(QString::fromUtf8(request.remote), QString("destination/folder/nested/file.txt")); + QVERIFY(created.contains("destination/folder/nested")); QCOMPARE(QString::fromUtf8(request.local), source); + QTimer::singleShot(0, &batch, [&, request] { + uint8_t bytes[51]{}; mf_put64(bytes, request.id); bytes[50] = 2; QVERIFY(batch.transfer(bytes, sizeof(bytes))); + }); + }; + batch.upload({root.filePath("folder"), source}, "destination"); + QTRY_VERIFY(done); QVERIFY2(success, qPrintable(summary)); QCOMPARE(transfers.size(), 1); + QVERIFY(created.contains("destination/folder/empty")); QCOMPARE(created.size(), 3); + requestStarted = {}; transferStarted = {}; + } + 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);"