Unit tests for diverse conflicts in one folder.
authoralex-z <blackslayer4@gmail.com>
Sun, 21 Apr 2024 21:48:01 +0000 (23:48 +0200)
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>
Tue, 23 Apr 2024 10:18:40 +0000 (10:18 +0000)
Signed-off-by: alex-z <blackslayer4@gmail.com>
test/syncenginetestutils.cpp
test/syncenginetestutils.h
test/testsyncengine.cpp

index a12261ce31b6fb921af65b8c3de2667650112bcb..5e097a34b34264675b3b2f24880811c5d6291a9a 100644 (file)
@@ -258,6 +258,21 @@ FileInfo *FileInfo::find(PathComponents pathComponents, const bool invalidateEta
     return nullptr;
 }
 
+FileInfo FileInfo::findRecursive(PathComponents pathComponents, const bool invalidateEtags)
+{
+    auto result = find({pathComponents.takeFirst()});
+    if (!result) {
+        return *result;
+    }
+    for (const auto &pathComponent : pathComponents) {
+        if (!result) {
+            break;
+        }
+        result = result->find({pathComponent});
+    }
+    return *result;
+}
+
 FileInfo *FileInfo::createDir(const QString &relativePath)
 {
     const PathComponents pathComponents { relativePath };
index c688eddce0441303e77661f95020371e3b42fed7..49cdac710338be4b79c6b287bcd27218585ff068 100644 (file)
@@ -147,6 +147,7 @@ public:
     void setE2EE(const QString &relativepath, const bool enabled) override;
 
     FileInfo *find(PathComponents pathComponents, const bool invalidateEtags = false);
+    FileInfo findRecursive(PathComponents pathComponents, const bool invalidateEtags = false);
 
     FileInfo *createDir(const QString &relativePath);
 
index 82a3c2390677595e58344010fc31c2c13cf8ff43..59b41ffc46010b31c212fe3af344b879b51abf53 100644 (file)
@@ -1703,6 +1703,147 @@ private slots:
         QVERIFY(fakeFolder.syncOnce());
     }
 
+    void testServer_caseClash_createDiverseConflictsInsideOneFolderAndSolveThem()
+    {
+        FakeFolder fakeFolder{FileInfo{}};
+
+        const QStringList conflictsFolderPathComponents = {"Documents", "DiverseConflicts"};
+
+        QString diverseConflictsFolderPath;
+        for (const auto &conflictsFolderPathComponent : conflictsFolderPathComponents) {
+            if (diverseConflictsFolderPath.isEmpty()) {
+                diverseConflictsFolderPath += conflictsFolderPathComponent;
+            } else {
+                diverseConflictsFolderPath += "/" + conflictsFolderPathComponent;
+            }
+            fakeFolder.remoteModifier().mkdir(diverseConflictsFolderPath);
+        }
+
+        constexpr auto testLowerCaseFile = "testfile";
+        constexpr auto testUpperCaseFile = "TESTFILE";
+
+        constexpr auto testLowerCaseFolder = "testfolder";
+        constexpr auto testUpperCaseFolder = "TESTFOLDER";
+
+        constexpr auto testInvalidCharFolder = "Really?";
+
+        fakeFolder.remoteModifier().insert(diverseConflictsFolderPath + "/" + testLowerCaseFile);
+        fakeFolder.remoteModifier().insert(diverseConflictsFolderPath + "/" + testUpperCaseFile);
+
+        fakeFolder.remoteModifier().mkdir(diverseConflictsFolderPath + "/" + testLowerCaseFolder);
+        fakeFolder.remoteModifier().mkdir(diverseConflictsFolderPath + "/" + testUpperCaseFolder);
+
+        fakeFolder.remoteModifier().mkdir(diverseConflictsFolderPath + "/" + testInvalidCharFolder);
+
+#if defined Q_OS_LINUX
+        constexpr auto shouldHaveCaseClashConflict = false;
+#else
+        constexpr auto shouldHaveCaseClashConflict = true;
+#endif
+
+        ItemCompletedSpy completeSpy(fakeFolder);
+
+        fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem);
+        QVERIFY(fakeFolder.syncOnce());
+
+        // verify the parent of a folder where caseclash and invalidchar conflicts were found, has corresponding flags set (conflict info must get propagated to the very top)
+        const auto diverseConflicttsFolderParent = completeSpy.findItem(conflictsFolderPathComponents.first());
+        QVERIFY(diverseConflicttsFolderParent);
+        QVERIFY(diverseConflicttsFolderParent->_isAnyCaseClashChild);
+        QVERIFY(diverseConflicttsFolderParent->_isAnyInvalidCharChild);
+        completeSpy.clear();
+
+        auto diverseConflictsFolderInfo = fakeFolder.currentLocalState().findRecursive(diverseConflictsFolderPath);
+        QVERIFY(!diverseConflictsFolderInfo.name.isEmpty());
+
+        auto conflictsFile = findCaseClashConflicts(diverseConflictsFolderInfo);
+        QCOMPARE(conflictsFile.size(), shouldHaveCaseClashConflict ? 1 : 0);
+        const auto hasFileCaseClashConflict = expectConflict(diverseConflictsFolderInfo, testLowerCaseFile);
+        QCOMPARE(hasFileCaseClashConflict, shouldHaveCaseClashConflict ? true : false);
+
+        fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem);
+        QVERIFY(fakeFolder.syncOnce());
+
+        diverseConflictsFolderInfo = fakeFolder.currentLocalState().findRecursive(diverseConflictsFolderPath);
+        QVERIFY(!diverseConflictsFolderInfo.name.isEmpty());
+
+        conflictsFile = findCaseClashConflicts(diverseConflictsFolderInfo);
+        QCOMPARE(conflictsFile.size(), shouldHaveCaseClashConflict ? 1 : 0);
+
+        if (shouldHaveCaseClashConflict) {
+            const auto conflictFileName = QString{conflictsFile.constFirst()};
+            qDebug() << conflictFileName;
+            CaseClashConflictSolver conflictSolver(fakeFolder.localPath() + diverseConflictsFolderPath + "/" + testLowerCaseFile,
+                                                   fakeFolder.localPath() + conflictFileName,
+                                                   QStringLiteral("/"),
+                                                   fakeFolder.localPath(),
+                                                   fakeFolder.account(),
+                                                   &fakeFolder.syncJournal());
+
+            QSignalSpy conflictSolverDone(&conflictSolver, &CaseClashConflictSolver::done);
+
+            conflictSolver.solveConflict("testfile2");
+
+            QVERIFY(conflictSolverDone.wait());
+
+            QVERIFY(fakeFolder.syncOnce());
+
+            diverseConflictsFolderInfo = fakeFolder.currentLocalState().findRecursive(diverseConflictsFolderPath);
+            QVERIFY(!diverseConflictsFolderInfo.name.isEmpty());
+
+            conflictsFile = findCaseClashConflicts(diverseConflictsFolderInfo);
+            QCOMPARE(conflictsFile.size(), 0);
+
+            fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem);
+            QVERIFY(fakeFolder.syncOnce());
+
+            // After solving file conflict, verify that we did not lose conflict detection of caseclash and invalidchar for folders
+            for (auto it = completeSpy.begin(); it != completeSpy.end(); ++it) {
+                auto item = (*it).first().value<OCC::SyncFileItemPtr>();
+                item = nullptr;
+            }
+            auto invalidFilenameConflictFolderItem = completeSpy.findItem(diverseConflictsFolderPath + "/" + testInvalidCharFolder);
+            QVERIFY(invalidFilenameConflictFolderItem);
+            QVERIFY(invalidFilenameConflictFolderItem->_status == SyncFileItem::FileNameInvalid);
+
+            auto caseClashConflictFolderItemLower = completeSpy.findItem(diverseConflictsFolderPath + "/" + testLowerCaseFolder);
+            auto caseClashConflictFolderItemUpper = completeSpy.findItem(diverseConflictsFolderPath + "/" + testUpperCaseFolder);
+            completeSpy.clear();
+   
+            // we always create UPPERCASE folder in current syncengine logic for now and then create a conflict for a lowercase folder, but this may change, so keep this check more future proof
+            const auto upperOrLowerCaseFolderCaseClashFound =
+                (caseClashConflictFolderItemLower && caseClashConflictFolderItemLower->_status == SyncFileItem::FileNameClash)
+                || (caseClashConflictFolderItemUpper && caseClashConflictFolderItemUpper->_status == SyncFileItem::FileNameClash);
+
+            QVERIFY(upperOrLowerCaseFolderCaseClashFound);
+
+            // solve case clash folders conflict
+            CaseClashConflictSolver conflictSolverCaseClashForFolder(fakeFolder.localPath() + diverseConflictsFolderPath + "/" + testLowerCaseFolder,
+                                                    fakeFolder.localPath() + diverseConflictsFolderPath + "/" + testUpperCaseFolder,
+                                                   QStringLiteral("/"),
+                                                   fakeFolder.localPath(),
+                                                   fakeFolder.account(),
+                                                   &fakeFolder.syncJournal());
+
+            QSignalSpy conflictSolverCaseClashForFolderDone(&conflictSolverCaseClashForFolder, &CaseClashConflictSolver::done);
+            conflictSolverCaseClashForFolder.solveConflict("testfolder1");
+            QVERIFY(conflictSolverCaseClashForFolderDone.wait());
+            QVERIFY(fakeFolder.syncOnce());
+
+            // verify no case clash conflicts folder items are found
+            caseClashConflictFolderItemLower = completeSpy.findItem(diverseConflictsFolderPath + "/" + testLowerCaseFolder);
+            caseClashConflictFolderItemUpper = completeSpy.findItem(diverseConflictsFolderPath + "/" + testUpperCaseFolder);
+            QVERIFY((!caseClashConflictFolderItemLower || caseClashConflictFolderItemLower->_file.isEmpty())
+                    && (!caseClashConflictFolderItemUpper || caseClashConflictFolderItemUpper->_file.isEmpty()));
+
+            // veriy invalid filename conflict folder item is still present
+            invalidFilenameConflictFolderItem = completeSpy.findItem(diverseConflictsFolderPath + "/" + testInvalidCharFolder);
+            completeSpy.clear();
+            QVERIFY(invalidFilenameConflictFolderItem);
+            QVERIFY(invalidFilenameConflictFolderItem->_status == SyncFileItem::FileNameInvalid);
+        }
+    }
+
     void testExistingFolderBecameBig()
     {
         constexpr auto testFolder = "folder";