| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249 |
- /*
- * This software is in the public domain, furnished "as is", without technical
- * support, and with no warranty, express or implied, as to its usefulness for
- * any purpose.
- *
- */
- #include <QtTest>
- #include "syncenginetestutils.h"
- #include <syncengine.h>
- #include <propagatorjobs.h>
- using namespace OCC;
- bool itemDidComplete(const ItemCompletedSpy &spy, const QString &path)
- {
- if (auto item = spy.findItem(path)) {
- return item->_instruction != CSYNC_INSTRUCTION_NONE && item->_instruction != CSYNC_INSTRUCTION_UPDATE_METADATA;
- }
- return false;
- }
- bool itemInstruction(const ItemCompletedSpy &spy, const QString &path, const SyncInstructions instr)
- {
- auto item = spy.findItem(path);
- return item->_instruction == instr;
- }
- bool itemDidCompleteSuccessfully(const ItemCompletedSpy &spy, const QString &path)
- {
- if (auto item = spy.findItem(path)) {
- return item->_status == SyncFileItem::Success;
- }
- return false;
- }
- bool itemDidCompleteSuccessfullyWithExpectedRank(const ItemCompletedSpy &spy, const QString &path, int rank)
- {
- if (auto item = spy.findItemWithExpectedRank(path, rank)) {
- return item->_status == SyncFileItem::Success;
- }
- return false;
- }
- int itemSuccessfullyCompletedGetRank(const ItemCompletedSpy &spy, const QString &path)
- {
- auto itItem = std::find_if(spy.begin(), spy.end(), [&path] (auto currentItem) {
- auto item = currentItem[0].template value<OCC::SyncFileItemPtr>();
- return item->destination() == path;
- });
- if (itItem != spy.end()) {
- return itItem - spy.begin();
- }
- return -1;
- }
- class TestSyncEngine : public QObject
- {
- Q_OBJECT
- private slots:
- void testFileDownload() {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- ItemCompletedSpy completeSpy(fakeFolder);
- fakeFolder.remoteModifier().insert("A/a0");
- fakeFolder.syncOnce();
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a0"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testFileUpload() {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- ItemCompletedSpy completeSpy(fakeFolder);
- fakeFolder.localModifier().insert("A/a0");
- fakeFolder.syncOnce();
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a0"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testDirDownload() {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- ItemCompletedSpy completeSpy(fakeFolder);
- fakeFolder.remoteModifier().mkdir("Y");
- fakeFolder.remoteModifier().mkdir("Z");
- fakeFolder.remoteModifier().insert("Z/d0");
- fakeFolder.syncOnce();
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Y"));
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z"));
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z/d0"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testDirUpload() {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- ItemCompletedSpy completeSpy(fakeFolder);
- fakeFolder.localModifier().mkdir("Y");
- fakeFolder.localModifier().mkdir("Z");
- fakeFolder.localModifier().insert("Z/d0");
- fakeFolder.syncOnce();
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Y"));
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z"));
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z/d0"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testDirUploadWithDelayedAlgorithm() {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"bulkupload", "1.0"} } } });
- ItemCompletedSpy completeSpy(fakeFolder);
- fakeFolder.localModifier().mkdir("Y");
- fakeFolder.localModifier().insert("Y/d0");
- fakeFolder.localModifier().mkdir("Z");
- fakeFolder.localModifier().insert("Z/d0");
- fakeFolder.localModifier().insert("A/a0");
- fakeFolder.localModifier().insert("B/b0");
- fakeFolder.localModifier().insert("r0");
- fakeFolder.localModifier().insert("r1");
- fakeFolder.syncOnce();
- QVERIFY(itemDidCompleteSuccessfullyWithExpectedRank(completeSpy, "Y", 0));
- QVERIFY(itemDidCompleteSuccessfullyWithExpectedRank(completeSpy, "Z", 1));
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Y/d0"));
- QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "Y/d0") > 1);
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z/d0"));
- QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "Z/d0") > 1);
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a0"));
- QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "A/a0") > 1);
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "B/b0"));
- QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "B/b0") > 1);
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "r0"));
- QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "r0") > 1);
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "r1"));
- QVERIFY(itemSuccessfullyCompletedGetRank(completeSpy, "r1") > 1);
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testLocalDelete() {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- ItemCompletedSpy completeSpy(fakeFolder);
- fakeFolder.remoteModifier().remove("A/a1");
- fakeFolder.syncOnce();
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a1"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testRemoteDelete() {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- ItemCompletedSpy completeSpy(fakeFolder);
- fakeFolder.localModifier().remove("A/a1");
- fakeFolder.syncOnce();
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a1"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testLocalDeleteWithReuploadForNewLocalFiles()
- {
- FakeFolder fakeFolder{FileInfo{}};
- // create folders hierarchy with some nested dirs and files
- fakeFolder.localModifier().mkdir("A");
- fakeFolder.localModifier().insert("A/existingfile_A.txt", 100);
- fakeFolder.localModifier().mkdir("A/B");
- fakeFolder.localModifier().insert("A/B/existingfile_B.data", 100);
- fakeFolder.localModifier().mkdir("A/B/C");
- fakeFolder.localModifier().mkdir("A/B/C/c1");
- fakeFolder.localModifier().mkdir("A/B/C/c1/c2");
- fakeFolder.localModifier().insert("A/B/C/c1/c2/existingfile_C2.md", 100);
- QVERIFY(fakeFolder.syncOnce());
- // make sure everything is uploaded
- QVERIFY(fakeFolder.currentRemoteState().find("A/B/C/c1/c2"));
- QVERIFY(fakeFolder.currentRemoteState().find("A/existingfile_A.txt"));
- QVERIFY(fakeFolder.currentRemoteState().find("A/B/existingfile_B.data"));
- QVERIFY(fakeFolder.currentRemoteState().find("A/B/C/c1/c2/existingfile_C2.md"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // remove a folder "A" on the server
- fakeFolder.remoteModifier().remove("A");
- // put new files and folders into a local folder "A"
- fakeFolder.localModifier().insert("A/B/C/c1/c2/newfile.txt", 100);
- fakeFolder.localModifier().insert("A/B/C/c1/c2/Readme.data", 100);
- fakeFolder.localModifier().mkdir("A/B/C/c1/c2/newfiles");
- fakeFolder.localModifier().insert("A/B/C/c1/c2/newfiles/newfile.txt", 100);
- fakeFolder.localModifier().insert("A/B/C/c1/c2/newfiles/Readme.data", 100);
- QVERIFY(fakeFolder.syncOnce());
- // make sure new files and folders are uploaded (restored)
- QVERIFY(fakeFolder.currentLocalState().find("A/B/C/c1/c2"));
- QVERIFY(fakeFolder.currentLocalState().find("A/B/C/c1/c2/Readme.data"));
- QVERIFY(fakeFolder.currentLocalState().find("A/B/C/c1/c2/newfiles/newfile.txt"));
- QVERIFY(fakeFolder.currentLocalState().find("A/B/C/c1/c2/newfiles/Readme.data"));
- // and the old files are removed
- QVERIFY(!fakeFolder.currentLocalState().find("A/existingfile_A.txt"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/B/existingfile_B.data"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/B/C/c1/c2/existingfile_C2.md"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testEmlLocalChecksum() {
- FakeFolder fakeFolder{FileInfo{}};
- fakeFolder.localModifier().insert("a1.eml", 64, 'A');
- fakeFolder.localModifier().insert("a2.eml", 64, 'A');
- fakeFolder.localModifier().insert("a3.eml", 64, 'A');
- fakeFolder.localModifier().insert("b3.txt", 64, 'A');
- // Upload and calculate the checksums
- // fakeFolder.syncOnce();
- fakeFolder.syncOnce();
- auto getDbChecksum = [&](QString path) {
- SyncJournalFileRecord record;
- [[maybe_unused]] const auto result = fakeFolder.syncJournal().getFileRecord(path, &record);
- return record._checksumHeader;
- };
- // printf 'A%.0s' {1..64} | sha1sum -
- QByteArray referenceChecksum("SHA1:30b86e44e6001403827a62c58b08893e77cf121f");
- QCOMPARE(getDbChecksum("a1.eml"), referenceChecksum);
- QCOMPARE(getDbChecksum("a2.eml"), referenceChecksum);
- QCOMPARE(getDbChecksum("a3.eml"), referenceChecksum);
- QCOMPARE(getDbChecksum("b3.txt"), referenceChecksum);
- ItemCompletedSpy completeSpy(fakeFolder);
- // Touch the file without changing the content, shouldn't upload
- fakeFolder.localModifier().setContents("a1.eml", 'A');
- // Change the content/size
- fakeFolder.localModifier().setContents("a2.eml", 'B');
- fakeFolder.localModifier().appendByte("a3.eml");
- fakeFolder.localModifier().appendByte("b3.txt");
- fakeFolder.syncOnce();
- QCOMPARE(getDbChecksum("a1.eml"), referenceChecksum);
- QCOMPARE(getDbChecksum("a2.eml"), QByteArray("SHA1:84951fc23a4dafd10020ac349da1f5530fa65949"));
- QCOMPARE(getDbChecksum("a3.eml"), QByteArray("SHA1:826b7e7a7af8a529ae1c7443c23bf185c0ad440c"));
- QCOMPARE(getDbChecksum("b3.eml"), getDbChecksum("a3.txt"));
- QVERIFY(!itemDidComplete(completeSpy, "a1.eml"));
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a2.eml"));
- QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a3.eml"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testSelectiveSyncBug() {
- // issue owncloud/enterprise#1965: files from selective-sync ignored
- // folders are uploaded anyway is some circumstances.
- FakeFolder fakeFolder{FileInfo{ QString(), {
- FileInfo { QStringLiteral("parentFolder"), {
- FileInfo{ QStringLiteral("subFolderA"), {
- { QStringLiteral("fileA.txt"), 400 },
- { QStringLiteral("fileB.txt"), 400, 'o' },
- FileInfo { QStringLiteral("subsubFolder"), {
- { QStringLiteral("fileC.txt"), 400 },
- { QStringLiteral("fileD.txt"), 400, 'o' }
- }},
- FileInfo{ QStringLiteral("anotherFolder"), {
- FileInfo { QStringLiteral("emptyFolder"), { } },
- FileInfo { QStringLiteral("subsubFolder"), {
- { QStringLiteral("fileE.txt"), 400 },
- { QStringLiteral("fileF.txt"), 400, 'o' }
- }}
- }}
- }},
- FileInfo{ QStringLiteral("subFolderB"), {} }
- }}
- }}};
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- auto expectedServerState = fakeFolder.currentRemoteState();
- // Remove subFolderA with selectiveSync:
- fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
- {"parentFolder/subFolderA/"});
- fakeFolder.syncEngine().journal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/"));
- auto getEtag = [&](const QByteArray &file) {
- SyncJournalFileRecord rec;
- [[maybe_unused]] const auto result = fakeFolder.syncJournal().getFileRecord(file, &rec);
- return rec._etag;
- };
- QVERIFY(getEtag("parentFolder") == "_invalid_");
- QVERIFY(getEtag("parentFolder/subFolderA") == "_invalid_");
- QVERIFY(getEtag("parentFolder/subFolderA/subsubFolder") != "_invalid_");
- // But touch local file before the next sync, such that the local folder
- // can't be removed
- fakeFolder.localModifier().setContents("parentFolder/subFolderA/fileB.txt", 'n');
- fakeFolder.localModifier().setContents("parentFolder/subFolderA/subsubFolder/fileD.txt", 'n');
- fakeFolder.localModifier().setContents("parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt", 'n');
- // Several follow-up syncs don't change the state at all,
- // in particular the remote state doesn't change and fileB.txt
- // isn't uploaded.
- for (int i = 0; i < 3; ++i) {
- fakeFolder.syncOnce();
- {
- // Nothing changed on the server
- QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
- // The local state should still have subFolderA
- auto local = fakeFolder.currentLocalState();
- QVERIFY(local.find("parentFolder/subFolderA"));
- QVERIFY(!local.find("parentFolder/subFolderA/fileA.txt"));
- QVERIFY(local.find("parentFolder/subFolderA/fileB.txt"));
- QVERIFY(!local.find("parentFolder/subFolderA/subsubFolder/fileC.txt"));
- QVERIFY(local.find("parentFolder/subFolderA/subsubFolder/fileD.txt"));
- QVERIFY(!local.find("parentFolder/subFolderA/anotherFolder/subsubFolder/fileE.txt"));
- QVERIFY(local.find("parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt"));
- QVERIFY(!local.find("parentFolder/subFolderA/anotherFolder/emptyFolder"));
- QVERIFY(local.find("parentFolder/subFolderB"));
- }
- }
- }
- void abortAfterFailedMkdir() {
- FakeFolder fakeFolder{FileInfo{}};
- QSignalSpy finishedSpy(&fakeFolder.syncEngine(), SIGNAL(finished(bool)));
- fakeFolder.serverErrorPaths().append("NewFolder");
- fakeFolder.localModifier().mkdir("NewFolder");
- // This should be aborted and would otherwise fail in FileInfo::create.
- fakeFolder.localModifier().insert("NewFolder/NewFile");
- fakeFolder.syncOnce();
- QCOMPARE(finishedSpy.size(), 1);
- QCOMPARE(finishedSpy.first().first().toBool(), false);
- }
- /** Verify that an incompletely propagated directory doesn't have the server's
- * etag stored in the database yet. */
- void testDirEtagAfterIncompleteSync() {
- FakeFolder fakeFolder{FileInfo{}};
- QSignalSpy finishedSpy(&fakeFolder.syncEngine(), SIGNAL(finished(bool)));
- fakeFolder.serverErrorPaths().append("NewFolder/foo");
- fakeFolder.remoteModifier().mkdir("NewFolder");
- fakeFolder.remoteModifier().insert("NewFolder/foo");
- QVERIFY(!fakeFolder.syncOnce());
- SyncJournalFileRecord rec;
- QVERIFY(fakeFolder.syncJournal().getFileRecord(QByteArrayLiteral("NewFolder"), &rec) && rec.isValid());
- QCOMPARE(rec._etag, QByteArrayLiteral("_invalid_"));
- QVERIFY(!rec._fileId.isEmpty());
- }
- void testDirDownloadWithError() {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- ItemCompletedSpy completeSpy(fakeFolder);
- fakeFolder.remoteModifier().mkdir("Y");
- fakeFolder.remoteModifier().mkdir("Y/Z");
- fakeFolder.remoteModifier().insert("Y/Z/d0");
- fakeFolder.remoteModifier().insert("Y/Z/d1");
- fakeFolder.remoteModifier().insert("Y/Z/d2");
- fakeFolder.remoteModifier().insert("Y/Z/d3");
- fakeFolder.remoteModifier().insert("Y/Z/d4");
- fakeFolder.remoteModifier().insert("Y/Z/d5");
- fakeFolder.remoteModifier().insert("Y/Z/d6");
- fakeFolder.remoteModifier().insert("Y/Z/d7");
- fakeFolder.remoteModifier().insert("Y/Z/d8");
- fakeFolder.remoteModifier().insert("Y/Z/d9");
- fakeFolder.serverErrorPaths().append("Y/Z/d2", 503);
- fakeFolder.serverErrorPaths().append("Y/Z/d3", 503);
- QVERIFY(!fakeFolder.syncOnce());
- QCoreApplication::processEvents(); // should not crash
- QSet<QString> seen;
- for(const QList<QVariant> &args : completeSpy) {
- auto item = args[0].value<SyncFileItemPtr>();
- qDebug() << item->_file << item->isDirectory() << item->_status;
- QVERIFY(!seen.contains(item->_file)); // signal only sent once per item
- seen.insert(item->_file);
- if (item->_file == "Y/Z/d2") {
- QVERIFY(item->_status == SyncFileItem::NormalError);
- } else if (item->_file == "Y/Z/d3") {
- QVERIFY(item->_status != SyncFileItem::Success);
- } else if (!item->isDirectory()) {
- QVERIFY(item->_status == SyncFileItem::Success);
- }
- }
- }
- void testFakeConflict_data()
- {
- QTest::addColumn<bool>("sameMtime");
- QTest::addColumn<QByteArray>("checksums");
- QTest::addColumn<int>("expectedGET");
- QTest::newRow("Same mtime, but no server checksum -> ignored in reconcile")
- << true << QByteArray()
- << 0;
- QTest::newRow("Same mtime, weak server checksum differ -> downloaded")
- << true << QByteArray("Adler32:bad")
- << 1;
- QTest::newRow("Same mtime, matching weak checksum -> skipped")
- << true << QByteArray("Adler32:2a2010d")
- << 0;
- QTest::newRow("Same mtime, strong server checksum differ -> downloaded")
- << true << QByteArray("SHA1:bad")
- << 1;
- QTest::newRow("Same mtime, matching strong checksum -> skipped")
- << true << QByteArray("SHA1:56900fb1d337cf7237ff766276b9c1e8ce507427")
- << 0;
- QTest::newRow("mtime changed, but no server checksum -> download")
- << false << QByteArray()
- << 1;
- QTest::newRow("mtime changed, weak checksum match -> download anyway")
- << false << QByteArray("Adler32:2a2010d")
- << 1;
- QTest::newRow("mtime changed, strong checksum match -> skip")
- << false << QByteArray("SHA1:56900fb1d337cf7237ff766276b9c1e8ce507427")
- << 0;
- }
- void testFakeConflict()
- {
- QFETCH(bool, sameMtime);
- QFETCH(QByteArray, checksums);
- QFETCH(int, expectedGET);
- FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- int nGET = 0;
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) {
- if (op == QNetworkAccessManager::GetOperation)
- ++nGET;
- return nullptr;
- });
- // For directly editing the remote checksum
- auto &remoteInfo = fakeFolder.remoteModifier();
- // Base mtime with no ms content (filesystem is seconds only)
- auto mtime = QDateTime::currentDateTimeUtc().addDays(-4);
- mtime.setMSecsSinceEpoch(mtime.toMSecsSinceEpoch() / 1000 * 1000);
- fakeFolder.localModifier().setContents("A/a1", 'C');
- fakeFolder.localModifier().setModTime("A/a1", mtime);
- fakeFolder.remoteModifier().setContents("A/a1", 'C');
- if (!sameMtime)
- mtime = mtime.addDays(1);
- fakeFolder.remoteModifier().setModTime("A/a1", mtime);
- remoteInfo.find("A/a1")->checksums = checksums;
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(nGET, expectedGET);
- // check that mtime in journal and filesystem agree
- QString a1path = fakeFolder.localPath() + "A/a1";
- SyncJournalFileRecord a1record;
- QVERIFY(fakeFolder.syncJournal().getFileRecord(QByteArray("A/a1"), &a1record));
- QCOMPARE(a1record._modtime, (qint64)FileSystem::getModTime(a1path));
- // Extra sync reads from db, no difference
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(nGET, expectedGET);
- }
- /**
- * Checks whether SyncFileItems have the expected properties before start
- * of propagation.
- */
- void testSyncFileItemProperties()
- {
- auto initialMtime = QDateTime::currentDateTimeUtc().addDays(-7);
- auto changedMtime = QDateTime::currentDateTimeUtc().addDays(-4);
- auto changedMtime2 = QDateTime::currentDateTimeUtc().addDays(-3);
- // Base mtime with no ms content (filesystem is seconds only)
- initialMtime.setMSecsSinceEpoch(initialMtime.toMSecsSinceEpoch() / 1000 * 1000);
- changedMtime.setMSecsSinceEpoch(changedMtime.toMSecsSinceEpoch() / 1000 * 1000);
- changedMtime2.setMSecsSinceEpoch(changedMtime2.toMSecsSinceEpoch() / 1000 * 1000);
- // Ensure the initial mtimes are as expected
- auto initialFileInfo = FileInfo::A12_B12_C12_S12();
- initialFileInfo.setModTime("A/a1", initialMtime);
- initialFileInfo.setModTime("B/b1", initialMtime);
- initialFileInfo.setModTime("C/c1", initialMtime);
- FakeFolder fakeFolder{ initialFileInfo };
- // upload a
- fakeFolder.localModifier().appendByte("A/a1");
- fakeFolder.localModifier().setModTime("A/a1", changedMtime);
- // download b
- fakeFolder.remoteModifier().appendByte("B/b1");
- fakeFolder.remoteModifier().setModTime("B/b1", changedMtime);
- // conflict c
- fakeFolder.localModifier().appendByte("C/c1");
- fakeFolder.localModifier().appendByte("C/c1");
- fakeFolder.localModifier().setModTime("C/c1", changedMtime);
- fakeFolder.remoteModifier().appendByte("C/c1");
- fakeFolder.remoteModifier().setModTime("C/c1", changedMtime2);
- connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](SyncFileItemVector &items) {
- SyncFileItemPtr a1, b1, c1;
- for (auto &item : items) {
- if (item->_file == "A/a1")
- a1 = item;
- if (item->_file == "B/b1")
- b1 = item;
- if (item->_file == "C/c1")
- c1 = item;
- }
- // a1: should have local size and modtime
- QVERIFY(a1);
- QCOMPARE(a1->_instruction, CSYNC_INSTRUCTION_SYNC);
- QCOMPARE(a1->_direction, SyncFileItem::Up);
- QCOMPARE(a1->_size, qint64(5));
- QCOMPARE(Utility::qDateTimeFromTime_t(a1->_modtime), changedMtime);
- QCOMPARE(a1->_previousSize, qint64(4));
- QCOMPARE(Utility::qDateTimeFromTime_t(a1->_previousModtime), initialMtime);
- // b2: should have remote size and modtime
- QVERIFY(b1);
- QCOMPARE(b1->_instruction, CSYNC_INSTRUCTION_SYNC);
- QCOMPARE(b1->_direction, SyncFileItem::Down);
- QCOMPARE(b1->_size, qint64(17));
- QCOMPARE(Utility::qDateTimeFromTime_t(b1->_modtime), changedMtime);
- QCOMPARE(b1->_previousSize, qint64(16));
- QCOMPARE(Utility::qDateTimeFromTime_t(b1->_previousModtime), initialMtime);
- // c1: conflicts are downloads, so remote size and modtime
- QVERIFY(c1);
- QCOMPARE(c1->_instruction, CSYNC_INSTRUCTION_CONFLICT);
- QCOMPARE(c1->_direction, SyncFileItem::None);
- QCOMPARE(c1->_size, qint64(25));
- QCOMPARE(Utility::qDateTimeFromTime_t(c1->_modtime), changedMtime2);
- QCOMPARE(c1->_previousSize, qint64(26));
- QCOMPARE(Utility::qDateTimeFromTime_t(c1->_previousModtime), changedMtime);
- });
- QVERIFY(fakeFolder.syncOnce());
- }
- /**
- * Checks whether subsequent large uploads are skipped after a 507 error
- */
- void testInsufficientRemoteStorage()
- {
- FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- // Disable parallel uploads
- SyncOptions syncOptions;
- syncOptions._parallelNetworkJobs = 0;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
- // Produce an error based on upload size
- int remoteQuota = 1000;
- int n507 = 0, nPUT = 0;
- QObject parent;
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * {
- Q_UNUSED(outgoingData)
- if (op == QNetworkAccessManager::PutOperation) {
- nPUT++;
- if (request.rawHeader("OC-Total-Length").toInt() > remoteQuota) {
- n507++;
- return new FakeErrorReply(op, request, &parent, 507);
- }
- }
- return nullptr;
- });
- fakeFolder.localModifier().insert("A/big", 800);
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(nPUT, 1);
- QCOMPARE(n507, 0);
- nPUT = 0;
- fakeFolder.localModifier().insert("A/big1", 500); // ok
- fakeFolder.localModifier().insert("A/big2", 1200); // 507 (quota guess now 1199)
- fakeFolder.localModifier().insert("A/big3", 1200); // skipped
- fakeFolder.localModifier().insert("A/big4", 1500); // skipped
- fakeFolder.localModifier().insert("A/big5", 1100); // 507 (quota guess now 1099)
- fakeFolder.localModifier().insert("A/big6", 900); // ok (quota guess now 199)
- fakeFolder.localModifier().insert("A/big7", 200); // skipped
- fakeFolder.localModifier().insert("A/big8", 199); // ok (quota guess now 0)
- fakeFolder.localModifier().insert("B/big8", 1150); // 507
- QVERIFY(!fakeFolder.syncOnce());
- QCOMPARE(nPUT, 6);
- QCOMPARE(n507, 3);
- }
- // Checks whether downloads with bad checksums are accepted
- void testChecksumValidation()
- {
- FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- QObject parent;
- QByteArray checksumValue;
- QByteArray checksumValueRecalculated;
- QByteArray contentMd5Value;
- bool isChecksumRecalculateSupported = false;
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
- if (op == QNetworkAccessManager::GetOperation) {
- auto reply = new FakeGetReply(fakeFolder.remoteModifier(), op, request, &parent);
- if (!checksumValue.isNull())
- reply->setRawHeader(OCC::checkSumHeaderC, checksumValue);
- if (!contentMd5Value.isNull())
- reply->setRawHeader(OCC::contentMd5HeaderC, contentMd5Value);
- return reply;
- } else if (op == QNetworkAccessManager::CustomOperation) {
- if (request.hasRawHeader(OCC::checksumRecalculateOnServerHeaderC)) {
- if (!isChecksumRecalculateSupported) {
- return new FakeErrorReply(op, request, &parent, 402);
- }
- auto reply = new FakeGetReply(fakeFolder.remoteModifier(), op, request, &parent);
- reply->setRawHeader(OCC::checkSumHeaderC, checksumValueRecalculated);
- return reply;
- }
- }
- return nullptr;
- });
- // Basic case
- fakeFolder.remoteModifier().create("A/a3", 16, 'A');
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // Bad OC-Checksum
- checksumValue = "SHA1:bad";
- fakeFolder.remoteModifier().create("A/a4", 16, 'A');
- QVERIFY(!fakeFolder.syncOnce());
- const QByteArray matchedSha1Checksum(QByteArrayLiteral("SHA1:19b1928d58a2030d08023f3d7054516dbc186f20"));
- const QByteArray mismatchedSha1Checksum(matchedSha1Checksum.chopped(1));
- // Good OC-Checksum
- checksumValue = matchedSha1Checksum; // printf 'A%.0s' {1..16} | sha1sum -
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- checksumValue = QByteArray();
- // Bad Content-MD5
- contentMd5Value = "bad";
- fakeFolder.remoteModifier().create("A/a5", 16, 'A');
- QVERIFY(!fakeFolder.syncOnce());
- // Good Content-MD5
- contentMd5Value = "d8a73157ce10cd94a91c2079fc9a92c8"; // printf 'A%.0s' {1..16} | md5sum -
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // Invalid OC-Checksum is ignored
- checksumValue = "garbage";
- // contentMd5Value is still good
- fakeFolder.remoteModifier().create("A/a6", 16, 'A');
- QVERIFY(fakeFolder.syncOnce());
- contentMd5Value = "bad";
- fakeFolder.remoteModifier().create("A/a7", 16, 'A');
- QVERIFY(!fakeFolder.syncOnce());
- contentMd5Value.clear();
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // OC-Checksum contains Unsupported checksums
- checksumValue = "Unsupported:XXXX SHA1:invalid Invalid:XxX";
- fakeFolder.remoteModifier().create("A/a8", 16, 'A');
- QVERIFY(!fakeFolder.syncOnce()); // Since the supported SHA1 checksum is invalid, no download
- checksumValue = "Unsupported:XXXX SHA1:19b1928d58a2030d08023f3d7054516dbc186f20 Invalid:XxX";
- QVERIFY(fakeFolder.syncOnce()); // The supported SHA1 checksum is valid now, so the file are downloaded
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // Begin Test mismatch recalculation---------------------------------------------------------------------------------
- const auto prevServerVersion = fakeFolder.account()->serverVersion();
- fakeFolder.account()->setServerVersion(QString("%1.0.0").arg(fakeFolder.account()->checksumRecalculateServerVersionMinSupportedMajor()));
- // Mismatched OC-Checksum and X-Recalculate-Hash is not supported -> sync must fail
- isChecksumRecalculateSupported = false;
- checksumValue = mismatchedSha1Checksum;
- checksumValueRecalculated = matchedSha1Checksum;
- fakeFolder.remoteModifier().create("A/a9", 16, 'A');
- QVERIFY(!fakeFolder.syncOnce());
- // Mismatched OC-Checksum and X-Recalculate-Hash is supported, but, recalculated checksum is again mismatched -> sync must fail
- isChecksumRecalculateSupported = true;
- checksumValue = mismatchedSha1Checksum;
- checksumValueRecalculated = mismatchedSha1Checksum;
- QVERIFY(!fakeFolder.syncOnce());
- // Mismatched OC-Checksum and X-Recalculate-Hash is supported, and, recalculated checksum is a match -> sync must succeed
- isChecksumRecalculateSupported = true;
- checksumValue = mismatchedSha1Checksum;
- checksumValueRecalculated = matchedSha1Checksum;
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- checksumValue = QByteArray();
- fakeFolder.account()->setServerVersion(prevServerVersion);
- // End Test mismatch recalculation-----------------------------------------------------------------------------------
- }
- // Tests the behavior of invalid filename detection
- void testInvalidFilenameRegex()
- {
- FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- #ifndef Q_OS_WIN // We can't have local file with these character
- // For current servers, no characters are forbidden
- fakeFolder.syncEngine().account()->setServerVersion("10.0.0");
- fakeFolder.localModifier().insert("A/\\:?*\"<>|.txt");
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // For legacy servers, some characters were forbidden by the client
- fakeFolder.syncEngine().account()->setServerVersion("8.0.0");
- fakeFolder.localModifier().insert("B/\\:?*\"<>|.txt");
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.currentRemoteState().find("B/\\:?*\"<>|.txt"));
- #endif
- // We can override that by setting the capability
- fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "invalidFilenameRegex", "" } } } });
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // Check that new servers also accept the capability
- fakeFolder.syncEngine().account()->setServerVersion("10.0.0");
- fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "invalidFilenameRegex", "my[fgh]ile" } } } });
- fakeFolder.localModifier().insert("C/myfile.txt");
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.currentRemoteState().find("C/myfile.txt"));
- }
- void testDiscoveryHiddenFile()
- {
- FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // We can't depend on currentLocalState for hidden files since
- // it should rightfully skip things like download temporaries
- auto localFileExists = [&](QString name) {
- return QFileInfo(fakeFolder.localPath() + name).exists();
- };
- fakeFolder.syncEngine().setIgnoreHiddenFiles(true);
- fakeFolder.remoteModifier().insert("A/.hidden");
- fakeFolder.localModifier().insert("B/.hidden");
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(!localFileExists("A/.hidden"));
- QVERIFY(!fakeFolder.currentRemoteState().find("B/.hidden"));
- fakeFolder.syncEngine().setIgnoreHiddenFiles(false);
- fakeFolder.syncJournal().forceRemoteDiscoveryNextSync();
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(localFileExists("A/.hidden"));
- QVERIFY(fakeFolder.currentRemoteState().find("B/.hidden"));
- }
- void testNoLocalEncoding()
- {
- auto utf8Locale = QTextCodec::codecForLocale();
- if (utf8Locale->mibEnum() != 106) {
- QSKIP("Test only works for UTF8 locale");
- }
- FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // Utf8 locale can sync both
- fakeFolder.remoteModifier().insert("A/tößt");
- fakeFolder.remoteModifier().insert("A/t𠜎t");
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/tößt"));
- QVERIFY(fakeFolder.currentLocalState().find("A/t𠜎t"));
- #if !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
- // Try again with a locale that can represent ö but not 𠜎 (4-byte utf8).
- QTextCodec::setCodecForLocale(QTextCodec::codecForName("ISO-8859-15"));
- QVERIFY(QTextCodec::codecForLocale()->mibEnum() == 111);
- fakeFolder.remoteModifier().insert("B/tößt");
- fakeFolder.remoteModifier().insert("B/t𠜎t");
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("B/tößt"));
- QVERIFY(!fakeFolder.currentLocalState().find("B/t𠜎t"));
- QVERIFY(!fakeFolder.currentLocalState().find("B/t?t"));
- QVERIFY(!fakeFolder.currentLocalState().find("B/t??t"));
- QVERIFY(!fakeFolder.currentLocalState().find("B/t???t"));
- QVERIFY(!fakeFolder.currentLocalState().find("B/t????t"));
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentRemoteState().find("B/tößt"));
- QVERIFY(fakeFolder.currentRemoteState().find("B/t𠜎t"));
- // Try again with plain ascii
- QTextCodec::setCodecForLocale(QTextCodec::codecForName("ASCII"));
- QVERIFY(QTextCodec::codecForLocale()->mibEnum() == 3);
- fakeFolder.remoteModifier().insert("C/tößt");
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.currentLocalState().find("C/tößt"));
- QVERIFY(!fakeFolder.currentLocalState().find("C/t??t"));
- QVERIFY(!fakeFolder.currentLocalState().find("C/t????t"));
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentRemoteState().find("C/tößt"));
- QTextCodec::setCodecForLocale(utf8Locale);
- #endif
- }
- // Aborting has had bugs when there are parallel upload jobs
- void testUploadV1Multiabort()
- {
- FakeFolder fakeFolder{ FileInfo{} };
- SyncOptions options;
- options._initialChunkSize = 10;
- options._maxChunkSize = 10;
- options._minChunkSize = 10;
- fakeFolder.syncEngine().setSyncOptions(options);
- QObject parent;
- int nPUT = 0;
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
- if (op == QNetworkAccessManager::PutOperation) {
- ++nPUT;
- return new FakeHangingReply(op, request, &parent);
- }
- return nullptr;
- });
- fakeFolder.localModifier().insert("file", 100, 'W');
- QTimer::singleShot(100, &fakeFolder.syncEngine(), [&]() { fakeFolder.syncEngine().abort(); });
- QVERIFY(!fakeFolder.syncOnce());
- QCOMPARE(nPUT, 3);
- }
- #ifndef Q_OS_WIN
- void testPropagatePermissions()
- {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- auto perm = QFileDevice::Permission(0x7704); // user/owner: rwx, group: r, other: -
- QFile::setPermissions(fakeFolder.localPath() + "A/a1", perm);
- QFile::setPermissions(fakeFolder.localPath() + "A/a2", perm);
- fakeFolder.syncOnce(); // get the metadata-only change out of the way
- fakeFolder.remoteModifier().appendByte("A/a1");
- fakeFolder.remoteModifier().appendByte("A/a2");
- fakeFolder.localModifier().appendByte("A/a2");
- fakeFolder.localModifier().appendByte("A/a2");
- fakeFolder.syncOnce(); // perms should be preserved
- QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").permissions(), perm);
- QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a2").permissions(), perm);
- auto conflictName = fakeFolder.syncJournal().conflictRecord(fakeFolder.syncJournal().conflictRecordPaths().first()).path;
- QVERIFY(conflictName.contains("A/a2"));
- QCOMPARE(QFileInfo(fakeFolder.localPath() + conflictName).permissions(), perm);
- }
- #endif
- void testEmptyLocalButHasRemote()
- {
- FakeFolder fakeFolder{ FileInfo{} };
- fakeFolder.remoteModifier().mkdir("foo");
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- QVERIFY(fakeFolder.currentLocalState().find("foo"));
- }
- // Check that server mtime is set on directories on initial propagation
- void testDirectoryInitialMtime()
- {
- FakeFolder fakeFolder{ FileInfo{} };
- fakeFolder.remoteModifier().mkdir("foo");
- fakeFolder.remoteModifier().insert("foo/bar");
- auto datetime = QDateTime::currentDateTime();
- datetime.setSecsSinceEpoch(datetime.toSecsSinceEpoch()); // wipe ms
- fakeFolder.remoteModifier().find("foo")->lastModified = datetime;
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- QCOMPARE(QFileInfo(fakeFolder.localPath() + "foo").lastModified(), datetime);
- }
- /**
- * Checks whether subsequent large uploads are skipped after a 507 error
- */
- void testErrorsWithBulkUpload()
- {
- FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"bulkupload", "1.0"} } } });
- // Disable parallel uploads
- SyncOptions syncOptions;
- syncOptions._parallelNetworkJobs = 0;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
- int nPUT = 0;
- int nPOST = 0;
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * {
- auto contentType = request.header(QNetworkRequest::ContentTypeHeader).toString();
- if (op == QNetworkAccessManager::PostOperation) {
- ++nPOST;
- if (contentType.startsWith(QStringLiteral("multipart/related; boundary="))) {
- auto jsonReplyObject = fakeFolder.forEachReplyPart(outgoingData, contentType, [] (const QMap<QString, QByteArray> &allHeaders) -> QJsonObject {
- auto reply = QJsonObject{};
- const auto fileName = allHeaders[QStringLiteral("X-File-Path")];
- if (fileName.endsWith("A/big2") ||
- fileName.endsWith("A/big3") ||
- fileName.endsWith("A/big4") ||
- fileName.endsWith("A/big5") ||
- fileName.endsWith("A/big7") ||
- fileName.endsWith("B/big8")) {
- reply.insert(QStringLiteral("error"), true);
- reply.insert(QStringLiteral("etag"), {});
- return reply;
- } else {
- reply.insert(QStringLiteral("error"), false);
- reply.insert(QStringLiteral("etag"), {});
- }
- return reply;
- });
- if (jsonReplyObject.size()) {
- auto jsonReply = QJsonDocument{};
- jsonReply.setObject(jsonReplyObject);
- return new FakeJsonErrorReply{op, request, this, 200, jsonReply};
- }
- return nullptr;
- }
- } else if (op == QNetworkAccessManager::PutOperation) {
- ++nPUT;
- const auto fileName = getFilePathFromUrl(request.url());
- if (fileName.endsWith("A/big2") ||
- fileName.endsWith("A/big3") ||
- fileName.endsWith("A/big4") ||
- fileName.endsWith("A/big5") ||
- fileName.endsWith("A/big7") ||
- fileName.endsWith("B/big8")) {
- return new FakeErrorReply(op, request, this, 412);
- }
- return nullptr;
- }
- return nullptr;
- });
- fakeFolder.localModifier().insert("A/big", 1);
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(nPUT, 0);
- QCOMPARE(nPOST, 1);
- nPUT = 0;
- nPOST = 0;
- fakeFolder.localModifier().insert("A/big1", 1); // ok
- fakeFolder.localModifier().insert("A/big2", 1); // ko
- fakeFolder.localModifier().insert("A/big3", 1); // ko
- fakeFolder.localModifier().insert("A/big4", 1); // ko
- fakeFolder.localModifier().insert("A/big5", 1); // ko
- fakeFolder.localModifier().insert("A/big6", 1); // ok
- fakeFolder.localModifier().insert("A/big7", 1); // ko
- fakeFolder.localModifier().insert("A/big8", 1); // ok
- fakeFolder.localModifier().insert("B/big8", 1); // ko
- QVERIFY(!fakeFolder.syncOnce());
- QCOMPARE(nPUT, 0);
- QCOMPARE(nPOST, 1);
- nPUT = 0;
- nPOST = 0;
- QVERIFY(!fakeFolder.syncOnce());
- QCOMPARE(nPUT, 6);
- QCOMPARE(nPOST, 0);
- }
- /**
- * Checks whether subsequent large uploads are skipped after a 507 error
- */
- void testNetworkErrorsWithBulkUpload()
- {
- FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"bulkupload", "1.0"} } } });
- // Disable parallel uploads
- SyncOptions syncOptions;
- syncOptions._parallelNetworkJobs = 0;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
- int nPUT = 0;
- int nPOST = 0;
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
- auto contentType = request.header(QNetworkRequest::ContentTypeHeader).toString();
- if (op == QNetworkAccessManager::PostOperation) {
- ++nPOST;
- if (contentType.startsWith(QStringLiteral("multipart/related; boundary="))) {
- return new FakeErrorReply(op, request, this, 400);
- }
- return nullptr;
- } else if (op == QNetworkAccessManager::PutOperation) {
- ++nPUT;
- }
- return nullptr;
- });
- fakeFolder.localModifier().insert("A/big1", 1);
- fakeFolder.localModifier().insert("A/big2", 1);
- fakeFolder.localModifier().insert("A/big3", 1);
- fakeFolder.localModifier().insert("A/big4", 1);
- fakeFolder.localModifier().insert("A/big5", 1);
- fakeFolder.localModifier().insert("A/big6", 1);
- fakeFolder.localModifier().insert("A/big7", 1);
- fakeFolder.localModifier().insert("A/big8", 1);
- fakeFolder.localModifier().insert("B/big8", 1);
- QVERIFY(!fakeFolder.syncOnce());
- QCOMPARE(nPUT, 0);
- QCOMPARE(nPOST, 1);
- nPUT = 0;
- nPOST = 0;
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(nPUT, 9);
- QCOMPARE(nPOST, 0);
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testRemoteMoveFailedInsufficientStorageLocalMoveRolledBack()
- {
- FakeFolder fakeFolder{FileInfo{}};
- // create a big shared folder with some files
- fakeFolder.remoteModifier().mkdir("big_shared_folder");
- fakeFolder.remoteModifier().mkdir("big_shared_folder/shared_files");
- fakeFolder.remoteModifier().insert("big_shared_folder/shared_files/big_shared_file_A.data", 1000);
- fakeFolder.remoteModifier().insert("big_shared_folder/shared_files/big_shared_file_B.data", 1000);
- // make sure big shared folder is synced
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("big_shared_folder/shared_files/big_shared_file_A.data"));
- QVERIFY(fakeFolder.currentLocalState().find("big_shared_folder/shared_files/big_shared_file_B.data"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // try to move from a big shared folder to your own folder
- fakeFolder.localModifier().mkdir("own_folder");
- fakeFolder.localModifier().rename(
- "big_shared_folder/shared_files/big_shared_file_A.data", "own_folder/big_shared_file_A.data");
- fakeFolder.localModifier().rename(
- "big_shared_folder/shared_files/big_shared_file_B.data", "own_folder/big_shared_file_B.data");
- // emulate server MOVE 507 error
- QObject parent;
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request,
- QIODevice *outgoingData) -> QNetworkReply * {
- Q_UNUSED(outgoingData)
- if (op == QNetworkAccessManager::CustomOperation
- && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("MOVE")) {
- return new FakeErrorReply(op, request, &parent, 507);
- }
- return nullptr;
- });
- // make sure the first sync failes and files get restored to original folder
- QVERIFY(!fakeFolder.syncOnce());
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("big_shared_folder/shared_files/big_shared_file_A.data"));
- QVERIFY(fakeFolder.currentLocalState().find("big_shared_folder/shared_files/big_shared_file_B.data"));
- QVERIFY(!fakeFolder.currentLocalState().find("own_folder/big_shared_file_A.data"));
- QVERIFY(!fakeFolder.currentLocalState().find("own_folder/big_shared_file_B.data"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testRemoteMoveFailedForbiddenLocalMoveRolledBack()
- {
- FakeFolder fakeFolder{FileInfo{}};
- // create a big shared folder with some files
- fakeFolder.remoteModifier().mkdir("big_shared_folder");
- fakeFolder.remoteModifier().mkdir("big_shared_folder/shared_files");
- fakeFolder.remoteModifier().insert("big_shared_folder/shared_files/big_shared_file_A.data", 1000);
- fakeFolder.remoteModifier().insert("big_shared_folder/shared_files/big_shared_file_B.data", 1000);
- // make sure big shared folder is synced
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("big_shared_folder/shared_files/big_shared_file_A.data"));
- QVERIFY(fakeFolder.currentLocalState().find("big_shared_folder/shared_files/big_shared_file_B.data"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // try to move from a big shared folder to your own folder
- fakeFolder.localModifier().mkdir("own_folder");
- fakeFolder.localModifier().rename(
- "big_shared_folder/shared_files/big_shared_file_A.data", "own_folder/big_shared_file_A.data");
- fakeFolder.localModifier().rename(
- "big_shared_folder/shared_files/big_shared_file_B.data", "own_folder/big_shared_file_B.data");
- // emulate server MOVE 507 error
- QObject parent;
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request,
- QIODevice *outgoingData) -> QNetworkReply * {
- Q_UNUSED(outgoingData)
- auto attributeCustomVerb = request.attribute(QNetworkRequest::CustomVerbAttribute).toString();
- if (op == QNetworkAccessManager::CustomOperation
- && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("MOVE")) {
- return new FakeErrorReply(op, request, &parent, 403);
- }
- return nullptr;
- });
- // make sure the first sync failes and files get restored to original folder
- QVERIFY(!fakeFolder.syncOnce());
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("big_shared_folder/shared_files/big_shared_file_A.data"));
- QVERIFY(fakeFolder.currentLocalState().find("big_shared_folder/shared_files/big_shared_file_B.data"));
- QVERIFY(!fakeFolder.currentLocalState().find("own_folder/big_shared_file_A.data"));
- QVERIFY(!fakeFolder.currentLocalState().find("own_folder/big_shared_file_B.data"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testFolderWithFilesInError()
- {
- FakeFolder fakeFolder{FileInfo{}};
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * {
- Q_UNUSED(outgoingData)
- if (op == QNetworkAccessManager::GetOperation) {
- const auto fileName = getFilePathFromUrl(request.url());
- if (fileName == QStringLiteral("aaa/subfolder/foo")) {
- return new FakeErrorReply(op, request, &fakeFolder.syncEngine(), 403);
- }
- }
- return nullptr;
- });
- fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa"));
- fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa/subfolder"));
- fakeFolder.remoteModifier().insert(QStringLiteral("aaa/subfolder/bar"));
- QVERIFY(fakeFolder.syncOnce());
- fakeFolder.remoteModifier().insert(QStringLiteral("aaa/subfolder/foo"));
- QVERIFY(!fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.syncOnce());
- }
- void testInvalidMtimeRecoveryAtStart()
- {
- constexpr auto INVALID_MTIME = 0;
- constexpr auto CURRENT_MTIME = 1646057277;
- FakeFolder fakeFolder{FileInfo{}};
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- const QString fooFileRootFolder("foo");
- const QString barFileRootFolder("bar");
- const QString fooFileSubFolder("subfolder/foo");
- const QString barFileSubFolder("subfolder/bar");
- const QString fooFileAaaSubFolder("aaa/subfolder/foo");
- const QString barFileAaaSubFolder("aaa/subfolder/bar");
- fakeFolder.remoteModifier().insert(fooFileRootFolder);
- fakeFolder.remoteModifier().insert(barFileRootFolder);
- fakeFolder.remoteModifier().mkdir(QStringLiteral("subfolder"));
- fakeFolder.remoteModifier().insert(fooFileSubFolder);
- fakeFolder.remoteModifier().insert(barFileSubFolder);
- fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa"));
- fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa/subfolder"));
- fakeFolder.remoteModifier().insert(fooFileAaaSubFolder);
- fakeFolder.remoteModifier().setModTime(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME));
- fakeFolder.remoteModifier().insert(barFileAaaSubFolder);
- fakeFolder.remoteModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME));
- QVERIFY(!fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.syncOnce());
- fakeFolder.remoteModifier().setModTimeKeepEtag(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME));
- fakeFolder.remoteModifier().setModTimeKeepEtag(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME));
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.syncOnce());
- auto expectedState = fakeFolder.currentLocalState();
- QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
- }
- void testInvalidMtimeRecovery()
- {
- constexpr auto INVALID_MTIME = 0;
- constexpr auto CURRENT_MTIME = 1646057277;
- FakeFolder fakeFolder{FileInfo{}};
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- const QString fooFileRootFolder("foo");
- const QString barFileRootFolder("bar");
- const QString fooFileSubFolder("subfolder/foo");
- const QString barFileSubFolder("subfolder/bar");
- const QString fooFileAaaSubFolder("aaa/subfolder/foo");
- const QString barFileAaaSubFolder("aaa/subfolder/bar");
- fakeFolder.remoteModifier().insert(fooFileRootFolder);
- fakeFolder.remoteModifier().insert(barFileRootFolder);
- fakeFolder.remoteModifier().mkdir(QStringLiteral("subfolder"));
- fakeFolder.remoteModifier().insert(fooFileSubFolder);
- fakeFolder.remoteModifier().insert(barFileSubFolder);
- fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa"));
- fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa/subfolder"));
- fakeFolder.remoteModifier().insert(fooFileAaaSubFolder);
- fakeFolder.remoteModifier().insert(barFileAaaSubFolder);
- QVERIFY(fakeFolder.syncOnce());
- fakeFolder.remoteModifier().setModTime(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME));
- fakeFolder.remoteModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME));
- QVERIFY(!fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.syncOnce());
- fakeFolder.remoteModifier().setModTimeKeepEtag(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME));
- fakeFolder.remoteModifier().setModTimeKeepEtag(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME));
- QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.syncOnce());
- auto expectedState = fakeFolder.currentLocalState();
- QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
- }
- };
- QTEST_GUILESS_MAIN(TestSyncEngine)
- #include "testsyncengine.moc"
|