/* * 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 #include "syncenginetestutils.h" #include using namespace OCC; bool itemDidComplete(const QSignalSpy &spy, const QString &path) { for(const QList &args : spy) { auto item = args[0].value(); if (item->destination() == path) return true; } return false; } bool itemDidCompleteSuccessfully(const QSignalSpy &spy, const QString &path) { for(const QList &args : spy) { auto item = args[0].value(); if (item->destination() == path) return item->_status == SyncFileItem::Success; } return false; } class TestSyncEngine : public QObject { Q_OBJECT private slots: void testFileDownload() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); 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()}; QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); 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()}; QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); 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()}; QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); 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 testLocalDelete() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); 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()}; QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); fakeFolder.localModifier().remove("A/a1"); fakeFolder.syncOnce(); QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a1")); 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'); // Upload and calculate the checksums // fakeFolder.syncOnce(); fakeFolder.syncOnce(); QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); // 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.syncOnce(); QVERIFY(!itemDidComplete(completeSpy, "a1.eml")); QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a2.eml")); QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a3.eml")); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void testRemoteChangeInMovedFolder() { // issue #5192 FakeFolder fakeFolder{FileInfo{ QString(), { FileInfo { QStringLiteral("folder"), { FileInfo{ QStringLiteral("folderA"), { { QStringLiteral("file.txt"), 400 } } }, QStringLiteral("folderB") } }}}}; QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Edit a file in a moved directory. fakeFolder.remoteModifier().setContents("folder/folderA/file.txt", 'a'); fakeFolder.remoteModifier().rename("folder/folderA", "folder/folderB/folderA"); fakeFolder.syncOnce(); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); auto oldState = fakeFolder.currentLocalState(); QVERIFY(oldState.find("folder/folderB/folderA/file.txt")); QVERIFY(!oldState.find("folder/folderA/file.txt")); // This sync should not remove the file fakeFolder.syncOnce(); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(fakeFolder.currentLocalState(), oldState); } void testSelectiveSyncModevFolder() { // issue #5224 FakeFolder fakeFolder{FileInfo{ QString(), { FileInfo { QStringLiteral("parentFolder"), { FileInfo{ QStringLiteral("subFolderA"), { { QStringLiteral("fileA.txt"), 400 } } }, FileInfo{ QStringLiteral("subFolderB"), { { QStringLiteral("fileB.txt"), 400 } } } } }}}}; QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); auto expectedServerState = fakeFolder.currentRemoteState(); // Remove subFolderA with selectiveSync: fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {"parentFolder/subFolderA/"}); fakeFolder.syncEngine().journal()->avoidReadFromDbOnNextSync("parentFolder/subFolderA/"); fakeFolder.syncOnce(); { // Nothing changed on the server QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState); // The local state should not have subFolderA auto remoteState = fakeFolder.currentRemoteState(); remoteState.remove("parentFolder/subFolderA"); QCOMPARE(fakeFolder.currentLocalState(), remoteState); } // Rename parentFolder on the server fakeFolder.remoteModifier().rename("parentFolder", "parentFolderRenamed"); expectedServerState = fakeFolder.currentRemoteState(); fakeFolder.syncOnce(); { QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState); auto remoteState = fakeFolder.currentRemoteState(); // The subFolderA should still be there on the server. QVERIFY(remoteState.find("parentFolderRenamed/subFolderA/fileA.txt")); // But not on the client because of the selective sync remoteState.remove("parentFolderRenamed/subFolderA"); QCOMPARE(fakeFolder.currentLocalState(), remoteState); } // Rename it again, locally this time. fakeFolder.localModifier().rename("parentFolderRenamed", "parentThirdName"); fakeFolder.syncOnce(); { auto remoteState = fakeFolder.currentRemoteState(); // The subFolderA should still be there on the server. QVERIFY(remoteState.find("parentThirdName/subFolderA/fileA.txt")); // But not on the client because of the selective sync remoteState.remove("parentThirdName/subFolderA"); QCOMPARE(fakeFolder.currentLocalState(), remoteState); expectedServerState = fakeFolder.currentRemoteState(); QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); fakeFolder.syncOnce(); // This sync should do nothing QCOMPARE(completeSpy.count(), 0); QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState); QCOMPARE(fakeFolder.currentLocalState(), remoteState); } } 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("subFolder"), { { QStringLiteral("fileA.txt"), 400 }, { QStringLiteral("fileB.txt"), 400, 'o' } }} }} }}}; QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); auto expectedServerState = fakeFolder.currentRemoteState(); // Remove subFolder with selectiveSync: fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {"parentFolder/subFolder/"}); fakeFolder.syncEngine().journal()->avoidReadFromDbOnNextSync("parentFolder/subFolder/"); // But touch a local file before the next sync, such that the local folder // can't be removed fakeFolder.localModifier().setContents("parentFolder/subFolder/fileB.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/subFolder")); QVERIFY(local.find("parentFolder/subFolder/fileA.txt")); QVERIFY(local.find("parentFolder/subFolder/fileB.txt")); } } } 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); } void testDirDownloadWithError() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); 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); // 503 is a fatal error fakeFolder.serverErrorPaths().append("Y/Z/d3", 503); // 503 is a fatal error QVERIFY(!fakeFolder.syncOnce()); QCoreApplication::processEvents(); // should not crash QSet seen; for(const QList &args : completeSpy) { auto item = args[0].value(); 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::FatalError); } else if(item->_file == "Y/Z/d3") { QVERIFY(item->_status != SyncFileItem::Success); } QVERIFY(item->_file != "Y/Z/d9"); // we should have aborted the sync before d9 starts } } }; QTEST_GUILESS_MAIN(TestSyncEngine) #include "testsyncengine.moc"