| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- /*
- * 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 <configfile.h>
- using namespace OCC;
- static void changeAllFileId(FileInfo &info) {
- info.fileId = generateFileId();
- if (!info.isDir)
- return;
- info.etag = generateEtag();
- for (auto &child : info.children) {
- changeAllFileId(child);
- }
- }
- /*
- * This test ensure that the SyncEngine::aboutToRemoveAllFiles is correctly called and that when
- * we the user choose to remove all files SyncJournalDb::clearFileTable makes works as expected
- */
- class TestAllFilesDeleted : public QObject
- {
- Q_OBJECT
- private slots:
- void testAllFilesDeletedKeep_data()
- {
- QTest::addColumn<bool>("deleteOnRemote");
- QTest::newRow("local") << false;
- QTest::newRow("remote") << true;
- }
- /*
- * In this test, all files are deleted in the client, or the server, and we simulate
- * that the users press "keep"
- */
- void testAllFilesDeletedKeep()
- {
- QFETCH(bool, deleteOnRemote);
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- ConfigFile config;
- config.setPromptDeleteFiles(true);
- //Just set a blacklist so we can check it is still there. This directory does not exists but
- // that does not matter for our purposes.
- QStringList selectiveSyncBlackList = { "Q/" };
- fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
- selectiveSyncBlackList);
- auto initialState = fakeFolder.currentLocalState();
- int aboutToRemoveAllFilesCalled = 0;
- QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
- [&](SyncFileItem::Direction dir, std::function<void(bool)> callback) {
- QCOMPARE(aboutToRemoveAllFilesCalled, 0);
- aboutToRemoveAllFilesCalled++;
- QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up);
- callback(true);
- fakeFolder.syncEngine().journal()->clearFileTable(); // That's what Folder is doing
- });
- auto &modifier = deleteOnRemote ? fakeFolder.remoteModifier() : fakeFolder.localModifier();
- const auto childrenKeys = fakeFolder.currentRemoteState().children.keys();
- for (const auto &key : childrenKeys) {
- modifier.remove(key);
- }
- QVERIFY(!fakeFolder.syncOnce()); // Should fail because we cancel the sync
- QCOMPARE(aboutToRemoveAllFilesCalled, 1);
- // Next sync should recover all files
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), initialState);
- QCOMPARE(fakeFolder.currentRemoteState(), initialState);
- // The selective sync blacklist should be not have been deleted.
- bool ok = true;
- QCOMPARE(fakeFolder.syncEngine().journal()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok),
- selectiveSyncBlackList);
- }
- void testAllFilesDeletedDelete_data()
- {
- testAllFilesDeletedKeep_data();
- }
- /*
- * This test is like the previous one but we simulate that the user presses "delete"
- */
- void testAllFilesDeletedDelete()
- {
- QFETCH(bool, deleteOnRemote);
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- int aboutToRemoveAllFilesCalled = 0;
- QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
- [&](SyncFileItem::Direction dir, std::function<void(bool)> callback) {
- QCOMPARE(aboutToRemoveAllFilesCalled, 0);
- aboutToRemoveAllFilesCalled++;
- QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up);
- callback(false);
- });
- auto &modifier = deleteOnRemote ? fakeFolder.remoteModifier() : fakeFolder.localModifier();
- const auto childrenKeys = fakeFolder.currentRemoteState().children.keys();
- for (const auto &key : childrenKeys) {
- modifier.remove(key);
- }
- QVERIFY(fakeFolder.syncOnce()); // Should succeed, and all files must then be deleted
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- QCOMPARE(fakeFolder.currentLocalState().children.count(), 0);
- // Try another sync to be sure.
- QVERIFY(fakeFolder.syncOnce()); // Should succeed (doing nothing)
- QCOMPARE(aboutToRemoveAllFilesCalled, 1); // should not have been called.
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- QCOMPARE(fakeFolder.currentLocalState().children.count(), 0);
- }
- void testNotDeleteMetaDataChange() {
- /**
- * This test make sure that we don't popup a file deleted message if all the metadata have
- * been updated (for example when the server is upgraded or something)
- **/
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- // We never remove all files.
- QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
- [&] { QVERIFY(false); });
- QVERIFY(fakeFolder.syncOnce());
- const auto childrenKeys = fakeFolder.currentRemoteState().children.keys();
- for (const auto &s : childrenKeys) {
- fakeFolder.syncJournal().avoidRenamesOnNextSync(s); // clears all the fileid and inodes.
- }
- fakeFolder.localModifier().remove("A/a1");
- auto expectedState = fakeFolder.currentLocalState();
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), expectedState);
- QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
- fakeFolder.remoteModifier().remove("B/b1");
- changeAllFileId(fakeFolder.remoteModifier());
- expectedState = fakeFolder.currentRemoteState();
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), expectedState);
- QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
- }
- void testResetServer()
- {
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- int aboutToRemoveAllFilesCalled = 0;
- QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
- [&](SyncFileItem::Direction dir, std::function<void(bool)> callback) {
- QCOMPARE(aboutToRemoveAllFilesCalled, 0);
- aboutToRemoveAllFilesCalled++;
- QCOMPARE(dir, SyncFileItem::Down);
- callback(false);
- });
- // Some small changes
- fakeFolder.localModifier().mkdir("Q");
- fakeFolder.localModifier().insert("Q/q1");
- fakeFolder.localModifier().appendByte("B/b1");
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(aboutToRemoveAllFilesCalled, 0);
- // Do some change locally
- fakeFolder.localModifier().appendByte("A/a1");
- // reset the server.
- fakeFolder.remoteModifier() = FileInfo::A12_B12_C12_S12();
- // Now, aboutToRemoveAllFiles with down as a direction
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(aboutToRemoveAllFilesCalled, 1);
- }
- void testDataFingetPrint_data()
- {
- QTest::addColumn<bool>("hasInitialFingerPrint");
- QTest::newRow("initial finger print") << true;
- QTest::newRow("no initial finger print") << false;
- }
- void testDataFingetPrint()
- {
- QFETCH(bool, hasInitialFingerPrint);
- FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- fakeFolder.remoteModifier().setContents("C/c1", 'N');
- fakeFolder.remoteModifier().setModTime("C/c1", QDateTime::currentDateTimeUtc().addDays(-2));
- fakeFolder.remoteModifier().remove("C/c2");
- if (hasInitialFingerPrint) {
- fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>initial_finger_print</oc:data-fingerprint>";
- } else {
- //Server support finger print, but none is set.
- fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint></oc:data-fingerprint>";
- }
- int fingerprintRequests = 0;
- fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation, const QNetworkRequest &request, QIODevice *stream) -> QNetworkReply * {
- auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute);
- if (verb == "PROPFIND") {
- auto data = stream->readAll();
- if (data.contains("data-fingerprint")) {
- if (request.url().path().endsWith("dav/files/admin/")) {
- ++fingerprintRequests;
- } else {
- fingerprintRequests = -10000; // fingerprint queried on incorrect path
- }
- }
- }
- return nullptr;
- });
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fingerprintRequests, 1);
- // First sync, we did not change the finger print, so the file should be downloaded as normal
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->contentChar, 'N');
- QVERIFY(!fakeFolder.currentRemoteState().find("C/c2"));
- /* Simulate a backup restoration */
- // A/a1 is an old file
- fakeFolder.remoteModifier().setContents("A/a1", 'O');
- fakeFolder.remoteModifier().setModTime("A/a1", QDateTime::currentDateTimeUtc().addDays(-2));
- // B/b1 did not exist at the time of the backup
- fakeFolder.remoteModifier().remove("B/b1");
- // B/b2 was uploaded by another user in the mean time.
- fakeFolder.remoteModifier().setContents("B/b2", 'N');
- fakeFolder.remoteModifier().setModTime("B/b2", QDateTime::currentDateTimeUtc().addDays(2));
- // C/c3 was removed since we made the backup
- fakeFolder.remoteModifier().insert("C/c3_removed");
- // C/c4 was moved to A/a2 since we made the backup
- fakeFolder.remoteModifier().rename("A/a2", "C/old_a2_location");
- // The admin sets the data-fingerprint property
- fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>new_finger_print</oc:data-fingerprint>";
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fingerprintRequests, 2);
- auto currentState = fakeFolder.currentLocalState();
- // Although the local file is kept as a conflict, the server file is downloaded
- QCOMPARE(currentState.find("A/a1")->contentChar, 'O');
- auto conflict = findConflict(currentState, "A/a1");
- QVERIFY(conflict);
- QCOMPARE(conflict->contentChar, 'W');
- fakeFolder.localModifier().remove(conflict->path());
- // b1 was restored (re-uploaded)
- QVERIFY(currentState.find("B/b1"));
- // b2 has the new content (was not restored), since its mode time goes forward in time
- QCOMPARE(currentState.find("B/b2")->contentChar, 'N');
- conflict = findConflict(currentState, "B/b2");
- QVERIFY(conflict); // Just to be sure, we kept the old file in a conflict
- QCOMPARE(conflict->contentChar, 'W');
- fakeFolder.localModifier().remove(conflict->path());
- // We actually do not remove files that technically should have been removed (we don't want data-loss)
- QVERIFY(currentState.find("C/c3_removed"));
- QVERIFY(currentState.find("C/old_a2_location"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testSingleFileRenamed() {
- FakeFolder fakeFolder{FileInfo{}};
- int aboutToRemoveAllFilesCalled = 0;
- QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
- [&](SyncFileItem::Direction , std::function<void(bool)> ) {
- aboutToRemoveAllFilesCalled++;
- QFAIL("should not be called");
- });
- // add a single file
- fakeFolder.localModifier().insert("hello.txt");
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(aboutToRemoveAllFilesCalled, 0);
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // rename it
- fakeFolder.localModifier().rename("hello.txt", "goodbye.txt");
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(aboutToRemoveAllFilesCalled, 0);
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- }
- void testSelectiveSyncNoPopup() {
- // Unselecting all folder should not cause the popup to be shown
- FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
- int aboutToRemoveAllFilesCalled = 0;
- QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
- [&](SyncFileItem::Direction , std::function<void(bool)>) {
- aboutToRemoveAllFilesCalled++;
- QFAIL("should not be called");
- });
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(aboutToRemoveAllFilesCalled, 0);
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
- QStringList() << "A/" << "B/" << "C/" << "S/");
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), FileInfo{}); // all files should be one locally
- QCOMPARE(fakeFolder.currentRemoteState(), FileInfo::A12_B12_C12_S12()); // Server not changed
- QCOMPARE(aboutToRemoveAllFilesCalled, 0); // But we did not show the popup
- }
- };
- QTEST_GUILESS_MAIN(TestAllFilesDeleted)
- #include "testallfilesdeleted.moc"
|