TestMerge.cpp 70 KB

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