testallfilesdeleted.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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, bool *cancel) {
  54. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  55. aboutToRemoveAllFilesCalled++;
  56. QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up);
  57. *cancel = 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, bool *cancel) {
  88. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  89. aboutToRemoveAllFilesCalled++;
  90. QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up);
  91. *cancel = 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, bool *cancel) {
  135. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  136. aboutToRemoveAllFilesCalled++;
  137. QCOMPARE(dir, SyncFileItem::Down);
  138. *cancel = 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("webdav/"))
  180. ++fingerprintRequests;
  181. else
  182. fingerprintRequests = -10000; // fingerprint queried on incorrect path
  183. }
  184. }
  185. return nullptr;
  186. });
  187. QVERIFY(fakeFolder.syncOnce());
  188. QCOMPARE(fingerprintRequests, 1);
  189. // First sync, we did not change the finger print, so the file should be downloaded as normal
  190. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  191. QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->contentChar, 'N');
  192. QVERIFY(!fakeFolder.currentRemoteState().find("C/c2"));
  193. /* Simulate a backup restoration */
  194. // A/a1 is an old file
  195. fakeFolder.remoteModifier().setContents("A/a1", 'O');
  196. fakeFolder.remoteModifier().setModTime("A/a1", QDateTime::currentDateTimeUtc().addDays(-2));
  197. // B/b1 did not exist at the time of the backup
  198. fakeFolder.remoteModifier().remove("B/b1");
  199. // B/b2 was uploaded by another user in the mean time.
  200. fakeFolder.remoteModifier().setContents("B/b2", 'N');
  201. fakeFolder.remoteModifier().setModTime("B/b2", QDateTime::currentDateTimeUtc().addDays(2));
  202. // C/c3 was removed since we made the backup
  203. fakeFolder.remoteModifier().insert("C/c3_removed");
  204. // C/c4 was moved to A/a2 since we made the backup
  205. fakeFolder.remoteModifier().rename("A/a2", "C/old_a2_location");
  206. // The admin sets the data-fingerprint property
  207. fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>new_finger_print</oc:data-fingerprint>";
  208. QVERIFY(fakeFolder.syncOnce());
  209. QCOMPARE(fingerprintRequests, 2);
  210. auto currentState = fakeFolder.currentLocalState();
  211. // Altough the local file is kept as a conflict, the server file is downloaded
  212. QCOMPARE(currentState.find("A/a1")->contentChar, 'O');
  213. auto conflict = findConflict(currentState, "A/a1");
  214. QVERIFY(conflict);
  215. QCOMPARE(conflict->contentChar, 'W');
  216. fakeFolder.localModifier().remove(conflict->path());
  217. // b1 was restored (re-uploaded)
  218. QVERIFY(currentState.find("B/b1"));
  219. // b2 has the new content (was not restored), since its mode time goes forward in time
  220. QCOMPARE(currentState.find("B/b2")->contentChar, 'N');
  221. conflict = findConflict(currentState, "B/b2");
  222. QVERIFY(conflict); // Just to be sure, we kept the old file in a conflict
  223. QCOMPARE(conflict->contentChar, 'W');
  224. fakeFolder.localModifier().remove(conflict->path());
  225. // We actually do not remove files that technically should have been removed (we don't want data-loss)
  226. QVERIFY(currentState.find("C/c3_removed"));
  227. QVERIFY(currentState.find("C/old_a2_location"));
  228. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  229. }
  230. void testSingleFileRenamed() {
  231. FakeFolder fakeFolder{FileInfo{}};
  232. int aboutToRemoveAllFilesCalled = 0;
  233. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  234. [&](SyncFileItem::Direction , bool *) {
  235. aboutToRemoveAllFilesCalled++;
  236. QFAIL("should not be called");
  237. });
  238. // add a single file
  239. fakeFolder.localModifier().insert("hello.txt");
  240. QVERIFY(fakeFolder.syncOnce());
  241. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  242. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  243. // rename it
  244. fakeFolder.localModifier().rename("hello.txt", "goodbye.txt");
  245. QVERIFY(fakeFolder.syncOnce());
  246. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  247. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  248. }
  249. void testSelectiveSyncNoPopup() {
  250. // Unselecting all folder should not cause the popup to be shown
  251. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  252. int aboutToRemoveAllFilesCalled = 0;
  253. QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles,
  254. [&](SyncFileItem::Direction , bool *) {
  255. aboutToRemoveAllFilesCalled++;
  256. QFAIL("should not be called");
  257. });
  258. QVERIFY(fakeFolder.syncOnce());
  259. QCOMPARE(aboutToRemoveAllFilesCalled, 0);
  260. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  261. fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
  262. QStringList() << "A/" << "B/" << "C/" << "S/");
  263. QVERIFY(fakeFolder.syncOnce());
  264. QCOMPARE(fakeFolder.currentLocalState(), FileInfo{}); // all files should be one localy
  265. QCOMPARE(fakeFolder.currentRemoteState(), FileInfo::A12_B12_C12_S12()); // Server not changed
  266. QCOMPARE(aboutToRemoveAllFilesCalled, 0); // But we did not show the popup
  267. }
  268. };
  269. QTEST_GUILESS_MAIN(TestAllFilesDeleted)
  270. #include "testallfilesdeleted.moc"