2
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
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.
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.
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/>.
19
#include "mock/MockClock.h"
21
#include "core/Merger.h"
22
#include "core/Metadata.h"
23
#include "crypto/Crypto.h"
28
QTEST_GUILESS_MAIN(TestMerge)
32
MockClock* m_clock = nullptr;
35
void TestMerge::initTestCase()
37
qRegisterMetaType<Entry*>("Entry*");
38
qRegisterMetaType<Group*>("Group*");
39
QVERIFY(Crypto::init());
44
Q_ASSERT(m_clock == nullptr);
45
m_clock = new MockClock(2010, 5, 5, 10, 30, 10);
46
MockClock::setup(m_clock);
49
void TestMerge::cleanup()
51
MockClock::teardown();
56
* Merge an existing database into a new one.
57
* All the entries of the existing should end
60
void TestMerge::testMergeIntoNew()
62
QScopedPointer<Database> dbSource(createTestDatabase());
63
QScopedPointer<Database> dbDestination(new Database());
65
Merger merger(dbSource.data(), dbDestination.data());
68
QCOMPARE(dbDestination->rootGroup()->children().size(), 2);
69
QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().size(), 2);
70
// Test for retention of history
71
QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(0)->historyItems().isEmpty(), false);
75
* Merging when no changes occurred should not
76
* have any side effect.
78
void TestMerge::testMergeNoChanges()
80
QScopedPointer<Database> dbDestination(createTestDatabase());
81
QScopedPointer<Database> dbSource(
82
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
84
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
85
QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2);
87
m_clock->advanceSecond(1);
89
Merger merger1(dbSource.data(), dbDestination.data());
90
auto changes = merger1.merge();
92
QVERIFY(changes.isEmpty());
93
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
94
QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2);
96
m_clock->advanceSecond(1);
98
Merger merger2(dbSource.data(), dbDestination.data());
99
changes = merger2.merge();
101
QVERIFY(changes.isEmpty());
102
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
103
QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2);
107
* Merging without database custom data (used by imports and KeeShare)
109
void TestMerge::testMergeCustomData()
111
QScopedPointer<Database> dbDestination(createTestDatabase());
112
QScopedPointer<Database> dbSource(
113
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
115
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
116
QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2);
118
dbDestination->metadata()->customData()->set("TEST_CUSTOM_DATA", "OLD TESTING");
120
m_clock->advanceSecond(1);
122
dbSource->metadata()->customData()->set("TEST_CUSTOM_DATA", "TESTING");
124
// First check that the custom data is not merged when skipped
125
Merger merger1(dbSource.data(), dbDestination.data());
126
merger1.setSkipDatabaseCustomData(true);
127
auto changes = merger1.merge();
129
QVERIFY(changes.isEmpty());
130
QCOMPARE(dbDestination->metadata()->customData()->value("TEST_CUSTOM_DATA"), QString("OLD TESTING"));
132
// Second check that the custom data is merged otherwise
133
Merger merger2(dbSource.data(), dbDestination.data());
134
changes = merger2.merge();
136
QCOMPARE(changes.size(), 1);
137
QCOMPARE(dbDestination->metadata()->customData()->value("TEST_CUSTOM_DATA"), QString("TESTING"));
141
* If the entry is updated in the source database, the update
142
* should propagate in the destination database.
144
void TestMerge::testResolveConflictNewer()
146
QScopedPointer<Database> dbDestination(createTestDatabase());
147
QScopedPointer<Database> dbSource(
148
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
151
QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group1");
152
QVERIFY(groupSourceInitial != nullptr);
153
QCOMPARE(groupSourceInitial->entries().size(), 2);
155
QPointer<Group> groupDestinationInitial = dbSource->rootGroup()->findChildByName("group1");
156
QVERIFY(groupDestinationInitial != nullptr);
157
QCOMPARE(groupDestinationInitial->entries().size(), 2);
159
QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
160
QVERIFY(entrySourceInitial != nullptr);
161
QVERIFY(entrySourceInitial->group() == groupSourceInitial);
163
const TimeInfo entrySourceInitialTimeInfo = entrySourceInitial->timeInfo();
164
const TimeInfo groupSourceInitialTimeInfo = groupSourceInitial->timeInfo();
165
const TimeInfo groupDestinationInitialTimeInfo = groupDestinationInitial->timeInfo();
167
// Make sure the two changes have a different timestamp.
168
m_clock->advanceSecond(1);
169
// make this entry newer than in destination db
170
entrySourceInitial->beginUpdate();
171
entrySourceInitial->setPassword("password");
172
entrySourceInitial->endUpdate();
174
const TimeInfo entrySourceUpdatedTimeInfo = entrySourceInitial->timeInfo();
175
const TimeInfo groupSourceUpdatedTimeInfo = groupSourceInitial->timeInfo();
177
QVERIFY(entrySourceInitialTimeInfo != entrySourceUpdatedTimeInfo);
178
QVERIFY(groupSourceInitialTimeInfo == groupSourceUpdatedTimeInfo);
179
QVERIFY(groupSourceInitialTimeInfo == groupDestinationInitialTimeInfo);
181
// Make sure the merge changes have a different timestamp.
182
m_clock->advanceSecond(1);
184
Merger merger(dbSource.data(), dbDestination.data());
188
QPointer<Group> groupDestinationMerged = dbDestination->rootGroup()->findChildByName("group1");
189
QVERIFY(groupDestinationMerged != nullptr);
190
QCOMPARE(groupDestinationMerged->entries().size(), 2);
191
QCOMPARE(groupDestinationMerged->timeInfo(), groupDestinationInitialTimeInfo);
193
QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
194
QVERIFY(entryDestinationMerged != nullptr);
195
QVERIFY(entryDestinationMerged->group() != nullptr);
196
QCOMPARE(entryDestinationMerged->password(), QString("password"));
197
QCOMPARE(entryDestinationMerged->timeInfo(), entrySourceUpdatedTimeInfo);
199
// When updating an entry, it should not end up in the
201
for (DeletedObject deletedObject : dbDestination->deletedObjects()) {
202
QVERIFY(deletedObject.uuid != entryDestinationMerged->uuid());
207
* If the entry is updated in the source database, and the
208
* destination database after, the entry should remain the
211
void TestMerge::testResolveConflictExisting()
213
QScopedPointer<Database> dbDestination(createTestDatabase());
214
QScopedPointer<Database> dbSource(
215
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
218
QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group1");
219
QVERIFY(groupSourceInitial != nullptr);
220
QCOMPARE(groupSourceInitial->entries().size(), 2);
222
QPointer<Group> groupDestinationInitial = dbDestination->rootGroup()->findChildByName("group1");
223
QVERIFY(groupDestinationInitial != nullptr);
224
QCOMPARE(groupSourceInitial->entries().size(), 2);
226
QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
227
QVERIFY(entrySourceInitial != nullptr);
228
QVERIFY(entrySourceInitial->group() == groupSourceInitial);
230
const TimeInfo entrySourceInitialTimeInfo = entrySourceInitial->timeInfo();
231
const TimeInfo groupSourceInitialTimeInfo = groupSourceInitial->timeInfo();
232
const TimeInfo groupDestinationInitialTimeInfo = groupDestinationInitial->timeInfo();
234
// Make sure the two changes have a different timestamp.
235
m_clock->advanceSecond(1);
236
// make this entry older than in destination db
237
entrySourceInitial->beginUpdate();
238
entrySourceInitial->setPassword("password1");
239
entrySourceInitial->endUpdate();
241
const TimeInfo entrySourceUpdatedOlderTimeInfo = entrySourceInitial->timeInfo();
242
const TimeInfo groupSourceUpdatedOlderTimeInfo = groupSourceInitial->timeInfo();
244
QPointer<Group> groupDestinationUpdated = dbDestination->rootGroup()->findChildByName("group1");
245
QVERIFY(groupDestinationUpdated != nullptr);
246
QCOMPARE(groupDestinationUpdated->entries().size(), 2);
247
QPointer<Entry> entryDestinationUpdated = dbDestination->rootGroup()->findEntryByPath("entry1");
248
QVERIFY(entryDestinationUpdated != nullptr);
249
QVERIFY(entryDestinationUpdated->group() == groupDestinationUpdated);
251
// Make sure the two changes have a different timestamp.
252
m_clock->advanceSecond(1);
253
// make this entry newer than in source db
254
entryDestinationUpdated->beginUpdate();
255
entryDestinationUpdated->setPassword("password2");
256
entryDestinationUpdated->endUpdate();
258
const TimeInfo entryDestinationUpdatedNewerTimeInfo = entryDestinationUpdated->timeInfo();
259
const TimeInfo groupDestinationUpdatedNewerTimeInfo = groupDestinationUpdated->timeInfo();
260
QVERIFY(entrySourceUpdatedOlderTimeInfo != entrySourceInitialTimeInfo);
261
QVERIFY(entrySourceUpdatedOlderTimeInfo != entryDestinationUpdatedNewerTimeInfo);
262
QVERIFY(groupSourceInitialTimeInfo == groupSourceUpdatedOlderTimeInfo);
263
QVERIFY(groupDestinationInitialTimeInfo == groupDestinationUpdatedNewerTimeInfo);
264
QVERIFY(groupSourceInitialTimeInfo == groupDestinationInitialTimeInfo);
266
// Make sure the merge changes have a different timestamp.
267
m_clock->advanceSecond(1);
269
Merger merger(dbSource.data(), dbDestination.data());
273
QPointer<Group> groupDestinationMerged = dbDestination->rootGroup()->findChildByName("group1");
274
QVERIFY(groupDestinationMerged != nullptr);
275
QCOMPARE(groupDestinationMerged->entries().size(), 2);
276
QCOMPARE(groupDestinationMerged->timeInfo(), groupDestinationUpdatedNewerTimeInfo);
278
QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
279
QVERIFY(entryDestinationMerged != nullptr);
280
QCOMPARE(entryDestinationMerged->password(), QString("password2"));
281
QCOMPARE(entryDestinationMerged->timeInfo(), entryDestinationUpdatedNewerTimeInfo);
283
// When updating an entry, it should not end up in the
285
for (DeletedObject deletedObject : dbDestination->deletedObjects()) {
286
QVERIFY(deletedObject.uuid != entryDestinationMerged->uuid());
290
void TestMerge::testResolveConflictTemplate(
292
std::function<void(Database*, const QMap<const char*, QDateTime>&)> verification)
294
QMap<const char*, QDateTime> timestamps;
295
timestamps["initialTime"] = m_clock->currentDateTimeUtc();
296
QScopedPointer<Database> dbDestination(createTestDatabase());
298
auto deletedEntry1 = new Entry();
299
deletedEntry1->setUuid(QUuid::createUuid());
301
deletedEntry1->beginUpdate();
302
deletedEntry1->setGroup(dbDestination->rootGroup());
303
deletedEntry1->setTitle("deletedDestination");
304
deletedEntry1->endUpdate();
306
auto deletedEntry2 = new Entry();
307
deletedEntry2->setUuid(QUuid::createUuid());
309
deletedEntry2->beginUpdate();
310
deletedEntry2->setGroup(dbDestination->rootGroup());
311
deletedEntry2->setTitle("deletedSource");
312
deletedEntry2->endUpdate();
314
QScopedPointer<Database> dbSource(
315
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneIncludeHistory, Group::CloneIncludeEntries));
317
timestamps["oldestCommonHistoryTime"] = m_clock->currentDateTimeUtc();
320
QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().size(), 2);
321
QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(0)->historyItems().count(), 1);
322
QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(1)->historyItems().count(), 1);
323
QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().size(), 2);
324
QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().at(0)->historyItems().count(), 1);
325
QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().at(1)->historyItems().count(), 1);
327
// simulate some work in the dbs (manipulate the history)
328
QPointer<Entry> destinationEntry1 = dbDestination->rootGroup()->children().at(0)->entries().at(0);
329
QPointer<Entry> destinationEntry2 = dbDestination->rootGroup()->children().at(0)->entries().at(1);
330
QPointer<Entry> sourceEntry1 = dbSource->rootGroup()->children().at(0)->entries().at(0);
331
QPointer<Entry> sourceEntry2 = dbSource->rootGroup()->children().at(0)->entries().at(1);
333
timestamps["newestCommonHistoryTime"] = m_clock->advanceMinute(1);
335
destinationEntry1->beginUpdate();
336
destinationEntry1->setNotes("1 Common");
337
destinationEntry1->endUpdate();
338
destinationEntry2->beginUpdate();
339
destinationEntry2->setNotes("1 Common");
340
destinationEntry2->endUpdate();
341
sourceEntry1->beginUpdate();
342
sourceEntry1->setNotes("1 Common");
343
sourceEntry1->endUpdate();
344
sourceEntry2->beginUpdate();
345
sourceEntry2->setNotes("1 Common");
346
sourceEntry2->endUpdate();
348
timestamps["oldestDivergingHistoryTime"] = m_clock->advanceSecond(1);
350
destinationEntry2->beginUpdate();
351
destinationEntry2->setNotes("2 Destination");
352
destinationEntry2->endUpdate();
353
sourceEntry1->beginUpdate();
354
sourceEntry1->setNotes("2 Source");
355
sourceEntry1->endUpdate();
357
timestamps["newestDivergingHistoryTime"] = m_clock->advanceHour(1);
359
destinationEntry1->beginUpdate();
360
destinationEntry1->setNotes("3 Destination");
361
destinationEntry1->endUpdate();
362
sourceEntry2->beginUpdate();
363
sourceEntry2->setNotes("3 Source");
364
sourceEntry2->endUpdate();
367
QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(0)->historyItems().count(), 3);
368
QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().at(1)->historyItems().count(), 3);
369
QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().at(0)->historyItems().count(), 3);
370
QCOMPARE(dbSource->rootGroup()->children().at(0)->entries().at(1)->historyItems().count(), 3);
372
m_clock->advanceMinute(1);
374
QPointer<Entry> deletedEntryDestination = dbDestination->rootGroup()->findEntryByPath("deletedDestination");
375
dbDestination->recycleEntry(deletedEntryDestination);
376
QPointer<Entry> deletedEntrySource = dbSource->rootGroup()->findEntryByPath("deletedSource");
377
dbSource->recycleEntry(deletedEntrySource);
379
m_clock->advanceMinute(1);
381
auto destinationEntrySingle = new Entry();
382
destinationEntrySingle->setUuid(QUuid::createUuid());
384
destinationEntrySingle->beginUpdate();
385
destinationEntrySingle->setGroup(dbDestination->rootGroup()->children().at(1));
386
destinationEntrySingle->setTitle("entryDestination");
387
destinationEntrySingle->endUpdate();
389
auto sourceEntrySingle = new Entry();
390
sourceEntrySingle->setUuid(QUuid::createUuid());
392
sourceEntrySingle->beginUpdate();
393
sourceEntrySingle->setGroup(dbSource->rootGroup()->children().at(1));
394
sourceEntrySingle->setTitle("entrySource");
395
sourceEntrySingle->endUpdate();
397
dbDestination->rootGroup()->setMergeMode(static_cast<Group::MergeMode>(mergeMode));
399
// Make sure the merge changes have a different timestamp.
400
timestamps["mergeTime"] = m_clock->advanceSecond(1);
402
Merger merger(dbSource.data(), dbDestination.data());
405
QPointer<Group> mergedRootGroup = dbDestination->rootGroup();
406
QCOMPARE(mergedRootGroup->entries().size(), 0);
407
// Both databases contain their own generated recycleBin - just one is considered a real recycleBin, the other
408
// exists as normal group, therefore only one entry is considered deleted
409
QCOMPARE(dbDestination->metadata()->recycleBin()->entries().size(), 1);
410
QPointer<Group> mergedGroup1 = mergedRootGroup->children().at(0);
411
QPointer<Group> mergedGroup2 = mergedRootGroup->children().at(1);
412
QVERIFY(mergedGroup1);
413
QVERIFY(mergedGroup2);
414
QCOMPARE(mergedGroup2->entries().size(), 2);
415
QVERIFY(mergedGroup1->entries().at(0));
416
QVERIFY(mergedGroup1->entries().at(1));
418
verification(dbDestination.data(), timestamps);
420
QVERIFY(dbDestination->rootGroup()->findEntryByPath("entryDestination"));
421
QVERIFY(dbDestination->rootGroup()->findEntryByPath("entrySource"));
424
void TestMerge::testDeletionConflictTemplate(int mergeMode,
425
std::function<void(Database*, const QMap<QString, QUuid>&)> verification)
427
QMap<QString, QUuid> identifiers;
428
m_clock->currentDateTimeUtc();
429
QScopedPointer<Database> dbDestination(createTestDatabase());
432
// entry directly deleted in source before updated in target
433
// entry directly deleted in source after updated in target
434
// entry directly deleted in target before updated in source
435
// entry directly deleted in target after updated in source
437
// entry indirectly deleted in source before updated in target
438
// entry indirectly deleted in source after updated in target
439
// entry indirectly deleted in target before updated in source
440
// entry indirectly deleted in target after updated in source
442
auto createGroup = [&](const char* name, Group* parent) {
443
auto group = new Group();
444
group->setUuid(QUuid::createUuid());
445
group->setName(name);
446
group->setParent(parent, 0);
447
identifiers[group->name()] = group->uuid();
450
auto createEntry = [&](const char* title, Group* parent) {
451
auto entry = new Entry();
452
entry->setUuid(QUuid::createUuid());
453
entry->setTitle(title);
454
entry->setGroup(parent);
455
identifiers[entry->title()] = entry->uuid();
458
auto changeEntry = [](Entry* entry) {
459
entry->beginUpdate();
460
entry->setNotes("Change");
464
Group* directlyDeletedEntryGroup = createGroup("DirectlyDeletedEntries", dbDestination->rootGroup());
465
createEntry("EntryDeletedInSourceBeforeChangedInTarget", directlyDeletedEntryGroup);
466
createEntry("EntryDeletedInSourceAfterChangedInTarget", directlyDeletedEntryGroup);
467
createEntry("EntryDeletedInTargetBeforeChangedInSource", directlyDeletedEntryGroup);
468
createEntry("EntryDeletedInTargetAfterChangedInSource", directlyDeletedEntryGroup);
470
Group* groupDeletedInSourceBeforeEntryUpdatedInTarget =
471
createGroup("GroupDeletedInSourceBeforeEntryUpdatedInTarget", dbDestination->rootGroup());
472
createEntry("EntryDeletedInSourceBeforeEntryUpdatedInTarget", groupDeletedInSourceBeforeEntryUpdatedInTarget);
474
Group* groupDeletedInSourceAfterEntryUpdatedInTarget =
475
createGroup("GroupDeletedInSourceAfterEntryUpdatedInTarget", dbDestination->rootGroup());
476
createEntry("EntryDeletedInSourceAfterEntryUpdatedInTarget", groupDeletedInSourceAfterEntryUpdatedInTarget);
478
Group* groupDeletedInTargetBeforeEntryUpdatedInSource =
479
createGroup("GroupDeletedInTargetBeforeEntryUpdatedInSource", dbDestination->rootGroup());
480
createEntry("EntryDeletedInTargetBeforeEntryUpdatedInSource", groupDeletedInTargetBeforeEntryUpdatedInSource);
482
Group* groupDeletedInTargetAfterEntryUpdatedInSource =
483
createGroup("GroupDeletedInTargetAfterEntryUpdatedInSource", dbDestination->rootGroup());
484
createEntry("EntryDeletedInTargetAfterEntryUpdatedInSource", groupDeletedInTargetAfterEntryUpdatedInSource);
486
QScopedPointer<Database> dbSource(
487
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneIncludeHistory, Group::CloneIncludeEntries));
489
QPointer<Entry> sourceEntryDeletedInSourceBeforeChangedInTarget =
490
dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]);
491
QPointer<Entry> targetEntryDeletedInSourceBeforeChangedInTarget =
492
dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]);
494
QPointer<Entry> sourceEntryDeletedInSourceAfterChangedInTarget =
495
dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceAfterChangedInTarget"]);
496
QPointer<Entry> targetEntryDeletedInSourceAfterChangedInTarget =
497
dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceAfterChangedInTarget"]);
499
QPointer<Entry> sourceEntryDeletedInTargetBeforeChangedInSource =
500
dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeChangedInSource"]);
501
QPointer<Entry> targetEntryDeletedInTargetBeforeChangedInSource =
502
dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeChangedInSource"]);
504
QPointer<Entry> sourceEntryDeletedInTargetAfterChangedInSource =
505
dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetAfterChangedInSource"]);
506
QPointer<Entry> targetEntryDeletedInTargetAfterChangedInSource =
507
dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetAfterChangedInSource"]);
509
QPointer<Group> sourceGroupDeletedInSourceBeforeEntryUpdatedInTarget =
510
dbSource->rootGroup()->findGroupByUuid(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]);
511
QPointer<Entry> targetEntryDeletedInSourceBeforeEntryUpdatedInTarget =
512
dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]);
514
QPointer<Group> sourceGroupDeletedInSourceAfterEntryUpdatedInTarget =
515
dbSource->rootGroup()->findGroupByUuid(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]);
516
QPointer<Entry> targetEntryDeletedInSourceAfterEntryUpdatedInTarget =
517
dbDestination->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]);
519
QPointer<Group> targetGroupDeletedInTargetBeforeEntryUpdatedInSource =
520
dbDestination->rootGroup()->findGroupByUuid(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]);
521
QPointer<Entry> sourceEntryDeletedInTargetBeforeEntryUpdatedInSource =
522
dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]);
524
QPointer<Group> targetGroupDeletedInTargetAfterEntryUpdatedInSource =
525
dbDestination->rootGroup()->findGroupByUuid(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]);
526
QPointer<Entry> sourceEntryDeletedInTargetAfterEntryUpdatedInSoruce =
527
dbSource->rootGroup()->findEntryByUuid(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]);
529
// simulate some work in the dbs (manipulate the history)
530
m_clock->advanceMinute(1);
532
delete sourceEntryDeletedInSourceBeforeChangedInTarget.data();
533
changeEntry(targetEntryDeletedInSourceAfterChangedInTarget);
534
delete targetEntryDeletedInTargetBeforeChangedInSource.data();
535
changeEntry(sourceEntryDeletedInTargetAfterChangedInSource);
537
delete sourceGroupDeletedInSourceBeforeEntryUpdatedInTarget.data();
538
changeEntry(targetEntryDeletedInSourceAfterEntryUpdatedInTarget);
539
delete targetGroupDeletedInTargetBeforeEntryUpdatedInSource.data();
540
changeEntry(sourceEntryDeletedInTargetAfterEntryUpdatedInSoruce);
542
m_clock->advanceMinute(1);
544
changeEntry(targetEntryDeletedInSourceBeforeChangedInTarget);
545
delete sourceEntryDeletedInSourceAfterChangedInTarget.data();
546
changeEntry(sourceEntryDeletedInTargetBeforeChangedInSource);
547
delete targetEntryDeletedInTargetAfterChangedInSource.data();
549
changeEntry(targetEntryDeletedInSourceBeforeEntryUpdatedInTarget);
550
delete sourceGroupDeletedInSourceAfterEntryUpdatedInTarget.data();
551
changeEntry(sourceEntryDeletedInTargetBeforeEntryUpdatedInSource);
552
delete targetGroupDeletedInTargetAfterEntryUpdatedInSource.data();
553
m_clock->advanceMinute(1);
555
dbDestination->rootGroup()->setMergeMode(static_cast<Group::MergeMode>(mergeMode));
557
Merger merger(dbSource.data(), dbDestination.data());
560
verification(dbDestination.data(), identifiers);
563
void TestMerge::assertDeletionNewerOnly(Database* db, const QMap<QString, QUuid>& identifiers)
565
QPointer<Group> mergedRootGroup = db->rootGroup();
566
// newer change in target prevents deletion
567
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]));
568
QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]));
569
// newer deletion in source forces deletion
570
QVERIFY(!mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceAfterChangedInTarget"]));
571
QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInSourceAfterChangedInTarget"]));
572
// newer change in source prevents deletion
573
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeChangedInSource"]));
574
QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInTargetBeforeChangedInSource"]));
575
// newer deletion in target forces deletion
576
QVERIFY(!mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetAfterChangedInSource"]));
577
QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetAfterChangedInSource"]));
578
// newer change in target prevents deletion
579
QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]));
580
QVERIFY(!db->containsDeletedObject(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]));
581
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]));
582
QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]));
583
// newer deletion in source forces deletion
584
QVERIFY(!mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]));
585
QVERIFY(db->containsDeletedObject(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]));
586
QVERIFY(!mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]));
587
QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]));
588
// newer change in source prevents deletion
589
QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]));
590
QVERIFY(!db->containsDeletedObject(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]));
591
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]));
592
QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]));
593
// newer deletion in target forces deletion
594
QVERIFY(!mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]));
595
QVERIFY(db->containsDeletedObject(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]));
596
QVERIFY(!mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]));
597
QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]));
600
void TestMerge::assertDeletionLocalOnly(Database* db, const QMap<QString, QUuid>& identifiers)
602
QPointer<Group> mergedRootGroup = db->rootGroup();
604
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]));
605
QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceBeforeChangedInTarget"]));
607
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceAfterChangedInTarget"]));
608
QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceAfterChangedInTarget"]));
610
// Uuids in db and deletedObjects is intended according to KeePass #1752
611
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeChangedInSource"]));
612
QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetBeforeChangedInSource"]));
614
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetAfterChangedInSource"]));
615
QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetAfterChangedInSource"]));
617
QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]));
618
QVERIFY(!db->containsDeletedObject(identifiers["GroupDeletedInSourceBeforeEntryUpdatedInTarget"]));
619
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]));
620
QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceBeforeEntryUpdatedInTarget"]));
622
QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]));
623
QVERIFY(!db->containsDeletedObject(identifiers["GroupDeletedInSourceAfterEntryUpdatedInTarget"]));
624
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]));
625
QVERIFY(!db->containsDeletedObject(identifiers["EntryDeletedInSourceAfterEntryUpdatedInTarget"]));
627
QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]));
628
QVERIFY(db->containsDeletedObject(identifiers["GroupDeletedInTargetBeforeEntryUpdatedInSource"]));
629
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]));
630
QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetBeforeEntryUpdatedInSource"]));
632
QVERIFY(mergedRootGroup->findGroupByUuid(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]));
633
QVERIFY(db->containsDeletedObject(identifiers["GroupDeletedInTargetAfterEntryUpdatedInSource"]));
634
QVERIFY(mergedRootGroup->findEntryByUuid(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]));
635
QVERIFY(db->containsDeletedObject(identifiers["EntryDeletedInTargetAfterEntryUpdatedInSource"]));
638
void TestMerge::assertUpdateMergedEntry1(Entry* mergedEntry1, const QMap<const char*, QDateTime>& timestamps)
640
QCOMPARE(mergedEntry1->historyItems().count(), 4);
641
QCOMPARE(mergedEntry1->historyItems().at(0)->notes(), QString(""));
642
QCOMPARE(mergedEntry1->historyItems().at(0)->timeInfo().lastModificationTime(), timestamps["initialTime"]);
643
QCOMPARE(mergedEntry1->historyItems().at(1)->notes(), QString(""));
644
QCOMPARE(mergedEntry1->historyItems().at(1)->timeInfo().lastModificationTime(),
645
timestamps["oldestCommonHistoryTime"]);
646
QCOMPARE(mergedEntry1->historyItems().at(2)->notes(), QString("1 Common"));
647
QCOMPARE(mergedEntry1->historyItems().at(2)->timeInfo().lastModificationTime(),
648
timestamps["newestCommonHistoryTime"]);
649
QCOMPARE(mergedEntry1->historyItems().at(3)->notes(), QString("2 Source"));
650
QCOMPARE(mergedEntry1->historyItems().at(3)->timeInfo().lastModificationTime(),
651
timestamps["oldestDivergingHistoryTime"]);
652
QCOMPARE(mergedEntry1->notes(), QString("3 Destination"));
653
QCOMPARE(mergedEntry1->timeInfo().lastModificationTime(), timestamps["newestDivergingHistoryTime"]);
656
void TestMerge::assertUpdateReappliedEntry2(Entry* mergedEntry2, const QMap<const char*, QDateTime>& timestamps)
658
QCOMPARE(mergedEntry2->historyItems().count(), 5);
659
QCOMPARE(mergedEntry2->historyItems().at(0)->notes(), QString(""));
660
QCOMPARE(mergedEntry2->historyItems().at(0)->timeInfo().lastModificationTime(), timestamps["initialTime"]);
661
QCOMPARE(mergedEntry2->historyItems().at(1)->notes(), QString(""));
662
QCOMPARE(mergedEntry2->historyItems().at(1)->timeInfo().lastModificationTime(),
663
timestamps["oldestCommonHistoryTime"]);
664
QCOMPARE(mergedEntry2->historyItems().at(2)->notes(), QString("1 Common"));
665
QCOMPARE(mergedEntry2->historyItems().at(2)->timeInfo().lastModificationTime(),
666
timestamps["newestCommonHistoryTime"]);
667
QCOMPARE(mergedEntry2->historyItems().at(3)->notes(), QString("2 Destination"));
668
QCOMPARE(mergedEntry2->historyItems().at(3)->timeInfo().lastModificationTime(),
669
timestamps["oldestDivergingHistoryTime"]);
670
QCOMPARE(mergedEntry2->historyItems().at(4)->notes(), QString("3 Source"));
671
QCOMPARE(mergedEntry2->historyItems().at(4)->timeInfo().lastModificationTime(),
672
timestamps["newestDivergingHistoryTime"]);
673
QCOMPARE(mergedEntry2->notes(), QString("2 Destination"));
674
QCOMPARE(mergedEntry2->timeInfo().lastModificationTime(), timestamps["mergeTime"]);
677
void TestMerge::assertUpdateReappliedEntry1(Entry* mergedEntry1, const QMap<const char*, QDateTime>& timestamps)
679
QCOMPARE(mergedEntry1->historyItems().count(), 5);
680
QCOMPARE(mergedEntry1->historyItems().at(0)->notes(), QString(""));
681
QCOMPARE(mergedEntry1->historyItems().at(0)->timeInfo().lastModificationTime(), timestamps["initialTime"]);
682
QCOMPARE(mergedEntry1->historyItems().at(1)->notes(), QString(""));
683
QCOMPARE(mergedEntry1->historyItems().at(1)->timeInfo().lastModificationTime(),
684
timestamps["oldestCommonHistoryTime"]);
685
QCOMPARE(mergedEntry1->historyItems().at(2)->notes(), QString("1 Common"));
686
QCOMPARE(mergedEntry1->historyItems().at(2)->timeInfo().lastModificationTime(),
687
timestamps["newestCommonHistoryTime"]);
688
QCOMPARE(mergedEntry1->historyItems().at(3)->notes(), QString("2 Source"));
689
QCOMPARE(mergedEntry1->historyItems().at(3)->timeInfo().lastModificationTime(),
690
timestamps["oldestDivergingHistoryTime"]);
691
QCOMPARE(mergedEntry1->historyItems().at(4)->notes(), QString("3 Destination"));
692
QCOMPARE(mergedEntry1->historyItems().at(4)->timeInfo().lastModificationTime(),
693
timestamps["newestDivergingHistoryTime"]);
694
QCOMPARE(mergedEntry1->notes(), QString("2 Source"));
695
QCOMPARE(mergedEntry1->timeInfo().lastModificationTime(), timestamps["mergeTime"]);
698
void TestMerge::assertUpdateMergedEntry2(Entry* mergedEntry2, const QMap<const char*, QDateTime>& timestamps)
700
QCOMPARE(mergedEntry2->historyItems().count(), 4);
701
QCOMPARE(mergedEntry2->historyItems().at(0)->notes(), QString(""));
702
QCOMPARE(mergedEntry2->historyItems().at(0)->timeInfo().lastModificationTime(), timestamps["initialTime"]);
703
QCOMPARE(mergedEntry2->historyItems().at(1)->notes(), QString(""));
704
QCOMPARE(mergedEntry2->historyItems().at(1)->timeInfo().lastModificationTime(),
705
timestamps["oldestCommonHistoryTime"]);
706
QCOMPARE(mergedEntry2->historyItems().at(2)->notes(), QString("1 Common"));
707
QCOMPARE(mergedEntry2->historyItems().at(2)->timeInfo().lastModificationTime(),
708
timestamps["newestCommonHistoryTime"]);
709
QCOMPARE(mergedEntry2->historyItems().at(3)->notes(), QString("2 Destination"));
710
QCOMPARE(mergedEntry2->historyItems().at(3)->timeInfo().lastModificationTime(),
711
timestamps["oldestDivergingHistoryTime"]);
712
QCOMPARE(mergedEntry2->notes(), QString("3 Source"));
713
QCOMPARE(mergedEntry2->timeInfo().lastModificationTime(), timestamps["newestDivergingHistoryTime"]);
716
void TestMerge::testDeletionConflictEntry_Synchronized()
718
testDeletionConflictTemplate(Group::Synchronize, &TestMerge::assertDeletionNewerOnly);
721
void TestMerge::testDeletionConflictEntry_KeepNewer()
723
testDeletionConflictTemplate(Group::KeepNewer, &TestMerge::assertDeletionLocalOnly);
727
* Tests the KeepNewer mode concerning history.
729
void TestMerge::testResolveConflictEntry_Synchronize()
731
testResolveConflictTemplate(Group::Synchronize, [](Database* db, const QMap<const char*, QDateTime>& timestamps) {
732
QPointer<Group> mergedRootGroup = db->rootGroup();
733
QPointer<Group> mergedGroup1 = mergedRootGroup->children().at(0);
734
TestMerge::assertUpdateMergedEntry1(mergedGroup1->entries().at(0), timestamps);
735
TestMerge::assertUpdateMergedEntry2(mergedGroup1->entries().at(1), timestamps);
739
void TestMerge::testResolveConflictEntry_KeepNewer()
741
testResolveConflictTemplate(Group::KeepNewer, [](Database* db, const QMap<const char*, QDateTime>& timestamps) {
742
QPointer<Group> mergedRootGroup = db->rootGroup();
743
QPointer<Group> mergedGroup1 = mergedRootGroup->children().at(0);
744
TestMerge::assertUpdateMergedEntry1(mergedGroup1->entries().at(0), timestamps);
745
TestMerge::assertUpdateMergedEntry2(mergedGroup1->entries().at(1), timestamps);
750
* The location of an entry should be updated in the
751
* destination database.
753
void TestMerge::testMoveEntry()
755
QScopedPointer<Database> dbDestination(createTestDatabase());
756
QScopedPointer<Database> dbSource(
757
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
759
QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
760
QVERIFY(entrySourceInitial != nullptr);
762
QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group2");
763
QVERIFY(groupSourceInitial != nullptr);
765
// Make sure the two changes have a different timestamp.
766
m_clock->advanceSecond(1);
768
entrySourceInitial->setGroup(groupSourceInitial);
769
QCOMPARE(entrySourceInitial->group()->name(), QString("group2"));
771
m_clock->advanceSecond(1);
773
Merger merger(dbSource.data(), dbDestination.data());
776
QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
777
QVERIFY(entryDestinationMerged != nullptr);
778
QCOMPARE(entryDestinationMerged->group()->name(), QString("group2"));
779
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
783
* The location of an entry should be updated in the
784
* destination database, but changes from the destination
785
* database should be preserved.
787
void TestMerge::testMoveEntryPreserveChanges()
789
QScopedPointer<Database> dbDestination(createTestDatabase());
790
QScopedPointer<Database> dbSource(
791
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
793
QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
794
QVERIFY(entrySourceInitial != nullptr);
796
QPointer<Group> group2Source = dbSource->rootGroup()->findChildByName("group2");
797
QVERIFY(group2Source != nullptr);
799
m_clock->advanceSecond(1);
801
entrySourceInitial->setGroup(group2Source);
802
QCOMPARE(entrySourceInitial->group()->name(), QString("group2"));
804
QPointer<Entry> entryDestinationInitial = dbDestination->rootGroup()->findEntryByPath("entry1");
805
QVERIFY(entryDestinationInitial != nullptr);
807
m_clock->advanceSecond(1);
809
entryDestinationInitial->beginUpdate();
810
entryDestinationInitial->setPassword("password");
811
entryDestinationInitial->endUpdate();
813
m_clock->advanceSecond(1);
815
Merger merger(dbSource.data(), dbDestination.data());
818
QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
819
QVERIFY(entryDestinationMerged != nullptr);
820
QCOMPARE(entryDestinationMerged->group()->name(), QString("group2"));
821
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
822
QCOMPARE(entryDestinationMerged->password(), QString("password"));
825
void TestMerge::testCreateNewGroups()
827
QScopedPointer<Database> dbDestination(createTestDatabase());
828
QScopedPointer<Database> dbSource(
829
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
831
m_clock->advanceSecond(1);
833
auto groupSourceCreated = new Group();
834
groupSourceCreated->setName("group3");
835
groupSourceCreated->setUuid(QUuid::createUuid());
836
groupSourceCreated->setParent(dbSource->rootGroup());
838
m_clock->advanceSecond(1);
840
Merger merger(dbSource.data(), dbDestination.data());
843
QPointer<Group> groupDestinationMerged = dbDestination->rootGroup()->findChildByName("group3");
844
QVERIFY(groupDestinationMerged != nullptr);
845
QCOMPARE(groupDestinationMerged->name(), QString("group3"));
848
void TestMerge::testMoveEntryIntoNewGroup()
850
QScopedPointer<Database> dbDestination(createTestDatabase());
851
QScopedPointer<Database> dbSource(
852
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
854
m_clock->advanceSecond(1);
856
auto groupSourceCreated = new Group();
857
groupSourceCreated->setName("group3");
858
groupSourceCreated->setUuid(QUuid::createUuid());
859
groupSourceCreated->setParent(dbSource->rootGroup());
861
QPointer<Entry> entrySourceMoved = dbSource->rootGroup()->findEntryByPath("entry1");
862
entrySourceMoved->setGroup(groupSourceCreated);
864
m_clock->advanceSecond(1);
866
Merger merger(dbSource.data(), dbDestination.data());
869
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
871
QPointer<Group> groupDestinationMerged = dbDestination->rootGroup()->findChildByName("group3");
872
QVERIFY(groupDestinationMerged != nullptr);
873
QCOMPARE(groupDestinationMerged->name(), QString("group3"));
874
QCOMPARE(groupDestinationMerged->entries().size(), 1);
876
QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
877
QVERIFY(entryDestinationMerged != nullptr);
878
QCOMPARE(entryDestinationMerged->group()->name(), QString("group3"));
882
* Even though the entries' locations are no longer
883
* the same, we will keep associating them.
885
void TestMerge::testUpdateEntryDifferentLocation()
887
QScopedPointer<Database> dbDestination(createTestDatabase());
888
QScopedPointer<Database> dbSource(
889
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
891
auto groupDestinationCreated = new Group();
892
groupDestinationCreated->setName("group3");
893
groupDestinationCreated->setUuid(QUuid::createUuid());
894
groupDestinationCreated->setParent(dbDestination->rootGroup());
896
m_clock->advanceSecond(1);
898
QPointer<Entry> entryDestinationMoved = dbDestination->rootGroup()->findEntryByPath("entry1");
899
QVERIFY(entryDestinationMoved != nullptr);
900
entryDestinationMoved->setGroup(groupDestinationCreated);
901
QUuid uuidBeforeSyncing = entryDestinationMoved->uuid();
902
QDateTime destinationLocationChanged = entryDestinationMoved->timeInfo().locationChanged();
904
// Change the entry in the source db.
905
m_clock->advanceSecond(1);
907
QPointer<Entry> entrySourceMoved = dbSource->rootGroup()->findEntryByPath("entry1");
908
QVERIFY(entrySourceMoved != nullptr);
909
entrySourceMoved->beginUpdate();
910
entrySourceMoved->setUsername("username");
911
entrySourceMoved->endUpdate();
912
QDateTime sourceLocationChanged = entrySourceMoved->timeInfo().locationChanged();
914
QVERIFY(destinationLocationChanged > sourceLocationChanged);
916
m_clock->advanceSecond(1);
918
Merger merger(dbSource.data(), dbDestination.data());
921
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
923
QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
924
QVERIFY(entryDestinationMerged != nullptr);
925
QVERIFY(entryDestinationMerged->group() != nullptr);
926
QCOMPARE(entryDestinationMerged->username(), QString("username"));
927
QCOMPARE(entryDestinationMerged->group()->name(), QString("group3"));
928
QCOMPARE(uuidBeforeSyncing, entryDestinationMerged->uuid());
929
// default merge strategy is KeepNewer - therefore the older location is used!
930
QCOMPARE(entryDestinationMerged->timeInfo().locationChanged(), sourceLocationChanged);
934
* Groups should be updated using the uuids.
936
void TestMerge::testUpdateGroup()
938
QScopedPointer<Database> dbDestination(createTestDatabase());
939
QScopedPointer<Database> dbSource(
940
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
942
m_clock->advanceSecond(1);
944
QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group2");
945
groupSourceInitial->setName("group2 renamed");
946
groupSourceInitial->setNotes("updated notes");
947
QUuid customIconId = QUuid::createUuid();
948
dbSource->metadata()->addCustomIcon(customIconId, QString("custom icon").toLocal8Bit());
949
groupSourceInitial->setIcon(customIconId);
951
QPointer<Entry> entrySourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
952
QVERIFY(entrySourceInitial != nullptr);
953
entrySourceInitial->setGroup(groupSourceInitial);
954
entrySourceInitial->setTitle("entry1 renamed");
955
QUuid uuidBeforeSyncing = entrySourceInitial->uuid();
957
m_clock->advanceSecond(1);
959
Merger merger(dbSource.data(), dbDestination.data());
962
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
964
QPointer<Entry> entryDestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1 renamed");
965
QVERIFY(entryDestinationMerged != nullptr);
966
QVERIFY(entryDestinationMerged->group() != nullptr);
967
QCOMPARE(entryDestinationMerged->group()->name(), QString("group2 renamed"));
968
QCOMPARE(uuidBeforeSyncing, entryDestinationMerged->uuid());
970
QPointer<Group> groupMerged = dbDestination->rootGroup()->findChildByName("group2 renamed");
971
QCOMPARE(groupMerged->notes(), QString("updated notes"));
972
QCOMPARE(groupMerged->iconUuid(), customIconId);
975
void TestMerge::testUpdateGroupLocation()
977
QScopedPointer<Database> dbDestination(createTestDatabase());
978
auto group3DestinationCreated = new Group();
979
QUuid group3Uuid = QUuid::createUuid();
980
group3DestinationCreated->setUuid(group3Uuid);
981
group3DestinationCreated->setName("group3");
982
group3DestinationCreated->setParent(dbDestination->rootGroup()->findChildByName("group1"));
984
QScopedPointer<Database> dbSource(
985
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
988
QPointer<Group> group3SourceInitial = dbSource->rootGroup()->findGroupByUuid(group3Uuid);
989
QVERIFY(group3DestinationCreated != nullptr);
991
QDateTime initialLocationChanged = group3SourceInitial->timeInfo().locationChanged();
993
m_clock->advanceSecond(1);
995
QPointer<Group> group3SourceMoved = dbSource->rootGroup()->findGroupByUuid(group3Uuid);
996
QVERIFY(group3SourceMoved != nullptr);
997
group3SourceMoved->setParent(dbSource->rootGroup()->findChildByName("group2"));
999
QDateTime movedLocaltionChanged = group3SourceMoved->timeInfo().locationChanged();
1000
QVERIFY(initialLocationChanged < movedLocaltionChanged);
1002
m_clock->advanceSecond(1);
1004
Merger merger1(dbSource.data(), dbDestination.data());
1007
QPointer<Group> group3DestinationMerged1 = dbDestination->rootGroup()->findGroupByUuid(group3Uuid);
1008
QVERIFY(group3DestinationMerged1 != nullptr);
1009
QCOMPARE(group3DestinationMerged1->parent(), dbDestination->rootGroup()->findChildByName("group2"));
1010
QCOMPARE(group3DestinationMerged1->timeInfo().locationChanged(), movedLocaltionChanged);
1012
m_clock->advanceSecond(1);
1014
Merger merger2(dbSource.data(), dbDestination.data());
1017
QPointer<Group> group3DestinationMerged2 = dbDestination->rootGroup()->findGroupByUuid(group3Uuid);
1018
QVERIFY(group3DestinationMerged2 != nullptr);
1019
QCOMPARE(group3DestinationMerged2->parent(), dbDestination->rootGroup()->findChildByName("group2"));
1020
QCOMPARE(group3DestinationMerged1->timeInfo().locationChanged(), movedLocaltionChanged);
1024
* The first merge should create new entries, the
1025
* second should only sync them, since they have
1026
* been created with the same UUIDs.
1028
void TestMerge::testMergeAndSync()
1030
QScopedPointer<Database> dbDestination(new Database());
1031
QScopedPointer<Database> dbSource(createTestDatabase());
1033
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 0);
1035
m_clock->advanceSecond(1);
1037
Merger merger1(dbSource.data(), dbDestination.data());
1040
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
1042
m_clock->advanceSecond(1);
1044
Merger merger2(dbSource.data(), dbDestination.data());
1047
// Still only 2 entries, since now we detect which are already present.
1048
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
1052
* Custom icons should be brought over when merging.
1054
void TestMerge::testMergeCustomIcons()
1056
QScopedPointer<Database> dbDestination(new Database());
1057
QScopedPointer<Database> dbSource(createTestDatabase());
1059
m_clock->advanceSecond(1);
1061
QUuid customIconId = QUuid::createUuid();
1063
dbSource->metadata()->addCustomIcon(customIconId, QString("custom icon").toLocal8Bit());
1065
QVERIFY(dbSource->metadata()->hasCustomIcon(customIconId));
1067
m_clock->advanceSecond(1);
1069
Merger merger(dbSource.data(), dbDestination.data());
1072
QVERIFY(dbDestination->metadata()->hasCustomIcon(customIconId));
1076
* No duplicate icons should be created
1078
void TestMerge::testMergeDuplicateCustomIcons()
1080
QScopedPointer<Database> dbDestination(new Database());
1081
QScopedPointer<Database> dbSource(createTestDatabase());
1083
m_clock->advanceSecond(1);
1085
QUuid customIconId = QUuid::createUuid();
1087
QByteArray customIcon1("custom icon 1");
1088
QByteArray customIcon2("custom icon 2");
1090
dbSource->metadata()->addCustomIcon(customIconId, customIcon1);
1091
dbDestination->metadata()->addCustomIcon(customIconId, customIcon2);
1093
QVERIFY(dbSource->metadata()->hasCustomIcon(customIconId));
1094
QVERIFY(dbDestination->metadata()->hasCustomIcon(customIconId));
1096
m_clock->advanceSecond(1);
1098
Merger merger(dbSource.data(), dbDestination.data());
1101
QVERIFY(dbDestination->metadata()->hasCustomIcon(customIconId));
1102
QCOMPARE(dbDestination->metadata()->customIconsOrder().count(), 1);
1103
QCOMPARE(dbDestination->metadata()->customIcon(customIconId).data, customIcon2);
1106
void TestMerge::testMetadata()
1108
QSKIP("Sophisticated merging for Metadata not implemented");
1109
// TODO HNH: I think a merge of recycle bins would be nice since duplicating them
1110
// is not really a good solution - the one to use as final recycle bin
1111
// is determined by the merge method - if only one has a bin, this one
1112
// will be used - exception is the target has no recycle bin activated
1115
void TestMerge::testCustomData()
1117
QScopedPointer<Database> dbDestination(new Database());
1118
QScopedPointer<Database> dbSource(createTestDatabase());
1119
QScopedPointer<Database> dbDestination2(new Database());
1120
QScopedPointer<Database> dbSource2(createTestDatabase());
1122
m_clock->advanceSecond(1);
1124
dbDestination->metadata()->customData()->set("toBeDeleted", "value");
1125
dbDestination->metadata()->customData()->set("key3", "oldValue");
1127
dbSource2->metadata()->customData()->set("key1", "value1");
1128
dbSource2->metadata()->customData()->set("key2", "value2");
1129
dbSource2->metadata()->customData()->set("key3", "newValue");
1130
dbSource2->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
1132
m_clock->advanceSecond(1);
1134
dbSource->metadata()->customData()->set("key1", "value1");
1135
dbSource->metadata()->customData()->set("key2", "value2");
1136
dbSource->metadata()->customData()->set("key3", "newValue");
1137
dbSource->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
1139
dbDestination2->metadata()->customData()->set("notToBeDeleted", "value");
1140
dbDestination2->metadata()->customData()->set("key3", "oldValue");
1143
QVERIFY(!dbSource->metadata()->customData()->isEmpty());
1144
QVERIFY(!dbSource2->metadata()->customData()->isEmpty());
1146
m_clock->advanceSecond(1);
1148
Merger merger(dbSource.data(), dbDestination.data());
1149
QStringList changes = merger.merge();
1151
QVERIFY(!changes.isEmpty());
1153
// Source is newer, data should be merged
1154
QVERIFY(!dbDestination->metadata()->customData()->isEmpty());
1155
QVERIFY(dbDestination->metadata()->customData()->contains("key1"));
1156
QVERIFY(dbDestination->metadata()->customData()->contains("key2"));
1157
QVERIFY(dbDestination->metadata()->customData()->contains("Browser"));
1158
QVERIFY(!dbDestination->metadata()->customData()->contains("toBeDeleted"));
1159
QCOMPARE(dbDestination->metadata()->customData()->value("key1"), QString("value1"));
1160
QCOMPARE(dbDestination->metadata()->customData()->value("key2"), QString("value2"));
1161
QCOMPARE(dbDestination->metadata()->customData()->value("Browser"), QString("n'8=3W@L^6d->d.]St_>]"));
1162
QCOMPARE(dbDestination->metadata()->customData()->value("key3"),
1163
QString("newValue")); // Old value should be replaced
1165
// Merging again should not do anything if the values are the same.
1166
m_clock->advanceSecond(1);
1167
dbSource->metadata()->customData()->set("key3", "oldValue");
1168
dbSource->metadata()->customData()->set("key3", "newValue");
1169
Merger merger2(dbSource.data(), dbDestination.data());
1170
QStringList changes2 = merger2.merge();
1171
QVERIFY(changes2.isEmpty());
1173
Merger merger3(dbSource2.data(), dbDestination2.data());
1176
// Target is newer, no data is merged
1177
QVERIFY(!dbDestination2->metadata()->customData()->isEmpty());
1178
QVERIFY(!dbDestination2->metadata()->customData()->contains("key1"));
1179
QVERIFY(!dbDestination2->metadata()->customData()->contains("key2"));
1180
QVERIFY(!dbDestination2->metadata()->customData()->contains("Browser"));
1181
QVERIFY(dbDestination2->metadata()->customData()->contains("notToBeDeleted"));
1182
QCOMPARE(dbDestination2->metadata()->customData()->value("key3"),
1183
QString("oldValue")); // Old value should not be replaced
1186
void TestMerge::testDeletedEntry()
1188
QScopedPointer<Database> dbDestination(createTestDatabase());
1189
QScopedPointer<Database> dbSource(
1190
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
1192
m_clock->advanceSecond(1);
1194
QPointer<Entry> entry1SourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
1195
QVERIFY(entry1SourceInitial != nullptr);
1196
QUuid entry1Uuid = entry1SourceInitial->uuid();
1197
delete entry1SourceInitial;
1198
QVERIFY(dbSource->containsDeletedObject(entry1Uuid));
1200
m_clock->advanceSecond(1);
1202
QPointer<Entry> entry2DestinationInitial = dbDestination->rootGroup()->findEntryByPath("entry2");
1203
QVERIFY(entry2DestinationInitial != nullptr);
1204
QUuid entry2Uuid = entry2DestinationInitial->uuid();
1205
delete entry2DestinationInitial;
1206
QVERIFY(dbDestination->containsDeletedObject(entry2Uuid));
1208
m_clock->advanceSecond(1);
1210
Merger merger(dbSource.data(), dbDestination.data());
1213
QPointer<Entry> entry1DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
1214
QVERIFY(entry1DestinationMerged);
1215
QVERIFY(!dbDestination->containsDeletedObject(entry1Uuid));
1216
QPointer<Entry> entry2DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry2");
1217
QVERIFY(entry2DestinationMerged);
1218
// Uuid in db and deletedObjects is intended according to KeePass #1752
1219
QVERIFY(dbDestination->containsDeletedObject(entry2Uuid));
1221
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2);
1224
void TestMerge::testDeletedGroup()
1226
QScopedPointer<Database> dbDestination(createTestDatabase());
1227
QScopedPointer<Database> dbSource(
1228
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
1230
m_clock->advanceSecond(1);
1232
QPointer<Group> group2DestinationInitial = dbDestination->rootGroup()->findChildByName("group2");
1233
QVERIFY(group2DestinationInitial != nullptr);
1234
auto entry3DestinationCreated = new Entry();
1235
entry3DestinationCreated->beginUpdate();
1236
entry3DestinationCreated->setUuid(QUuid::createUuid());
1237
entry3DestinationCreated->setGroup(group2DestinationInitial);
1238
entry3DestinationCreated->setTitle("entry3");
1239
entry3DestinationCreated->endUpdate();
1241
m_clock->advanceSecond(1);
1243
QPointer<Group> group1SourceInitial = dbSource->rootGroup()->findChildByName("group1");
1244
QVERIFY(group1SourceInitial != nullptr);
1245
QPointer<Entry> entry1SourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
1246
QVERIFY(entry1SourceInitial != nullptr);
1247
QPointer<Entry> entry2SourceInitial = dbSource->rootGroup()->findEntryByPath("entry2");
1248
QVERIFY(entry2SourceInitial != nullptr);
1249
QUuid group1Uuid = group1SourceInitial->uuid();
1250
QUuid entry1Uuid = entry1SourceInitial->uuid();
1251
QUuid entry2Uuid = entry2SourceInitial->uuid();
1252
delete group1SourceInitial;
1253
QVERIFY(dbSource->containsDeletedObject(group1Uuid));
1254
QVERIFY(dbSource->containsDeletedObject(entry1Uuid));
1255
QVERIFY(dbSource->containsDeletedObject(entry2Uuid));
1257
m_clock->advanceSecond(1);
1259
QPointer<Group> group2SourceInitial = dbSource->rootGroup()->findChildByName("group2");
1260
QVERIFY(group2SourceInitial != nullptr);
1261
QUuid group2Uuid = group2SourceInitial->uuid();
1262
delete group2SourceInitial;
1263
QVERIFY(dbSource->containsDeletedObject(group2Uuid));
1265
m_clock->advanceSecond(1);
1267
Merger merger(dbSource.data(), dbDestination.data());
1270
QVERIFY(!dbDestination->containsDeletedObject(group1Uuid));
1271
QVERIFY(!dbDestination->containsDeletedObject(entry1Uuid));
1272
QVERIFY(!dbDestination->containsDeletedObject(entry2Uuid));
1273
QVERIFY(!dbDestination->containsDeletedObject(group2Uuid));
1275
QPointer<Entry> entry1DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
1276
QVERIFY(entry1DestinationMerged);
1277
QPointer<Entry> entry2DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry2");
1278
QVERIFY(entry2DestinationMerged);
1279
QPointer<Entry> entry3DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry3");
1280
QVERIFY(entry3DestinationMerged);
1281
QPointer<Group> group1DestinationMerged = dbDestination->rootGroup()->findChildByName("group1");
1282
QVERIFY(group1DestinationMerged);
1283
QPointer<Group> group2DestinationMerged = dbDestination->rootGroup()->findChildByName("group2");
1284
QVERIFY(group2DestinationMerged);
1286
QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 3);
1289
void TestMerge::testDeletedRevertedEntry()
1291
QScopedPointer<Database> dbDestination(createTestDatabase());
1292
QScopedPointer<Database> dbSource(
1293
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
1295
m_clock->advanceSecond(1);
1297
QPointer<Entry> entry1DestinationInitial = dbDestination->rootGroup()->findEntryByPath("entry1");
1298
QVERIFY(entry1DestinationInitial != nullptr);
1299
QUuid entry1Uuid = entry1DestinationInitial->uuid();
1300
delete entry1DestinationInitial;
1301
QVERIFY(dbDestination->containsDeletedObject(entry1Uuid));
1303
m_clock->advanceSecond(1);
1305
QPointer<Entry> entry2SourceInitial = dbSource->rootGroup()->findEntryByPath("entry2");
1306
QVERIFY(entry2SourceInitial != nullptr);
1307
QUuid entry2Uuid = entry2SourceInitial->uuid();
1308
delete entry2SourceInitial;
1309
QVERIFY(dbSource->containsDeletedObject(entry2Uuid));
1311
m_clock->advanceSecond(1);
1313
QPointer<Entry> entry1SourceInitial = dbSource->rootGroup()->findEntryByPath("entry1");
1314
QVERIFY(entry1SourceInitial != nullptr);
1315
entry1SourceInitial->setNotes("Updated");
1317
QPointer<Entry> entry2DestinationInitial = dbDestination->rootGroup()->findEntryByPath("entry2");
1318
QVERIFY(entry2DestinationInitial != nullptr);
1319
entry2DestinationInitial->setNotes("Updated");
1321
Merger merger(dbSource.data(), dbDestination.data());
1324
// Uuid in db and deletedObjects is intended according to KeePass #1752
1325
QVERIFY(dbDestination->containsDeletedObject(entry1Uuid));
1326
QVERIFY(!dbDestination->containsDeletedObject(entry2Uuid));
1328
QPointer<Entry> entry1DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry1");
1329
QVERIFY(entry1DestinationMerged);
1330
QVERIFY(entry1DestinationMerged->notes() == "Updated");
1331
QPointer<Entry> entry2DestinationMerged = dbDestination->rootGroup()->findEntryByPath("entry2");
1332
QVERIFY(entry2DestinationMerged);
1333
QVERIFY(entry2DestinationMerged->notes() == "Updated");
1336
void TestMerge::testDeletedRevertedGroup()
1338
QScopedPointer<Database> dbDestination(createTestDatabase());
1339
QScopedPointer<Database> dbSource(
1340
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
1342
m_clock->advanceSecond(1);
1344
QPointer<Group> group2SourceInitial = dbSource->rootGroup()->findChildByName("group2");
1345
QVERIFY(group2SourceInitial);
1346
QUuid group2Uuid = group2SourceInitial->uuid();
1347
delete group2SourceInitial;
1348
QVERIFY(dbSource->containsDeletedObject(group2Uuid));
1350
m_clock->advanceSecond(1);
1352
QPointer<Group> group1DestinationInitial = dbDestination->rootGroup()->findChildByName("group1");
1353
QVERIFY(group1DestinationInitial);
1354
QUuid group1Uuid = group1DestinationInitial->uuid();
1355
delete group1DestinationInitial;
1356
QVERIFY(dbDestination->containsDeletedObject(group1Uuid));
1358
m_clock->advanceSecond(1);
1360
QPointer<Group> group1SourceInitial = dbSource->rootGroup()->findChildByName("group1");
1361
QVERIFY(group1SourceInitial);
1362
group1SourceInitial->setNotes("Updated");
1364
m_clock->advanceSecond(1);
1366
QPointer<Group> group2DestinationInitial = dbDestination->rootGroup()->findChildByName("group2");
1367
QVERIFY(group2DestinationInitial);
1368
group2DestinationInitial->setNotes("Updated");
1370
m_clock->advanceSecond(1);
1372
Merger merger(dbSource.data(), dbDestination.data());
1375
// Uuid in db and deletedObjects is intended according to KeePass #1752
1376
QVERIFY(dbDestination->containsDeletedObject(group1Uuid));
1377
QVERIFY(!dbDestination->containsDeletedObject(group2Uuid));
1379
QPointer<Group> group1DestinationMerged = dbDestination->rootGroup()->findChildByName("group1");
1380
QVERIFY(group1DestinationMerged);
1381
QVERIFY(group1DestinationMerged->notes() == "Updated");
1382
QPointer<Group> group2DestinationMerged = dbDestination->rootGroup()->findChildByName("group2");
1383
QVERIFY(group2DestinationMerged);
1384
QVERIFY(group2DestinationMerged->notes() == "Updated");
1388
* If the group is updated in the source database, and the
1389
* destination database after, the group should remain the
1392
void TestMerge::testResolveGroupConflictOlder()
1394
QScopedPointer<Database> dbDestination(createTestDatabase());
1395
QScopedPointer<Database> dbSource(
1396
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
1399
QPointer<Group> groupSourceInitial = dbSource->rootGroup()->findChildByName("group1");
1400
QVERIFY(groupSourceInitial != nullptr);
1402
// Make sure the two changes have a different timestamp.
1403
m_clock->advanceSecond(1);
1405
groupSourceInitial->setName("group1 updated in source");
1407
// Make sure the two changes have a different timestamp.
1408
m_clock->advanceSecond(1);
1410
QPointer<Group> groupDestinationUpdated = dbDestination->rootGroup()->findChildByName("group1");
1411
groupDestinationUpdated->setName("group1 updated in destination");
1413
m_clock->advanceSecond(1);
1415
Merger merger(dbSource.data(), dbDestination.data());
1419
QPointer<Group> groupDestinationMerged =
1420
dbDestination->rootGroup()->findChildByName("group1 updated in destination");
1421
QVERIFY(groupDestinationMerged != nullptr);
1424
void TestMerge::testMergeNotModified()
1426
QScopedPointer<Database> dbDestination(createTestDatabase());
1427
QScopedPointer<Database> dbSource(
1428
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
1430
QSignalSpy modifiedSignalSpy(dbDestination.data(), SIGNAL(modified()));
1431
Merger merger(dbSource.data(), dbDestination.data());
1433
QTRY_VERIFY(modifiedSignalSpy.empty());
1436
void TestMerge::testMergeModified()
1438
QScopedPointer<Database> dbDestination(createTestDatabase());
1439
QScopedPointer<Database> dbSource(
1440
createTestDatabaseStructureClone(dbDestination.data(), Entry::CloneNoFlags, Group::CloneIncludeEntries));
1442
QSignalSpy modifiedSignalSpy(dbDestination.data(), SIGNAL(modified()));
1443
// Make sure the two changes have a different timestamp.
1445
Entry* entry = dbSource->rootGroup()->findEntryByPath("entry1");
1446
entry->beginUpdate();
1447
entry->setTitle("new title");
1450
Merger merger(dbSource.data(), dbDestination.data());
1452
QTRY_VERIFY(!modifiedSignalSpy.empty());
1455
Database* TestMerge::createTestDatabase()
1457
auto db = new Database();
1459
auto group1 = new Group();
1460
group1->setName("group1");
1461
group1->setUuid(QUuid::createUuid());
1463
auto group2 = new Group();
1464
group2->setName("group2");
1465
group2->setUuid(QUuid::createUuid());
1467
auto entry1 = new Entry();
1468
entry1->setUuid(QUuid::createUuid());
1469
auto entry2 = new Entry();
1470
entry2->setUuid(QUuid::createUuid());
1472
m_clock->advanceYear(1);
1474
// Give Entry 1 a history
1475
entry1->beginUpdate();
1476
entry1->setGroup(group1);
1477
entry1->setTitle("entry1");
1478
entry1->endUpdate();
1480
// Give Entry 2 a history
1481
entry2->beginUpdate();
1482
entry2->setGroup(group1);
1483
entry2->setTitle("entry2");
1484
entry2->endUpdate();
1486
group1->setParent(db->rootGroup());
1487
group2->setParent(db->rootGroup());
1492
Database* TestMerge::createTestDatabaseStructureClone(Database* source, int entryFlags, int groupFlags)
1494
auto db = new Database();
1495
auto oldGroup = db->setRootGroup(source->rootGroup()->clone(static_cast<Entry::CloneFlag>(entryFlags),
1496
static_cast<Group::CloneFlag>(groupFlags)));