|
|
|
@ -3,19 +3,31 @@ |
|
|
|
#include "../src/member_files/browser.h" |
|
|
|
#include "../src/member_files/browser.h" |
|
|
|
#include "../db/db_manager.h" |
|
|
|
#include "../db/db_manager.h" |
|
|
|
#include "../transport/gui_bridge.h" |
|
|
|
#include "../transport/gui_bridge.h" |
|
|
|
|
|
|
|
#include "../src/channellist.h" |
|
|
|
|
|
|
|
#include "../src/channeldelegate.h" |
|
|
|
#include <QtTest> |
|
|
|
#include <QtTest> |
|
|
|
#include <QComboBox> |
|
|
|
#include <QComboBox> |
|
|
|
#include <QTreeWidget> |
|
|
|
#include <QTreeWidget> |
|
|
|
#include <QPushButton> |
|
|
|
#include <QPushButton> |
|
|
|
#include <QLabel> |
|
|
|
#include <QLabel> |
|
|
|
#include <QLocale> |
|
|
|
#include <QLocale> |
|
|
|
|
|
|
|
#include <QInputDialog> |
|
|
|
|
|
|
|
#include <QMessageBox> |
|
|
|
|
|
|
|
#include <QTemporaryDir> |
|
|
|
|
|
|
|
#include <QFile> |
|
|
|
|
|
|
|
#include <QMimeData> |
|
|
|
|
|
|
|
#include <QDragEnterEvent> |
|
|
|
|
|
|
|
#include <QDropEvent> |
|
|
|
extern "C" { |
|
|
|
extern "C" { |
|
|
|
#include "member_files/member_files.h" |
|
|
|
#include "member_files/member_files.h" |
|
|
|
#include "mem.h" |
|
|
|
#include "mem.h" |
|
|
|
} |
|
|
|
} |
|
|
|
static mf_gui_request captured; |
|
|
|
static mf_gui_request captured; |
|
|
|
static mf_gui_request quotaCaptured; |
|
|
|
static mf_gui_request quotaCaptured; |
|
|
|
extern "C" int gui_bridge_is_uasync_ready(void) { return 1; } |
|
|
|
static QList<mf_transfer_request> transfers; |
|
|
|
|
|
|
|
static QList<quint64> cancelled; |
|
|
|
|
|
|
|
static bool ready = true; |
|
|
|
|
|
|
|
extern "C" int gui_bridge_is_uasync_ready(void) { return ready; } |
|
|
|
extern "C" UTUN_INSTANCE* gui_bridge_get_inst(void) { return nullptr; } |
|
|
|
extern "C" UTUN_INSTANCE* gui_bridge_get_inst(void) { return nullptr; } |
|
|
|
extern "C" void gui_bridge_post_uasync_fn(void (*function)(void*), void* arg) { |
|
|
|
extern "C" void gui_bridge_post_uasync_fn(void (*function)(void*), void* arg) { |
|
|
|
if (function == member_files_gui_request) { |
|
|
|
if (function == member_files_gui_request) { |
|
|
|
@ -23,12 +35,135 @@ extern "C" void gui_bridge_post_uasync_fn(void (*function)(void*), void* arg) { |
|
|
|
if (request->message.command == MF_QUOTA) quotaCaptured = *request; |
|
|
|
if (request->message.command == MF_QUOTA) quotaCaptured = *request; |
|
|
|
else captured = *request; |
|
|
|
else captured = *request; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (function == member_files_gui_transfer) transfers.append(*(mf_transfer_request*)arg); |
|
|
|
|
|
|
|
if (function == member_files_gui_cancel) cancelled.append(((mf_cancel_request*)arg)->id); |
|
|
|
u_free(arg); |
|
|
|
u_free(arg); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void answerDialog(int result) { |
|
|
|
|
|
|
|
QTimer::singleShot(0, [result] { |
|
|
|
|
|
|
|
if (auto* box = qobject_cast<QMessageBox*>(QApplication::activeModalWidget())) box->button((QMessageBox::StandardButton)result)->click(); |
|
|
|
|
|
|
|
else if (auto* input = qobject_cast<QInputDialog*>(QApplication::activeModalWidget())) input->done(result); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void respond(MemberFileBrowser& browser, const mf_gui_request& request, uint16_t status, uint8_t type = MF_FILE) { |
|
|
|
|
|
|
|
mf_message r = request.message; r.kind = MF_RESPONSE; r.status = status; r.type = type; |
|
|
|
|
|
|
|
memset(r.version, 7, sizeof(r.version)); |
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void complete(MemberFileBrowser& browser, const mf_transfer_request& request, uint16_t status = MF_OK) { |
|
|
|
|
|
|
|
uint8_t bytes[51]{}; mf_put64(bytes, request.id); mf_put64(bytes + 8, request.group); |
|
|
|
|
|
|
|
mf_put64(bytes + 16, request.server); mf_put64(bytes + 24, request.owner); |
|
|
|
|
|
|
|
bytes[48] = status; bytes[49] = status >> 8; bytes[50] = 2; browser.onTransfer(bytes, sizeof(bytes)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static QPushButton* cancelButton(MemberFileBrowser& browser) { |
|
|
|
|
|
|
|
for (auto* button : browser.findChildren<QPushButton*>()) if (button->text() == "Cancel transfer") return button; |
|
|
|
|
|
|
|
return nullptr; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class BrowserTest : public QObject { |
|
|
|
class BrowserTest : public QObject { |
|
|
|
Q_OBJECT |
|
|
|
Q_OBJECT |
|
|
|
private slots: |
|
|
|
private slots: |
|
|
|
|
|
|
|
void init() { transfers.clear(); cancelled.clear(); ready = true; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void footerDropsCopyAndCaptureGroup() { |
|
|
|
|
|
|
|
DbManager db; db.setMyNodeId(22); ChannelList list(&db); list.resize(350, 400); list.show(); |
|
|
|
|
|
|
|
auto* view = list.findChild<QListView*>(); auto* model = qobject_cast<QStandardItemModel*>(view->model()); QVERIFY(model); |
|
|
|
|
|
|
|
for (const auto& id : {QString("42"), QString("43"), QString("dm:abc")}) { |
|
|
|
|
|
|
|
auto* item = new QStandardItem; item->setData(id, ChannelChannelIdRole); model->appendRow(item); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
auto* footer = list.findChild<QWidget*>("channelFooter"); QVERIFY(footer); |
|
|
|
|
|
|
|
QSignalSpy drops(&list, &ChannelList::ownFilesDropped); |
|
|
|
|
|
|
|
QMimeData mime; mime.setUrls({QUrl::fromLocalFile("/tmp/a.txt"), QUrl::fromLocalFile("/tmp/b.txt")}); |
|
|
|
|
|
|
|
list.selectChannel("42"); |
|
|
|
|
|
|
|
auto targets = footer->findChildren<QPushButton*>(); QVERIFY(!targets.isEmpty()); |
|
|
|
|
|
|
|
QList<QWidget*> widgets{footer}; for (auto* button : targets) widgets.append(button); |
|
|
|
|
|
|
|
for (auto* target : widgets) { |
|
|
|
|
|
|
|
list.selectChannel("42"); |
|
|
|
|
|
|
|
QDragEnterEvent enter(QPoint(3, 3), Qt::CopyAction | Qt::MoveAction, &mime, Qt::LeftButton, Qt::NoModifier); |
|
|
|
|
|
|
|
QApplication::sendEvent(target, &enter); QVERIFY(enter.isAccepted()); QCOMPARE(enter.dropAction(), Qt::CopyAction); |
|
|
|
|
|
|
|
QVERIFY(list.findChild<QLabel*>("ownFilesDropHint")->isVisible()); |
|
|
|
|
|
|
|
QDropEvent drop(QPointF(3, 3), Qt::CopyAction | Qt::MoveAction, &mime, Qt::LeftButton, Qt::NoModifier); |
|
|
|
|
|
|
|
QApplication::sendEvent(target, &drop); QVERIFY(drop.isAccepted()); QCOMPARE(drop.dropAction(), Qt::CopyAction); |
|
|
|
|
|
|
|
list.selectChannel("43"); QCoreApplication::processEvents(); |
|
|
|
|
|
|
|
QCOMPARE(drops.last()[0].toString(), QString("42")); |
|
|
|
|
|
|
|
QCOMPARE(drops.last()[1].toStringList(), QStringList({"/tmp/a.txt", "/tmp/b.txt"})); |
|
|
|
|
|
|
|
QVERIFY(!list.findChild<QLabel*>("ownFilesDropHint")->isVisible()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
list.selectChannel("dm:abc"); |
|
|
|
|
|
|
|
QDragEnterEvent dm(QPoint(3, 3), Qt::CopyAction, &mime, Qt::LeftButton, Qt::NoModifier); |
|
|
|
|
|
|
|
QApplication::sendEvent(footer, &dm); QVERIFY(!dm.isAccepted()); |
|
|
|
|
|
|
|
list.selectChannel("42"); ready = false; |
|
|
|
|
|
|
|
QDragEnterEvent stopped(QPoint(3, 3), Qt::CopyAction, &mime, Qt::LeftButton, Qt::NoModifier); |
|
|
|
|
|
|
|
QApplication::sendEvent(footer, &stopped); QVERIFY(!stopped.isAccepted()); ready = true; |
|
|
|
|
|
|
|
mime.setUrls({QUrl("https://example.com/file")}); |
|
|
|
|
|
|
|
QDragEnterEvent remote(QPoint(3, 3), Qt::CopyAction, &mime, Qt::LeftButton, Qt::NoModifier); |
|
|
|
|
|
|
|
QApplication::sendEvent(footer, &remote); QVERIFY(!remote.isAccepted()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void uploadQueueConflictsAndCancellation() { |
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
QTemporaryDir dir; QVERIFY(dir.isValid()); QStringList paths; |
|
|
|
|
|
|
|
for (const auto& name : {"a.txt", "b.txt", "c.txt"}) { |
|
|
|
|
|
|
|
QString path = dir.filePath(name); QFile file(path); QVERIFY(file.open(QIODevice::WriteOnly)); |
|
|
|
|
|
|
|
QCOMPARE(file.write("test"), 4LL); paths.append(path); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
MemberFileBrowser browser(&db); auto* cancel = cancelButton(browser); QVERIFY(cancel); |
|
|
|
|
|
|
|
auto* status = browser.findChild<QLabel*>("memberFilesUploadStatus"); QVERIFY(status); |
|
|
|
|
|
|
|
answerDialog(QDialog::Accepted); browser.uploadOwnFiles("42", paths); |
|
|
|
|
|
|
|
QTRY_COMPARE(captured.message.command, uint8_t(MF_STAT)); |
|
|
|
|
|
|
|
QCOMPARE(captured.message.owner, 22ULL); QCOMPARE(captured.server, 33ULL); QVERIFY(transfers.isEmpty()); |
|
|
|
|
|
|
|
quint64 firstStat = captured.message.request_id; |
|
|
|
|
|
|
|
browser.setChannel("43", 11); QCOMPARE(captured.message.request_id, firstStat); |
|
|
|
|
|
|
|
for (auto* selector : browser.findChildren<QComboBox*>()) QVERIFY(!selector->isEnabled()); |
|
|
|
|
|
|
|
respond(browser, captured, MF_NOT_FOUND); QCOMPARE(transfers.size(), 1); |
|
|
|
|
|
|
|
QCOMPARE(QString::fromUtf8(transfers[0].remote), QString("a.txt")); QCOMPARE(transfers[0].owner, 22ULL); |
|
|
|
|
|
|
|
QCOMPARE(QString::fromUtf8(transfers[0].local), paths[0]); QCOMPARE(transfers[0].group, 42ULL); |
|
|
|
|
|
|
|
QCOMPARE(QByteArray((const char*)transfers[0].version, 16), QByteArray(16, '\0')); |
|
|
|
|
|
|
|
complete(browser, transfers[0]); QCOMPARE(QString::fromUtf8(captured.message.path), QString("b.txt")); |
|
|
|
|
|
|
|
answerDialog(QMessageBox::No); respond(browser, captured, MF_OK); |
|
|
|
|
|
|
|
QCOMPARE(transfers.size(), 1); QCOMPARE(QString::fromUtf8(captured.message.path), QString("c.txt")); |
|
|
|
|
|
|
|
answerDialog(QMessageBox::Yes); respond(browser, captured, MF_OK); QCOMPARE(transfers.size(), 2); |
|
|
|
|
|
|
|
QCOMPARE(QByteArray((const char*)transfers[1].version, 16), QByteArray(16, '\7')); |
|
|
|
|
|
|
|
complete(browser, transfers[1]); QVERIFY(status->text().contains("2 uploaded, 1 skipped")); QVERIFY(!cancel->isEnabled()); |
|
|
|
|
|
|
|
for (const auto& path : paths) QVERIFY(QFile::exists(path)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
answerDialog(QDialog::Accepted); browser.uploadOwnFiles("42", paths); |
|
|
|
|
|
|
|
QTRY_COMPARE(captured.message.command, uint8_t(MF_STAT)); mf_gui_request stale = captured; |
|
|
|
|
|
|
|
cancel->click(); QVERIFY(cancelled.contains(stale.message.request_id)); |
|
|
|
|
|
|
|
respond(browser, stale, MF_NOT_FOUND); QCOMPARE(transfers.size(), 2); QVERIFY(!cancel->isEnabled()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
answerDialog(QDialog::Accepted); browser.uploadOwnFiles("42", paths); |
|
|
|
|
|
|
|
QTRY_COMPARE(captured.message.command, uint8_t(MF_STAT)); respond(browser, captured, MF_NOT_FOUND); |
|
|
|
|
|
|
|
QCOMPARE(transfers.size(), 3); auto active = transfers.last(); cancel->click(); QVERIFY(cancelled.contains(active.id)); |
|
|
|
|
|
|
|
QVERIFY(cancel->isEnabled()); complete(browser, active, MF_CANCELLED); QVERIFY(!cancel->isEnabled()); |
|
|
|
|
|
|
|
QCOMPARE(transfers.size(), 3); QVERIFY(status->text().contains("cancelled")); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
answerDialog(QDialog::Accepted); browser.uploadOwnFiles("42", paths); |
|
|
|
|
|
|
|
QTRY_COMPARE(captured.message.command, uint8_t(MF_STAT)); respond(browser, captured, MF_NOT_FOUND); |
|
|
|
|
|
|
|
QCOMPARE(transfers.size(), 4); complete(browser, transfers.last(), MF_NO_SPACE); |
|
|
|
|
|
|
|
QVERIFY(status->text().contains("Upload stopped: a.txt")); QVERIFY(!cancel->isEnabled()); QCOMPARE(transfers.size(), 4); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
answerDialog(QDialog::Accepted); browser.uploadOwnFiles("42", {dir.path()}); |
|
|
|
|
|
|
|
QTRY_VERIFY(status->text().contains("not folders or links")); QVERIFY(!cancel->isEnabled()); QCOMPARE(transfers.size(), 4); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QCOMPARE(sqlite3_exec(sql, "DELETE FROM peers_42 WHERE node_id=33", nullptr, nullptr, nullptr), SQLITE_OK); |
|
|
|
|
|
|
|
answerDialog(QMessageBox::Ok); browser.uploadOwnFiles("42", paths); QVERIFY(!cancel->isEnabled()); QCOMPARE(transfers.size(), 4); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
db.setDb(nullptr); QCOMPARE(sqlite3_close(sql), SQLITE_OK); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void rolePermissionsAndLateResponse() { |
|
|
|
void rolePermissionsAndLateResponse() { |
|
|
|
sqlite3* sql = nullptr; QCOMPARE(sqlite3_open(":memory:", &sql), SQLITE_OK); |
|
|
|
sqlite3* sql = nullptr; QCOMPARE(sqlite3_open(":memory:", &sql), SQLITE_OK); |
|
|
|
QCOMPARE(sqlite3_exec(sql, "CREATE TABLE accounts(node_id INTEGER,display_name TEXT);" |
|
|
|
QCOMPARE(sqlite3_exec(sql, "CREATE TABLE accounts(node_id INTEGER,display_name TEXT);" |
|
|
|
|