diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/CMakeLists.txt | 9 | ||||
-rw-r--r-- | tests/TestAutoType.cpp | 32 | ||||
-rw-r--r-- | tests/TestAutoType.h | 1 | ||||
-rw-r--r-- | tests/TestCsvExporter.cpp | 2 | ||||
-rw-r--r-- | tests/TestCsvExporter.h | 2 | ||||
-rw-r--r-- | tests/TestEntry.cpp | 12 | ||||
-rw-r--r-- | tests/TestEntryModel.cpp | 14 | ||||
-rw-r--r-- | tests/TestGroup.cpp | 121 | ||||
-rw-r--r-- | tests/TestGroup.h | 8 | ||||
-rw-r--r-- | tests/TestKeys.cpp | 16 | ||||
-rw-r--r-- | tests/TestKeys.h | 1 | ||||
-rw-r--r-- | tests/TestWildcardMatcher.cpp | 6 | ||||
-rw-r--r-- | tests/TestWildcardMatcher.h | 1 | ||||
-rw-r--r-- | tests/config-keepassx-tests.h.cmake | 4 | ||||
-rw-r--r-- | tests/data/MergeDatabase.kdbx | bin | 0 -> 15150 bytes | |||
-rw-r--r-- | tests/gui/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/gui/TemporaryFile.cpp | 92 | ||||
-rw-r--r-- | tests/gui/TemporaryFile.h | 64 | ||||
-rw-r--r-- | tests/gui/TestGui.cpp | 485 | ||||
-rw-r--r-- | tests/gui/TestGui.h | 21 |
20 files changed, 758 insertions, 135 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0dcb4a77e..0ea73b2fe 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -85,13 +85,14 @@ endmacro(add_unit_test) set(TEST_LIBRARIES keepassx_core + ${keepasshttp_LIB} + ${autotype_LIB} Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test ${GCRYPT_LIBRARIES} ${ZLIB_LIBRARIES} - ${MHD_LIBRARIES} ) set(testsupport_SOURCES modeltest.cpp FailDevice.cpp) @@ -143,9 +144,11 @@ add_unit_test(NAME testkeepass1reader SOURCES TestKeePass1Reader.cpp add_unit_test(NAME testwildcardmatcher SOURCES TestWildcardMatcher.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testautotype SOURCES TestAutoType.cpp +if(WITH_XC_AUTOTYPE) + add_unit_test(NAME testautotype SOURCES TestAutoType.cpp LIBS ${TEST_LIBRARIES}) -set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON) + set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON) +endif() add_unit_test(NAME testentry SOURCES TestEntry.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index cbd927fe6..c5c1a5933 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -90,6 +90,20 @@ void TestAutoType::init() association.window = "//^REGEX3-([rd]\\d){2}$//"; association.sequence = "regex3"; m_entry3->autoTypeAssociations()->add(association); + + m_entry4 = new Entry(); + m_entry4->setGroup(m_group); + m_entry4->setPassword("custom_attr"); + m_entry4->attributes()->set("CUSTOM","Attribute",false); + association.window = "//^CustomAttr1$//"; + association.sequence = "{PASSWORD}:{CUSTOM}"; + m_entry4->autoTypeAssociations()->add(association); + association.window = "//^CustomAttr2$//"; + association.sequence = "{CuStOm}"; + m_entry4->autoTypeAssociations()->add(association); + association.window = "//^CustomAttr3$//"; + association.sequence = "{PaSSworD}"; + m_entry4->autoTypeAssociations()->add(association); } void TestAutoType::cleanup() @@ -192,4 +206,22 @@ void TestAutoType::testGlobalAutoTypeRegExp() m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("regex3")); m_test->clearActions(); + + // with custom attributes + m_test->setActiveWindowTitle("CustomAttr1"); + m_autoType->performGlobalAutoType(m_dbList); + QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute")); + m_test->clearActions(); + + // with (non uppercase) undefined custom attributes + m_test->setActiveWindowTitle("CustomAttr2"); + m_autoType->performGlobalAutoType(m_dbList); + QCOMPARE(m_test->actionChars(), QString("")); + m_test->clearActions(); + + // with mixedcase default attributes + m_test->setActiveWindowTitle("CustomAttr3"); + m_autoType->performGlobalAutoType(m_dbList); + QCOMPARE(m_test->actionChars(), QString("custom_attr")); + m_test->clearActions(); } diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index c12817b1d..c585fec25 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -55,6 +55,7 @@ private: Entry* m_entry1; Entry* m_entry2; Entry* m_entry3; + Entry* m_entry4; }; #endif // KEEPASSX_TESTAUTOTYPE_H diff --git a/tests/TestCsvExporter.cpp b/tests/TestCsvExporter.cpp index 1fa663477..6515c39c2 100644 --- a/tests/TestCsvExporter.cpp +++ b/tests/TestCsvExporter.cpp @@ -42,7 +42,7 @@ void TestCsvExporter::initTestCase() Crypto::init(); } -void TestCsvExporter::cleanUp() +void TestCsvExporter::cleanup() { delete m_db; delete m_csvExporter; diff --git a/tests/TestCsvExporter.h b/tests/TestCsvExporter.h index c8cc4dc10..a8cfe7f25 100644 --- a/tests/TestCsvExporter.h +++ b/tests/TestCsvExporter.h @@ -34,7 +34,7 @@ public: private Q_SLOTS: void init(); void initTestCase(); - void cleanUp(); + void cleanup(); void testExport(); void testEmptyDatabase(); void testNestedGroups(); diff --git a/tests/TestEntry.cpp b/tests/TestEntry.cpp index 1eaca3243..4d34cf31b 100644 --- a/tests/TestEntry.cpp +++ b/tests/TestEntry.cpp @@ -79,6 +79,8 @@ void TestEntry::testCopyDataFrom() QCOMPARE(entry2->autoTypeAssociations()->size(), 2); QCOMPARE(entry2->autoTypeAssociations()->get(0).window, QString("1")); QCOMPARE(entry2->autoTypeAssociations()->get(1).window, QString("3")); + + delete entry2; } void TestEntry::testClone() @@ -101,6 +103,7 @@ void TestEntry::testClone() QCOMPARE(entryCloneNone->title(), QString("New Title")); QCOMPARE(entryCloneNone->historyItems().size(), 0); QCOMPARE(entryCloneNone->timeInfo().creationTime(), entryOrg->timeInfo().creationTime()); + delete entryCloneNone; Entry* entryCloneNewUuid = entryOrg->clone(Entry::CloneNewUuid); QVERIFY(entryCloneNewUuid->uuid() != entryOrg->uuid()); @@ -108,17 +111,22 @@ void TestEntry::testClone() QCOMPARE(entryCloneNewUuid->title(), QString("New Title")); QCOMPARE(entryCloneNewUuid->historyItems().size(), 0); QCOMPARE(entryCloneNewUuid->timeInfo().creationTime(), entryOrg->timeInfo().creationTime()); + delete entryCloneNewUuid; Entry* entryCloneResetTime = entryOrg->clone(Entry::CloneResetTimeInfo); - QCOMPARE(entryCloneNone->uuid(), entryOrg->uuid()); + QCOMPARE(entryCloneResetTime->uuid(), entryOrg->uuid()); QCOMPARE(entryCloneResetTime->title(), QString("New Title")); QCOMPARE(entryCloneResetTime->historyItems().size(), 0); QVERIFY(entryCloneResetTime->timeInfo().creationTime() != entryOrg->timeInfo().creationTime()); + delete entryCloneResetTime; Entry* entryCloneHistory = entryOrg->clone(Entry::CloneIncludeHistory); - QCOMPARE(entryCloneNone->uuid(), entryOrg->uuid()); + QCOMPARE(entryCloneHistory->uuid(), entryOrg->uuid()); QCOMPARE(entryCloneHistory->title(), QString("New Title")); QCOMPARE(entryCloneHistory->historyItems().size(), 1); QCOMPARE(entryCloneHistory->historyItems().at(0)->title(), QString("Original Title")); QCOMPARE(entryCloneHistory->timeInfo().creationTime(), entryOrg->timeInfo().creationTime()); + delete entryCloneHistory; + + delete entryOrg; } diff --git a/tests/TestEntryModel.cpp b/tests/TestEntryModel.cpp index 3f956d7f3..d5a16ebab 100644 --- a/tests/TestEntryModel.cpp +++ b/tests/TestEntryModel.cpp @@ -236,15 +236,15 @@ void TestEntryModel::testAutoTypeAssociationsModel() QCOMPARE(model->rowCount(), 0); - AutoTypeAssociations* assocications = new AutoTypeAssociations(this); - model->setAutoTypeAssociations(assocications); + AutoTypeAssociations* associations = new AutoTypeAssociations(this); + model->setAutoTypeAssociations(associations); QCOMPARE(model->rowCount(), 0); AutoTypeAssociations::Association assoc; assoc.window = "1"; assoc.sequence = "2"; - assocications->add(assoc); + associations->add(assoc); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0)).toString(), QString("1")); @@ -252,17 +252,17 @@ void TestEntryModel::testAutoTypeAssociationsModel() assoc.window = "3"; assoc.sequence = "4"; - assocications->update(0, assoc); + associations->update(0, assoc); QCOMPARE(model->data(model->index(0, 0)).toString(), QString("3")); QCOMPARE(model->data(model->index(0, 1)).toString(), QString("4")); - assocications->add(assoc); - assocications->remove(0); + associations->add(assoc); + associations->remove(0); QCOMPARE(model->rowCount(), 1); delete modelTest; delete model; - delete assocications; + delete associations; } void TestEntryModel::testProxyModel() diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index a923776de..e87e6cedc 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -19,6 +19,7 @@ #include <QPointer> #include <QSignalSpy> +#include <QDebug> #include <QTest> #include "core/Database.h" @@ -445,4 +446,124 @@ void TestGroup::testCopyCustomIcons() QCOMPARE(metaTarget->customIcon(group1Icon).pixel(0, 0), qRgb(1, 2, 3)); QCOMPARE(metaTarget->customIcon(group2Icon).pixel(0, 0), qRgb(4, 5, 6)); + + delete dbTarget; + 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; } diff --git a/tests/TestGroup.h b/tests/TestGroup.h index c612a3ac6..4a891ae6f 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -19,6 +19,7 @@ #define KEEPASSX_TESTGROUP_H #include <QObject> +#include "core/Database.h" class TestGroup : public QObject { @@ -33,6 +34,13 @@ private Q_SLOTS: void testCopyCustomIcon(); void testClone(); void testCopyCustomIcons(); + void testMerge(); + void testMergeConflict(); + void testMergeDatabase(); + void testMergeConflictKeepBoth(); + +private: + Database* createMergeTestDatabase(); }; #endif // KEEPASSX_TESTGROUP_H diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index 6c1953faf..d5b35b1fb 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -83,6 +83,22 @@ void TestKeys::testComposite() delete compositeKey4; } +void TestKeys::testCompositeKeyReadFromLine() +{ + + QString keyFilename = QString("%1/FileKeyXml.key").arg(QString(KEEPASSX_TEST_DATA_DIR)); + + CompositeKey compositeFileKey = CompositeKey::readFromLine(keyFilename); + FileKey fileKey; + fileKey.load(keyFilename); + QCOMPARE(compositeFileKey.rawKey().size(), fileKey.rawKey().size()); + + CompositeKey compositePasswordKey = CompositeKey::readFromLine(QString("password")); + PasswordKey passwordKey(QString("password")); + QCOMPARE(compositePasswordKey.rawKey().size(), passwordKey.rawKey().size()); + +} + void TestKeys::testFileKey() { QFETCH(QString, type); diff --git a/tests/TestKeys.h b/tests/TestKeys.h index 0f14117fd..a6d0b7e1a 100644 --- a/tests/TestKeys.h +++ b/tests/TestKeys.h @@ -27,6 +27,7 @@ class TestKeys : public QObject private Q_SLOTS: void initTestCase(); void testComposite(); + void testCompositeKeyReadFromLine(); void testFileKey(); void testFileKey_data(); void testCreateFileKey(); diff --git a/tests/TestWildcardMatcher.cpp b/tests/TestWildcardMatcher.cpp index dffe1c854..621dc898e 100644 --- a/tests/TestWildcardMatcher.cpp +++ b/tests/TestWildcardMatcher.cpp @@ -55,6 +55,7 @@ void TestWildcardMatcher::testMatcher() initMatcher(text); verifyMatchResult(pattern, match); + cleanupMatcher(); } void TestWildcardMatcher::initMatcher(QString text) @@ -62,6 +63,11 @@ void TestWildcardMatcher::initMatcher(QString text) m_matcher = new WildcardMatcher(text); } +void TestWildcardMatcher::cleanupMatcher() +{ + delete m_matcher; +} + void TestWildcardMatcher::verifyMatchResult(QString pattern, bool expected) { if (expected) { diff --git a/tests/TestWildcardMatcher.h b/tests/TestWildcardMatcher.h index a1f8b5f56..c241c7553 100644 --- a/tests/TestWildcardMatcher.h +++ b/tests/TestWildcardMatcher.h @@ -35,6 +35,7 @@ private: static const QString AlternativeText; void initMatcher(QString text); + void cleanupMatcher(); void verifyMatchResult(QString pattern, bool expected); void verifyMatch(QString pattern); void verifyNoMatch(QString pattern); diff --git a/tests/config-keepassx-tests.h.cmake b/tests/config-keepassx-tests.h.cmake index b515f5c5f..26204f7dd 100644 --- a/tests/config-keepassx-tests.h.cmake +++ b/tests/config-keepassx-tests.h.cmake @@ -5,4 +5,8 @@ #define KEEPASSX_TEST_DATA_DIR "${KEEPASSX_TEST_DATA_DIR}" +#cmakedefine WITH_XC_HTTP +#cmakedefine WITH_XC_AUTOTYPE +#cmakedefine WITH_XC_YUBIKEY + #endif // KEEPASSX_CONFIG_TESTS_H diff --git a/tests/data/MergeDatabase.kdbx b/tests/data/MergeDatabase.kdbx Binary files differnew file mode 100644 index 000000000..f45929de2 --- /dev/null +++ b/tests/data/MergeDatabase.kdbx diff --git a/tests/gui/CMakeLists.txt b/tests/gui/CMakeLists.txt index a1ca914fd..4c0eebbe6 100644 --- a/tests/gui/CMakeLists.txt +++ b/tests/gui/CMakeLists.txt @@ -13,6 +13,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -add_unit_test(NAME testgui SOURCES TestGui.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testgui SOURCES TestGui.cpp TemporaryFile.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testguipixmaps SOURCES TestGuiPixmaps.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/gui/TemporaryFile.cpp b/tests/gui/TemporaryFile.cpp new file mode 100644 index 000000000..879a558a9 --- /dev/null +++ b/tests/gui/TemporaryFile.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 Danny Su <contact@dannysu.com> + * + * 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 "TemporaryFile.h" + +#include <QFileInfo> + +#ifdef Q_OS_WIN +const QString TemporaryFile::SUFFIX = ".win"; + +TemporaryFile::~TemporaryFile() +{ + if (m_tempFile.autoRemove()) { + m_file.remove(); + } +} +#endif + +bool TemporaryFile::open() +{ +#ifdef Q_OS_WIN + // Still call QTemporaryFile::open() so that it figures out the temporary + // file name to use. Assuming that by appending the SUFFIX to whatever + // QTemporaryFile chooses is also an available file. + bool tempFileOpened = m_tempFile.open(); + if (tempFileOpened) { + m_file.setFileName(filePath()); + return m_file.open(QIODevice::WriteOnly); + } + return false; +#else + return m_tempFile.open(); +#endif +} + +void TemporaryFile::close() +{ + m_tempFile.close(); +#ifdef Q_OS_WIN + m_file.close(); +#endif +} + +qint64 TemporaryFile::write(const char *data, qint64 maxSize) +{ +#ifdef Q_OS_WIN + return m_file.write(data, maxSize); +#else + return m_tempFile.write(data, maxSize); +#endif +} + +qint64 TemporaryFile::write(const QByteArray &byteArray) +{ +#ifdef Q_OS_WIN + return m_file.write(byteArray); +#else + return m_tempFile.write(byteArray); +#endif +} + +QString TemporaryFile::fileName() const +{ +#ifdef Q_OS_WIN + return QFileInfo(m_tempFile).fileName() + TemporaryFile::SUFFIX; +#else + return QFileInfo(m_tempFile).fileName(); +#endif +} + +QString TemporaryFile::filePath() const +{ +#ifdef Q_OS_WIN + return m_tempFile.fileName() + TemporaryFile::SUFFIX; +#else + return m_tempFile.fileName(); +#endif +} diff --git a/tests/gui/TemporaryFile.h b/tests/gui/TemporaryFile.h new file mode 100644 index 000000000..b16e2161a --- /dev/null +++ b/tests/gui/TemporaryFile.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Danny Su <contact@dannysu.com> + * + * 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_TEMPORARYFILE_H +#define KEEPASSX_TEMPORARYFILE_H + +#include <QObject> +#include <QFile> +#include <QTemporaryFile> + +/** + * QTemporaryFile::close() doesn't actually close the file according to + * http://doc.qt.io/qt-5/qtemporaryfile.html: "For as long as the + * QTemporaryFile object itself is not destroyed, the unique temporary file + * will exist and be kept open internally by QTemporaryFile." + * + * This behavior causes issues when running tests on Windows. If the file is + * not closed, the testSave test will fail due to Access Denied. The + * auto-reload test also fails from Windows not triggering file change + * notification because the file isn't actually closed by QTemporaryFile. + * + * This class isolates the Windows specific logic that uses QFile to really + * close the test file when requested to. + */ +class TemporaryFile : public QObject +{ + Q_OBJECT + +public: +#ifdef Q_OS_WIN + ~TemporaryFile(); +#endif + + bool open(); + void close(); + qint64 write(const char *data, qint64 maxSize); + qint64 write(const QByteArray &byteArray); + + QString fileName() const; + QString filePath() const; + +private: + QTemporaryFile m_tempFile; +#ifdef Q_OS_WIN + QFile m_file; + static const QString SUFFIX; +#endif +}; + +#endif // KEEPASSX_TEMPORARYFILE_H diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 90d7fc2b0..c23226a28 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -21,6 +21,7 @@ #include <QApplication> #include <QDialogButtonBox> #include <QLineEdit> +#include <QLabel> #include <QMimeData> #include <QPushButton> #include <QSpinBox> @@ -28,6 +29,10 @@ #include <QTest> #include <QToolBar> #include <QToolButton> +#include <QTimer> +#include <QSignalSpy> +#include <QClipboard> +#include <QDebug> #include "config-keepassx-tests.h" #include "core/Config.h" @@ -43,6 +48,7 @@ #include "gui/FileDialog.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" +#include "gui/SearchWidget.h" #include "gui/entry/EditEntryWidget.h" #include "gui/entry/EntryView.h" #include "gui/group/GroupModel.h" @@ -59,20 +65,26 @@ void TestGui::initTestCase() m_mainWindow->activateWindow(); Tools::wait(50); + // Load the NewDatabase.kdbx file into temporary storage QByteArray tmpData; QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx")); QVERIFY(sourceDbFile.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceDbFile, tmpData)); - - QVERIFY(m_orgDbFile.open()); - m_orgDbFileName = QFileInfo(m_orgDbFile.fileName()).fileName(); - QCOMPARE(m_orgDbFile.write(tmpData), static_cast<qint64>((tmpData.size()))); - m_orgDbFile.close(); + QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData)); + sourceDbFile.close(); } -void TestGui::testOpenDatabase() +// Every test starts with opening the temp database +void TestGui::init() { - fileDialog()->setNextFileName(m_orgDbFile.fileName()); + // Write the temp storage to a temp database file for use in our tests + QVERIFY(m_dbFile.open()); + QCOMPARE(m_dbFile.write(m_dbData), static_cast<qint64>((m_dbData.size()))); + m_dbFile.close(); + + m_dbFileName = m_dbFile.fileName(); + m_dbFilePath = m_dbFile.filePath(); + + fileDialog()->setNextFileName(m_dbFilePath); triggerAction("actionDatabaseOpen"); QWidget* databaseOpenWidget = m_mainWindow->findChild<QWidget*>("databaseOpenWidget"); @@ -81,60 +93,190 @@ void TestGui::testOpenDatabase() QTest::keyClicks(editPassword, "a"); QTest::keyClick(editPassword, Qt::Key_Enter); + Tools::wait(100); + + QVERIFY(m_tabWidget->currentDatabaseWidget()); + + m_dbWidget = m_tabWidget->currentDatabaseWidget(); + m_db = m_dbWidget->database(); } -void TestGui::testTabs() +// Every test ends with closing the temp database without saving +void TestGui::cleanup() { - QCOMPARE(m_tabWidget->count(), 1); - QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), m_orgDbFileName); + // DO NOT save the database + MessageBox::setNextAnswer(QMessageBox::No); + triggerAction("actionDatabaseClose"); + Tools::wait(100); + + m_db = nullptr; + m_dbWidget = nullptr; +} + +void TestGui::testMergeDatabase() +{ + // It is safe to ignore the warning this line produces + QSignalSpy dbMergeSpy(m_dbWidget, SIGNAL(databaseMerged(Database*))); + + // set file to merge from + fileDialog()->setNextFileName(QString(KEEPASSX_TEST_DATA_DIR).append("/MergeDatabase.kdbx")); + triggerAction("actionDatabaseMerge"); + + QWidget* databaseOpenMergeWidget = m_mainWindow->findChild<QWidget*>("databaseOpenMergeWidget"); + QLineEdit* editPasswordMerge = databaseOpenMergeWidget->findChild<QLineEdit*>("editPassword"); + QVERIFY(editPasswordMerge->isVisible()); + + m_tabWidget->currentDatabaseWidget()->setCurrentWidget(databaseOpenMergeWidget); + + QTest::keyClicks(editPasswordMerge, "a"); + QTest::keyClick(editPasswordMerge, Qt::Key_Enter); + + QTRY_COMPARE(dbMergeSpy.count(), 1); + QTRY_VERIFY(m_tabWidget->tabText(m_tabWidget->currentIndex()).contains("*")); + + m_db = m_tabWidget->currentDatabaseWidget()->database(); + + // there are seven child groups of the root group + QCOMPARE(m_db->rootGroup()->children().size(), 7); + // the merged group should contain an entry + QCOMPARE(m_db->rootGroup()->children().at(6)->entries().size(), 1); + // the General group contains one entry merged from the other db + QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 1); +} + +void TestGui::testAutoreloadDatabase() +{ + config()->set("AutoReloadOnChange", false); + + // Load the MergeDatabase.kdbx file into temporary storage + QByteArray tmpData; + QFile mergeDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/MergeDatabase.kdbx")); + QVERIFY(mergeDbFile.open(QIODevice::ReadOnly)); + QVERIFY(Tools::readAllFromDevice(&mergeDbFile, tmpData)); + mergeDbFile.close(); + + // Test accepting new file in autoreload + MessageBox::setNextAnswer(QMessageBox::Yes); + // Overwrite the current database with the temp data + QVERIFY(m_dbFile.open()); + QVERIFY(m_dbFile.write(tmpData, static_cast<qint64>(tmpData.size()))); + m_dbFile.close(); + Tools::wait(1500); + + m_db = m_dbWidget->database(); + + // the General group contains one entry from the new db data + QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 1); + QVERIFY(! m_tabWidget->tabText(m_tabWidget->currentIndex()).endsWith("*")); + + // Reset the state + cleanup(); + init(); + + // Test rejecting new file in autoreload + MessageBox::setNextAnswer(QMessageBox::No); + // Overwrite the current temp database with a new file + m_dbFile.open(); + QVERIFY(m_dbFile.write(tmpData, static_cast<qint64>(tmpData.size()))); + m_dbFile.close(); + Tools::wait(1500); - m_dbWidget = m_tabWidget->currentDatabaseWidget(); m_db = m_dbWidget->database(); + + // Ensure the merge did not take place + QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 0); + QVERIFY(m_tabWidget->tabText(m_tabWidget->currentIndex()).endsWith("*")); + + // Reset the state + cleanup(); + init(); + + // Test accepting a merge of edits into autoreload + // Turn on autoload so we only get one messagebox (for the merge) + config()->set("AutoReloadOnChange", true); + + // Modify some entries + testEditEntry(); + + // This is saying yes to merging the entries + MessageBox::setNextAnswer(QMessageBox::Yes); + // Overwrite the current database with the temp data + QVERIFY(m_dbFile.open()); + QVERIFY(m_dbFile.write(tmpData, static_cast<qint64>(tmpData.size()))); + m_dbFile.close(); + Tools::wait(1500); + + m_db = m_dbWidget->database(); + + QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 1); + QVERIFY(m_tabWidget->tabText(m_tabWidget->currentIndex()).endsWith("*")); +} + +void TestGui::testTabs() +{ + QCOMPARE(m_tabWidget->count(), 1); + QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), m_dbFileName); } void TestGui::testEditEntry() { + QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar"); + + // Select the first entry in the database EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView"); - QModelIndex item = entryView->model()->index(0, 1); - QRect itemRect = entryView->visualRect(item); - QTest::mouseClick(entryView->viewport(), Qt::LeftButton, Qt::NoModifier, itemRect.center()); + QModelIndex entryItem = entryView->model()->index(0, 1); + clickIndex(entryItem, entryView, Qt::LeftButton); + // Confirm the edit action button is enabled QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit"); QVERIFY(entryEditAction->isEnabled()); - QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar"); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); QVERIFY(entryEditWidget->isVisible()); QVERIFY(entryEditWidget->isEnabled()); - QTest::mouseClick(entryEditWidget, Qt::LeftButton); + // Edit the first entry ("Sample Entry") + QTest::mouseClick(entryEditWidget, Qt::LeftButton); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget"); - QVERIFY(m_dbWidget->currentWidget() == editEntryWidget); + QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit"); + QTest::keyClicks(titleEdit, "_test"); + + // Save the edit QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox"); - QVERIFY(editEntryWidgetButtonBox); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - // make sure the database isn't marked as modified - // wait for modified timer - QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), m_orgDbFileName); + + // Confirm edit was made + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); + Entry* entry = entryView->entryFromIndex(entryItem); + QCOMPARE(entry->title(), QString("Sample Entry_test")); + QCOMPARE(entry->historyItems().size(), 1); + + // Confirm modified indicator is showing + QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("%1*").arg(m_dbFileName)); } void TestGui::testAddEntry() { + QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar"); EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView"); + + // Find the new entry action QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); - QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar"); + + // Find the button associated with the new entry action QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction); QVERIFY(entryNewWidget->isVisible()); QVERIFY(entryNewWidget->isEnabled()); + // Click the new entry button and check that we enter edit mode QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); + // Add entry "test" and confirm added EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget"); QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit"); QTest::keyClicks(titleEdit, "test"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); @@ -144,98 +286,219 @@ void TestGui::testAddEntry() QCOMPARE(entry->title(), QString("test")); QCOMPARE(entry->historyItems().size(), 0); - // wait for modified timer - QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("%1*").arg(m_orgDbFileName)); - QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit"); - QVERIFY(entryEditAction->isEnabled()); - QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); - QVERIFY(entryEditWidget->isVisible()); - QVERIFY(entryEditWidget->isEnabled()); - QTest::mouseClick(entryEditWidget, Qt::LeftButton); + // Add entry "something 2" + QTest::mouseClick(entryNewWidget, Qt::LeftButton); + QTest::keyClicks(titleEdit, "something 2"); + QLineEdit* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit"); + QLineEdit* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit"); + QTest::keyClicks(passwordEdit, "something 2"); + QTest::keyClicks(passwordRepeatEdit, "something 2"); + QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); - QTest::keyClicks(titleEdit, "something"); + // Add entry "something 3" + QTest::mouseClick(entryNewWidget, Qt::LeftButton); + QTest::keyClicks(titleEdit, "something 3"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - QCOMPARE(entry->title(), QString("testsomething")); - QCOMPARE(entry->historyItems().size(), 1); + // Confirm that 4 entries now exist + QTRY_COMPARE(entryView->model()->rowCount(), 4); +} +void TestGui::testEntryEntropy() +{ + QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar"); - QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QTest::keyClicks(titleEdit, "something 2"); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); + // Find the new entry action + QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew"); + QVERIFY(entryNewAction->isEnabled()); + // Find the button associated with the new entry action + QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction); + QVERIFY(entryNewWidget->isVisible()); + QVERIFY(entryNewWidget->isEnabled()); + // Click the new entry button and check that we enter edit mode QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QTest::keyClicks(titleEdit, "something 3"); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); + // Add entry "test" and confirm added + EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget"); + QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit"); + QTest::keyClicks(titleEdit, "test"); - QTRY_COMPARE(entryView->model()->rowCount(), 4); + // Open the password generator + QToolButton* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton"); + QTest::mouseClick(generatorButton, Qt::LeftButton); + + // Type in some password + QLineEdit* editNewPassword = editEntryWidget->findChild<QLineEdit*>("editNewPassword"); + QLabel* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel"); + QLabel* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel"); + + editNewPassword->setText(""); + QTest::keyClicks(editNewPassword, "hello"); + QCOMPARE(entropyLabel->text(), QString("Entropy: 6.38 bit")); + QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor")); + + editNewPassword->setText(""); + QTest::keyClicks(editNewPassword, "helloworld"); + QCOMPARE(entropyLabel->text(), QString("Entropy: 13.10 bit")); + QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor")); + + editNewPassword->setText(""); + QTest::keyClicks(editNewPassword, "password1"); + QCOMPARE(entropyLabel->text(), QString("Entropy: 4.00 bit")); + QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor")); + + editNewPassword->setText(""); + QTest::keyClicks(editNewPassword, "D0g.................."); + QCOMPARE(entropyLabel->text(), QString("Entropy: 19.02 bit")); + QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor")); + + editNewPassword->setText(""); + QTest::keyClicks(editNewPassword, "Tr0ub4dour&3"); + QCOMPARE(entropyLabel->text(), QString("Entropy: 30.87 bit")); + QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor")); + + editNewPassword->setText(""); + QTest::keyClicks(editNewPassword, "correcthorsebatterystaple"); + QCOMPARE(entropyLabel->text(), QString("Entropy: 47.98 bit")); + QCOMPARE(strengthLabel->text(), QString("Password Quality: Weak")); + + editNewPassword->setText(""); + QTest::keyClicks(editNewPassword, "YQC3kbXbjC652dTDH"); + QCOMPARE(entropyLabel->text(), QString("Entropy: 96.07 bit")); + QCOMPARE(strengthLabel->text(), QString("Password Quality: Good")); + + editNewPassword->setText(""); + QTest::keyClicks(editNewPassword, "Bs5ZFfthWzR8DGFEjaCM6bGqhmCT4km"); + QCOMPARE(entropyLabel->text(), QString("Entropy: 174.59 bit")); + QCOMPARE(strengthLabel->text(), QString("Password Quality: Excellent")); + + // We are done } void TestGui::testSearch() { - QAction* searchAction = m_mainWindow->findChild<QAction*>("actionSearch"); - QVERIFY(searchAction->isEnabled()); + // Add canned entries for consistent testing + testAddEntry(); + QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar"); - QWidget* searchActionWidget = toolBar->widgetForAction(searchAction); - EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView"); - QLineEdit* searchEdit = m_dbWidget->findChild<QLineEdit*>("searchEdit"); - QToolButton* clearSearch = m_dbWidget->findChild<QToolButton*>("clearButton"); - QVERIFY(!searchEdit->isVisible()); + SearchWidget* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget"); + QVERIFY(searchWidget->isEnabled()); + QLineEdit* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit"); + + EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView"); + QVERIFY(entryView->isVisible()); // Enter search - QTest::mouseClick(searchActionWidget, Qt::LeftButton); - QTRY_VERIFY(searchEdit->hasFocus()); + QTest::mouseClick(searchTextEdit, Qt::LeftButton); + QTRY_VERIFY(searchTextEdit->hasFocus()); // Search for "ZZZ" - QTest::keyClicks(searchEdit, "ZZZ"); + QTest::keyClicks(searchTextEdit, "ZZZ"); + QTRY_COMPARE(searchTextEdit->text(), QString("ZZZ")); + QTRY_VERIFY(m_dbWidget->isInSearchMode()); QTRY_COMPARE(entryView->model()->rowCount(), 0); - // Escape - QTest::keyClick(m_mainWindow, Qt::Key_Escape); - QTRY_VERIFY(!searchEdit->hasFocus()); - // Enter search again - QTest::mouseClick(searchActionWidget, Qt::LeftButton); - QTRY_VERIFY(searchEdit->hasFocus()); - // Input and clear - QTest::keyClicks(searchEdit, "ZZZ"); - QTRY_COMPARE(searchEdit->text(), QString("ZZZ")); - QTest::mouseClick(clearSearch, Qt::LeftButton); - QTRY_COMPARE(searchEdit->text(), QString("")); - // Triggering search should select the existing text - QTest::keyClicks(searchEdit, "ZZZ"); - QTest::mouseClick(searchActionWidget, Qt::LeftButton); - QTRY_VERIFY(searchEdit->hasFocus()); + // Press the search clear button + QToolButton* clearButton = searchWidget->findChild<QToolButton*>("clearIcon"); + QTest::mouseClick(clearButton, Qt::LeftButton); + QTRY_VERIFY(searchTextEdit->text().isEmpty()); + QTRY_VERIFY(searchTextEdit->hasFocus()); + // Escape clears searchedit and retains focus + QTest::keyClicks(searchTextEdit, "ZZZ"); + QTest::keyClick(searchTextEdit, Qt::Key_Escape); + QTRY_VERIFY(searchTextEdit->text().isEmpty()); + QTRY_VERIFY(searchTextEdit->hasFocus()); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); // Search for "some" - QTest::keyClicks(searchEdit, "some"); - QTRY_COMPARE(entryView->model()->rowCount(), 4); + QTest::keyClicks(searchTextEdit, "some"); + QTRY_VERIFY(m_dbWidget->isInSearchMode()); + QTRY_COMPARE(entryView->model()->rowCount(), 3); + // Search for "someTHING" + QTest::keyClicks(searchTextEdit, "THING"); + QTRY_COMPARE(entryView->model()->rowCount(), 2); // Press Down to focus on the entry view - QVERIFY(!entryView->hasFocus()); - QTest::keyClick(searchEdit, Qt::Key_Down); - QVERIFY(entryView->hasFocus()); + QTest::keyClick(searchTextEdit, Qt::Key_Right, Qt::ControlModifier); + QTRY_VERIFY(searchTextEdit->hasFocus()); + QTest::keyClick(searchTextEdit, Qt::Key_Down); + QTRY_VERIFY(entryView->hasFocus()); + // Restore focus and search text selection + QTest::keyClick(m_mainWindow, Qt::Key_F, Qt::ControlModifier); + QTRY_COMPARE(searchTextEdit->selectedText(), QString("someTHING")); + // Ensure Down focuses on entry view when search text is selected + QTest::keyClick(searchTextEdit, Qt::Key_Down); + QTRY_VERIFY(entryView->hasFocus()); + // Refocus back to search edit + QTest::mouseClick(searchTextEdit, Qt::LeftButton); + QTRY_VERIFY(searchTextEdit->hasFocus()); + // Test password copy + QClipboard *clipboard = QApplication::clipboard(); + QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier); + QModelIndex searchedItem = entryView->model()->index(0, 1); + Entry* searchedEntry = entryView->entryFromIndex(searchedItem); + QTRY_COMPARE(searchedEntry->password(), clipboard->text()); + + // Test case sensitive search + searchWidget->setCaseSensitive(true); + QTRY_COMPARE(entryView->model()->rowCount(), 0); + searchWidget->setCaseSensitive(false); + QTRY_COMPARE(entryView->model()->rowCount(), 2); - clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton); - QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit"); - QVERIFY(entryEditAction->isEnabled()); - QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); - QVERIFY(entryEditWidget->isVisible()); - QVERIFY(entryEditWidget->isEnabled()); - QTest::mouseClick(entryEditWidget, Qt::LeftButton); + // Test group search + GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView"); + QCOMPARE(groupView->currentGroup(), m_db->rootGroup()); + QModelIndex rootGroupIndex = groupView->model()->index(0, 0); + clickIndex(groupView->model()->index(0, 0, rootGroupIndex), groupView, Qt::LeftButton); + QCOMPARE(groupView->currentGroup()->name(), QString("General")); + QTRY_COMPARE(entryView->model()->rowCount(), 0); + // reset + clickIndex(rootGroupIndex, groupView, Qt::LeftButton); + QCOMPARE(groupView->currentGroup(), m_db->rootGroup()); + + // Try to edit the first entry from the search view + // Refocus back to search edit + QTest::mouseClick(searchTextEdit, Qt::LeftButton); + QTRY_VERIFY(searchTextEdit->hasFocus()); + QVERIFY(m_dbWidget->isInSearchMode()); + QModelIndex item = entryView->model()->index(0, 1); + Entry* entry = entryView->entryFromIndex(item); + QTest::keyClick(searchTextEdit, Qt::Key_Return); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); + // Perform the edit and save it EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget"); + QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit"); + QString origTitle = titleEdit->text(); + QTest::keyClicks(titleEdit, "_edited"); QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); + // Confirm the edit was made and we are back in search mode + QTRY_VERIFY(m_dbWidget->isInSearchMode()); + QCOMPARE(entry->title(), origTitle.append("_edited")); - clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton); - QAction* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete"); + // Cancel search, should return to normal view + QTest::mouseClick(searchTextEdit, Qt::LeftButton); + QTest::keyClick(searchTextEdit, Qt::Key_Escape); + QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); +} + +void TestGui::testDeleteEntry() +{ + // Add canned entries for consistent testing + testAddEntry(); + GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView"); + EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView"); + QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar"); + QAction* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete"); QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction); + + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); + clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton); QVERIFY(entryDeleteWidget->isVisible()); QVERIFY(entryDeleteWidget->isEnabled()); QVERIFY(!m_db->metadata()->recycleBin()); @@ -260,21 +523,7 @@ void TestGui::testSearch() QCOMPARE(entryView->model()->rowCount(), 1); QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 3); - QWidget* closeSearchButton = m_dbWidget->findChild<QToolButton*>("closeSearchButton"); - QTest::mouseClick(closeSearchButton, Qt::LeftButton); - - QCOMPARE(entryView->model()->rowCount(), 1); -} - -void TestGui::testDeleteEntry() -{ - GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView"); - EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView"); - QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar"); - QAction* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete"); - QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction); QCOMPARE(groupView->currentGroup(), m_db->rootGroup()); - QModelIndex rootGroupIndex = groupView->model()->index(0, 0); clickIndex(groupView->model()->index(groupView->model()->rowCount(rootGroupIndex) - 1, 0, rootGroupIndex), groupView, Qt::LeftButton); @@ -363,28 +612,29 @@ void TestGui::testDragAndDropGroup() dragAndDropGroup(groupModel->index(0, 0, rootIndex), rootIndex, - -1, true, "NewDatabase", 5); + -1, true, "NewDatabase", 4); } void TestGui::testSaveAs() { - QFileInfo fileInfo(m_orgDbFile.fileName()); + QFileInfo fileInfo(m_dbFilePath); QDateTime lastModified = fileInfo.lastModified(); m_db->metadata()->setName("SaveAs"); - QTemporaryFile* tmpFile = new QTemporaryFile(); // open temporary file so it creates a filename - QVERIFY(tmpFile->open()); - m_tmpFileName = tmpFile->fileName(); - delete tmpFile; - fileDialog()->setNextFileName(m_tmpFileName); + QTemporaryFile tmpFile; + QVERIFY(tmpFile.open()); + QString tmpFileName = tmpFile.fileName(); + tmpFile.remove(); + + fileDialog()->setNextFileName(tmpFileName); triggerAction("actionDatabaseSaveAs"); QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("SaveAs")); - checkDatabase(); + checkDatabase(tmpFileName); fileInfo.refresh(); QCOMPARE(fileInfo.lastModified(), lastModified); @@ -404,6 +654,7 @@ void TestGui::testSave() void TestGui::testDatabaseSettings() { + m_db->metadata()->setName("Save"); triggerAction("actionChangeDatabaseSettings"); QWidget* dbSettingsWidget = m_dbWidget->findChild<QWidget*>("databaseSettingsWidget"); QSpinBox* transformRoundsSpinBox = dbSettingsWidget->findChild<QSpinBox*>("transformRoundsSpinBox"); @@ -433,40 +684,48 @@ void TestGui::testKeePass1Import() QCOMPARE(m_tabWidget->count(), 2); QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("basic [New database]*")); + + // Close the KeePass1 Database + MessageBox::setNextAnswer(QMessageBox::No); + triggerAction("actionDatabaseClose"); + Tools::wait(100); + } void TestGui::testDatabaseLocking() { - MessageBox::setNextAnswer(QMessageBox::Cancel); + QString origDbName = m_tabWidget->tabText(0); + MessageBox::setNextAnswer(QMessageBox::Cancel); triggerAction("actionLockDatabases"); - QCOMPARE(m_tabWidget->tabText(0).remove('&'), QString("Save [locked]")); - QCOMPARE(m_tabWidget->tabText(1).remove('&'), QString("basic [New database]*")); + QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName + " [locked]"); QWidget* dbWidget = m_tabWidget->currentDatabaseWidget(); QWidget* unlockDatabaseWidget = dbWidget->findChild<QWidget*>("unlockDatabaseWidget"); QWidget* editPassword = unlockDatabaseWidget->findChild<QLineEdit*>("editPassword"); QVERIFY(editPassword); - QTest::keyClicks(editPassword, "masterpw"); + QTest::keyClicks(editPassword, "a"); QTest::keyClick(editPassword, Qt::Key_Enter); - QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()).remove('&'), QString("basic [New database]*")); + QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName); } void TestGui::cleanupTestCase() { delete m_mainWindow; - QFile::remove(m_tmpFileName); } -void TestGui::checkDatabase() +void TestGui::checkDatabase(QString dbFileName) { + if (dbFileName.isEmpty()) + dbFileName = m_dbFilePath; + CompositeKey key; key.addKey(PasswordKey("a")); KeePass2Reader reader; - QScopedPointer<Database> dbSaved(reader.readDatabase(m_tmpFileName, key)); + QScopedPointer<Database> dbSaved(reader.readDatabase(dbFileName, key)); QVERIFY(dbSaved); QVERIFY(!reader.hasError()); QCOMPARE(dbSaved->metadata()->name(), m_db->metadata()->name()); diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index a7474ca54..c2e0e372e 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -18,9 +18,10 @@ #ifndef KEEPASSX_TESTGUI_H #define KEEPASSX_TESTGUI_H +#include "TemporaryFile.h" + #include <QAbstractItemModel> #include <QObject> -#include <QTemporaryFile> class Database; class DatabaseTabWidget; @@ -34,10 +35,16 @@ class TestGui : public QObject private Q_SLOTS: void initTestCase(); - void testOpenDatabase(); + void init(); + void cleanup(); + void cleanupTestCase(); + + void testMergeDatabase(); + void testAutoreloadDatabase(); void testTabs(); void testEditEntry(); void testAddEntry(); + void testEntryEntropy(); void testSearch(); void testDeleteEntry(); void testCloneEntry(); @@ -48,10 +55,9 @@ private Q_SLOTS: void testDatabaseSettings(); void testKeePass1Import(); void testDatabaseLocking(); - void cleanupTestCase(); private: - void checkDatabase(); + void checkDatabase(QString dbFileName = ""); void triggerAction(const QString& name); void dragAndDropGroup(const QModelIndex& sourceIndex, const QModelIndex& targetIndex, int row, bool expectedResult, const QString& expectedParentName, int expectedPos); @@ -61,9 +67,10 @@ private: MainWindow* m_mainWindow; DatabaseTabWidget* m_tabWidget; DatabaseWidget* m_dbWidget; - QTemporaryFile m_orgDbFile; - QString m_orgDbFileName; - QString m_tmpFileName; + QByteArray m_dbData; + TemporaryFile m_dbFile; + QString m_dbFileName; + QString m_dbFilePath; Database* m_db; }; |