testallfilesdeleted.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. * This software is in the public domain, furnished "as is", without technical
  3. * support, and with no warranty, express or implied, as to its usefulness for
  4. * any purpose.
  5. *
  6. */
  7. #include <QtTest>
  8. #include "syncenginetestutils.h"
  9. #include <syncengine.h>
  10. #include <configfile.h>
  11. using namespace OCC;
  12. static void changeAllFileId(FileInfo &info) {
  13. info.fileId = generateFileId();
  14. if (!info.isDir)
  15. return;
  16. info.etag = generateEtag();
  17. for (auto &child : info.children) {
  18. changeAllFileId(child);
  19. }
  20. }
  21. /*
  22. * This test ensure that the SyncEngine::aboutToRemoveAllFiles is correctly called and that when
  23. * we the user choose to remove all files SyncJournalDb::clearFileTable makes works as expected
  24. */
  25. class TestAllFilesDeleted : public QObject
  26. {
  27. Q_OBJECT
  28. private slots:
  29. void testAllFilesDeletedKeep_data()
  30. {
  31. QTest::addColumn<bool>("deleteOnRemote");
  32. QTest::newRow("local") << false;
  33. QTest::newRow("remote") << true;
  34. }
  35. /*
  36. * In this test, all files are deleted in the client, or the server, and we simulate
  37. * that the users press "keep"
  38. */
  39. void testAllFilesDeletedKeep()
  40. {
  41. QFETCH(bool, deleteOnRemote);
  42. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  43. ConfigFile config;
  44. config.setPromptDeleteFiles(true);
  45. //Just set a blacklist so we can check it is still there. This directory does not exists but
  46. // that does not matter for our purposes.
  47. QStringList selectiveSyncBlackList = { "Q/" };
  48. fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
  49. selectiveSyncBlackList);
  50. auto initialState = fakeFolder.currentLocalState();
  51. int aboutToRemoveAllFilesCalled = 0;
  52. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  53. [&](SyncFileItem::Direction dir, std::function<void(bool)> callback) {
  54. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  55. aboutToRemoveAllFilesCalled++;
  56. QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up);
  57. callback(true);
  58. fakeFolder.syncEngine().journal()->clearFileTable(); // That's what Folder is doing
  59. });
  60. auto &modifier = deleteOnRemote ? fakeFolder.remoteModifier() : fakeFolder.localModifier();
  61. for (const auto &s : fakeFolder.currentRemoteState().children.keys())
  62. modifier.remove(s);
  63. QVERIFY(!fakeFolder.syncOnce()); // Should fail because we cancel the sync
  64. QCOMPARE(aboutToRemoveAllFilesCalled, 1);
  65. // Next sync should recover all files
  66. QVERIFY(fakeFolder.syncOnce());
  67. QCOMPARE(fakeFolder.currentLocalState(), initialState);
  68. QCOMPARE(fakeFolder.currentRemoteState(), initialState);
  69. // The selective sync blacklist should be not have been deleted.
  70. bool ok = true;
  71. QCOMPARE(fakeFolder.syncEngine().journal()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok),
  72. selectiveSyncBlackList);
  73. }
  74. void testAllFilesDeletedDelete_data()
  75. {
  76. testAllFilesDeletedKeep_data();
  77. }
  78. /*
  79. * This test is like the previous one but we simulate that the user presses "delete"
  80. */
  81. void testAllFilesDeletedDelete()
  82. {
  83. QFETCH(bool, deleteOnRemote);
  84. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  85. int aboutToRemoveAllFilesCalled = 0;
  86. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  87. [&](SyncFileItem::Direction dir, std::function<void(bool)> callback) {
  88. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  89. aboutToRemoveAllFilesCalled++;
  90. QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up);
  91. callback(false);
  92. });
  93. auto &modifier = deleteOnRemote ? fakeFolder.remoteModifier() : fakeFolder.localModifier();
  94. for (const auto &s : fakeFolder.currentRemoteState().children.keys())
  95. modifier.remove(s);
  96. QVERIFY(fakeFolder.syncOnce()); // Should succeed, and all files must then be deleted
  97. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  98. QCOMPARE(fakeFolder.currentLocalState().children.count(), 0);
  99. // Try another sync to be sure.
  100. QVERIFY(fakeFolder.syncOnce()); // Should succeed (doing nothing)
  101. QCOMPARE(aboutToRemoveAllFilesCalled, 1); // should not have been called.
  102. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  103. QCOMPARE(fakeFolder.currentLocalState().children.count(), 0);
  104. }
  105. void testNotDeleteMetaDataChange() {
  106. /**
  107. * This test make sure that we don't popup a file deleted message if all the metadata have
  108. * been updated (for example when the server is upgraded or something)
  109. **/
  110. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  111. // We never remove all files.
  112. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  113. [&] { QVERIFY(false); });
  114. QVERIFY(fakeFolder.syncOnce());
  115. for (const auto &s : fakeFolder.currentRemoteState().children.keys())
  116. fakeFolder.syncJournal().avoidRenamesOnNextSync(s); // clears all the fileid and inodes.
  117. fakeFolder.localModifier().remove("A/a1");
  118. auto expectedState = fakeFolder.currentLocalState();
  119. QVERIFY(fakeFolder.syncOnce());
  120. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  121. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  122. fakeFolder.remoteModifier().remove("B/b1");
  123. changeAllFileId(fakeFolder.remoteModifier());
  124. expectedState = fakeFolder.currentRemoteState();
  125. QVERIFY(fakeFolder.syncOnce());
  126. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  127. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  128. }
  129. void testResetServer()
  130. {
  131. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  132. int aboutToRemoveAllFilesCalled = 0;
  133. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  134. [&](SyncFileItem::Direction dir, std::function<void(bool)> callback) {
  135. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  136. aboutToRemoveAllFilesCalled++;
  137. QCOMPARE(dir, SyncFileItem::Down);
  138. callback(false);
  139. });
  140. // Some small changes
  141. fakeFolder.localModifier().mkdir("Q");
  142. fakeFolder.localModifier().insert("Q/q1");
  143. fakeFolder.localModifier().appendByte("B/b1");
  144. QVERIFY(fakeFolder.syncOnce());
  145. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  146. // Do some change localy
  147. fakeFolder.localModifier().appendByte("A/a1");
  148. // reset the server.
  149. fakeFolder.remoteModifier() = FileInfo::A12_B12_C12_S12();
  150. // Now, aboutToRemoveAllFiles with down as a direction
  151. QVERIFY(fakeFolder.syncOnce());
  152. QCOMPARE(aboutToRemoveAllFilesCalled, 1);
  153. }
  154. void testDataFingetPrint_data()
  155. {
  156. QTest::addColumn<bool>("hasInitialFingerPrint");
  157. QTest::newRow("initial finger print") << true;
  158. QTest::newRow("no initial finger print") << false;
  159. }
  160. void testDataFingetPrint()
  161. {
  162. QFETCH(bool, hasInitialFingerPrint);
  163. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  164. fakeFolder.remoteModifier().setContents("C/c1", 'N');
  165. fakeFolder.remoteModifier().setModTime("C/c1", QDateTime::currentDateTimeUtc().addDays(-2));
  166. fakeFolder.remoteModifier().remove("C/c2");
  167. if (hasInitialFingerPrint) {
  168. fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>initial_finger_print</oc:data-fingerprint>";
  169. } else {
  170. //Server support finger print, but none is set.
  171. fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint></oc:data-fingerprint>";
  172. }
  173. int fingerprintRequests = 0;
  174. fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation, const QNetworkRequest &request, QIODevice *stream) -> QNetworkReply * {
  175. auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute);
  176. if (verb == "PROPFIND") {
  177. auto data = stream->readAll();
  178. if (data.contains("data-fingerprint")) {
  179. if (request.url().path().endsWith("dav/files/admin/")) {
  180. ++fingerprintRequests;
  181. } else {
  182. fingerprintRequests = -10000; // fingerprint queried on incorrect path
  183. }
  184. }
  185. }
  186. return nullptr;
  187. });
  188. QVERIFY(fakeFolder.syncOnce());
  189. QCOMPARE(fingerprintRequests, 1);
  190. // First sync, we did not change the finger print, so the file should be downloaded as normal
  191. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  192. QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->contentChar, 'N');
  193. QVERIFY(!fakeFolder.currentRemoteState().find("C/c2"));
  194. /* Simulate a backup restoration */
  195. // A/a1 is an old file
  196. fakeFolder.remoteModifier().setContents("A/a1", 'O');
  197. fakeFolder.remoteModifier().setModTime("A/a1", QDateTime::currentDateTimeUtc().addDays(-2));
  198. // B/b1 did not exist at the time of the backup
  199. fakeFolder.remoteModifier().remove("B/b1");
  200. // B/b2 was uploaded by another user in the mean time.
  201. fakeFolder.remoteModifier().setContents("B/b2", 'N');
  202. fakeFolder.remoteModifier().setModTime("B/b2", QDateTime::currentDateTimeUtc().addDays(2));
  203. // C/c3 was removed since we made the backup
  204. fakeFolder.remoteModifier().insert("C/c3_removed");
  205. // C/c4 was moved to A/a2 since we made the backup
  206. fakeFolder.remoteModifier().rename("A/a2", "C/old_a2_location");
  207. // The admin sets the data-fingerprint property
  208. fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>new_finger_print</oc:data-fingerprint>";
  209. QVERIFY(fakeFolder.syncOnce());
  210. QCOMPARE(fingerprintRequests, 2);
  211. auto currentState = fakeFolder.currentLocalState();
  212. // Altough the local file is kept as a conflict, the server file is downloaded
  213. QCOMPARE(currentState.find("A/a1")->contentChar, 'O');
  214. auto conflict = findConflict(currentState, "A/a1");
  215. QVERIFY(conflict);
  216. QCOMPARE(conflict->contentChar, 'W');
  217. fakeFolder.localModifier().remove(conflict->path());
  218. // b1 was restored (re-uploaded)
  219. QVERIFY(currentState.find("B/b1"));
  220. // b2 has the new content (was not restored), since its mode time goes forward in time
  221. QCOMPARE(currentState.find("B/b2")->contentChar, 'N');
  222. conflict = findConflict(currentState, "B/b2");
  223. QVERIFY(conflict); // Just to be sure, we kept the old file in a conflict
  224. QCOMPARE(conflict->contentChar, 'W');
  225. fakeFolder.localModifier().remove(conflict->path());
  226. // We actually do not remove files that technically should have been removed (we don't want data-loss)
  227. QVERIFY(currentState.find("C/c3_removed"));
  228. QVERIFY(currentState.find("C/old_a2_location"));
  229. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  230. }
  231. void testSingleFileRenamed() {
  232. FakeFolder fakeFolder{FileInfo{}};
  233. int aboutToRemoveAllFilesCalled = 0;
  234. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  235. [&](SyncFileItem::Direction , std::function<void(bool)> ) {
  236. aboutToRemoveAllFilesCalled++;
  237. QFAIL("should not be called");
  238. });
  239. // add a single file
  240. fakeFolder.localModifier().insert("hello.txt");
  241. QVERIFY(fakeFolder.syncOnce());
  242. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  243. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  244. // rename it
  245. fakeFolder.localModifier().rename("hello.txt", "goodbye.txt");
  246. QVERIFY(fakeFolder.syncOnce());
  247. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  248. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  249. }
  250. void testSelectiveSyncNoPopup() {
  251. // Unselecting all folder should not cause the popup to be shown
  252. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  253. int aboutToRemoveAllFilesCalled = 0;
  254. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  255. [&](SyncFileItem::Direction , std::function<void(bool)>) {
  256. aboutToRemoveAllFilesCalled++;
  257. QFAIL("should not be called");
  258. });
  259. QVERIFY(fakeFolder.syncOnce());
  260. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  261. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  262. fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
  263. QStringList() << "A/" << "B/" << "C/" << "S/");
  264. QVERIFY(fakeFolder.syncOnce());
  265. QCOMPARE(fakeFolder.currentLocalState(), FileInfo{}); // all files should be one localy
  266. QCOMPARE(fakeFolder.currentRemoteState(), FileInfo::A12_B12_C12_S12()); // Server not changed
  267. QCOMPARE(aboutToRemoveAllFilesCalled, 0); // But we did not show the popup
  268. }
  269. };
  270. QTEST_GUILESS_MAIN(TestAllFilesDeleted)
  271. #include "testallfilesdeleted.moc"