TestMerge.cpp 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500
  1. /*
  2. * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 2 or (at your option)
  7. * version 3 of the License.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include "TestMerge.h"
  18. #include "mock/MockClock.h"
  19. #include "core/Merger.h"
  20. #include "core/Metadata.h"
  21. #include "crypto/Crypto.h"
  22. #include <QSignalSpy>
  23. #include <QTest>
  24. QTEST_GUILESS_MAIN(TestMerge)
  25. namespace
  26. {
  27. MockClock* m_clock = nullptr;
  28. } // namespace
  29. void TestMerge::initTestCase()
  30. {
  31. qRegisterMetaType<Entry*>("Entry*");
  32. qRegisterMetaType<Group*>("Group*");
  33. QVERIFY(Crypto::init());
  34. }
  35. void TestMerge::init()
  36. {
  37. Q_ASSERT(m_clock == nullptr);
  38. m_clock = new MockClock(2010, 5, 5, 10, 30, 10);
  39. MockClock::setup(m_clock);
  40. }
  41. void TestMerge::cleanup()
  42. {
  43. MockClock::teardown();
  44. m_clock = nullptr;
  45. }
  46. /**
  47. * Merge an existing database into a new one.
  48. * All the entries of the existing should end
  49. * up in the new one.
  50. */
  51. void TestMerge::testMergeIntoNew()
  52. {
  53. QScopedPointer<Database> dbSource(createTestDatabase());
  54. QScopedPointer<Database> dbDestination(new Database());
  55. Merger merger(dbSource.data(), dbDestination.data());
  56. merger.merge();
  57. QCOMPARE(dbDestination->rootGroup()->children().size(), 2);
  58. QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().size(), 2);
  59. // Test for retention of history
  60. QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(0)->historyItems().isEmpty(), false);
  61. }
  62. /**
  63. * Merging when no changes occurred should not
  64. * have any side effect.
  65. */
  66. void TestMerge::testMergeNoChanges()
  67. {
  68. QScopedPointer<Database> dbDestination(createTestDatabase());
  69. QScopedPointer<Database> dbSource(
  70. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  71. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  72. QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2);
  73. m_clock->advanceSecond(1);
  74. Merger merger1(dbSource.data(), dbDestination.data());
  75. auto changes = merger1.merge();
  76. QVERIFY(changes.isEmpty());
  77. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  78. QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2);
  79. m_clock->advanceSecond(1);
  80. Merger merger2(dbSource.data(), dbDestination.data());
  81. changes = merger2.merge();
  82. QVERIFY(changes.isEmpty());
  83. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  84. QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2);
  85. }
  86. /**
  87. * Merging without database custom data (used by imports and KeeShare)
  88. */
  89. void TestMerge::testMergeCustomData()
  90. {
  91. QScopedPointer<Database> dbDestination(createTestDatabase());
  92. QScopedPointer<Database> dbSource(
  93. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  94. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  95. QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2);
  96. dbDestination->metadata()->customData()->set("TEST_CUSTOM_DATA", "OLD TESTING");
  97. m_clock->advanceSecond(1);
  98. dbSource->metadata()->customData()->set("TEST_CUSTOM_DATA", "TESTING");
  99. // First check that the custom data is not merged when skipped
  100. Merger merger1(dbSource.data(), dbDestination.data());
  101. merger1.setSkipDatabaseCustomData(true);
  102. auto changes = merger1.merge();
  103. QVERIFY(changes.isEmpty());
  104. QCOMPARE(dbDestination->metadata()->customData()->value("TEST_CUSTOM_DATA"), QString("OLD TESTING"));
  105. // Second check that the custom data is merged otherwise
  106. Merger merger2(dbSource.data(), dbDestination.data());
  107. changes = merger2.merge();
  108. QCOMPARE(changes.size(), 1);
  109. QCOMPARE(dbDestination->metadata()->customData()->value("TEST_CUSTOM_DATA"), QString("TESTING"));
  110. }
  111. /**
  112. * If the entry is updated in the source database, the update
  113. * should propagate in the destination database.
  114. */
  115. void TestMerge::testResolveConflictNewer()
  116. {
  117. QScopedPointer<Database> dbDestination(createTestDatabase());
  118. QScopedPointer<Database> dbSource(
  119. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  120. // sanity check
  121. QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group1");
  122. QVERIFY(groupSourceInitial != nullptr);
  123. QCOMPARE(groupSourceInitial->entries().size(), 2);
  124. QPointer<Group> groupDestinationInitial = dbSource->rootGroup()->findChildByName("group1");
  125. QVERIFY(groupDestinationInitial != nullptr);
  126. QCOMPARE(groupDestinationInitial->entries().size(), 2);
  127. QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
  128. QVERIFY(entrySourceInitial != nullptr);
  129. QVERIFY(entrySourceInitial->group() == groupSourceInitial);
  130. const TimeInfo entrySourceInitialTimeInfo = entrySourceInitial->timeInfo();
  131. const TimeInfo groupSourceInitialTimeInfo = groupSourceInitial->timeInfo();
  132. const TimeInfo groupDestinationInitialTimeInfo = groupDestinationInitial->timeInfo();
  133. // Make sure the two changes have a different timestamp.
  134. m_clock->advanceSecond(1);
  135. // make this entry newer than in destination db
  136. entrySourceInitial->beginUpdate();
  137. entrySourceInitial->setPassword("password");
  138. entrySourceInitial->endUpdate();
  139. const TimeInfo entrySourceUpdatedTimeInfo = entrySourceInitial->timeInfo();
  140. const TimeInfo groupSourceUpdatedTimeInfo = groupSourceInitial->timeInfo();
  141. QVERIFY(entrySourceInitialTimeInfo != entrySourceUpdatedTimeInfo);
  142. QVERIFY(groupSourceInitialTimeInfo == groupSourceUpdatedTimeInfo);
  143. QVERIFY(groupSourceInitialTimeInfo == groupDestinationInitialTimeInfo);
  144. // Make sure the merge changes have a different timestamp.
  145. m_clock->advanceSecond(1);
  146. Merger merger(dbSource.data(), dbDestination.data());
  147. merger.merge();
  148. // sanity check
  149. QPointer<Group> groupDestinationMerged = dbDestination->rootGroup()->findChildByName("group1");
  150. QVERIFY(groupDestinationMerged != nullptr);
  151. QCOMPARE(groupDestinationMerged->entries().size(), 2);
  152. QCOMPARE(groupDestinationMerged->timeInfo(), groupDestinationInitialTimeInfo);
  153. QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
  154. QVERIFY(entryDestinationMerged != nullptr);
  155. QVERIFY(entryDestinationMerged->group() != nullptr);
  156. QCOMPARE(entryDestinationMerged->password(), QString("password"));
  157. QCOMPARE(entryDestinationMerged->timeInfo(), entrySourceUpdatedTimeInfo);
  158. // When updating an entry, it should not end up in the
  159. // deleted objects.
  160. for (DeletedObject deletedObject : dbDestination->deletedObjects()) {
  161. QVERIFY(deletedObject.uuid != entryDestinationMerged->uuid());
  162. }
  163. }
  164. /**
  165. * If the entry is updated in the source database, and the
  166. * destination database after, the entry should remain the
  167. * same.
  168. */
  169. void TestMerge::testResolveConflictExisting()
  170. {
  171. QScopedPointer<Database> dbDestination(createTestDatabase());
  172. QScopedPointer<Database> dbSource(
  173. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  174. // sanity check
  175. QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group1");
  176. QVERIFY(groupSourceInitial != nullptr);
  177. QCOMPARE(groupSourceInitial->entries().size(), 2);
  178. QPointer<Group> groupDestinationInitial = dbDestination->rootGroup()->findChildByName("group1");
  179. QVERIFY(groupDestinationInitial != nullptr);
  180. QCOMPARE(groupSourceInitial->entries().size(), 2);
  181. QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
  182. QVERIFY(entrySourceInitial != nullptr);
  183. QVERIFY(entrySourceInitial->group() == groupSourceInitial);
  184. const TimeInfo entrySourceInitialTimeInfo = entrySourceInitial->timeInfo();
  185. const TimeInfo groupSourceInitialTimeInfo = groupSourceInitial->timeInfo();
  186. const TimeInfo groupDestinationInitialTimeInfo = groupDestinationInitial->timeInfo();
  187. // Make sure the two changes have a different timestamp.
  188. m_clock->advanceSecond(1);
  189. // make this entry older than in destination db
  190. entrySourceInitial->beginUpdate();
  191. entrySourceInitial->setPassword("password1");
  192. entrySourceInitial->endUpdate();
  193. const TimeInfo entrySourceUpdatedOlderTimeInfo = entrySourceInitial->timeInfo();
  194. const TimeInfo groupSourceUpdatedOlderTimeInfo = groupSourceInitial->timeInfo();
  195. QPointer<Group> groupDestinationUpdated = dbDestination->rootGroup()->findChildByName("group1");
  196. QVERIFY(groupDestinationUpdated != nullptr);
  197. QCOMPARE(groupDestinationUpdated->entries().size(), 2);
  198. QPointer<Entry> entryDestinationUpdated = dbDestination->rootGroup()->findEntryByPath("entry1");
  199. QVERIFY(entryDestinationUpdated != nullptr);
  200. QVERIFY(entryDestinationUpdated->group() == groupDestinationUpdated);
  201. // Make sure the two changes have a different timestamp.
  202. m_clock->advanceSecond(1);
  203. // make this entry newer than in source db
  204. entryDestinationUpdated->beginUpdate();
  205. entryDestinationUpdated->setPassword("password2");
  206. entryDestinationUpdated->endUpdate();
  207. const TimeInfo entryDestinationUpdatedNewerTimeInfo = entryDestinationUpdated->timeInfo();
  208. const TimeInfo groupDestinationUpdatedNewerTimeInfo = groupDestinationUpdated->timeInfo();
  209. QVERIFY(entrySourceUpdatedOlderTimeInfo != entrySourceInitialTimeInfo);
  210. QVERIFY(entrySourceUpdatedOlderTimeInfo != entryDestinationUpdatedNewerTimeInfo);
  211. QVERIFY(groupSourceInitialTimeInfo == groupSourceUpdatedOlderTimeInfo);
  212. QVERIFY(groupDestinationInitialTimeInfo == groupDestinationUpdatedNewerTimeInfo);
  213. QVERIFY(groupSourceInitialTimeInfo == groupDestinationInitialTimeInfo);
  214. // Make sure the merge changes have a different timestamp.
  215. m_clock->advanceSecond(1);
  216. Merger merger(dbSource.data(), dbDestination.data());
  217. merger.merge();
  218. // sanity check
  219. QPointer<Group> groupDestinationMerged = dbDestination->rootGroup()->findChildByName("group1");
  220. QVERIFY(groupDestinationMerged != nullptr);
  221. QCOMPARE(groupDestinationMerged->entries().size(), 2);
  222. QCOMPARE(groupDestinationMerged->timeInfo(), groupDestinationUpdatedNewerTimeInfo);
  223. QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
  224. QVERIFY(entryDestinationMerged != nullptr);
  225. QCOMPARE(entryDestinationMerged->password(), QString("password2"));
  226. QCOMPARE(entryDestinationMerged->timeInfo(), entryDestinationUpdatedNewerTimeInfo);
  227. // When updating an entry, it should not end up in the
  228. // deleted objects.
  229. for (DeletedObject deletedObject : dbDestination->deletedObjects()) {
  230. QVERIFY(deletedObject.uuid != entryDestinationMerged->uuid());
  231. }
  232. }
  233. void TestMerge::testResolveConflictTemplate(
  234. int mergeMode,
  235. std::function<void(Database*, const QMap<const char*, QDateTime>&)> verification)
  236. {
  237. QMap<const char*, QDateTime> timestamps;
  238. timestamps["initialTime"] = m_clock->currentDateTimeUtc();
  239. QScopedPointer<Database> dbDestination(createTestDatabase());
  240. auto deletedEntry1 = new Entry();
  241. deletedEntry1->setUuid(QUuid::createUuid());
  242. deletedEntry1->beginUpdate();
  243. deletedEntry1->setGroup(dbDestination->rootGroup());
  244. deletedEntry1->setTitle("deletedDestination");
  245. deletedEntry1->endUpdate();
  246. auto deletedEntry2 = new Entry();
  247. deletedEntry2->setUuid(QUuid::createUuid());
  248. deletedEntry2->beginUpdate();
  249. deletedEntry2->setGroup(dbDestination->rootGroup());
  250. deletedEntry2->setTitle("deletedSource");
  251. deletedEntry2->endUpdate();
  252. QScopedPointer<Database> dbSource(
  253. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneIncludeHistory, Group::CloneIncludeEntries));
  254. timestamps["oldestCommonHistoryTime"] = m_clock->currentDateTimeUtc();
  255. // sanity check
  256. QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().size(), 2);
  257. QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(0)->historyItems().count(), 1);
  258. QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(1)->historyItems().count(), 1);
  259. QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().size(), 2);
  260. QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().at(0)->historyItems().count(), 1);
  261. QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().at(1)->historyItems().count(), 1);
  262. // simulate some work in the dbs (manipulate the history)
  263. QPointer<Entry> destinationEntry1 = dbDestination->rootGroup()->children().at(0)->entries().at(0);
  264. QPointer<Entry> destinationEntry2 = dbDestination->rootGroup()->children().at(0)->entries().at(1);
  265. QPointer<Entry> sourceEntry1 = dbSource->rootGroup()->children().at(0)->entries().at(0);
  266. QPointer<Entry> sourceEntry2 = dbSource->rootGroup()->children().at(0)->entries().at(1);
  267. timestamps["newestCommonHistoryTime"] = m_clock->advanceMinute(1);
  268. destinationEntry1->beginUpdate();
  269. destinationEntry1->setNotes("1 Common");
  270. destinationEntry1->endUpdate();
  271. destinationEntry2->beginUpdate();
  272. destinationEntry2->setNotes("1 Common");
  273. destinationEntry2->endUpdate();
  274. sourceEntry1->beginUpdate();
  275. sourceEntry1->setNotes("1 Common");
  276. sourceEntry1->endUpdate();
  277. sourceEntry2->beginUpdate();
  278. sourceEntry2->setNotes("1 Common");
  279. sourceEntry2->endUpdate();
  280. timestamps["oldestDivergingHistoryTime"] = m_clock->advanceSecond(1);
  281. destinationEntry2->beginUpdate();
  282. destinationEntry2->setNotes("2 Destination");
  283. destinationEntry2->endUpdate();
  284. sourceEntry1->beginUpdate();
  285. sourceEntry1->setNotes("2 Source");
  286. sourceEntry1->endUpdate();
  287. timestamps["newestDivergingHistoryTime"] = m_clock->advanceHour(1);
  288. destinationEntry1->beginUpdate();
  289. destinationEntry1->setNotes("3 Destination");
  290. destinationEntry1->endUpdate();
  291. sourceEntry2->beginUpdate();
  292. sourceEntry2->setNotes("3 Source");
  293. sourceEntry2->endUpdate();
  294. // sanity check
  295. QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(0)->historyItems().count(), 3);
  296. QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(1)->historyItems().count(), 3);
  297. QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().at(0)->historyItems().count(), 3);
  298. QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().at(1)->historyItems().count(), 3);
  299. m_clock->advanceMinute(1);
  300. QPointer<Entry> deletedEntryDestination = dbDestination->rootGroup()->findEntryByPath("deletedDestination");
  301. dbDestination->recycleEntry(deletedEntryDestination);
  302. QPointer<Entry> deletedEntrySource = dbSource->rootGroup()->findEntryByPath("deletedSource");
  303. dbSource->recycleEntry(deletedEntrySource);
  304. m_clock->advanceMinute(1);
  305. auto destinationEntrySingle = new Entry();
  306. destinationEntrySingle->setUuid(QUuid::createUuid());
  307. destinationEntrySingle->beginUpdate();
  308. destinationEntrySingle->setGroup(dbDestination->rootGroup()->children().at(1));
  309. destinationEntrySingle->setTitle("entryDestination");
  310. destinationEntrySingle->endUpdate();
  311. auto sourceEntrySingle = new Entry();
  312. sourceEntrySingle->setUuid(QUuid::createUuid());
  313. sourceEntrySingle->beginUpdate();
  314. sourceEntrySingle->setGroup(dbSource->rootGroup()->children().at(1));
  315. sourceEntrySingle->setTitle("entrySource");
  316. sourceEntrySingle->endUpdate();
  317. dbDestination->rootGroup()->setMergeMode(static_cast<Group::MergeMode>(mergeMode));
  318. // Make sure the merge changes have a different timestamp.
  319. timestamps["mergeTime"] = m_clock->advanceSecond(1);
  320. Merger merger(dbSource.data(), dbDestination.data());
  321. merger.merge();
  322. QPointer<Group> mergedRootGroup = dbDestination->rootGroup();
  323. QCOMPARE(mergedRootGroup->entries().size(), 0);
  324. // Both databases contain their own generated recycleBin - just one is considered a real recycleBin, the other
  325. // exists as normal group, therefore only one entry is considered deleted
  326. QCOMPARE(dbDestination->metadata()->recycleBin()->entries().size(), 1);
  327. QPointer<Group> mergedGroup1 = mergedRootGroup->children().at(0);
  328. QPointer<Group> mergedGroup2 = mergedRootGroup->children().at(1);
  329. QVERIFY(mergedGroup1);
  330. QVERIFY(mergedGroup2);
  331. QCOMPARE(mergedGroup2->entries().size(), 2);
  332. QVERIFY(mergedGroup1->entries().at(0));
  333. QVERIFY(mergedGroup1->entries().at(1));
  334. verification(dbDestination.data(), timestamps);
  335. QVERIFY(dbDestination->rootGroup()->findEntryByPath("entryDestination"));
  336. QVERIFY(dbDestination->rootGroup()->findEntryByPath("entrySource"));
  337. }
  338. void TestMerge::testDeletionConflictTemplate(int mergeMode,
  339. std::function<void(Database*, const QMap<QString, QUuid>&)> verification)
  340. {
  341. QMap<QString, QUuid> identifiers;
  342. m_clock->currentDateTimeUtc();
  343. QScopedPointer<Database> dbDestination(createTestDatabase());
  344. // scenarios:
  345. // entry directly deleted in source before updated in target
  346. // entry directly deleted in source after updated in target
  347. // entry directly deleted in target before updated in source
  348. // entry directly deleted in target after updated in source
  349. // entry indirectly deleted in source before updated in target
  350. // entry indirectly deleted in source after updated in target
  351. // entry indirectly deleted in target before updated in source
  352. // entry indirectly deleted in target after updated in source
  353. auto createGroup = [&](const char* name, Group* parent) {
  354. auto group = new Group();
  355. group->setUuid(QUuid::createUuid());
  356. group->setName(name);
  357. group->setParent(parent, 0);
  358. identifiers[group->name()] = group->uuid();
  359. return group;
  360. };
  361. auto createEntry = [&](const char* title, Group* parent) {
  362. auto entry = new Entry();
  363. entry->setUuid(QUuid::createUuid());
  364. entry->setTitle(title);
  365. entry->setGroup(parent);
  366. identifiers[entry->title()] = entry->uuid();
  367. return entry;
  368. };
  369. auto changeEntry = [](Entry* entry) {
  370. entry->beginUpdate();
  371. entry->setNotes("Change");
  372. entry->endUpdate();
  373. };
  374. Group* directlyDeletedEntryGroup = createGroup("DirectlyDeletedEntries", dbDestination->rootGroup());
  375. createEntry("EntryDeletedInSourceBeforeChangedInTarget", directlyDeletedEntryGroup);
  376. createEntry("EntryDeletedInSourceAfterChangedInTarget", directlyDeletedEntryGroup);
  377. createEntry("EntryDeletedInTargetBeforeChangedInSource", directlyDeletedEntryGroup);
  378. createEntry("EntryDeletedInTargetAfterChangedInSource", directlyDeletedEntryGroup);
  379. Group* groupDeletedInSourceBeforeEntryUpdatedInTarget =
  380. createGroup("GroupDeletedInSourceBeforeEntryUpdatedInTarget", dbDestination->rootGroup());
  381. createEntry("EntryDeletedInSourceBeforeEntryUpdatedInTarget", groupDeletedInSourceBeforeEntryUpdatedInTarget);
  382. Group* groupDeletedInSourceAfterEntryUpdatedInTarget =
  383. createGroup("GroupDeletedInSourceAfterEntryUpdatedInTarget", dbDestination->rootGroup());
  384. createEntry("EntryDeletedInSourceAfterEntryUpdatedInTarget", groupDeletedInSourceAfterEntryUpdatedInTarget);
  385. Group* groupDeletedInTargetBeforeEntryUpdatedInSource =
  386. createGroup("GroupDeletedInTargetBeforeEntryUpdatedInSource", dbDestination->rootGroup());
  387. createEntry("EntryDeletedInTargetBeforeEntryUpdatedInSource", groupDeletedInTargetBeforeEntryUpdatedInSource);
  388. Group* groupDeletedInTargetAfterEntryUpdatedInSource =
  389. createGroup("GroupDeletedInTargetAfterEntryUpdatedInSource", dbDestination->rootGroup());
  390. createEntry("EntryDeletedInTargetAfterEntryUpdatedInSource", groupDeletedInTargetAfterEntryUpdatedInSource);
  391. QScopedPointer<Database> dbSource(
  392. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneIncludeHistory, Group::CloneIncludeEntries));
  393. QPointer<Entry> sourceEntryDeletedInSourceBeforeChangedInTarget =
  394. dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]);
  395. QPointer<Entry> targetEntryDeletedInSourceBeforeChangedInTarget =
  396. dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]);
  397. QPointer<Entry> sourceEntryDeletedInSourceAfterChangedInTarget =
  398. dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceAfterChangedInTarget"]);
  399. QPointer<Entry> targetEntryDeletedInSourceAfterChangedInTarget =
  400. dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceAfterChangedInTarget"]);
  401. QPointer<Entry> sourceEntryDeletedInTargetBeforeChangedInSource =
  402. dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeChangedInSource"]);
  403. QPointer<Entry> targetEntryDeletedInTargetBeforeChangedInSource =
  404. dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeChangedInSource"]);
  405. QPointer<Entry> sourceEntryDeletedInTargetAfterChangedInSource =
  406. dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetAfterChangedInSource"]);
  407. QPointer<Entry> targetEntryDeletedInTargetAfterChangedInSource =
  408. dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetAfterChangedInSource"]);
  409. QPointer<Group> sourceGroupDeletedInSourceBeforeEntryUpdatedInTarget =
  410. dbSource->rootGroup()->findGroupByUuid(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]);
  411. QPointer<Entry> targetEntryDeletedInSourceBeforeEntryUpdatedInTarget =
  412. dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]);
  413. QPointer<Group> sourceGroupDeletedInSourceAfterEntryUpdatedInTarget =
  414. dbSource->rootGroup()->findGroupByUuid(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]);
  415. QPointer<Entry> targetEntryDeletedInSourceAfterEntryUpdatedInTarget =
  416. dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]);
  417. QPointer<Group> targetGroupDeletedInTargetBeforeEntryUpdatedInSource =
  418. dbDestination->rootGroup()->findGroupByUuid(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]);
  419. QPointer<Entry> sourceEntryDeletedInTargetBeforeEntryUpdatedInSource =
  420. dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]);
  421. QPointer<Group> targetGroupDeletedInTargetAfterEntryUpdatedInSource =
  422. dbDestination->rootGroup()->findGroupByUuid(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]);
  423. QPointer<Entry> sourceEntryDeletedInTargetAfterEntryUpdatedInSoruce =
  424. dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]);
  425. // simulate some work in the dbs (manipulate the history)
  426. m_clock->advanceMinute(1);
  427. delete sourceEntryDeletedInSourceBeforeChangedInTarget.data();
  428. changeEntry(targetEntryDeletedInSourceAfterChangedInTarget);
  429. delete targetEntryDeletedInTargetBeforeChangedInSource.data();
  430. changeEntry(sourceEntryDeletedInTargetAfterChangedInSource);
  431. delete sourceGroupDeletedInSourceBeforeEntryUpdatedInTarget.data();
  432. changeEntry(targetEntryDeletedInSourceAfterEntryUpdatedInTarget);
  433. delete targetGroupDeletedInTargetBeforeEntryUpdatedInSource.data();
  434. changeEntry(sourceEntryDeletedInTargetAfterEntryUpdatedInSoruce);
  435. m_clock->advanceMinute(1);
  436. changeEntry(targetEntryDeletedInSourceBeforeChangedInTarget);
  437. delete sourceEntryDeletedInSourceAfterChangedInTarget.data();
  438. changeEntry(sourceEntryDeletedInTargetBeforeChangedInSource);
  439. delete targetEntryDeletedInTargetAfterChangedInSource.data();
  440. changeEntry(targetEntryDeletedInSourceBeforeEntryUpdatedInTarget);
  441. delete sourceGroupDeletedInSourceAfterEntryUpdatedInTarget.data();
  442. changeEntry(sourceEntryDeletedInTargetBeforeEntryUpdatedInSource);
  443. delete targetGroupDeletedInTargetAfterEntryUpdatedInSource.data();
  444. m_clock->advanceMinute(1);
  445. dbDestination->rootGroup()->setMergeMode(static_cast<Group::MergeMode>(mergeMode));
  446. Merger merger(dbSource.data(), dbDestination.data());
  447. merger.merge();
  448. verification(dbDestination.data(), identifiers);
  449. }
  450. void TestMerge::assertDeletionNewerOnly(Database* db, const QMap<QString, QUuid>& identifiers)
  451. {
  452. QPointer<Group> mergedRootGroup = db->rootGroup();
  453. // newer change in target prevents deletion
  454. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]));
  455. QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]));
  456. // newer deletion in source forces deletion
  457. QVERIFY(!mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceAfterChangedInTarget"]));
  458. QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInSourceAfterChangedInTarget"]));
  459. // newer change in source prevents deletion
  460. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeChangedInSource"]));
  461. QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInTargetBeforeChangedInSource"]));
  462. // newer deletion in target forces deletion
  463. QVERIFY(!mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetAfterChangedInSource"]));
  464. QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetAfterChangedInSource"]));
  465. // newer change in target prevents deletion
  466. QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]));
  467. QVERIFY(!db->containsDeletedObject(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]));
  468. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]));
  469. QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]));
  470. // newer deletion in source forces deletion
  471. QVERIFY(!mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]));
  472. QVERIFY(db->containsDeletedObject(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]));
  473. QVERIFY(!mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]));
  474. QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]));
  475. // newer change in source prevents deletion
  476. QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]));
  477. QVERIFY(!db->containsDeletedObject(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]));
  478. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]));
  479. QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]));
  480. // newer deletion in target forces deletion
  481. QVERIFY(!mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]));
  482. QVERIFY(db->containsDeletedObject(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]));
  483. QVERIFY(!mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]));
  484. QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]));
  485. }
  486. void TestMerge::assertDeletionLocalOnly(Database* db, const QMap<QString, QUuid>& identifiers)
  487. {
  488. QPointer<Group> mergedRootGroup = db->rootGroup();
  489. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]));
  490. QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]));
  491. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceAfterChangedInTarget"]));
  492. QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceAfterChangedInTarget"]));
  493. // Uuids in db and deletedObjects is intended according to KeePass #1752
  494. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeChangedInSource"]));
  495. QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetBeforeChangedInSource"]));
  496. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetAfterChangedInSource"]));
  497. QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetAfterChangedInSource"]));
  498. QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]));
  499. QVERIFY(!db->containsDeletedObject(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]));
  500. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]));
  501. QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]));
  502. QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]));
  503. QVERIFY(!db->containsDeletedObject(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]));
  504. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]));
  505. QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]));
  506. QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]));
  507. QVERIFY(db->containsDeletedObject(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]));
  508. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]));
  509. QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]));
  510. QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]));
  511. QVERIFY(db->containsDeletedObject(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]));
  512. QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]));
  513. QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]));
  514. }
  515. void TestMerge::assertUpdateMergedEntry1(Entry* mergedEntry1, const QMap<const char*, QDateTime>& timestamps)
  516. {
  517. QCOMPARE(mergedEntry1->historyItems().count(), 4);
  518. QCOMPARE(mergedEntry1->historyItems().at(0)->notes(), QString(""));
  519. QCOMPARE(mergedEntry1->historyItems().at(0)->timeInfo().lastModificationTime(), timestamps["initialTime"]);
  520. QCOMPARE(mergedEntry1->historyItems().at(1)->notes(), QString(""));
  521. QCOMPARE(mergedEntry1->historyItems().at(1)->timeInfo().lastModificationTime(),
  522. timestamps["oldestCommonHistoryTime"]);
  523. QCOMPARE(mergedEntry1->historyItems().at(2)->notes(), QString("1 Common"));
  524. QCOMPARE(mergedEntry1->historyItems().at(2)->timeInfo().lastModificationTime(),
  525. timestamps["newestCommonHistoryTime"]);
  526. QCOMPARE(mergedEntry1->historyItems().at(3)->notes(), QString("2 Source"));
  527. QCOMPARE(mergedEntry1->historyItems().at(3)->timeInfo().lastModificationTime(),
  528. timestamps["oldestDivergingHistoryTime"]);
  529. QCOMPARE(mergedEntry1->notes(), QString("3 Destination"));
  530. QCOMPARE(mergedEntry1->timeInfo().lastModificationTime(), timestamps["newestDivergingHistoryTime"]);
  531. }
  532. void TestMerge::assertUpdateReappliedEntry2(Entry* mergedEntry2, const QMap<const char*, QDateTime>& timestamps)
  533. {
  534. QCOMPARE(mergedEntry2->historyItems().count(), 5);
  535. QCOMPARE(mergedEntry2->historyItems().at(0)->notes(), QString(""));
  536. QCOMPARE(mergedEntry2->historyItems().at(0)->timeInfo().lastModificationTime(), timestamps["initialTime"]);
  537. QCOMPARE(mergedEntry2->historyItems().at(1)->notes(), QString(""));
  538. QCOMPARE(mergedEntry2->historyItems().at(1)->timeInfo().lastModificationTime(),
  539. timestamps["oldestCommonHistoryTime"]);
  540. QCOMPARE(mergedEntry2->historyItems().at(2)->notes(), QString("1 Common"));
  541. QCOMPARE(mergedEntry2->historyItems().at(2)->timeInfo().lastModificationTime(),
  542. timestamps["newestCommonHistoryTime"]);
  543. QCOMPARE(mergedEntry2->historyItems().at(3)->notes(), QString("2 Destination"));
  544. QCOMPARE(mergedEntry2->historyItems().at(3)->timeInfo().lastModificationTime(),
  545. timestamps["oldestDivergingHistoryTime"]);
  546. QCOMPARE(mergedEntry2->historyItems().at(4)->notes(), QString("3 Source"));
  547. QCOMPARE(mergedEntry2->historyItems().at(4)->timeInfo().lastModificationTime(),
  548. timestamps["newestDivergingHistoryTime"]);
  549. QCOMPARE(mergedEntry2->notes(), QString("2 Destination"));
  550. QCOMPARE(mergedEntry2->timeInfo().lastModificationTime(), timestamps["mergeTime"]);
  551. }
  552. void TestMerge::assertUpdateReappliedEntry1(Entry* mergedEntry1, const QMap<const char*, QDateTime>& timestamps)
  553. {
  554. QCOMPARE(mergedEntry1->historyItems().count(), 5);
  555. QCOMPARE(mergedEntry1->historyItems().at(0)->notes(), QString(""));
  556. QCOMPARE(mergedEntry1->historyItems().at(0)->timeInfo().lastModificationTime(), timestamps["initialTime"]);
  557. QCOMPARE(mergedEntry1->historyItems().at(1)->notes(), QString(""));
  558. QCOMPARE(mergedEntry1->historyItems().at(1)->timeInfo().lastModificationTime(),
  559. timestamps["oldestCommonHistoryTime"]);
  560. QCOMPARE(mergedEntry1->historyItems().at(2)->notes(), QString("1 Common"));
  561. QCOMPARE(mergedEntry1->historyItems().at(2)->timeInfo().lastModificationTime(),
  562. timestamps["newestCommonHistoryTime"]);
  563. QCOMPARE(mergedEntry1->historyItems().at(3)->notes(), QString("2 Source"));
  564. QCOMPARE(mergedEntry1->historyItems().at(3)->timeInfo().lastModificationTime(),
  565. timestamps["oldestDivergingHistoryTime"]);
  566. QCOMPARE(mergedEntry1->historyItems().at(4)->notes(), QString("3 Destination"));
  567. QCOMPARE(mergedEntry1->historyItems().at(4)->timeInfo().lastModificationTime(),
  568. timestamps["newestDivergingHistoryTime"]);
  569. QCOMPARE(mergedEntry1->notes(), QString("2 Source"));
  570. QCOMPARE(mergedEntry1->timeInfo().lastModificationTime(), timestamps["mergeTime"]);
  571. }
  572. void TestMerge::assertUpdateMergedEntry2(Entry* mergedEntry2, const QMap<const char*, QDateTime>& timestamps)
  573. {
  574. QCOMPARE(mergedEntry2->historyItems().count(), 4);
  575. QCOMPARE(mergedEntry2->historyItems().at(0)->notes(), QString(""));
  576. QCOMPARE(mergedEntry2->historyItems().at(0)->timeInfo().lastModificationTime(), timestamps["initialTime"]);
  577. QCOMPARE(mergedEntry2->historyItems().at(1)->notes(), QString(""));
  578. QCOMPARE(mergedEntry2->historyItems().at(1)->timeInfo().lastModificationTime(),
  579. timestamps["oldestCommonHistoryTime"]);
  580. QCOMPARE(mergedEntry2->historyItems().at(2)->notes(), QString("1 Common"));
  581. QCOMPARE(mergedEntry2->historyItems().at(2)->timeInfo().lastModificationTime(),
  582. timestamps["newestCommonHistoryTime"]);
  583. QCOMPARE(mergedEntry2->historyItems().at(3)->notes(), QString("2 Destination"));
  584. QCOMPARE(mergedEntry2->historyItems().at(3)->timeInfo().lastModificationTime(),
  585. timestamps["oldestDivergingHistoryTime"]);
  586. QCOMPARE(mergedEntry2->notes(), QString("3 Source"));
  587. QCOMPARE(mergedEntry2->timeInfo().lastModificationTime(), timestamps["newestDivergingHistoryTime"]);
  588. }
  589. void TestMerge::testDeletionConflictEntry_Synchronized()
  590. {
  591. testDeletionConflictTemplate(Group::Synchronize, &TestMerge::assertDeletionNewerOnly);
  592. }
  593. void TestMerge::testDeletionConflictEntry_KeepNewer()
  594. {
  595. testDeletionConflictTemplate(Group::KeepNewer, &TestMerge::assertDeletionLocalOnly);
  596. }
  597. /**
  598. * Tests the KeepNewer mode concerning history.
  599. */
  600. void TestMerge::testResolveConflictEntry_Synchronize()
  601. {
  602. testResolveConflictTemplate(Group::Synchronize, [](Database* db, const QMap<const char*, QDateTime>& timestamps) {
  603. QPointer<Group> mergedRootGroup = db->rootGroup();
  604. QPointer<Group> mergedGroup1 = mergedRootGroup->children().at(0);
  605. TestMerge::assertUpdateMergedEntry1(mergedGroup1->entries().at(0), timestamps);
  606. TestMerge::assertUpdateMergedEntry2(mergedGroup1->entries().at(1), timestamps);
  607. });
  608. }
  609. void TestMerge::testResolveConflictEntry_KeepNewer()
  610. {
  611. testResolveConflictTemplate(Group::KeepNewer, [](Database* db, const QMap<const char*, QDateTime>& timestamps) {
  612. QPointer<Group> mergedRootGroup = db->rootGroup();
  613. QPointer<Group> mergedGroup1 = mergedRootGroup->children().at(0);
  614. TestMerge::assertUpdateMergedEntry1(mergedGroup1->entries().at(0), timestamps);
  615. TestMerge::assertUpdateMergedEntry2(mergedGroup1->entries().at(1), timestamps);
  616. });
  617. }
  618. /**
  619. * The location of an entry should be updated in the
  620. * destination database.
  621. */
  622. void TestMerge::testMoveEntry()
  623. {
  624. QScopedPointer<Database> dbDestination(createTestDatabase());
  625. QScopedPointer<Database> dbSource(
  626. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  627. QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
  628. QVERIFY(entrySourceInitial != nullptr);
  629. QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group2");
  630. QVERIFY(groupSourceInitial != nullptr);
  631. // Make sure the two changes have a different timestamp.
  632. m_clock->advanceSecond(1);
  633. entrySourceInitial->setGroup(groupSourceInitial);
  634. QCOMPARE(entrySourceInitial->group()->name(), QString("group2"));
  635. m_clock->advanceSecond(1);
  636. Merger merger(dbSource.data(), dbDestination.data());
  637. merger.merge();
  638. QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
  639. QVERIFY(entryDestinationMerged != nullptr);
  640. QCOMPARE(entryDestinationMerged->group()->name(), QString("group2"));
  641. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  642. }
  643. /**
  644. * The location of an entry should be updated in the
  645. * destination database, but changes from the destination
  646. * database should be preserved.
  647. */
  648. void TestMerge::testMoveEntryPreserveChanges()
  649. {
  650. QScopedPointer<Database> dbDestination(createTestDatabase());
  651. QScopedPointer<Database> dbSource(
  652. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  653. QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
  654. QVERIFY(entrySourceInitial != nullptr);
  655. QPointer<Group> group2Source = dbSource->rootGroup()->findChildByName("group2");
  656. QVERIFY(group2Source != nullptr);
  657. m_clock->advanceSecond(1);
  658. entrySourceInitial->setGroup(group2Source);
  659. QCOMPARE(entrySourceInitial->group()->name(), QString("group2"));
  660. QPointer<Entry> entryDestinationInitial = dbDestination->rootGroup()->findEntryByPath("entry1");
  661. QVERIFY(entryDestinationInitial != nullptr);
  662. m_clock->advanceSecond(1);
  663. entryDestinationInitial->beginUpdate();
  664. entryDestinationInitial->setPassword("password");
  665. entryDestinationInitial->endUpdate();
  666. m_clock->advanceSecond(1);
  667. Merger merger(dbSource.data(), dbDestination.data());
  668. merger.merge();
  669. QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
  670. QVERIFY(entryDestinationMerged != nullptr);
  671. QCOMPARE(entryDestinationMerged->group()->name(), QString("group2"));
  672. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  673. QCOMPARE(entryDestinationMerged->password(), QString("password"));
  674. }
  675. void TestMerge::testCreateNewGroups()
  676. {
  677. QScopedPointer<Database> dbDestination(createTestDatabase());
  678. QScopedPointer<Database> dbSource(
  679. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  680. m_clock->advanceSecond(1);
  681. auto groupSourceCreated = new Group();
  682. groupSourceCreated->setName("group3");
  683. groupSourceCreated->setUuid(QUuid::createUuid());
  684. groupSourceCreated->setParent(dbSource->rootGroup());
  685. m_clock->advanceSecond(1);
  686. Merger merger(dbSource.data(), dbDestination.data());
  687. merger.merge();
  688. QPointer<Group> groupDestinationMerged = dbDestination->rootGroup()->findChildByName("group3");
  689. QVERIFY(groupDestinationMerged != nullptr);
  690. QCOMPARE(groupDestinationMerged->name(), QString("group3"));
  691. }
  692. void TestMerge::testMoveEntryIntoNewGroup()
  693. {
  694. QScopedPointer<Database> dbDestination(createTestDatabase());
  695. QScopedPointer<Database> dbSource(
  696. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  697. m_clock->advanceSecond(1);
  698. auto groupSourceCreated = new Group();
  699. groupSourceCreated->setName("group3");
  700. groupSourceCreated->setUuid(QUuid::createUuid());
  701. groupSourceCreated->setParent(dbSource->rootGroup());
  702. QPointer<Entry> entrySourceMoved = dbSource->rootGroup()->findEntryByPath("entry1");
  703. entrySourceMoved->setGroup(groupSourceCreated);
  704. m_clock->advanceSecond(1);
  705. Merger merger(dbSource.data(), dbDestination.data());
  706. merger.merge();
  707. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  708. QPointer<Group> groupDestinationMerged = dbDestination->rootGroup()->findChildByName("group3");
  709. QVERIFY(groupDestinationMerged != nullptr);
  710. QCOMPARE(groupDestinationMerged->name(), QString("group3"));
  711. QCOMPARE(groupDestinationMerged->entries().size(), 1);
  712. QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
  713. QVERIFY(entryDestinationMerged != nullptr);
  714. QCOMPARE(entryDestinationMerged->group()->name(), QString("group3"));
  715. }
  716. /**
  717. * Even though the entries' locations are no longer
  718. * the same, we will keep associating them.
  719. */
  720. void TestMerge::testUpdateEntryDifferentLocation()
  721. {
  722. QScopedPointer<Database> dbDestination(createTestDatabase());
  723. QScopedPointer<Database> dbSource(
  724. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  725. auto groupDestinationCreated = new Group();
  726. groupDestinationCreated->setName("group3");
  727. groupDestinationCreated->setUuid(QUuid::createUuid());
  728. groupDestinationCreated->setParent(dbDestination->rootGroup());
  729. m_clock->advanceSecond(1);
  730. QPointer<Entry> entryDestinationMoved = dbDestination->rootGroup()->findEntryByPath("entry1");
  731. QVERIFY(entryDestinationMoved != nullptr);
  732. entryDestinationMoved->setGroup(groupDestinationCreated);
  733. QUuid uuidBeforeSyncing = entryDestinationMoved->uuid();
  734. QDateTime destinationLocationChanged = entryDestinationMoved->timeInfo().locationChanged();
  735. // Change the entry in the source db.
  736. m_clock->advanceSecond(1);
  737. QPointer<Entry> entrySourceMoved = dbSource->rootGroup()->findEntryByPath("entry1");
  738. QVERIFY(entrySourceMoved != nullptr);
  739. entrySourceMoved->beginUpdate();
  740. entrySourceMoved->setUsername("username");
  741. entrySourceMoved->endUpdate();
  742. QDateTime sourceLocationChanged = entrySourceMoved->timeInfo().locationChanged();
  743. QVERIFY(destinationLocationChanged > sourceLocationChanged);
  744. m_clock->advanceSecond(1);
  745. Merger merger(dbSource.data(), dbDestination.data());
  746. merger.merge();
  747. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  748. QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
  749. QVERIFY(entryDestinationMerged != nullptr);
  750. QVERIFY(entryDestinationMerged->group() != nullptr);
  751. QCOMPARE(entryDestinationMerged->username(), QString("username"));
  752. QCOMPARE(entryDestinationMerged->group()->name(), QString("group3"));
  753. QCOMPARE(uuidBeforeSyncing, entryDestinationMerged->uuid());
  754. // default merge strategy is KeepNewer - therefore the older location is used!
  755. QCOMPARE(entryDestinationMerged->timeInfo().locationChanged(), sourceLocationChanged);
  756. }
  757. /**
  758. * Groups should be updated using the uuids.
  759. */
  760. void TestMerge::testUpdateGroup()
  761. {
  762. QScopedPointer<Database> dbDestination(createTestDatabase());
  763. QScopedPointer<Database> dbSource(
  764. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  765. m_clock->advanceSecond(1);
  766. QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group2");
  767. groupSourceInitial->setName("group2 renamed");
  768. groupSourceInitial->setNotes("updated notes");
  769. QUuid customIconId = QUuid::createUuid();
  770. dbSource->metadata()->addCustomIcon(customIconId, QString("custom icon").toLocal8Bit());
  771. groupSourceInitial->setIcon(customIconId);
  772. QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
  773. QVERIFY(entrySourceInitial != nullptr);
  774. entrySourceInitial->setGroup(groupSourceInitial);
  775. entrySourceInitial->setTitle("entry1 renamed");
  776. QUuid uuidBeforeSyncing = entrySourceInitial->uuid();
  777. m_clock->advanceSecond(1);
  778. Merger merger(dbSource.data(), dbDestination.data());
  779. merger.merge();
  780. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  781. QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1 renamed");
  782. QVERIFY(entryDestinationMerged != nullptr);
  783. QVERIFY(entryDestinationMerged->group() != nullptr);
  784. QCOMPARE(entryDestinationMerged->group()->name(), QString("group2 renamed"));
  785. QCOMPARE(uuidBeforeSyncing, entryDestinationMerged->uuid());
  786. QPointer<Group> groupMerged = dbDestination->rootGroup()->findChildByName("group2 renamed");
  787. QCOMPARE(groupMerged->notes(), QString("updated notes"));
  788. QCOMPARE(groupMerged->iconUuid(), customIconId);
  789. }
  790. void TestMerge::testUpdateGroupLocation()
  791. {
  792. QScopedPointer<Database> dbDestination(createTestDatabase());
  793. auto group3DestinationCreated = new Group();
  794. QUuid group3Uuid = QUuid::createUuid();
  795. group3DestinationCreated->setUuid(group3Uuid);
  796. group3DestinationCreated->setName("group3");
  797. group3DestinationCreated->setParent(dbDestination->rootGroup()->findChildByName("group1"));
  798. QScopedPointer<Database> dbSource(
  799. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  800. // Sanity check
  801. QPointer<Group> group3SourceInitial = dbSource->rootGroup()->findGroupByUuid(group3Uuid);
  802. QVERIFY(group3DestinationCreated != nullptr);
  803. QDateTime initialLocationChanged = group3SourceInitial->timeInfo().locationChanged();
  804. m_clock->advanceSecond(1);
  805. QPointer<Group> group3SourceMoved = dbSource->rootGroup()->findGroupByUuid(group3Uuid);
  806. QVERIFY(group3SourceMoved != nullptr);
  807. group3SourceMoved->setParent(dbSource->rootGroup()->findChildByName("group2"));
  808. QDateTime movedLocaltionChanged = group3SourceMoved->timeInfo().locationChanged();
  809. QVERIFY(initialLocationChanged < movedLocaltionChanged);
  810. m_clock->advanceSecond(1);
  811. Merger merger1(dbSource.data(), dbDestination.data());
  812. merger1.merge();
  813. QPointer<Group> group3DestinationMerged1 = dbDestination->rootGroup()->findGroupByUuid(group3Uuid);
  814. QVERIFY(group3DestinationMerged1 != nullptr);
  815. QCOMPARE(group3DestinationMerged1->parent(), dbDestination->rootGroup()->findChildByName("group2"));
  816. QCOMPARE(group3DestinationMerged1->timeInfo().locationChanged(), movedLocaltionChanged);
  817. m_clock->advanceSecond(1);
  818. Merger merger2(dbSource.data(), dbDestination.data());
  819. merger2.merge();
  820. QPointer<Group> group3DestinationMerged2 = dbDestination->rootGroup()->findGroupByUuid(group3Uuid);
  821. QVERIFY(group3DestinationMerged2 != nullptr);
  822. QCOMPARE(group3DestinationMerged2->parent(), dbDestination->rootGroup()->findChildByName("group2"));
  823. QCOMPARE(group3DestinationMerged1->timeInfo().locationChanged(), movedLocaltionChanged);
  824. }
  825. /**
  826. * The first merge should create new entries, the
  827. * second should only sync them, since they have
  828. * been created with the same UUIDs.
  829. */
  830. void TestMerge::testMergeAndSync()
  831. {
  832. QScopedPointer<Database> dbDestination(new Database());
  833. QScopedPointer<Database> dbSource(createTestDatabase());
  834. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 0);
  835. m_clock->advanceSecond(1);
  836. Merger merger1(dbSource.data(), dbDestination.data());
  837. merger1.merge();
  838. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  839. m_clock->advanceSecond(1);
  840. Merger merger2(dbSource.data(), dbDestination.data());
  841. merger2.merge();
  842. // Still only 2 entries, since now we detect which are already present.
  843. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  844. }
  845. /**
  846. * Custom icons should be brought over when merging.
  847. */
  848. void TestMerge::testMergeCustomIcons()
  849. {
  850. QScopedPointer<Database> dbDestination(new Database());
  851. QScopedPointer<Database> dbSource(createTestDatabase());
  852. m_clock->advanceSecond(1);
  853. QUuid customIconId = QUuid::createUuid();
  854. dbSource->metadata()->addCustomIcon(customIconId, QString("custom icon").toLocal8Bit());
  855. // Sanity check.
  856. QVERIFY(dbSource->metadata()->hasCustomIcon(customIconId));
  857. m_clock->advanceSecond(1);
  858. Merger merger(dbSource.data(), dbDestination.data());
  859. merger.merge();
  860. QVERIFY(dbDestination->metadata()->hasCustomIcon(customIconId));
  861. }
  862. /**
  863. * No duplicate icons should be created
  864. */
  865. void TestMerge::testMergeDuplicateCustomIcons()
  866. {
  867. QScopedPointer<Database> dbDestination(new Database());
  868. QScopedPointer<Database> dbSource(createTestDatabase());
  869. m_clock->advanceSecond(1);
  870. QUuid customIconId = QUuid::createUuid();
  871. QByteArray customIcon1("custom icon 1");
  872. QByteArray customIcon2("custom icon 2");
  873. dbSource->metadata()->addCustomIcon(customIconId, customIcon1);
  874. dbDestination->metadata()->addCustomIcon(customIconId, customIcon2);
  875. // Sanity check.
  876. QVERIFY(dbSource->metadata()->hasCustomIcon(customIconId));
  877. QVERIFY(dbDestination->metadata()->hasCustomIcon(customIconId));
  878. m_clock->advanceSecond(1);
  879. Merger merger(dbSource.data(), dbDestination.data());
  880. merger.merge();
  881. QVERIFY(dbDestination->metadata()->hasCustomIcon(customIconId));
  882. QCOMPARE(dbDestination->metadata()->customIconsOrder().count(), 1);
  883. QCOMPARE(dbDestination->metadata()->customIcon(customIconId).data, customIcon2);
  884. }
  885. void TestMerge::testMetadata()
  886. {
  887. QSKIP("Sophisticated merging for Metadata not implemented");
  888. // TODO HNH: I think a merge of recycle bins would be nice since duplicating them
  889. // is not really a good solution - the one to use as final recycle bin
  890. // is determined by the merge method - if only one has a bin, this one
  891. // will be used - exception is the target has no recycle bin activated
  892. }
  893. void TestMerge::testCustomData()
  894. {
  895. QScopedPointer<Database> dbDestination(new Database());
  896. QScopedPointer<Database> dbSource(createTestDatabase());
  897. QScopedPointer<Database> dbDestination2(new Database());
  898. QScopedPointer<Database> dbSource2(createTestDatabase());
  899. m_clock->advanceSecond(1);
  900. dbDestination->metadata()->customData()->set("toBeDeleted", "value");
  901. dbDestination->metadata()->customData()->set("key3", "oldValue");
  902. dbSource2->metadata()->customData()->set("key1", "value1");
  903. dbSource2->metadata()->customData()->set("key2", "value2");
  904. dbSource2->metadata()->customData()->set("key3", "newValue");
  905. dbSource2->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
  906. m_clock->advanceSecond(1);
  907. dbSource->metadata()->customData()->set("key1", "value1");
  908. dbSource->metadata()->customData()->set("key2", "value2");
  909. dbSource->metadata()->customData()->set("key3", "newValue");
  910. dbSource->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
  911. dbDestination2->metadata()->customData()->set("notToBeDeleted", "value");
  912. dbDestination2->metadata()->customData()->set("key3", "oldValue");
  913. // Sanity check.
  914. QVERIFY(!dbSource->metadata()->customData()->isEmpty());
  915. QVERIFY(!dbSource2->metadata()->customData()->isEmpty());
  916. m_clock->advanceSecond(1);
  917. Merger merger(dbSource.data(), dbDestination.data());
  918. QStringList changes = merger.merge();
  919. QVERIFY(!changes.isEmpty());
  920. // Source is newer, data should be merged
  921. QVERIFY(!dbDestination->metadata()->customData()->isEmpty());
  922. QVERIFY(dbDestination->metadata()->customData()->contains("key1"));
  923. QVERIFY(dbDestination->metadata()->customData()->contains("key2"));
  924. QVERIFY(dbDestination->metadata()->customData()->contains("Browser"));
  925. QVERIFY(!dbDestination->metadata()->customData()->contains("toBeDeleted"));
  926. QCOMPARE(dbDestination->metadata()->customData()->value("key1"), QString("value1"));
  927. QCOMPARE(dbDestination->metadata()->customData()->value("key2"), QString("value2"));
  928. QCOMPARE(dbDestination->metadata()->customData()->value("Browser"), QString("n'8=3W@L^6d->d.]St_>]"));
  929. QCOMPARE(dbDestination->metadata()->customData()->value("key3"),
  930. QString("newValue")); // Old value should be replaced
  931. // Merging again should not do anything if the values are the same.
  932. m_clock->advanceSecond(1);
  933. dbSource->metadata()->customData()->set("key3", "oldValue");
  934. dbSource->metadata()->customData()->set("key3", "newValue");
  935. Merger merger2(dbSource.data(), dbDestination.data());
  936. QStringList changes2 = merger2.merge();
  937. QVERIFY(changes2.isEmpty());
  938. Merger merger3(dbSource2.data(), dbDestination2.data());
  939. merger3.merge();
  940. // Target is newer, no data is merged
  941. QVERIFY(!dbDestination2->metadata()->customData()->isEmpty());
  942. QVERIFY(!dbDestination2->metadata()->customData()->contains("key1"));
  943. QVERIFY(!dbDestination2->metadata()->customData()->contains("key2"));
  944. QVERIFY(!dbDestination2->metadata()->customData()->contains("Browser"));
  945. QVERIFY(dbDestination2->metadata()->customData()->contains("notToBeDeleted"));
  946. QCOMPARE(dbDestination2->metadata()->customData()->value("key3"),
  947. QString("oldValue")); // Old value should not be replaced
  948. }
  949. void TestMerge::testDeletedEntry()
  950. {
  951. QScopedPointer<Database> dbDestination(createTestDatabase());
  952. QScopedPointer<Database> dbSource(
  953. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  954. m_clock->advanceSecond(1);
  955. QPointer<Entry> entry1SourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
  956. QVERIFY(entry1SourceInitial != nullptr);
  957. QUuid entry1Uuid = entry1SourceInitial->uuid();
  958. delete entry1SourceInitial;
  959. QVERIFY(dbSource->containsDeletedObject(entry1Uuid));
  960. m_clock->advanceSecond(1);
  961. QPointer<Entry> entry2DestinationInitial = dbDestination->rootGroup()->findEntryByPath("entry2");
  962. QVERIFY(entry2DestinationInitial != nullptr);
  963. QUuid entry2Uuid = entry2DestinationInitial->uuid();
  964. delete entry2DestinationInitial;
  965. QVERIFY(dbDestination->containsDeletedObject(entry2Uuid));
  966. m_clock->advanceSecond(1);
  967. Merger merger(dbSource.data(), dbDestination.data());
  968. merger.merge();
  969. QPointer<Entry> entry1DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
  970. QVERIFY(entry1DestinationMerged);
  971. QVERIFY(!dbDestination->containsDeletedObject(entry1Uuid));
  972. QPointer<Entry> entry2DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry2");
  973. QVERIFY(entry2DestinationMerged);
  974. // Uuid in db and deletedObjects is intended according to KeePass #1752
  975. QVERIFY(dbDestination->containsDeletedObject(entry2Uuid));
  976. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
  977. }
  978. void TestMerge::testDeletedGroup()
  979. {
  980. QScopedPointer<Database> dbDestination(createTestDatabase());
  981. QScopedPointer<Database> dbSource(
  982. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  983. m_clock->advanceSecond(1);
  984. QPointer<Group> group2DestinationInitial = dbDestination->rootGroup()->findChildByName("group2");
  985. QVERIFY(group2DestinationInitial != nullptr);
  986. auto entry3DestinationCreated = new Entry();
  987. entry3DestinationCreated->beginUpdate();
  988. entry3DestinationCreated->setUuid(QUuid::createUuid());
  989. entry3DestinationCreated->setGroup(group2DestinationInitial);
  990. entry3DestinationCreated->setTitle("entry3");
  991. entry3DestinationCreated->endUpdate();
  992. m_clock->advanceSecond(1);
  993. QPointer<Group> group1SourceInitial = dbSource->rootGroup()->findChildByName("group1");
  994. QVERIFY(group1SourceInitial != nullptr);
  995. QPointer<Entry> entry1SourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
  996. QVERIFY(entry1SourceInitial != nullptr);
  997. QPointer<Entry> entry2SourceInitial = dbSource->rootGroup()->findEntryByPath("entry2");
  998. QVERIFY(entry2SourceInitial != nullptr);
  999. QUuid group1Uuid = group1SourceInitial->uuid();
  1000. QUuid entry1Uuid = entry1SourceInitial->uuid();
  1001. QUuid entry2Uuid = entry2SourceInitial->uuid();
  1002. delete group1SourceInitial;
  1003. QVERIFY(dbSource->containsDeletedObject(group1Uuid));
  1004. QVERIFY(dbSource->containsDeletedObject(entry1Uuid));
  1005. QVERIFY(dbSource->containsDeletedObject(entry2Uuid));
  1006. m_clock->advanceSecond(1);
  1007. QPointer<Group> group2SourceInitial = dbSource->rootGroup()->findChildByName("group2");
  1008. QVERIFY(group2SourceInitial != nullptr);
  1009. QUuid group2Uuid = group2SourceInitial->uuid();
  1010. delete group2SourceInitial;
  1011. QVERIFY(dbSource->containsDeletedObject(group2Uuid));
  1012. m_clock->advanceSecond(1);
  1013. Merger merger(dbSource.data(), dbDestination.data());
  1014. merger.merge();
  1015. QVERIFY(!dbDestination->containsDeletedObject(group1Uuid));
  1016. QVERIFY(!dbDestination->containsDeletedObject(entry1Uuid));
  1017. QVERIFY(!dbDestination->containsDeletedObject(entry2Uuid));
  1018. QVERIFY(!dbDestination->containsDeletedObject(group2Uuid));
  1019. QPointer<Entry> entry1DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
  1020. QVERIFY(entry1DestinationMerged);
  1021. QPointer<Entry> entry2DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry2");
  1022. QVERIFY(entry2DestinationMerged);
  1023. QPointer<Entry> entry3DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry3");
  1024. QVERIFY(entry3DestinationMerged);
  1025. QPointer<Group> group1DestinationMerged = dbDestination->rootGroup()->findChildByName("group1");
  1026. QVERIFY(group1DestinationMerged);
  1027. QPointer<Group> group2DestinationMerged = dbDestination->rootGroup()->findChildByName("group2");
  1028. QVERIFY(group2DestinationMerged);
  1029. QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 3);
  1030. }
  1031. void TestMerge::testDeletedRevertedEntry()
  1032. {
  1033. QScopedPointer<Database> dbDestination(createTestDatabase());
  1034. QScopedPointer<Database> dbSource(
  1035. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  1036. m_clock->advanceSecond(1);
  1037. QPointer<Entry> entry1DestinationInitial = dbDestination->rootGroup()->findEntryByPath("entry1");
  1038. QVERIFY(entry1DestinationInitial != nullptr);
  1039. QUuid entry1Uuid = entry1DestinationInitial->uuid();
  1040. delete entry1DestinationInitial;
  1041. QVERIFY(dbDestination->containsDeletedObject(entry1Uuid));
  1042. m_clock->advanceSecond(1);
  1043. QPointer<Entry> entry2SourceInitial = dbSource->rootGroup()->findEntryByPath("entry2");
  1044. QVERIFY(entry2SourceInitial != nullptr);
  1045. QUuid entry2Uuid = entry2SourceInitial->uuid();
  1046. delete entry2SourceInitial;
  1047. QVERIFY(dbSource->containsDeletedObject(entry2Uuid));
  1048. m_clock->advanceSecond(1);
  1049. QPointer<Entry> entry1SourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
  1050. QVERIFY(entry1SourceInitial != nullptr);
  1051. entry1SourceInitial->setNotes("Updated");
  1052. QPointer<Entry> entry2DestinationInitial = dbDestination->rootGroup()->findEntryByPath("entry2");
  1053. QVERIFY(entry2DestinationInitial != nullptr);
  1054. entry2DestinationInitial->setNotes("Updated");
  1055. Merger merger(dbSource.data(), dbDestination.data());
  1056. merger.merge();
  1057. // Uuid in db and deletedObjects is intended according to KeePass #1752
  1058. QVERIFY(dbDestination->containsDeletedObject(entry1Uuid));
  1059. QVERIFY(!dbDestination->containsDeletedObject(entry2Uuid));
  1060. QPointer<Entry> entry1DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
  1061. QVERIFY(entry1DestinationMerged);
  1062. QVERIFY(entry1DestinationMerged->notes() == "Updated");
  1063. QPointer<Entry> entry2DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry2");
  1064. QVERIFY(entry2DestinationMerged);
  1065. QVERIFY(entry2DestinationMerged->notes() == "Updated");
  1066. }
  1067. void TestMerge::testDeletedRevertedGroup()
  1068. {
  1069. QScopedPointer<Database> dbDestination(createTestDatabase());
  1070. QScopedPointer<Database> dbSource(
  1071. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  1072. m_clock->advanceSecond(1);
  1073. QPointer<Group> group2SourceInitial = dbSource->rootGroup()->findChildByName("group2");
  1074. QVERIFY(group2SourceInitial);
  1075. QUuid group2Uuid = group2SourceInitial->uuid();
  1076. delete group2SourceInitial;
  1077. QVERIFY(dbSource->containsDeletedObject(group2Uuid));
  1078. m_clock->advanceSecond(1);
  1079. QPointer<Group> group1DestinationInitial = dbDestination->rootGroup()->findChildByName("group1");
  1080. QVERIFY(group1DestinationInitial);
  1081. QUuid group1Uuid = group1DestinationInitial->uuid();
  1082. delete group1DestinationInitial;
  1083. QVERIFY(dbDestination->containsDeletedObject(group1Uuid));
  1084. m_clock->advanceSecond(1);
  1085. QPointer<Group> group1SourceInitial = dbSource->rootGroup()->findChildByName("group1");
  1086. QVERIFY(group1SourceInitial);
  1087. group1SourceInitial->setNotes("Updated");
  1088. m_clock->advanceSecond(1);
  1089. QPointer<Group> group2DestinationInitial = dbDestination->rootGroup()->findChildByName("group2");
  1090. QVERIFY(group2DestinationInitial);
  1091. group2DestinationInitial->setNotes("Updated");
  1092. m_clock->advanceSecond(1);
  1093. Merger merger(dbSource.data(), dbDestination.data());
  1094. merger.merge();
  1095. // Uuid in db and deletedObjects is intended according to KeePass #1752
  1096. QVERIFY(dbDestination->containsDeletedObject(group1Uuid));
  1097. QVERIFY(!dbDestination->containsDeletedObject(group2Uuid));
  1098. QPointer<Group> group1DestinationMerged = dbDestination->rootGroup()->findChildByName("group1");
  1099. QVERIFY(group1DestinationMerged);
  1100. QVERIFY(group1DestinationMerged->notes() == "Updated");
  1101. QPointer<Group> group2DestinationMerged = dbDestination->rootGroup()->findChildByName("group2");
  1102. QVERIFY(group2DestinationMerged);
  1103. QVERIFY(group2DestinationMerged->notes() == "Updated");
  1104. }
  1105. /**
  1106. * If the group is updated in the source database, and the
  1107. * destination database after, the group should remain the
  1108. * same.
  1109. */
  1110. void TestMerge::testResolveGroupConflictOlder()
  1111. {
  1112. QScopedPointer<Database> dbDestination(createTestDatabase());
  1113. QScopedPointer<Database> dbSource(
  1114. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  1115. // sanity check
  1116. QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group1");
  1117. QVERIFY(groupSourceInitial != nullptr);
  1118. // Make sure the two changes have a different timestamp.
  1119. m_clock->advanceSecond(1);
  1120. groupSourceInitial->setName("group1 updated in source");
  1121. // Make sure the two changes have a different timestamp.
  1122. m_clock->advanceSecond(1);
  1123. QPointer<Group> groupDestinationUpdated = dbDestination->rootGroup()->findChildByName("group1");
  1124. groupDestinationUpdated->setName("group1 updated in destination");
  1125. m_clock->advanceSecond(1);
  1126. Merger merger(dbSource.data(), dbDestination.data());
  1127. merger.merge();
  1128. // sanity check
  1129. QPointer<Group> groupDestinationMerged =
  1130. dbDestination->rootGroup()->findChildByName("group1 updated in destination");
  1131. QVERIFY(groupDestinationMerged != nullptr);
  1132. }
  1133. void TestMerge::testMergeNotModified()
  1134. {
  1135. QScopedPointer<Database> dbDestination(createTestDatabase());
  1136. QScopedPointer<Database> dbSource(
  1137. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  1138. QSignalSpy modifiedSignalSpy(dbDestination.data(), SIGNAL(modified()));
  1139. Merger merger(dbSource.data(), dbDestination.data());
  1140. merger.merge();
  1141. QTRY_VERIFY(modifiedSignalSpy.empty());
  1142. }
  1143. void TestMerge::testMergeModified()
  1144. {
  1145. QScopedPointer<Database> dbDestination(createTestDatabase());
  1146. QScopedPointer<Database> dbSource(
  1147. createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
  1148. QSignalSpy modifiedSignalSpy(dbDestination.data(), SIGNAL(modified()));
  1149. // Make sure the two changes have a different timestamp.
  1150. QTest::qSleep(1);
  1151. Entry* entry = dbSource->rootGroup()->findEntryByPath("entry1");
  1152. entry->beginUpdate();
  1153. entry->setTitle("new title");
  1154. entry->endUpdate();
  1155. Merger merger(dbSource.data(), dbDestination.data());
  1156. merger.merge();
  1157. QTRY_VERIFY(!modifiedSignalSpy.empty());
  1158. }
  1159. Database* TestMerge::createTestDatabase()
  1160. {
  1161. auto db = new Database();
  1162. auto group1 = new Group();
  1163. group1->setName("group1");
  1164. group1->setUuid(QUuid::createUuid());
  1165. auto group2 = new Group();
  1166. group2->setName("group2");
  1167. group2->setUuid(QUuid::createUuid());
  1168. auto entry1 = new Entry();
  1169. entry1->setUuid(QUuid::createUuid());
  1170. auto entry2 = new Entry();
  1171. entry2->setUuid(QUuid::createUuid());
  1172. m_clock->advanceYear(1);
  1173. // Give Entry 1 a history
  1174. entry1->beginUpdate();
  1175. entry1->setGroup(group1);
  1176. entry1->setTitle("entry1");
  1177. entry1->endUpdate();
  1178. // Give Entry 2 a history
  1179. entry2->beginUpdate();
  1180. entry2->setGroup(group1);
  1181. entry2->setTitle("entry2");
  1182. entry2->endUpdate();
  1183. group1->setParent(db->rootGroup());
  1184. group2->setParent(db->rootGroup());
  1185. return db;
  1186. }
  1187. Database* TestMerge::createTestDatabaseStructureClone(Database* source, int entryFlags, int groupFlags)
  1188. {
  1189. auto db = new Database();
  1190. auto oldGroup = db->setRootGroup(source->rootGroup()->clone(static_cast<Entry::CloneFlag>(entryFlags),
  1191. static_cast<Group::CloneFlag>(groupFlags)));
  1192. delete oldGroup;
  1193. return db;
  1194. }