diff options
author | louib <louib@users.noreply.github.com> | 2017-09-05 17:28:47 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-05 17:28:47 +0300 |
commit | 1220b7d5017f4b855110e4459aff2ae26e0f34d1 (patch) | |
tree | be1fb9fbc1be653612ddbca8252fcb469ac1d9f9 /tests | |
parent | 321d4e9c8717a548630e83077b1e14e41ceef503 (diff) |
Feature : Update entries across groups when merging (#807)
* Feature : Update entries across groups when merging
* Styling
Diffstat (limited to 'tests')
-rw-r--r-- | tests/CMakeLists.txt | 3 | ||||
-rw-r--r-- | tests/TestGroup.cpp | 117 | ||||
-rw-r--r-- | tests/TestGroup.h | 7 | ||||
-rw-r--r-- | tests/TestMerge.cpp | 445 | ||||
-rw-r--r-- | tests/TestMerge.h | 46 |
5 files changed, 494 insertions, 124 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2a420270a..33baf564f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -158,6 +158,9 @@ endif() add_unit_test(NAME testentry SOURCES TestEntry.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testmerge SOURCES TestMerge.cpp + LIBS ${TEST_LIBRARIES}) + add_unit_test(NAME testtotp SOURCES TestTotp.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 5553feae8..efdcb32e6 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -452,123 +452,6 @@ void TestGroup::testCopyCustomIcons() delete dbSource; } -void TestGroup::testMerge() -{ - Group* group1 = new Group(); - group1->setName("group 1"); - Group* group2 = new Group(); - group2->setName("group 2"); - - Entry* entry1 = new Entry(); - Entry* entry2 = new Entry(); - - entry1->setGroup(group1); - entry1->setUuid(Uuid::random()); - entry2->setGroup(group1); - entry2->setUuid(Uuid::random()); - - group2->merge(group1); - - QCOMPARE(group1->entries().size(), 2); - QCOMPARE(group2->entries().size(), 2); -} - -void TestGroup::testMergeDatabase() -{ - Database* dbSource = createMergeTestDatabase(); - Database* dbDest = new Database(); - - dbDest->merge(dbSource); - - QCOMPARE(dbDest->rootGroup()->children().size(), 2); - QCOMPARE(dbDest->rootGroup()->children().at(0)->entries().size(), 2); - - delete dbDest; - delete dbSource; -} - -void TestGroup::testMergeConflict() -{ - Database* dbSource = createMergeTestDatabase(); - - // test merging updated entries - // falls back to KeepBoth mode - Database* dbCopy = new Database(); - dbCopy->setRootGroup(dbSource->rootGroup()->clone(Entry::CloneNoFlags)); - - // sanity check - QCOMPARE(dbCopy->rootGroup()->children().at(0)->entries().size(), 2); - - // make this entry newer than in original db - Entry* updatedEntry = dbCopy->rootGroup()->children().at(0)->entries().at(0); - TimeInfo updatedTimeInfo = updatedEntry->timeInfo(); - updatedTimeInfo.setLastModificationTime(updatedTimeInfo.lastModificationTime().addYears(1)); - updatedEntry->setTimeInfo(updatedTimeInfo); - - dbCopy->merge(dbSource); - - // one entry is duplicated because of mode - QCOMPARE(dbCopy->rootGroup()->children().at(0)->entries().size(), 2); - - delete dbSource; - delete dbCopy; -} - -void TestGroup::testMergeConflictKeepBoth() -{ - Database* dbSource = createMergeTestDatabase(); - - // test merging updated entries - // falls back to KeepBoth mode - Database* dbCopy = new Database(); - dbCopy->setRootGroup(dbSource->rootGroup()->clone(Entry::CloneNoFlags)); - - // sanity check - QCOMPARE(dbCopy->rootGroup()->children().at(0)->entries().size(), 2); - - // make this entry newer than in original db - Entry* updatedEntry = dbCopy->rootGroup()->children().at(0)->entries().at(0); - TimeInfo updatedTimeInfo = updatedEntry->timeInfo(); - updatedTimeInfo.setLastModificationTime(updatedTimeInfo.lastModificationTime().addYears(1)); - updatedEntry->setTimeInfo(updatedTimeInfo); - - dbCopy->rootGroup()->setMergeMode(Group::MergeMode::KeepBoth); - - dbCopy->merge(dbSource); - - // one entry is duplicated because of mode - QCOMPARE(dbCopy->rootGroup()->children().at(0)->entries().size(), 3); - // the older entry was merged from the other db as last in the group - Entry* olderEntry = dbCopy->rootGroup()->children().at(0)->entries().at(2); - QVERIFY2(olderEntry->attributes()->hasKey("merged"), "older entry is marked with an attribute \"merged\""); - - delete dbSource; - delete dbCopy; -} - -Database* TestGroup::createMergeTestDatabase() -{ - Database* db = new Database(); - - Group* group1 = new Group(); - group1->setName("group 1"); - Group* group2 = new Group(); - group2->setName("group 2"); - - Entry* entry1 = new Entry(); - Entry* entry2 = new Entry(); - - entry1->setGroup(group1); - entry1->setUuid(Uuid::random()); - entry2->setGroup(group1); - entry2->setUuid(Uuid::random()); - - group1->setParent(db->rootGroup()); - group2->setParent(db->rootGroup()); - - return db; -} - void TestGroup::testFindEntry() { Database* db = new Database(); diff --git a/tests/TestGroup.h b/tests/TestGroup.h index 16f4906ee..fabe860af 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -35,17 +35,10 @@ private slots: void testCopyCustomIcon(); void testClone(); void testCopyCustomIcons(); - void testMerge(); - void testMergeConflict(); - void testMergeDatabase(); - void testMergeConflictKeepBoth(); void testFindEntry(); void testFindGroupByPath(); void testPrint(); void testLocate(); - -private: - Database* createMergeTestDatabase(); }; #endif // KEEPASSX_TESTGROUP_H diff --git a/tests/TestMerge.cpp b/tests/TestMerge.cpp new file mode 100644 index 000000000..e9876f183 --- /dev/null +++ b/tests/TestMerge.cpp @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TestMerge.h" + +#include <QDebug> +#include <QTest> + +#include "core/Database.h" +#include "core/Group.h" +#include "core/Metadata.h" +#include "crypto/Crypto.h" + +QTEST_GUILESS_MAIN(TestMerge) + +void TestMerge::initTestCase() +{ + qRegisterMetaType<Entry*>("Entry*"); + qRegisterMetaType<Group*>("Group*"); + QVERIFY(Crypto::init()); +} + +/** + * Merge an existing database into a new one. + * All the entries of the existing should end + * up in the new one. + */ +void TestMerge::testMergeIntoNew() +{ + Database* dbSource = createTestDatabase(); + Database* dbDestination = new Database(); + + dbDestination->merge(dbSource); + + QCOMPARE(dbDestination->rootGroup()->children().size(), 2); + QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().size(), 2); + + delete dbDestination; + delete dbSource; +} + +/** + * Merging when no changes occured should not + * have any side effect. + */ +void TestMerge::testMergeNoChanges() +{ + Database* dbDestination = createTestDatabase(); + + Database* dbSource = new Database(); + dbSource->setRootGroup(dbDestination->rootGroup()->clone(Entry::CloneNoFlags)); + + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2); + QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2); + + dbDestination->merge(dbSource); + + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2); + QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2); + + dbDestination->merge(dbSource); + + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2); + QCOMPARE(dbSource->rootGroup()->entriesRecursive().size(), 2); + + delete dbDestination; + delete dbSource; +} + +/** + * If the entry is updated in the source database, the update + * should propagate in the destination database. + */ +void TestMerge::testResolveConflictNewer() +{ + Database* dbDestination = createTestDatabase(); + + Database* dbSource = new Database(); + dbSource->setRootGroup(dbDestination->rootGroup()->clone(Entry::CloneNoFlags)); + + // sanity check + Group* group1 = dbSource->rootGroup()->findChildByName("group1"); + QVERIFY(group1 != nullptr); + QCOMPARE(group1->entries().size(), 2); + + Entry* entry1 = dbSource->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + + // Make sure the two changes have a different timestamp. + QTest::qSleep(1); + // make this entry newer than in destination db + entry1->beginUpdate(); + entry1->setPassword("password"); + entry1->endUpdate(); + + dbDestination->merge(dbSource); + + // sanity check + group1 = dbDestination->rootGroup()->findChildByName("group1"); + QVERIFY(group1 != nullptr); + QCOMPARE(group1->entries().size(), 2); + + entry1 = dbDestination->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + QCOMPARE(entry1->password(), QString("password")); + + // When updating an entry, it should not end up in the + // deleted objects. + for (DeletedObject deletedObject : dbDestination->deletedObjects()) { + QVERIFY(deletedObject.uuid != entry1->uuid()); + } + + delete dbDestination; + delete dbSource; +} + +/** + * If the entry is updated in the source database, and the + * destination database after, the entry should remain the + * same. + */ +void TestMerge::testResolveConflictOlder() +{ + Database* dbDestination = createTestDatabase(); + + Database* dbSource = new Database(); + dbSource->setRootGroup(dbDestination->rootGroup()->clone(Entry::CloneNoFlags)); + + // sanity check + Group* group1 = dbSource->rootGroup()->findChildByName("group1"); + QVERIFY(group1 != nullptr); + QCOMPARE(group1->entries().size(), 2); + + Entry* entry1 = dbSource->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + + // Make sure the two changes have a different timestamp. + QTest::qSleep(1); + // make this entry newer than in destination db + entry1->beginUpdate(); + entry1->setPassword("password1"); + entry1->endUpdate(); + + entry1 = dbDestination->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + + // Make sure the two changes have a different timestamp. + QTest::qSleep(1); + // make this entry newer than in destination db + entry1->beginUpdate(); + entry1->setPassword("password2"); + entry1->endUpdate(); + + dbDestination->merge(dbSource); + + // sanity check + group1 = dbDestination->rootGroup()->findChildByName("group1"); + QVERIFY(group1 != nullptr); + QCOMPARE(group1->entries().size(), 2); + + entry1 = dbDestination->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + QCOMPARE(entry1->password(), QString("password2")); + + // When updating an entry, it should not end up in the + // deleted objects. + for (DeletedObject deletedObject : dbDestination->deletedObjects()) { + QVERIFY(deletedObject.uuid != entry1->uuid()); + } + + delete dbDestination; + delete dbSource; +} + +/** + * Tests the KeepBoth merge mode. + */ +void TestMerge::testResolveConflictKeepBoth() +{ + Database* dbDestination = createTestDatabase(); + + Database* dbSource = new Database(); + dbSource->setRootGroup(dbDestination->rootGroup()->clone(Entry::CloneNoFlags)); + + // sanity check + QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().size(), 2); + + // make this entry newer than in original db + Entry* updatedEntry = dbDestination->rootGroup()->children().at(0)->entries().at(0); + TimeInfo updatedTimeInfo = updatedEntry->timeInfo(); + updatedTimeInfo.setLastModificationTime(updatedTimeInfo.lastModificationTime().addYears(1)); + updatedEntry->setTimeInfo(updatedTimeInfo); + + dbDestination->rootGroup()->setMergeMode(Group::MergeMode::KeepBoth); + + dbDestination->merge(dbSource); + + // one entry is duplicated because of mode + QCOMPARE(dbDestination->rootGroup()->children().at(0)->entries().size(), 3); + // the older entry was merged from the other db as last in the group + Entry* olderEntry = dbDestination->rootGroup()->children().at(0)->entries().at(2); + QVERIFY2(olderEntry->attributes()->hasKey("merged"), "older entry is marked with an attribute \"merged\""); + + QVERIFY2(olderEntry->uuid().toHex() != updatedEntry->uuid().toHex(), + "KeepBoth should not reuse the UUIDs when cloning."); + + delete dbSource; + delete dbDestination; +} + +/** + * The location of an entry should be updated in the + * destination database. + */ +void TestMerge::testMoveEntry() +{ + Database* dbDestination = createTestDatabase(); + + Database* dbSource = new Database(); + dbSource->setRootGroup(dbDestination->rootGroup()->clone(Entry::CloneNoFlags)); + + Entry* entry1 = dbSource->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + + Group* group2 = dbSource->rootGroup()->findChildByName("group2"); + QVERIFY(group2 != nullptr); + + // Make sure the two changes have a different timestamp. + QTest::qSleep(1); + entry1->setGroup(group2); + QCOMPARE(entry1->group()->name(), QString("group2")); + + dbDestination->merge(dbSource); + + entry1 = dbDestination->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + QCOMPARE(entry1->group()->name(), QString("group2")); + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2); + + delete dbDestination; + delete dbSource; +} + +/** + * The location of an entry should be updated in the + * destination database, but changes from the destination + * database should be preserved. + */ +void TestMerge::testMoveEntryPreserveChanges() +{ + Database* dbDestination = createTestDatabase(); + + Database* dbSource = new Database(); + dbSource->setRootGroup(dbDestination->rootGroup()->clone(Entry::CloneNoFlags)); + + Entry* entry1 = dbSource->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + + Group* group2 = dbSource->rootGroup()->findChildByName("group2"); + QVERIFY(group2 != nullptr); + + QTest::qSleep(1); + entry1->setGroup(group2); + QCOMPARE(entry1->group()->name(), QString("group2")); + + entry1 = dbDestination->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + + QTest::qSleep(1); + entry1->beginUpdate(); + entry1->setPassword("password"); + entry1->endUpdate(); + + dbDestination->merge(dbSource); + + entry1 = dbDestination->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + QCOMPARE(entry1->group()->name(), QString("group2")); + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2); + QCOMPARE(entry1->password(), QString("password")); + + delete dbDestination; + delete dbSource; +} + +void TestMerge::testCreateNewGroups() +{ + Database* dbDestination = createTestDatabase(); + + Database* dbSource = new Database(); + dbSource->setRootGroup(dbDestination->rootGroup()->clone(Entry::CloneNoFlags)); + + QTest::qSleep(1); + Group* group3 = new Group(); + group3->setName("group3"); + group3->setParent(dbSource->rootGroup()); + + dbDestination->merge(dbSource); + + group3 = dbDestination->rootGroup()->findChildByName("group3"); + QVERIFY(group3 != nullptr); + QCOMPARE(group3->name(), QString("group3")); + + delete dbDestination; + delete dbSource; +} + +void TestMerge::testMoveEntryIntoNewGroup() +{ + Database* dbDestination = createTestDatabase(); + + Database* dbSource = new Database(); + dbSource->setRootGroup(dbDestination->rootGroup()->clone(Entry::CloneNoFlags)); + + QTest::qSleep(1); + Group* group3 = new Group(); + group3->setName("group3"); + group3->setParent(dbSource->rootGroup()); + + Entry* entry1 = dbSource->rootGroup()->findEntry("entry1"); + entry1->setGroup(group3); + + dbDestination->merge(dbSource); + + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2); + + group3 = dbDestination->rootGroup()->findChildByName("group3"); + QVERIFY(group3 != nullptr); + QCOMPARE(group3->name(), QString("group3")); + QCOMPARE(group3->entries().size(), 1); + + entry1 = dbDestination->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + QCOMPARE(entry1->group()->name(), QString("group3")); + + delete dbDestination; + delete dbSource; +} + +/** + * Even though the entries' locations are no longer + * the same, we will keep associating them. + */ +void TestMerge::testUpdateEntryDifferentLocation() +{ + Database* dbDestination = createTestDatabase(); + + Database* dbSource = new Database(); + dbSource->setRootGroup(dbDestination->rootGroup()->clone(Entry::CloneNoFlags)); + + Group* group3 = new Group(); + group3->setName("group3"); + group3->setParent(dbDestination->rootGroup()); + + Entry* entry1 = dbDestination->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + entry1->setGroup(group3); + Uuid uuidBeforeSyncing = entry1->uuid(); + + // Change the entry in the source db. + QTest::qSleep(1); + entry1 = dbSource->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + entry1->beginUpdate(); + entry1->setUsername("username"); + entry1->endUpdate(); + + dbDestination->merge(dbSource); + + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2); + + entry1 = dbDestination->rootGroup()->findEntry("entry1"); + QVERIFY(entry1 != nullptr); + QVERIFY(entry1->group() != nullptr); + QCOMPARE(entry1->username(), QString("username")); + QCOMPARE(entry1->group()->name(), QString("group3")); + QCOMPARE(uuidBeforeSyncing, entry1->uuid()); + + delete dbDestination; + delete dbSource; +} + +/** + * The first merge should create new entries, the + * second should only sync them, since they have + * been created with the same UUIDs. + */ +void TestMerge::testMergeAndSync() +{ + Database* dbDestination = new Database(); + Database* dbSource = createTestDatabase(); + + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 0); + + dbDestination->merge(dbSource); + + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2); + + dbDestination->merge(dbSource); + + // Still only 2 entries, since now we detect which are already present. + QCOMPARE(dbDestination->rootGroup()->entriesRecursive().size(), 2); + + delete dbDestination; + delete dbSource; +} + +Database* TestMerge::createTestDatabase() +{ + Database* db = new Database(); + + Group* group1 = new Group(); + group1->setName("group1"); + Group* group2 = new Group(); + group2->setName("group2"); + + Entry* entry1 = new Entry(); + Entry* entry2 = new Entry(); + + entry1->setGroup(group1); + entry1->setUuid(Uuid::random()); + entry1->setTitle("entry1"); + entry2->setGroup(group1); + entry2->setUuid(Uuid::random()); + entry2->setTitle("entry2"); + + group1->setParent(db->rootGroup()); + group2->setParent(db->rootGroup()); + + return db; +} diff --git a/tests/TestMerge.h b/tests/TestMerge.h new file mode 100644 index 000000000..bfb18c305 --- /dev/null +++ b/tests/TestMerge.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef KEEPASSX_TESTMERGE_H +#define KEEPASSX_TESTMERGE_H + +#include "core/Database.h" +#include <QObject> + +class TestMerge : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testMergeIntoNew(); + void testMergeNoChanges(); + void testResolveConflictNewer(); + void testResolveConflictOlder(); + void testResolveConflictKeepBoth(); + void testMoveEntry(); + void testMoveEntryPreserveChanges(); + void testMoveEntryIntoNewGroup(); + void testCreateNewGroups(); + void testUpdateEntryDifferentLocation(); + void testMergeAndSync(); + +private: + Database* createTestDatabase(); +}; + +#endif // KEEPASSX_TESTMERGE_H |