testallfilesdeleted.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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. const auto childrenKeys = fakeFolder.currentRemoteState().children.keys();
  62. for (const auto &key : childrenKeys) {
  63. modifier.remove(key);
  64. }
  65. QVERIFY(!fakeFolder.syncOnce()); // Should fail because we cancel the sync
  66. QCOMPARE(aboutToRemoveAllFilesCalled, 1);
  67. // Next sync should recover all files
  68. QVERIFY(fakeFolder.syncOnce());
  69. QCOMPARE(fakeFolder.currentLocalState(), initialState);
  70. QCOMPARE(fakeFolder.currentRemoteState(), initialState);
  71. // The selective sync blacklist should be not have been deleted.
  72. bool ok = true;
  73. QCOMPARE(fakeFolder.syncEngine().journal()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok),
  74. selectiveSyncBlackList);
  75. }
  76. void testAllFilesDeletedDelete_data()
  77. {
  78. testAllFilesDeletedKeep_data();
  79. }
  80. /*
  81. * This test is like the previous one but we simulate that the user presses "delete"
  82. */
  83. void testAllFilesDeletedDelete()
  84. {
  85. QFETCH(bool, deleteOnRemote);
  86. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  87. int aboutToRemoveAllFilesCalled = 0;
  88. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  89. [&](SyncFileItem::Direction dir, std::function<void(bool)> callback) {
  90. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  91. aboutToRemoveAllFilesCalled++;
  92. QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up);
  93. callback(false);
  94. });
  95. auto &modifier = deleteOnRemote ? fakeFolder.remoteModifier() : fakeFolder.localModifier();
  96. const auto childrenKeys = fakeFolder.currentRemoteState().children.keys();
  97. for (const auto &key : childrenKeys) {
  98. modifier.remove(key);
  99. }
  100. QVERIFY(fakeFolder.syncOnce()); // Should succeed, and all files must then be deleted
  101. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  102. QCOMPARE(fakeFolder.currentLocalState().children.count(), 0);
  103. // Try another sync to be sure.
  104. QVERIFY(fakeFolder.syncOnce()); // Should succeed (doing nothing)
  105. QCOMPARE(aboutToRemoveAllFilesCalled, 1); // should not have been called.
  106. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  107. QCOMPARE(fakeFolder.currentLocalState().children.count(), 0);
  108. }
  109. void testNotDeleteMetaDataChange() {
  110. /**
  111. * This test make sure that we don't popup a file deleted message if all the metadata have
  112. * been updated (for example when the server is upgraded or something)
  113. **/
  114. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  115. // We never remove all files.
  116. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  117. [&] { QVERIFY(false); });
  118. QVERIFY(fakeFolder.syncOnce());
  119. const auto childrenKeys = fakeFolder.currentRemoteState().children.keys();
  120. for (const auto &s : childrenKeys) {
  121. fakeFolder.syncJournal().avoidRenamesOnNextSync(s); // clears all the fileid and inodes.
  122. }
  123. fakeFolder.localModifier().remove("A/a1");
  124. auto expectedState = fakeFolder.currentLocalState();
  125. QVERIFY(fakeFolder.syncOnce());
  126. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  127. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  128. fakeFolder.remoteModifier().remove("B/b1");
  129. changeAllFileId(fakeFolder.remoteModifier());
  130. expectedState = fakeFolder.currentRemoteState();
  131. QVERIFY(fakeFolder.syncOnce());
  132. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  133. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  134. }
  135. void testResetServer()
  136. {
  137. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  138. int aboutToRemoveAllFilesCalled = 0;
  139. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  140. [&](SyncFileItem::Direction dir, std::function<void(bool)> callback) {
  141. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  142. aboutToRemoveAllFilesCalled++;
  143. QCOMPARE(dir, SyncFileItem::Down);
  144. callback(false);
  145. });
  146. // Some small changes
  147. fakeFolder.localModifier().mkdir("Q");
  148. fakeFolder.localModifier().insert("Q/q1");
  149. fakeFolder.localModifier().appendByte("B/b1");
  150. QVERIFY(fakeFolder.syncOnce());
  151. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  152. // Do some change locally
  153. fakeFolder.localModifier().appendByte("A/a1");
  154. // reset the server.
  155. fakeFolder.remoteModifier() = FileInfo::A12_B12_C12_S12();
  156. // Now, aboutToRemoveAllFiles with down as a direction
  157. QVERIFY(fakeFolder.syncOnce());
  158. QCOMPARE(aboutToRemoveAllFilesCalled, 1);
  159. }
  160. void testDataFingetPrint_data()
  161. {
  162. QTest::addColumn<bool>("hasInitialFingerPrint");
  163. QTest::newRow("initial finger print") << true;
  164. QTest::newRow("no initial finger print") << false;
  165. }
  166. void testDataFingetPrint()
  167. {
  168. QFETCH(bool, hasInitialFingerPrint);
  169. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  170. fakeFolder.remoteModifier().setContents("C/c1", 'N');
  171. fakeFolder.remoteModifier().setModTime("C/c1", QDateTime::currentDateTimeUtc().addDays(-2));
  172. fakeFolder.remoteModifier().remove("C/c2");
  173. if (hasInitialFingerPrint) {
  174. fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>initial_finger_print</oc:data-fingerprint>";
  175. } else {
  176. //Server support finger print, but none is set.
  177. fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint></oc:data-fingerprint>";
  178. }
  179. int fingerprintRequests = 0;
  180. fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation, const QNetworkRequest &request, QIODevice *stream) -> QNetworkReply * {
  181. auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute);
  182. if (verb == "PROPFIND") {
  183. auto data = stream->readAll();
  184. if (data.contains("data-fingerprint")) {
  185. if (request.url().path().endsWith("dav/files/admin/")) {
  186. ++fingerprintRequests;
  187. } else {
  188. fingerprintRequests = -10000; // fingerprint queried on incorrect path
  189. }
  190. }
  191. }
  192. return nullptr;
  193. });
  194. QVERIFY(fakeFolder.syncOnce());
  195. QCOMPARE(fingerprintRequests, 1);
  196. // First sync, we did not change the finger print, so the file should be downloaded as normal
  197. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  198. QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->contentChar, 'N');
  199. QVERIFY(!fakeFolder.currentRemoteState().find("C/c2"));
  200. /* Simulate a backup restoration */
  201. // A/a1 is an old file
  202. fakeFolder.remoteModifier().setContents("A/a1", 'O');
  203. fakeFolder.remoteModifier().setModTime("A/a1", QDateTime::currentDateTimeUtc().addDays(-2));
  204. // B/b1 did not exist at the time of the backup
  205. fakeFolder.remoteModifier().remove("B/b1");
  206. // B/b2 was uploaded by another user in the mean time.
  207. fakeFolder.remoteModifier().setContents("B/b2", 'N');
  208. fakeFolder.remoteModifier().setModTime("B/b2", QDateTime::currentDateTimeUtc().addDays(2));
  209. // C/c3 was removed since we made the backup
  210. fakeFolder.remoteModifier().insert("C/c3_removed");
  211. // C/c4 was moved to A/a2 since we made the backup
  212. fakeFolder.remoteModifier().rename("A/a2", "C/old_a2_location");
  213. // The admin sets the data-fingerprint property
  214. fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>new_finger_print</oc:data-fingerprint>";
  215. QVERIFY(fakeFolder.syncOnce());
  216. QCOMPARE(fingerprintRequests, 2);
  217. auto currentState = fakeFolder.currentLocalState();
  218. // Although the local file is kept as a conflict, the server file is downloaded
  219. QCOMPARE(currentState.find("A/a1")->contentChar, 'O');
  220. auto conflict = findConflict(currentState, "A/a1");
  221. QVERIFY(conflict);
  222. QCOMPARE(conflict->contentChar, 'W');
  223. fakeFolder.localModifier().remove(conflict->path());
  224. // b1 was restored (re-uploaded)
  225. QVERIFY(currentState.find("B/b1"));
  226. // b2 has the new content (was not restored), since its mode time goes forward in time
  227. QCOMPARE(currentState.find("B/b2")->contentChar, 'N');
  228. conflict = findConflict(currentState, "B/b2");
  229. QVERIFY(conflict); // Just to be sure, we kept the old file in a conflict
  230. QCOMPARE(conflict->contentChar, 'W');
  231. fakeFolder.localModifier().remove(conflict->path());
  232. // We actually do not remove files that technically should have been removed (we don't want data-loss)
  233. QVERIFY(currentState.find("C/c3_removed"));
  234. QVERIFY(currentState.find("C/old_a2_location"));
  235. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  236. }
  237. void testSingleFileRenamed() {
  238. FakeFolder fakeFolder{FileInfo{}};
  239. int aboutToRemoveAllFilesCalled = 0;
  240. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  241. [&](SyncFileItem::Direction , std::function<void(bool)> ) {
  242. aboutToRemoveAllFilesCalled++;
  243. QFAIL("should not be called");
  244. });
  245. // add a single file
  246. fakeFolder.localModifier().insert("hello.txt");
  247. QVERIFY(fakeFolder.syncOnce());
  248. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  249. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  250. // rename it
  251. fakeFolder.localModifier().rename("hello.txt", "goodbye.txt");
  252. QVERIFY(fakeFolder.syncOnce());
  253. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  254. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  255. }
  256. void testSelectiveSyncNoPopup() {
  257. // Unselecting all folder should not cause the popup to be shown
  258. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  259. int aboutToRemoveAllFilesCalled = 0;
  260. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  261. [&](SyncFileItem::Direction , std::function<void(bool)>) {
  262. aboutToRemoveAllFilesCalled++;
  263. QFAIL("should not be called");
  264. });
  265. QVERIFY(fakeFolder.syncOnce());
  266. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  267. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  268. fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
  269. QStringList() << "A/" << "B/" << "C/" << "S/");
  270. QVERIFY(fakeFolder.syncOnce());
  271. QCOMPARE(fakeFolder.currentLocalState(), FileInfo{}); // all files should be one locally
  272. QCOMPARE(fakeFolder.currentRemoteState(), FileInfo::A12_B12_C12_S12()); // Server not changed
  273. QCOMPARE(aboutToRemoveAllFilesCalled, 0); // But we did not show the popup
  274. }
  275. };
  276. QTEST_GUILESS_MAIN(TestAllFilesDeleted)
  277. #include "testallfilesdeleted.moc"