Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt9
-rw-r--r--tests/TestAutoType.cpp32
-rw-r--r--tests/TestAutoType.h1
-rw-r--r--tests/TestCsvExporter.cpp2
-rw-r--r--tests/TestCsvExporter.h2
-rw-r--r--tests/TestEntry.cpp12
-rw-r--r--tests/TestEntryModel.cpp14
-rw-r--r--tests/TestGroup.cpp121
-rw-r--r--tests/TestGroup.h8
-rw-r--r--tests/TestKeys.cpp16
-rw-r--r--tests/TestKeys.h1
-rw-r--r--tests/TestWildcardMatcher.cpp6
-rw-r--r--tests/TestWildcardMatcher.h1
-rw-r--r--tests/config-keepassx-tests.h.cmake4
-rw-r--r--tests/data/MergeDatabase.kdbxbin0 -> 15150 bytes
-rw-r--r--tests/gui/CMakeLists.txt2
-rw-r--r--tests/gui/TemporaryFile.cpp92
-rw-r--r--tests/gui/TemporaryFile.h64
-rw-r--r--tests/gui/TestGui.cpp485
-rw-r--r--tests/gui/TestGui.h21
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
new file mode 100644
index 000000000..f45929de2
--- /dev/null
+++ b/tests/data/MergeDatabase.kdbx
Binary files differ
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;
};