Add CLI tests and improve coding style and i18n
The CLI module was lacking unit test coverage and showed some severe coding style violations, which this patch addresses. In addition, all uses of qCritical() with untranslatble raw char* sequences were removed in favor of proper locale strings. These are written to STDERR through QTextStreams and support output redirection for testing purposes. With this change, error messages don't depend on the global Qt logging settings and targets anymore and go directly to the terminal or into a file if needed. This patch also fixes a bug discovered during unit test development, where the extract command would just dump the raw XML contents without decrypting embedded Salsa20-protected values first, making the XML export mostly useless, since passwords are scrambled. Lastly, all CLI commands received a dedicated -h/--help option.
This commit is contained in:
@@ -163,7 +163,10 @@ add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testmerge SOURCES TestMerge.cpp
|
||||
LIBS testsupport ${TEST_LIBRARIES})
|
||||
LIBS testsupport ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testpasswordgenerator SOURCES TestPasswordGenerator.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testtotp SOURCES TestTotp.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
@@ -180,7 +183,7 @@ add_unit_test(NAME testrandom SOURCES TestRandom.cpp
|
||||
add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testcsvexporter SOURCES TestCsvExporter.cpp
|
||||
add_unit_test(NAME testcsveporter SOURCES TestCsvExporter.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testykchallengeresponsekey
|
||||
@@ -193,6 +196,11 @@ add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
|
||||
add_unit_test(NAME testtools SOURCES TestTools.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
|
||||
if(WITH_GUI_TESTS)
|
||||
# CLI clip tests need X environment on Linux
|
||||
add_unit_test(NAME testcli SOURCES TestCli.cpp
|
||||
LIBS testsupport cli ${TEST_LIBRARIES})
|
||||
|
||||
add_subdirectory(gui)
|
||||
endif(WITH_GUI_TESTS)
|
||||
|
||||
756
tests/TestCli.cpp
Normal file
756
tests/TestCli.cpp
Normal file
@@ -0,0 +1,756 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TestCli.h"
|
||||
#include "config-keepassx-tests.h"
|
||||
#include "core/Global.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Bootstrap.h"
|
||||
#include "core/Tools.h"
|
||||
#include "core/PasswordGenerator.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "format/Kdbx3Reader.h"
|
||||
#include "format/Kdbx4Reader.h"
|
||||
#include "format/Kdbx4Writer.h"
|
||||
#include "format/Kdbx3Writer.h"
|
||||
#include "format/KdbxXmlReader.h"
|
||||
|
||||
#include "cli/Command.h"
|
||||
#include "cli/Utils.h"
|
||||
#include "cli/Add.h"
|
||||
#include "cli/Clip.h"
|
||||
#include "cli/Diceware.h"
|
||||
#include "cli/Edit.h"
|
||||
#include "cli/Estimate.h"
|
||||
#include "cli/Extract.h"
|
||||
#include "cli/Generate.h"
|
||||
#include "cli/List.h"
|
||||
#include "cli/Locate.h"
|
||||
#include "cli/Merge.h"
|
||||
#include "cli/Remove.h"
|
||||
#include "cli/Show.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QClipboard>
|
||||
#include <QFuture>
|
||||
#include <QtConcurrent>
|
||||
#include <QSet>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
QTEST_MAIN(TestCli)
|
||||
|
||||
void TestCli::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
|
||||
Config::createTempFileInstance();
|
||||
Bootstrap::bootstrapApplication();
|
||||
|
||||
// Load the NewDatabase.kdbx file into temporary storage
|
||||
QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx"));
|
||||
QVERIFY(sourceDbFile.open(QIODevice::ReadOnly));
|
||||
QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData));
|
||||
sourceDbFile.close();
|
||||
}
|
||||
|
||||
void TestCli::init()
|
||||
{
|
||||
m_dbFile.reset(new QTemporaryFile());
|
||||
m_dbFile->open();
|
||||
m_dbFile->write(m_dbData);
|
||||
m_dbFile->flush();
|
||||
|
||||
m_stdinFile.reset(new QTemporaryFile());
|
||||
m_stdinFile->open();
|
||||
m_stdinHandle = fdopen(m_stdinFile->handle(), "r+");
|
||||
Utils::STDIN = m_stdinHandle;
|
||||
|
||||
m_stdoutFile.reset(new QTemporaryFile());
|
||||
m_stdoutFile->open();
|
||||
m_stdoutHandle = fdopen(m_stdoutFile->handle(), "r+");
|
||||
Utils::STDOUT = m_stdoutHandle;
|
||||
|
||||
m_stderrFile.reset(new QTemporaryFile());
|
||||
m_stderrFile->open();
|
||||
m_stderrHandle = fdopen(m_stderrFile->handle(), "r+");
|
||||
Utils::STDERR = m_stderrHandle;
|
||||
}
|
||||
|
||||
void TestCli::cleanup()
|
||||
{
|
||||
m_dbFile.reset();
|
||||
|
||||
m_stdinFile.reset();
|
||||
m_stdinHandle = stdin;
|
||||
Utils::STDIN = stdin;
|
||||
|
||||
m_stdoutFile.reset();
|
||||
Utils::STDOUT = stdout;
|
||||
m_stdoutHandle = stdout;
|
||||
|
||||
m_stderrFile.reset();
|
||||
m_stderrHandle = stderr;
|
||||
Utils::STDERR = stderr;
|
||||
}
|
||||
|
||||
void TestCli::cleanupTestCase()
|
||||
{
|
||||
}
|
||||
|
||||
QSharedPointer<Database> TestCli::readTestDatabase() const
|
||||
{
|
||||
Utils::setNextPassword("a");
|
||||
auto db = QSharedPointer<Database>(Database::unlockFromStdin(m_dbFile->fileName(), "", m_stdoutHandle));
|
||||
m_stdoutFile->seek(ftell(m_stdoutHandle)); // re-synchronize handles
|
||||
return db;
|
||||
}
|
||||
|
||||
void TestCli::testCommand()
|
||||
{
|
||||
QCOMPARE(Command::getCommands().size(), 12);
|
||||
QVERIFY(Command::getCommand("add"));
|
||||
QVERIFY(Command::getCommand("clip"));
|
||||
QVERIFY(Command::getCommand("diceware"));
|
||||
QVERIFY(Command::getCommand("edit"));
|
||||
QVERIFY(Command::getCommand("estimate"));
|
||||
QVERIFY(Command::getCommand("extract"));
|
||||
QVERIFY(Command::getCommand("generate"));
|
||||
QVERIFY(Command::getCommand("locate"));
|
||||
QVERIFY(Command::getCommand("ls"));
|
||||
QVERIFY(Command::getCommand("merge"));
|
||||
QVERIFY(Command::getCommand("rm"));
|
||||
QVERIFY(Command::getCommand("show"));
|
||||
QVERIFY(!Command::getCommand("doesnotexist"));
|
||||
}
|
||||
|
||||
void TestCli::testAdd()
|
||||
{
|
||||
Add addCmd;
|
||||
QVERIFY(!addCmd.name.isEmpty());
|
||||
QVERIFY(addCmd.getDescriptionLine().contains(addCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
addCmd.execute({"add", "-u", "newuser", "--url", "https://example.com/", "-g", "-l", "20", m_dbFile->fileName(), "/newuser-entry"});
|
||||
|
||||
auto db = readTestDatabase();
|
||||
auto* entry = db->rootGroup()->findEntryByPath("/newuser-entry");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->username(), QString("newuser"));
|
||||
QCOMPARE(entry->url(), QString("https://example.com/"));
|
||||
QCOMPARE(entry->password().size(), 20);
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
Utils::setNextPassword("newpassword");
|
||||
addCmd.execute({"add", "-u", "newuser2", "--url", "https://example.net/", "-g", "-l", "20", "-p", m_dbFile->fileName(), "/newuser-entry2"});
|
||||
|
||||
db = readTestDatabase();
|
||||
entry = db->rootGroup()->findEntryByPath("/newuser-entry2");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->username(), QString("newuser2"));
|
||||
QCOMPARE(entry->url(), QString("https://example.net/"));
|
||||
QCOMPARE(entry->password(), QString("newpassword"));
|
||||
}
|
||||
|
||||
void TestCli::testClip()
|
||||
{
|
||||
QClipboard* clipboard = QGuiApplication::clipboard();
|
||||
clipboard->clear();
|
||||
|
||||
Clip clipCmd;
|
||||
QVERIFY(!clipCmd.name.isEmpty());
|
||||
QVERIFY(clipCmd.getDescriptionLine().contains(clipCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry"});
|
||||
|
||||
m_stderrFile->reset();
|
||||
QString errorOutput(m_stderrFile->readAll());
|
||||
|
||||
if (errorOutput.contains("Unable to start program")
|
||||
|| errorOutput.contains("No program defined for clipboard manipulation")) {
|
||||
QSKIP("Clip test skipped due to missing clipboard tool");
|
||||
}
|
||||
|
||||
QCOMPARE(clipboard->text(), QString("Password"));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
QFuture<void> future = QtConcurrent::run(&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"});
|
||||
|
||||
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString("Password"), 500);
|
||||
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString(""), 1500);
|
||||
|
||||
future.waitForFinished();
|
||||
}
|
||||
|
||||
void TestCli::testDiceware()
|
||||
{
|
||||
Diceware dicewareCmd;
|
||||
QVERIFY(!dicewareCmd.name.isEmpty());
|
||||
QVERIFY(dicewareCmd.getDescriptionLine().contains(dicewareCmd.name));
|
||||
|
||||
dicewareCmd.execute({"diceware"});
|
||||
m_stdoutFile->reset();
|
||||
QString passphrase(m_stdoutFile->readLine());
|
||||
QVERIFY(!passphrase.isEmpty());
|
||||
|
||||
dicewareCmd.execute({"diceware", "-W", "2"});
|
||||
m_stdoutFile->seek(passphrase.toLatin1().size());
|
||||
passphrase = m_stdoutFile->readLine();
|
||||
QCOMPARE(passphrase.split(" ").size(), 2);
|
||||
|
||||
auto pos = m_stdoutFile->pos();
|
||||
dicewareCmd.execute({"diceware", "-W", "10"});
|
||||
m_stdoutFile->seek(pos);
|
||||
passphrase = m_stdoutFile->readLine();
|
||||
QCOMPARE(passphrase.split(" ").size(), 10);
|
||||
|
||||
QTemporaryFile wordFile;
|
||||
wordFile.open();
|
||||
for (int i = 0; i < 4500; ++i) {
|
||||
wordFile.write(QString("word" + QString::number(i) + "\n").toLatin1());
|
||||
}
|
||||
wordFile.close();
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
dicewareCmd.execute({"diceware", "-W", "11", "-w", wordFile.fileName()});
|
||||
m_stdoutFile->seek(pos);
|
||||
passphrase = m_stdoutFile->readLine();
|
||||
const auto words = passphrase.split(" ");
|
||||
QCOMPARE(words.size(), 11);
|
||||
QRegularExpression regex("^word\\d+$");
|
||||
for (const auto& word: words) {
|
||||
QVERIFY2(regex.match(word).hasMatch(), qPrintable("Word " + word + " was not on the word list"));
|
||||
}
|
||||
}
|
||||
|
||||
void TestCli::testEdit()
|
||||
{
|
||||
Edit editCmd;
|
||||
QVERIFY(!editCmd.name.isEmpty());
|
||||
QVERIFY(editCmd.getDescriptionLine().contains(editCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
editCmd.execute({"edit", "-u", "newuser", "--url", "https://otherurl.example.com/", "-t", "newtitle", m_dbFile->fileName(), "/Sample Entry"});
|
||||
|
||||
auto db = readTestDatabase();
|
||||
auto* entry = db->rootGroup()->findEntryByPath("/newtitle");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->username(), QString("newuser"));
|
||||
QCOMPARE(entry->url(), QString("https://otherurl.example.com/"));
|
||||
QCOMPARE(entry->password(), QString("Password"));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
editCmd.execute({"edit", "-g", m_dbFile->fileName(), "/newtitle"});
|
||||
db = readTestDatabase();
|
||||
entry = db->rootGroup()->findEntryByPath("/newtitle");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->username(), QString("newuser"));
|
||||
QCOMPARE(entry->url(), QString("https://otherurl.example.com/"));
|
||||
QVERIFY(!entry->password().isEmpty());
|
||||
QVERIFY(entry->password() != QString("Password"));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
editCmd.execute({"edit", "-g", "-l", "34", "-t", "yet another title", m_dbFile->fileName(), "/newtitle"});
|
||||
db = readTestDatabase();
|
||||
entry = db->rootGroup()->findEntryByPath("/yet another title");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->username(), QString("newuser"));
|
||||
QCOMPARE(entry->url(), QString("https://otherurl.example.com/"));
|
||||
QVERIFY(entry->password() != QString("Password"));
|
||||
QCOMPARE(entry->password().size(), 34);
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
Utils::setNextPassword("newpassword");
|
||||
editCmd.execute({"edit", "-p", m_dbFile->fileName(), "/yet another title"});
|
||||
db = readTestDatabase();
|
||||
entry = db->rootGroup()->findEntryByPath("/yet another title");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->password(), QString("newpassword"));
|
||||
}
|
||||
|
||||
void TestCli::testEstimate_data()
|
||||
{
|
||||
QTest::addColumn<QString>("input");
|
||||
QTest::addColumn<QString>("length");
|
||||
QTest::addColumn<QString>("entropy");
|
||||
QTest::addColumn<QString>("log10");
|
||||
QTest::addColumn<QStringList>("searchStrings");
|
||||
|
||||
QTest::newRow("Dictionary")
|
||||
<< "password" << "8" << "1.0" << "0.3"
|
||||
<< QStringList{"Type: Dictionary", "\tpassword"};
|
||||
|
||||
QTest::newRow("Spatial")
|
||||
<< "zxcv" << "4" << "10.3" << "3.1"
|
||||
<< QStringList{"Type: Spatial", "\tzxcv"};
|
||||
|
||||
QTest::newRow("Spatial(Rep)")
|
||||
<< "sdfgsdfg" << "8" << "11.3" << "3.4"
|
||||
<< QStringList{"Type: Spatial(Rep)", "\tsdfgsdfg"};
|
||||
|
||||
QTest::newRow("Dictionary / Sequence")
|
||||
<< "password123" << "11" << "4.5" << "1.3"
|
||||
<< QStringList{"Type: Dictionary", "Type: Sequence", "\tpassword", "\t123"};
|
||||
|
||||
QTest::newRow("Dict+Leet")
|
||||
<< "p455w0rd" << "8" << "2.5" << "0.7"
|
||||
<< QStringList{"Type: Dict+Leet", "\tp455w0rd"};
|
||||
|
||||
QTest::newRow("Dictionary(Rep)")
|
||||
<< "hellohello" << "10" << "7.3" << "2.2"
|
||||
<< QStringList{"Type: Dictionary(Rep)", "\thellohello"};
|
||||
|
||||
QTest::newRow("Sequence(Rep) / Dictionary")
|
||||
<< "456456foobar" << "12" << "16.7" << "5.0"
|
||||
<< QStringList{"Type: Sequence(Rep)", "Type: Dictionary", "\t456456", "\tfoobar"};
|
||||
|
||||
QTest::newRow("Bruteforce(Rep) / Bruteforce")
|
||||
<< "xzxzy" << "5" << "16.1" << "4.8"
|
||||
<< QStringList{"Type: Bruteforce(Rep)", "Type: Bruteforce", "\txzxz", "\ty"};
|
||||
|
||||
QTest::newRow("Dictionary / Date(Rep)")
|
||||
<< "pass20182018" << "12" << "15.1" << "4.56"
|
||||
<< QStringList{"Type: Dictionary", "Type: Date(Rep)", "\tpass", "\t20182018"};
|
||||
|
||||
QTest::newRow("Dictionary / Date / Bruteforce")
|
||||
<< "mypass2018-2" << "12" << "32.9" << "9.9"
|
||||
<< QStringList{"Type: Dictionary", "Type: Date", "Type: Bruteforce", "\tmypass", "\t2018", "\t-2"};
|
||||
|
||||
QTest::newRow("Strong Password")
|
||||
<< "E*!%.Qw{t.X,&bafw)\"Q!ah$%;U/" << "28" << "165.7" << "49.8"
|
||||
<< QStringList{"Type: Bruteforce", "\tE*"};
|
||||
|
||||
// TODO: detect passphrases and adjust entropy calculation accordingly (issue #2347)
|
||||
QTest::newRow("Strong Passphrase")
|
||||
<< "squint wooing resupply dangle isolation axis headsman" << "53" << "151.2" << "45.5"
|
||||
<< QStringList{"Type: Dictionary", "Type: Bruteforce", "Multi-word extra bits 22.0", "\tsquint", "\t ", "\twooing"};
|
||||
}
|
||||
|
||||
void TestCli::testEstimate()
|
||||
{
|
||||
QFETCH(QString, input);
|
||||
QFETCH(QString, length);
|
||||
QFETCH(QString, entropy);
|
||||
QFETCH(QString, log10);
|
||||
QFETCH(QStringList, searchStrings);
|
||||
|
||||
Estimate estimateCmd;
|
||||
QVERIFY(!estimateCmd.name.isEmpty());
|
||||
QVERIFY(estimateCmd.getDescriptionLine().contains(estimateCmd.name));
|
||||
|
||||
QTextStream in(m_stdinFile.data());
|
||||
QTextStream out(m_stdoutFile.data());
|
||||
|
||||
in << input << endl;
|
||||
auto inEnd = in.pos();
|
||||
in.seek(0);
|
||||
estimateCmd.execute({"estimate"});
|
||||
auto outEnd = out.pos();
|
||||
out.seek(0);
|
||||
auto result = out.readAll();
|
||||
QVERIFY(result.startsWith("Length " + length));
|
||||
QVERIFY(result.contains("Entropy " + entropy));
|
||||
QVERIFY(result.contains("Log10 " + log10));
|
||||
|
||||
// seek to end of stream
|
||||
in.seek(inEnd);
|
||||
out.seek(outEnd);
|
||||
|
||||
in << input << endl;
|
||||
in.seek(inEnd);
|
||||
estimateCmd.execute({"estimate", "-a"});
|
||||
out.seek(outEnd);
|
||||
result = out.readAll();
|
||||
QVERIFY(result.startsWith("Length " + length));
|
||||
QVERIFY(result.contains("Entropy " + entropy));
|
||||
QVERIFY(result.contains("Log10 " + log10));
|
||||
for (const auto& string: asConst(searchStrings)) {
|
||||
QVERIFY2(result.contains(string), qPrintable("String " + string + " missing"));
|
||||
}
|
||||
}
|
||||
|
||||
void TestCli::testExtract()
|
||||
{
|
||||
Extract extractCmd;
|
||||
QVERIFY(!extractCmd.name.isEmpty());
|
||||
QVERIFY(extractCmd.getDescriptionLine().contains(extractCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
extractCmd.execute({"extract", m_dbFile->fileName()});
|
||||
|
||||
m_stdoutFile->seek(0);
|
||||
m_stdoutFile->readLine(); // skip prompt line
|
||||
|
||||
KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1);
|
||||
QScopedPointer<Database> db(new Database());
|
||||
reader.readDatabase(m_stdoutFile.data(), db.data());
|
||||
QVERIFY(!reader.hasError());
|
||||
QVERIFY(db.data());
|
||||
auto* entry = db->rootGroup()->findEntryByPath("/Sample Entry");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->password(), QString("Password"));
|
||||
}
|
||||
|
||||
void TestCli::testGenerate_data()
|
||||
{
|
||||
QTest::addColumn<QStringList>("parameters");
|
||||
QTest::addColumn<QString>("pattern");
|
||||
|
||||
QTest::newRow("default") << QStringList{"generate"} << "^[^\r\n]+$";
|
||||
QTest::newRow("length") << QStringList{"generate", "-L", "13"} << "^.{13}$";
|
||||
QTest::newRow("lowercase") << QStringList{"generate", "-L", "14", "-l"} << "^[a-z]{14}$";
|
||||
QTest::newRow("uppercase") << QStringList{"generate", "-L", "15", "-u"} << "^[A-Z]{15}$";
|
||||
QTest::newRow("numbers")<< QStringList{"generate", "-L", "16", "-n"} << "^[0-9]{16}$";
|
||||
QTest::newRow("special")
|
||||
<< QStringList{"generate", "-L", "200", "-s"}
|
||||
<< R"(^[\(\)\[\]\{\}\.\-*|\\,:;"'\/\_!+-<=>?#$%&^`@~]{200}$)";
|
||||
QTest::newRow("special (exclude)")
|
||||
<< QStringList{"generate", "-L", "200", "-s" , "-x", "+.?@&"}
|
||||
<< R"(^[\(\)\[\]\{\}\.\-*|\\,:;"'\/\_!-<=>#$%^`~]{200}$)";
|
||||
QTest::newRow("extended")
|
||||
<< QStringList{"generate", "-L", "50", "-e"}
|
||||
<< R"(^[^a-zA-Z0-9\(\)\[\]\{\}\.\-\*\|\\,:;"'\/\_!+-<=>?#$%&^`@~]{50}$)";
|
||||
QTest::newRow("numbers + lowercase + uppercase")
|
||||
<< QStringList{"generate", "-L", "16", "-n", "-u", "-l"}
|
||||
<< "^[0-9a-zA-Z]{16}$";
|
||||
QTest::newRow("numbers + lowercase + uppercase (exclude)")
|
||||
<< QStringList{"generate", "-L", "500", "-n", "-u", "-l", "-x", "abcdefg0123@"}
|
||||
<< "^[^abcdefg0123@]{500}$";
|
||||
QTest::newRow("numbers + lowercase + uppercase (exclude similar)")
|
||||
<< QStringList{"generate", "-L", "200", "-n", "-u", "-l", "--exclude-similar"}
|
||||
<< "^[^l1IO0]{200}$";
|
||||
QTest::newRow("uppercase + lowercase (every)")
|
||||
<< QStringList{"generate", "-L", "2", "-u", "-l", "--every-group"}
|
||||
<< "^[a-z][A-Z]|[A-Z][a-z]$";
|
||||
QTest::newRow("numbers + lowercase (every)")
|
||||
<< QStringList{"generate", "-L", "2", "-n", "-l", "--every-group"}
|
||||
<< "^[a-z][0-9]|[0-9][a-z]$";
|
||||
}
|
||||
|
||||
void TestCli::testGenerate()
|
||||
{
|
||||
QFETCH(QStringList, parameters);
|
||||
QFETCH(QString, pattern);
|
||||
|
||||
Generate generateCmd;
|
||||
QVERIFY(!generateCmd.name.isEmpty());
|
||||
QVERIFY(generateCmd.getDescriptionLine().contains(generateCmd.name));
|
||||
|
||||
qint64 pos = 0;
|
||||
// run multiple times to make accidental passes unlikely
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
generateCmd.execute(parameters);
|
||||
m_stdoutFile->seek(pos);
|
||||
QRegularExpression regex(pattern);
|
||||
QString password = QString::fromUtf8(m_stdoutFile->readLine());
|
||||
pos = m_stdoutFile->pos();
|
||||
QVERIFY2(regex.match(password).hasMatch(), qPrintable("Password " + password + " does not match pattern " + pattern));
|
||||
}
|
||||
}
|
||||
|
||||
void TestCli::testList()
|
||||
{
|
||||
List listCmd;
|
||||
QVERIFY(!listCmd.name.isEmpty());
|
||||
QVERIFY(listCmd.getDescriptionLine().contains(listCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
listCmd.execute({"ls", m_dbFile->fileName()});
|
||||
m_stdoutFile->reset();
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n"
|
||||
"General/\n"
|
||||
"Windows/\n"
|
||||
"Network/\n"
|
||||
"Internet/\n"
|
||||
"eMail/\n"
|
||||
"Homebanking/\n"));
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
listCmd.execute({"ls", "-R", m_dbFile->fileName()});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n"
|
||||
"General/\n"
|
||||
" [empty]\n"
|
||||
"Windows/\n"
|
||||
" [empty]\n"
|
||||
"Network/\n"
|
||||
" [empty]\n"
|
||||
"Internet/\n"
|
||||
" [empty]\n"
|
||||
"eMail/\n"
|
||||
" [empty]\n"
|
||||
"Homebanking/\n"
|
||||
" [empty]\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
listCmd.execute({"ls", m_dbFile->fileName(), "/General/"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("[empty]\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
listCmd.execute({"ls", m_dbFile->fileName(), "/DoesNotExist/"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
m_stderrFile->reset();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||
QCOMPARE(m_stderrFile->readAll(), QByteArray("Cannot find group /DoesNotExist/.\n"));
|
||||
}
|
||||
|
||||
void TestCli::testLocate()
|
||||
{
|
||||
Locate locateCmd;
|
||||
QVERIFY(!locateCmd.name.isEmpty());
|
||||
QVERIFY(locateCmd.getDescriptionLine().contains(locateCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
locateCmd.execute({"locate", m_dbFile->fileName(), "Sample"});
|
||||
m_stdoutFile->reset();
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n"));
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
locateCmd.execute({"locate", m_dbFile->fileName(), "Does Not Exist"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
m_stderrFile->reset();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||
QCOMPARE(m_stderrFile->readAll(), QByteArray("No results for that search term.\n"));
|
||||
|
||||
// write a modified database
|
||||
auto db = readTestDatabase();
|
||||
QVERIFY(db);
|
||||
auto* group = db->rootGroup()->findGroupByPath("/General/");
|
||||
QVERIFY(group);
|
||||
auto* entry = new Entry();
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle("New Entry");
|
||||
group->addEntry(entry);
|
||||
QTemporaryFile tmpFile;
|
||||
tmpFile.open();
|
||||
Kdbx4Writer writer;
|
||||
writer.writeDatabase(&tmpFile, db.data());
|
||||
tmpFile.close();
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
locateCmd.execute({"locate", tmpFile.fileName(), "New"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("/General/New Entry\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
locateCmd.execute({"locate", tmpFile.fileName(), "Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n/General/New Entry\n"));
|
||||
}
|
||||
|
||||
void TestCli::testMerge()
|
||||
{
|
||||
Merge mergeCmd;
|
||||
QVERIFY(!mergeCmd.name.isEmpty());
|
||||
QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name));
|
||||
|
||||
Kdbx4Writer writer;
|
||||
Kdbx4Reader reader;
|
||||
|
||||
// load test database and save a copy
|
||||
auto db = readTestDatabase();
|
||||
QVERIFY(db);
|
||||
QTemporaryFile targetFile1;
|
||||
targetFile1.open();
|
||||
writer.writeDatabase(&targetFile1, db.data());
|
||||
targetFile1.close();
|
||||
|
||||
// save another copy with a different password
|
||||
QTemporaryFile targetFile2;
|
||||
targetFile2.open();
|
||||
auto oldKey = db->key();
|
||||
auto key = QSharedPointer<CompositeKey>::create();
|
||||
key->addKey(QSharedPointer<PasswordKey>::create("b"));
|
||||
db->setKey(key);
|
||||
writer.writeDatabase(&targetFile2, db.data());
|
||||
targetFile2.close();
|
||||
db->setKey(oldKey);
|
||||
|
||||
// then add a new entry to the in-memory database and save another copy
|
||||
auto* entry = new Entry();
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle("Some Website");
|
||||
entry->setPassword("secretsecretsecret");
|
||||
auto* group = db->rootGroup()->findGroupByPath("/Internet/");
|
||||
QVERIFY(group);
|
||||
group->addEntry(entry);
|
||||
QTemporaryFile sourceFile;
|
||||
sourceFile.open();
|
||||
writer.writeDatabase(&sourceFile, db.data());
|
||||
sourceFile.close();
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
mergeCmd.execute({"merge", "-s", targetFile1.fileName(), sourceFile.fileName()});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully merged the database files.\n"));
|
||||
|
||||
QFile readBack(targetFile1.fileName());
|
||||
readBack.open(QIODevice::ReadOnly);
|
||||
QScopedPointer<Database> mergedDb(reader.readDatabase(&readBack, oldKey));
|
||||
readBack.close();
|
||||
QVERIFY(mergedDb);
|
||||
auto* entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website");
|
||||
QVERIFY(entry1);
|
||||
QCOMPARE(entry1->title(), QString("Some Website"));
|
||||
QCOMPARE(entry1->password(), QString("secretsecretsecret"));
|
||||
|
||||
// try again with different passwords for both files
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("b");
|
||||
Utils::setNextPassword("a");
|
||||
mergeCmd.execute({"merge", targetFile2.fileName(), sourceFile.fileName()});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine();
|
||||
m_stdoutFile->readLine();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully merged the database files.\n"));
|
||||
|
||||
readBack.setFileName(targetFile2.fileName());
|
||||
readBack.open(QIODevice::ReadOnly);
|
||||
mergedDb.reset(reader.readDatabase(&readBack, key));
|
||||
readBack.close();
|
||||
QVERIFY(mergedDb);
|
||||
entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website");
|
||||
QVERIFY(entry1);
|
||||
QCOMPARE(entry1->title(), QString("Some Website"));
|
||||
QCOMPARE(entry1->password(), QString("secretsecretsecret"));
|
||||
}
|
||||
|
||||
void TestCli::testRemove()
|
||||
{
|
||||
Remove removeCmd;
|
||||
QVERIFY(!removeCmd.name.isEmpty());
|
||||
QVERIFY(removeCmd.getDescriptionLine().contains(removeCmd.name));
|
||||
|
||||
Kdbx3Reader reader;
|
||||
Kdbx3Writer writer;
|
||||
|
||||
// load test database and save a copy with disabled recycle bin
|
||||
auto db = readTestDatabase();
|
||||
QVERIFY(db);
|
||||
QTemporaryFile fileCopy;
|
||||
fileCopy.open();
|
||||
db->metadata()->setRecycleBinEnabled(false);
|
||||
writer.writeDatabase(&fileCopy, db.data());
|
||||
fileCopy.close();
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
|
||||
// delete entry and verify
|
||||
Utils::setNextPassword("a");
|
||||
removeCmd.execute({"rm", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully recycled entry Sample Entry.\n"));
|
||||
|
||||
auto key = QSharedPointer<CompositeKey>::create();
|
||||
key->addKey(QSharedPointer<PasswordKey>::create("a"));
|
||||
QFile readBack(m_dbFile->fileName());
|
||||
readBack.open(QIODevice::ReadOnly);
|
||||
QScopedPointer<Database> readBackDb(reader.readDatabase(&readBack, key));
|
||||
readBack.close();
|
||||
QVERIFY(readBackDb);
|
||||
QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry"));
|
||||
QVERIFY(readBackDb->rootGroup()->findEntryByPath("/Recycle Bin/Sample Entry"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
|
||||
// try again, this time without recycle bin
|
||||
Utils::setNextPassword("a");
|
||||
removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully deleted entry Sample Entry.\n"));
|
||||
|
||||
readBack.setFileName(fileCopy.fileName());
|
||||
readBack.open(QIODevice::ReadOnly);
|
||||
readBackDb.reset(reader.readDatabase(&readBack, key));
|
||||
readBack.close();
|
||||
QVERIFY(readBackDb);
|
||||
QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry"));
|
||||
QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Recycle Bin/Sample Entry"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
|
||||
// finally, try deleting a non-existent entry
|
||||
Utils::setNextPassword("a");
|
||||
removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
m_stderrFile->reset();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||
QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry /Sample Entry not found.\n"));
|
||||
}
|
||||
|
||||
void TestCli::testShow()
|
||||
{
|
||||
Show showCmd;
|
||||
QVERIFY(!showCmd.name.isEmpty());
|
||||
QVERIFY(showCmd.getDescriptionLine().contains(showCmd.name));
|
||||
|
||||
Utils::setNextPassword("a");
|
||||
showCmd.execute({"show", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->reset();
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Title: Sample Entry\n"
|
||||
"UserName: User Name\n"
|
||||
"Password: Password\n"
|
||||
"URL: http://www.somesite.com/\n"
|
||||
"Notes: Notes\n"));
|
||||
|
||||
qint64 pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
showCmd.execute({"show", "-a", "Title", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
showCmd.execute({"show", "-a", "Title", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n"
|
||||
"http://www.somesite.com/\n"));
|
||||
|
||||
pos = m_stdoutFile->pos();
|
||||
Utils::setNextPassword("a");
|
||||
showCmd.execute({"show", "-a", "DoesNotExist", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stdoutFile->seek(pos);
|
||||
m_stdoutFile->readLine(); // skip password prompt
|
||||
m_stderrFile->reset();
|
||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||
QCOMPARE(m_stderrFile->readAll(), QByteArray("ERROR: unknown attribute DoesNotExist.\n"));
|
||||
}
|
||||
69
tests/TestCli.h
Normal file
69
tests/TestCli.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_TESTCLI_H
|
||||
#define KEEPASSXC_TESTCLI_H
|
||||
|
||||
#include "core/Database.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <QTextStream>
|
||||
#include <QFile>
|
||||
#include <QScopedPointer>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
class TestCli : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QSharedPointer<Database> readTestDatabase() const;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void init();
|
||||
void cleanup();
|
||||
void cleanupTestCase();
|
||||
|
||||
void testCommand();
|
||||
void testAdd();
|
||||
void testClip();
|
||||
void testDiceware();
|
||||
void testEdit();
|
||||
void testEstimate_data();
|
||||
void testEstimate();
|
||||
void testExtract();
|
||||
void testGenerate_data();
|
||||
void testGenerate();
|
||||
void testList();
|
||||
void testLocate();
|
||||
void testMerge();
|
||||
void testRemove();
|
||||
void testShow();
|
||||
|
||||
private:
|
||||
QByteArray m_dbData;
|
||||
QScopedPointer<QTemporaryFile> m_dbFile;
|
||||
QScopedPointer<QTemporaryFile> m_stdoutFile;
|
||||
QScopedPointer<QTemporaryFile> m_stderrFile;
|
||||
QScopedPointer<QTemporaryFile> m_stdinFile;
|
||||
FILE* m_stdoutHandle = stdout;
|
||||
FILE* m_stderrHandle = stderr;
|
||||
FILE* m_stdinHandle = stdin;
|
||||
};
|
||||
|
||||
#endif //KEEPASSXC_TESTCLI_H
|
||||
@@ -199,12 +199,12 @@ void TestKdbx4::testFormat400Upgrade_data()
|
||||
auto constexpr kdbx3 = KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
|
||||
QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << false << kdbx4;
|
||||
QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << false << kdbx4;
|
||||
QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << false << kdbx3;
|
||||
QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << true << kdbx4;
|
||||
QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << true << kdbx4;
|
||||
QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << true << kdbx4;
|
||||
QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << false << kdbx4;
|
||||
QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << false << kdbx4;
|
||||
QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << false << kdbx3;
|
||||
QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << true << kdbx4;
|
||||
QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << true << kdbx4;
|
||||
QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << true << kdbx4;
|
||||
|
||||
QTest::newRow("Argon2 + ChaCha20") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
|
||||
QTest::newRow("AES-KDF + ChaCha20") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
|
||||
|
||||
131
tests/TestPasswordGenerator.cpp
Normal file
131
tests/TestPasswordGenerator.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TestPasswordGenerator.h"
|
||||
#include "core/PasswordGenerator.h"
|
||||
#include "crypto/Crypto.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <QRegularExpression>
|
||||
|
||||
QTEST_GUILESS_MAIN(TestPasswordGenerator)
|
||||
|
||||
void TestPasswordGenerator::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
}
|
||||
|
||||
void TestPasswordGenerator::testCharClasses()
|
||||
{
|
||||
PasswordGenerator generator;
|
||||
QVERIFY(!generator.isValid());
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters);
|
||||
generator.setLength(16);
|
||||
QVERIFY(generator.isValid());
|
||||
QCOMPARE(generator.generatePassword().size(), 16);
|
||||
|
||||
generator.setLength(2000);
|
||||
QString password = generator.generatePassword();
|
||||
QCOMPARE(password.size(), 2000);
|
||||
QRegularExpression regex(R"(^[a-z]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::UpperLetters);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[A-Z]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Numbers);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^\d+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Braces);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[\(\)\[\]\{\}]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Punctuation);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[\.,:;]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Quotes);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^["']+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Dashes);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[\-/\\_|]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Math);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[!\*\+\-<=>\?]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Logograms);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[#`~%&^$@]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::EASCII);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[^a-zA-Z0-9\.,:;"'\-/\\_|!\*\+\-<=>\?#`~%&^$@]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters
|
||||
| PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Braces);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^[a-zA-Z\(\)\[\]\{\}]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::Quotes
|
||||
| PasswordGenerator::CharClass::Numbers | PasswordGenerator::CharClass::Dashes);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern(R"(^["'\d\-/\\_|]+$)");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
}
|
||||
|
||||
void TestPasswordGenerator::testLookalikeExclusion()
|
||||
{
|
||||
PasswordGenerator generator;
|
||||
generator.setLength(2000);
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters);
|
||||
QVERIFY(generator.isValid());
|
||||
QString password = generator.generatePassword();
|
||||
QCOMPARE(password.size(), 2000);
|
||||
|
||||
generator.setFlags(PasswordGenerator::GeneratorFlag::ExcludeLookAlike);
|
||||
password = generator.generatePassword();
|
||||
QRegularExpression regex("^[^lI0]+$");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters |
|
||||
PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Numbers);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern("^[^lI01]+$");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
|
||||
generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters
|
||||
| PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Numbers
|
||||
| PasswordGenerator::CharClass::EASCII);
|
||||
password = generator.generatePassword();
|
||||
regex.setPattern("^[^lI01﹒]+$");
|
||||
QVERIFY(regex.match(password).hasMatch());
|
||||
}
|
||||
33
tests/TestPasswordGenerator.h
Normal file
33
tests/TestPasswordGenerator.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_TESTPASSWORDGENERATOR_H
|
||||
#define KEEPASSXC_TESTPASSWORDGENERATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class TestPasswordGenerator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testCharClasses();
|
||||
void testLookalikeExclusion();
|
||||
};
|
||||
|
||||
#endif //KEEPASSXC_TESTPASSWORDGENERATOR_H
|
||||
@@ -26,76 +26,171 @@
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestSymmetricCipher)
|
||||
Q_DECLARE_METATYPE(SymmetricCipher::Algorithm);
|
||||
Q_DECLARE_METATYPE(SymmetricCipher::Mode);
|
||||
Q_DECLARE_METATYPE(SymmetricCipher::Direction);
|
||||
|
||||
void TestSymmetricCipher::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes128CbcEncryption()
|
||||
void TestSymmetricCipher::testAlgorithmToCipher()
|
||||
{
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes128), KeePass2::CIPHER_AES128);
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes256), KeePass2::CIPHER_AES256);
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Twofish), KeePass2::CIPHER_TWOFISH);
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::ChaCha20), KeePass2::CIPHER_CHACHA20);
|
||||
QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::InvalidAlgorithm), QUuid());
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testEncryptionDecryption_data()
|
||||
{
|
||||
QTest::addColumn<SymmetricCipher::Algorithm>("algorithm");
|
||||
QTest::addColumn<SymmetricCipher::Mode>("mode");
|
||||
QTest::addColumn<SymmetricCipher::Direction>("direction");
|
||||
QTest::addColumn<QByteArray>("key");
|
||||
QTest::addColumn<QByteArray>("iv");
|
||||
QTest::addColumn<QByteArray>("plainText");
|
||||
QTest::addColumn<QByteArray>("cipherText");
|
||||
|
||||
// http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
QTest::newRow("AES128-CBC Encryption")
|
||||
<< SymmetricCipher::Aes128
|
||||
<< SymmetricCipher::Cbc
|
||||
<< SymmetricCipher::Encrypt
|
||||
<< QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2");
|
||||
|
||||
QTest::newRow("AES128-CBC Decryption")
|
||||
<< SymmetricCipher::Aes128
|
||||
<< SymmetricCipher::Cbc
|
||||
<< SymmetricCipher::Decrypt
|
||||
<< QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51");
|
||||
|
||||
QTest::newRow("AES256-CBC Encryption")
|
||||
<< SymmetricCipher::Aes256
|
||||
<< SymmetricCipher::Cbc
|
||||
<< SymmetricCipher::Encrypt
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d");
|
||||
|
||||
QTest::newRow("AES256-CBC Decryption")
|
||||
<< SymmetricCipher::Aes256
|
||||
<< SymmetricCipher::Cbc
|
||||
<< SymmetricCipher::Decrypt
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51");
|
||||
|
||||
QTest::newRow("AES256-CTR Encryption")
|
||||
<< SymmetricCipher::Aes256
|
||||
<< SymmetricCipher::Ctr
|
||||
<< SymmetricCipher::Encrypt
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5");
|
||||
|
||||
QTest::newRow("AES256-CTR Decryption")
|
||||
<< SymmetricCipher::Aes256
|
||||
<< SymmetricCipher::Ctr
|
||||
<< SymmetricCipher::Decrypt
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
|
||||
<< QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51");
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testEncryptionDecryption()
|
||||
{
|
||||
QFETCH(SymmetricCipher::Algorithm, algorithm);
|
||||
QFETCH(SymmetricCipher::Mode, mode);
|
||||
QFETCH(SymmetricCipher::Direction, direction);
|
||||
QFETCH(QByteArray, key);
|
||||
QFETCH(QByteArray, iv);
|
||||
QFETCH(QByteArray, plainText);
|
||||
QFETCH(QByteArray, cipherText);
|
||||
|
||||
QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d");
|
||||
cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
SymmetricCipher cipher(algorithm, mode, direction);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
QCOMPARE(cipher.process(plainText, &ok), cipherText);
|
||||
QVERIFY(ok);
|
||||
|
||||
QBuffer buffer;
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QVERIFY(stream.open(QIODevice::WriteOnly));
|
||||
QVERIFY(stream.reset());
|
||||
if (mode == SymmetricCipher::Cbc) {
|
||||
QBuffer buffer;
|
||||
SymmetricCipherStream stream(&buffer, algorithm, mode, direction);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QVERIFY(stream.open(QIODevice::WriteOnly));
|
||||
QVERIFY(stream.reset());
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(16)), qint64(16));
|
||||
QCOMPARE(buffer.data(), cipherText.left(16));
|
||||
QVERIFY(stream.reset());
|
||||
// make sure padding is written
|
||||
QCOMPARE(buffer.data().size(), 32);
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(16)), qint64(16));
|
||||
QCOMPARE(buffer.data(), cipherText.left(16));
|
||||
QVERIFY(stream.reset());
|
||||
// make sure padding is written
|
||||
QCOMPARE(buffer.data().size(), 32);
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
QVERIFY(buffer.data().isEmpty());
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
QVERIFY(buffer.data().isEmpty());
|
||||
|
||||
QVERIFY(stream.reset());
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
stream.close();
|
||||
QCOMPARE(buffer.data().size(), 16);
|
||||
QVERIFY(stream.reset());
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
stream.close();
|
||||
QCOMPARE(buffer.data().size(), 16);
|
||||
}
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes128CbcDecryption()
|
||||
void TestSymmetricCipher::testAesCbcPadding_data()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d");
|
||||
cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2"));
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
bool ok;
|
||||
QTest::addColumn<QByteArray>("key");
|
||||
QTest::addColumn<QByteArray>("iv");
|
||||
QTest::addColumn<QByteArray>("cipherText");
|
||||
QTest::addColumn<QByteArray>("plainText");
|
||||
QTest::addColumn<QByteArray>("padding");
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
QCOMPARE(cipher.process(cipherText, &ok), plainText);
|
||||
QVERIFY(ok);
|
||||
QTest::newRow("AES128")
|
||||
<< QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538");
|
||||
|
||||
QTest::newRow("AES256")
|
||||
<< QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
|
||||
<< QByteArray::fromHex("000102030405060708090a0b0c0d0e0f")
|
||||
<< QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51")
|
||||
<< QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAesCbcPadding()
|
||||
{
|
||||
QFETCH(QByteArray, key);
|
||||
QFETCH(QByteArray, iv);
|
||||
QFETCH(QByteArray, cipherText);
|
||||
QFETCH(QByteArray, plainText);
|
||||
QFETCH(QByteArray, padding);
|
||||
|
||||
// padded with 16 0x10 bytes
|
||||
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538");
|
||||
QByteArray cipherTextPadded = cipherText + padding;
|
||||
|
||||
QBuffer buffer(&cipherTextPadded);
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
@@ -114,126 +209,48 @@ void TestSymmetricCipher::testAes128CbcDecryption()
|
||||
QCOMPARE(stream.read(100), plainText);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes256CbcEncryption()
|
||||
void TestSymmetricCipher::testInplaceEcb_data()
|
||||
{
|
||||
// http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
QTest::addColumn<QByteArray>("key");
|
||||
QTest::addColumn<QByteArray>("plainText");
|
||||
QTest::addColumn<QByteArray>("cipherText");
|
||||
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
|
||||
QCOMPARE(cipher.process(plainText, &ok), cipherText);
|
||||
QVERIFY(ok);
|
||||
|
||||
QBuffer buffer;
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QVERIFY(stream.open(QIODevice::WriteOnly));
|
||||
QVERIFY(stream.reset());
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(16)), qint64(16));
|
||||
QCOMPARE(buffer.data(), cipherText.left(16));
|
||||
QVERIFY(stream.reset());
|
||||
// make sure padding is written
|
||||
QCOMPARE(buffer.data().size(), 32);
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
QVERIFY(buffer.data().isEmpty());
|
||||
|
||||
QVERIFY(stream.reset());
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
stream.close();
|
||||
QCOMPARE(buffer.data().size(), 16);
|
||||
QTest::newRow("AES128")
|
||||
<< QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c")
|
||||
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a")
|
||||
<< QByteArray::fromHex("3ad77bb40d7a3660a89ecaf32466ef97");
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes256CbcDecryption()
|
||||
void TestSymmetricCipher::testInplaceEcb()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
bool ok;
|
||||
QFETCH(QByteArray, key);
|
||||
QFETCH(QByteArray, plainText);
|
||||
QFETCH(QByteArray, cipherText);
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
SymmetricCipher cipherInPlaceEnc(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipherInPlaceEnc.init(key, QByteArray(16, 0)));
|
||||
QCOMPARE(cipherInPlaceEnc.blockSize(), 16);
|
||||
auto data = QByteArray(plainText);
|
||||
QVERIFY(cipherInPlaceEnc.processInPlace(data));
|
||||
QCOMPARE(data, cipherText);
|
||||
|
||||
QCOMPARE(cipher.process(cipherText, &ok), plainText);
|
||||
QVERIFY(ok);
|
||||
SymmetricCipher cipherInPlaceDec(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipherInPlaceDec.init(key, QByteArray(16, 0)));
|
||||
QCOMPARE(cipherInPlaceDec.blockSize(), 16);
|
||||
QVERIFY(cipherInPlaceDec.processInPlace(data));
|
||||
QCOMPARE(data, plainText);
|
||||
|
||||
// padded with 16 0x16 bytes
|
||||
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
|
||||
QBuffer buffer(&cipherTextPadded);
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QVERIFY(stream.open(QIODevice::ReadOnly));
|
||||
SymmetricCipher cipherInPlaceEnc2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipherInPlaceEnc2.init(key, QByteArray(16, 0)));
|
||||
QCOMPARE(cipherInPlaceEnc2.blockSize(), 16);
|
||||
data = QByteArray(plainText);
|
||||
QVERIFY(cipherInPlaceEnc2.processInPlace(data, 100));
|
||||
|
||||
QCOMPARE(stream.read(10), plainText.left(10));
|
||||
buffer.reset();
|
||||
QVERIFY(stream.reset());
|
||||
QCOMPARE(stream.read(20), plainText.left(20));
|
||||
buffer.reset();
|
||||
QVERIFY(stream.reset());
|
||||
QCOMPARE(stream.read(16), plainText.left(16));
|
||||
buffer.reset();
|
||||
QVERIFY(stream.reset());
|
||||
QCOMPARE(stream.read(100), plainText);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes256CtrEncryption()
|
||||
{
|
||||
// http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray ctr = QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228");
|
||||
cipherText.append(QByteArray::fromHex("f443e3ca4d62b59aca84e990cacaf5c5"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(key, ctr));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
|
||||
QCOMPARE(cipher.process(plainText, &ok), cipherText);
|
||||
QVERIFY(ok);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testAes256CtrDecryption()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray ctr = QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
|
||||
QByteArray cipherText = QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228");
|
||||
cipherText.append(QByteArray::fromHex("f443e3ca4d62b59aca84e990cacaf5c5"));
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipher.init(key, ctr));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
|
||||
QCOMPARE(cipher.process(cipherText, &ok), plainText);
|
||||
QVERIFY(ok);
|
||||
SymmetricCipher cipherInPlaceDec2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt);
|
||||
QVERIFY(cipherInPlaceDec2.init(key, QByteArray(16, 0)));
|
||||
QCOMPARE(cipherInPlaceDec2.blockSize(), 16);
|
||||
QVERIFY(cipherInPlaceDec2.processInPlace(data, 100));
|
||||
QCOMPARE(data, plainText);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testTwofish256CbcEncryption()
|
||||
|
||||
@@ -27,12 +27,13 @@ class TestSymmetricCipher : public QObject
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testAes128CbcEncryption();
|
||||
void testAes128CbcDecryption();
|
||||
void testAes256CbcEncryption();
|
||||
void testAes256CbcDecryption();
|
||||
void testAes256CtrEncryption();
|
||||
void testAes256CtrDecryption();
|
||||
void testAlgorithmToCipher();
|
||||
void testEncryptionDecryption_data();
|
||||
void testEncryptionDecryption();
|
||||
void testAesCbcPadding_data();
|
||||
void testAesCbcPadding();
|
||||
void testInplaceEcb_data();
|
||||
void testInplaceEcb();
|
||||
void testTwofish256CbcEncryption();
|
||||
void testTwofish256CbcDecryption();
|
||||
void testSalsa20();
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
|
||||
add_unit_test(NAME testgui SOURCES TestGui.cpp TemporaryFile.cpp LIBS ${TEST_LIBRARIES})
|
||||
add_unit_test(NAME testgui SOURCES TestGui.cpp LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testguipixmaps SOURCES TestGuiPixmaps.cpp LIBS ${TEST_LIBRARIES})
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Danny Su <contact@dannysu.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "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
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Danny Su <contact@dannysu.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_TEMPORARYFILE_H
|
||||
#define KEEPASSX_TEMPORARYFILE_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
#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
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "TestGui.h"
|
||||
#include "TestGlobal.h"
|
||||
#include "gui/Application.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
@@ -33,12 +34,12 @@
|
||||
#include <QPushButton>
|
||||
#include <QSignalSpy>
|
||||
#include <QSpinBox>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTimer>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
|
||||
#include "config-keepassx-tests.h"
|
||||
#include "core/Bootstrap.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
@@ -59,7 +60,6 @@
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include "gui/FileDialog.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/PasswordEdit.h"
|
||||
#include "gui/SearchWidget.h"
|
||||
@@ -74,22 +74,23 @@
|
||||
#include "gui/masterkey/KeyComponentWidget.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
QTEST_MAIN(TestGui)
|
||||
|
||||
void TestGui::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
Config::createTempFileInstance();
|
||||
// Disable autosave so we can test the modified file indicator
|
||||
config()->set("AutoSaveAfterEveryChange", false);
|
||||
// Enable the tray icon so we can test hiding/restoring the window
|
||||
// Enable the tray icon so we can test hiding/restoring the windowQByteArray
|
||||
config()->set("GUI/ShowTrayIcon", true);
|
||||
// Disable advanced settings mode (activate within individual tests to test advanced settings)
|
||||
config()->set("GUI/AdvancedSettings", false);
|
||||
|
||||
m_mainWindow = new MainWindow();
|
||||
m_mainWindow.reset(new MainWindow());
|
||||
Bootstrap::restoreMainWindowState(*m_mainWindow);
|
||||
m_tabWidget = m_mainWindow->findChild<DatabaseTabWidget*>("tabWidget");
|
||||
m_mainWindow->show();
|
||||
m_mainWindow->activateWindow();
|
||||
Tools::wait(50);
|
||||
|
||||
// Load the NewDatabase.kdbx file into temporary storage
|
||||
QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx"));
|
||||
@@ -101,29 +102,32 @@ void TestGui::initTestCase()
|
||||
// Every test starts with opening the temp database
|
||||
void TestGui::init()
|
||||
{
|
||||
m_dbFile.reset(new QTemporaryFile());
|
||||
// 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();
|
||||
QVERIFY(m_dbFile->open());
|
||||
QCOMPARE(m_dbFile->write(m_dbData), static_cast<qint64>((m_dbData.size())));
|
||||
m_dbFileName = QFileInfo(m_dbFile->fileName()).fileName();
|
||||
m_dbFilePath = m_dbFile->fileName();
|
||||
m_dbFile->close();
|
||||
|
||||
fileDialog()->setNextFileName(m_dbFilePath);
|
||||
triggerAction("actionDatabaseOpen");
|
||||
|
||||
QWidget* databaseOpenWidget = m_mainWindow->findChild<QWidget*>("databaseOpenWidget");
|
||||
QLineEdit* editPassword = databaseOpenWidget->findChild<QLineEdit*>("editPassword");
|
||||
auto* databaseOpenWidget = m_mainWindow->findChild<QWidget*>("databaseOpenWidget");
|
||||
auto* editPassword = databaseOpenWidget->findChild<QLineEdit*>("editPassword");
|
||||
QVERIFY(editPassword);
|
||||
|
||||
QTest::keyClicks(editPassword, "a");
|
||||
QTest::keyClick(editPassword, Qt::Key_Enter);
|
||||
Tools::wait(100);
|
||||
|
||||
QVERIFY(m_tabWidget->currentDatabaseWidget());
|
||||
QTRY_VERIFY(m_tabWidget->currentDatabaseWidget());
|
||||
|
||||
m_dbWidget = m_tabWidget->currentDatabaseWidget();
|
||||
m_db = m_dbWidget->database();
|
||||
|
||||
// make sure window is activated or focus tests may fail
|
||||
m_mainWindow->activateWindow();
|
||||
QApplication::processEvents();
|
||||
}
|
||||
|
||||
// Every test ends with closing the temp database without saving
|
||||
@@ -132,17 +136,21 @@ void TestGui::cleanup()
|
||||
// DO NOT save the database
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
triggerAction("actionDatabaseClose");
|
||||
Tools::wait(100);
|
||||
QApplication::processEvents();
|
||||
|
||||
if (m_db) {
|
||||
delete m_db;
|
||||
}
|
||||
m_db = nullptr;
|
||||
|
||||
if (m_dbWidget) {
|
||||
delete m_dbWidget;
|
||||
}
|
||||
m_dbWidget = nullptr;
|
||||
|
||||
m_dbFile->remove();
|
||||
}
|
||||
|
||||
void TestGui::cleanupTestCase()
|
||||
{
|
||||
m_dbFile->remove();
|
||||
}
|
||||
|
||||
void TestGui::testSettingsDefaultTabOrder()
|
||||
@@ -187,8 +195,9 @@ void TestGui::testCreateDatabase()
|
||||
|
||||
// check key and encryption
|
||||
QCOMPARE(m_db->key()->keys().size(), 2);
|
||||
QCOMPARE(m_db->kdf()->rounds(), 2);
|
||||
QCOMPARE(m_db->kdf()->uuid(), KeePass2::KDF_ARGON2);
|
||||
QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES);
|
||||
QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES256);
|
||||
auto compositeKey = QSharedPointer<CompositeKey>::create();
|
||||
compositeKey->addKey(QSharedPointer<PasswordKey>::create("test"));
|
||||
auto fileKey = QSharedPointer<FileKey>::create();
|
||||
@@ -213,7 +222,40 @@ void TestGui::createDatabaseCallback()
|
||||
QTest::keyClick(wizard, Qt::Key_Enter);
|
||||
QCOMPARE(wizard->currentId(), 1);
|
||||
|
||||
QTest::keyClick(wizard, Qt::Key_Enter);
|
||||
auto decryptionTimeSlider = wizard->currentPage()->findChild<QSlider*>("decryptionTimeSlider");
|
||||
auto algorithmComboBox = wizard->currentPage()->findChild<QComboBox*>("algorithmComboBox");
|
||||
QTRY_VERIFY(decryptionTimeSlider->isVisible());
|
||||
QVERIFY(!algorithmComboBox->isVisible());
|
||||
auto advancedToggle = wizard->currentPage()->findChild<QPushButton*>("advancedSettingsButton");
|
||||
QTest::mouseClick(advancedToggle, Qt::MouseButton::LeftButton);
|
||||
QTRY_VERIFY(!decryptionTimeSlider->isVisible());
|
||||
QVERIFY(algorithmComboBox->isVisible());
|
||||
|
||||
auto rounds = wizard->currentPage()->findChild<QSpinBox*>("transformRoundsSpinBox");
|
||||
QVERIFY(rounds);
|
||||
QVERIFY(rounds->isVisible());
|
||||
QTest::mouseClick(rounds, Qt::MouseButton::LeftButton);
|
||||
QTest::keyClick(rounds, Qt::Key_A, Qt::ControlModifier);
|
||||
QTest::keyClicks(rounds, "2");
|
||||
QTest::keyClick(rounds, Qt::Key_Tab);
|
||||
QTest::keyClick(rounds, Qt::Key_Tab);
|
||||
|
||||
auto memory = wizard->currentPage()->findChild<QSpinBox*>("memorySpinBox");
|
||||
QVERIFY(memory);
|
||||
QVERIFY(memory->isVisible());
|
||||
QTest::mouseClick(memory, Qt::MouseButton::LeftButton);
|
||||
QTest::keyClick(memory, Qt::Key_A, Qt::ControlModifier);
|
||||
QTest::keyClicks(memory, "50");
|
||||
QTest::keyClick(memory, Qt::Key_Tab);
|
||||
|
||||
auto parallelism = wizard->currentPage()->findChild<QSpinBox*>("parallelismSpinBox");
|
||||
QVERIFY(parallelism);
|
||||
QVERIFY(parallelism->isVisible());
|
||||
QTest::mouseClick(parallelism, Qt::MouseButton::LeftButton);
|
||||
QTest::keyClick(parallelism, Qt::Key_A, Qt::ControlModifier);
|
||||
QTest::keyClicks(parallelism, "1");
|
||||
QTest::keyClick(parallelism, Qt::Key_Enter);
|
||||
|
||||
QCOMPARE(wizard->currentId(), 2);
|
||||
|
||||
// enter password
|
||||
@@ -222,7 +264,7 @@ void TestGui::createDatabaseCallback()
|
||||
auto* passwordEdit = passwordWidget->findChild<QLineEdit*>("enterPasswordEdit");
|
||||
auto* passwordRepeatEdit = passwordWidget->findChild<QLineEdit*>("repeatPasswordEdit");
|
||||
QTRY_VERIFY(passwordEdit->isVisible());
|
||||
QVERIFY(passwordEdit->hasFocus());
|
||||
QTRY_VERIFY(passwordEdit->hasFocus());
|
||||
QTest::keyClicks(passwordEdit, "test");
|
||||
QTest::keyClick(passwordEdit, Qt::Key::Key_Tab);
|
||||
QTest::keyClicks(passwordRepeatEdit, "test");
|
||||
@@ -247,25 +289,26 @@ void TestGui::createDatabaseCallback()
|
||||
QCOMPARE(fileCombo->currentText(), QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key"));
|
||||
|
||||
// save database to temporary file
|
||||
TemporaryFile tmpFile;
|
||||
QTemporaryFile tmpFile;
|
||||
QVERIFY(tmpFile.open());
|
||||
tmpFile.close();
|
||||
fileDialog()->setNextFileName(tmpFile.filePath());
|
||||
fileDialog()->setNextFileName(tmpFile.fileName());
|
||||
|
||||
QTest::keyClick(fileCombo, Qt::Key::Key_Enter);
|
||||
tmpFile.remove();
|
||||
}
|
||||
|
||||
void TestGui::testMergeDatabase()
|
||||
{
|
||||
// It is safe to ignore the warning this line produces
|
||||
QSignalSpy dbMergeSpy(m_dbWidget, SIGNAL(databaseMerged(Database*)));
|
||||
QSignalSpy dbMergeSpy(m_dbWidget.data(), 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");
|
||||
auto* databaseOpenMergeWidget = m_mainWindow->findChild<QWidget*>("databaseOpenMergeWidget");
|
||||
auto* editPasswordMerge = databaseOpenMergeWidget->findChild<QLineEdit*>("editPassword");
|
||||
QVERIFY(editPasswordMerge->isVisible());
|
||||
|
||||
m_tabWidget->currentDatabaseWidget()->setCurrentWidget(databaseOpenMergeWidget);
|
||||
@@ -300,11 +343,11 @@ void TestGui::testAutoreloadDatabase()
|
||||
// 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(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile.close();
|
||||
Tools::wait(1500);
|
||||
QVERIFY(m_dbFile->open());
|
||||
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile->close();
|
||||
|
||||
Tools::wait(800);
|
||||
m_db = m_dbWidget->database();
|
||||
|
||||
// the General group contains one entry from the new db data
|
||||
@@ -318,10 +361,10 @@ void TestGui::testAutoreloadDatabase()
|
||||
// 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(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile.close();
|
||||
Tools::wait(1500);
|
||||
m_dbFile->open();
|
||||
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile->close();
|
||||
Tools::wait(800);
|
||||
|
||||
m_db = m_dbWidget->database();
|
||||
|
||||
@@ -342,10 +385,10 @@ void TestGui::testAutoreloadDatabase()
|
||||
// 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(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile.close();
|
||||
Tools::wait(1500);
|
||||
QVERIFY(m_dbFile->open());
|
||||
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(unmodifiedMergeDatabase.size())));
|
||||
m_dbFile->close();
|
||||
Tools::wait(800);
|
||||
|
||||
m_db = m_dbWidget->database();
|
||||
|
||||
@@ -361,17 +404,17 @@ void TestGui::testTabs()
|
||||
|
||||
void TestGui::testEditEntry()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
int editCount = 0;
|
||||
|
||||
// Select the first entry in the database
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
QModelIndex entryItem = entryView->model()->index(0, 1);
|
||||
Entry* entry = entryView->entryFromIndex(entryItem);
|
||||
clickIndex(entryItem, entryView, Qt::LeftButton);
|
||||
|
||||
// Confirm the edit action button is enabled
|
||||
QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
auto* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
QVERIFY(entryEditAction->isEnabled());
|
||||
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
|
||||
QVERIFY(entryEditWidget->isVisible());
|
||||
@@ -380,12 +423,12 @@ void TestGui::testEditEntry()
|
||||
// Edit the first entry ("Sample Entry")
|
||||
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "_test");
|
||||
|
||||
// Apply the edit
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
QCOMPARE(entry->title(), QString("Sample Entry_test"));
|
||||
@@ -410,7 +453,7 @@ void TestGui::testEditEntry()
|
||||
|
||||
// Test protected attributes
|
||||
editEntryWidget->setCurrentPage(1);
|
||||
QPlainTextEdit* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||
auto* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||
QTest::mouseClick(editEntryWidget->findChild<QAbstractButton*>("addAttributeButton"), Qt::LeftButton);
|
||||
QString attrText = "TEST TEXT";
|
||||
QTest::keyClicks(attrTextEdit, attrText);
|
||||
@@ -422,11 +465,11 @@ void TestGui::testEditEntry()
|
||||
editEntryWidget->setCurrentPage(0);
|
||||
|
||||
// Test mismatch passwords
|
||||
QLineEdit* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
QString originalPassword = passwordEdit->text();
|
||||
passwordEdit->setText("newpass");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
MessageWidget* messageWiget = editEntryWidget->findChild<MessageWidget*>("messageWidget");
|
||||
auto* messageWiget = editEntryWidget->findChild<MessageWidget*>("messageWidget");
|
||||
QTRY_VERIFY(messageWiget->isVisible());
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
QCOMPARE(passwordEdit->text(), QString("newpass"));
|
||||
@@ -469,9 +512,9 @@ void TestGui::testSearchEditEntry()
|
||||
// Regression test for Issue #1447 -- Uses example from issue description
|
||||
|
||||
// Find buttons for group creation
|
||||
EditGroupWidget* editGroupWidget = m_dbWidget->findChild<EditGroupWidget*>("editGroupWidget");
|
||||
QLineEdit* nameEdit = editGroupWidget->findChild<QLineEdit*>("editName");
|
||||
QDialogButtonBox* editGroupWidgetButtonBox = editGroupWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editGroupWidget = m_dbWidget->findChild<EditGroupWidget*>("editGroupWidget");
|
||||
auto* nameEdit = editGroupWidget->findChild<QLineEdit*>("editName");
|
||||
auto* editGroupWidgetButtonBox = editGroupWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
|
||||
// Add groups "Good" and "Bad"
|
||||
m_dbWidget->createGroup();
|
||||
@@ -484,11 +527,11 @@ void TestGui::testSearchEditEntry()
|
||||
m_dbWidget->groupView()->setCurrentGroup(m_db->rootGroup());
|
||||
|
||||
// Find buttons for entry creation
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild<QAction*>("actionEntryNew"));
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
|
||||
// Create "Doggy" in "Good"
|
||||
Group* goodGroup = m_dbWidget->currentGroup()->findChildByName(QString("Good"));
|
||||
@@ -501,8 +544,8 @@ void TestGui::testSearchEditEntry()
|
||||
m_dbWidget->groupView()->setCurrentGroup(badGroup);
|
||||
|
||||
// Search for "Doggy" entry
|
||||
SearchWidget* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
|
||||
QLineEdit* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
|
||||
auto* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
|
||||
auto* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
|
||||
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
||||
QTest::keyClicks(searchTextEdit, "Doggy");
|
||||
QTRY_VERIFY(m_dbWidget->isInSearchMode());
|
||||
@@ -518,11 +561,11 @@ void TestGui::testSearchEditEntry()
|
||||
|
||||
void TestGui::testAddEntry()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
// Find the new entry action
|
||||
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
auto* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
QVERIFY(entryNewAction->isEnabled());
|
||||
|
||||
// Find the button associated with the new entry action
|
||||
@@ -535,10 +578,10 @@ void TestGui::testAddEntry()
|
||||
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");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
@@ -551,28 +594,12 @@ void TestGui::testAddEntry()
|
||||
// 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");
|
||||
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
auto* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
|
||||
QTest::keyClicks(passwordEdit, "something 2");
|
||||
QTest::keyClicks(passwordRepeatEdit, "something 2");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
/* All apply tests disabled due to data loss workaround
|
||||
* that disables apply button on new entry creation
|
||||
*
|
||||
// Add entry "something 3" using the apply button then click ok
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
QTest::keyClicks(titleEdit, "something 3");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
// Add entry "something 4" using the apply button then click cancel
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
QTest::keyClicks(titleEdit, "something 4");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Cancel), Qt::LeftButton);
|
||||
*/
|
||||
|
||||
// Add entry "something 5" but click cancel button (does NOT add entry)
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
QTest::keyClicks(titleEdit, "something 5");
|
||||
@@ -587,10 +614,10 @@ void TestGui::testAddEntry()
|
||||
|
||||
void TestGui::testPasswordEntryEntropy()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
|
||||
// Find the new entry action
|
||||
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
auto* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
QVERIFY(entryNewAction->isEnabled());
|
||||
|
||||
// Find the button associated with the new entry action
|
||||
@@ -603,18 +630,18 @@ void TestGui::testPasswordEntryEntropy()
|
||||
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");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
|
||||
// Open the password generator
|
||||
QToolButton* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
|
||||
auto* 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");
|
||||
auto* editNewPassword = editEntryWidget->findChild<QLineEdit*>("editNewPassword");
|
||||
auto* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
|
||||
auto* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
|
||||
|
||||
editNewPassword->setText("");
|
||||
QTest::keyClicks(editNewPassword, "hello");
|
||||
@@ -659,10 +686,10 @@ void TestGui::testPasswordEntryEntropy()
|
||||
|
||||
void TestGui::testDicewareEntryEntropy()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
|
||||
// Find the new entry action
|
||||
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
auto* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
QVERIFY(entryNewAction->isEnabled());
|
||||
|
||||
// Find the button associated with the new entry action
|
||||
@@ -675,27 +702,27 @@ void TestGui::testDicewareEntryEntropy()
|
||||
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");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
|
||||
// Open the password generator
|
||||
QToolButton* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
|
||||
auto* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
|
||||
QTest::mouseClick(generatorButton, Qt::LeftButton);
|
||||
|
||||
// Select Diceware
|
||||
QTabWidget* tabWidget = editEntryWidget->findChild<QTabWidget*>("tabWidget");
|
||||
QWidget* dicewareWidget = editEntryWidget->findChild<QWidget*>("dicewareWidget");
|
||||
auto* tabWidget = editEntryWidget->findChild<QTabWidget*>("tabWidget");
|
||||
auto* dicewareWidget = editEntryWidget->findChild<QWidget*>("dicewareWidget");
|
||||
tabWidget->setCurrentWidget(dicewareWidget);
|
||||
|
||||
QComboBox* comboBoxWordList = dicewareWidget->findChild<QComboBox*>("comboBoxWordList");
|
||||
auto* comboBoxWordList = dicewareWidget->findChild<QComboBox*>("comboBoxWordList");
|
||||
comboBoxWordList->setCurrentText("eff_large.wordlist");
|
||||
QSpinBox* spinBoxWordCount = dicewareWidget->findChild<QSpinBox*>("spinBoxWordCount");
|
||||
auto* spinBoxWordCount = dicewareWidget->findChild<QSpinBox*>("spinBoxWordCount");
|
||||
spinBoxWordCount->setValue(6);
|
||||
|
||||
// Type in some password
|
||||
QLabel* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
|
||||
QLabel* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
|
||||
auto* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
|
||||
auto* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
|
||||
|
||||
QCOMPARE(entropyLabel->text(), QString("Entropy: 77.55 bit"));
|
||||
QCOMPARE(strengthLabel->text(), QString("Password Quality: Good"));
|
||||
@@ -703,8 +730,8 @@ void TestGui::testDicewareEntryEntropy()
|
||||
|
||||
void TestGui::testTotp()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 1);
|
||||
|
||||
@@ -716,36 +743,36 @@ void TestGui::testTotp()
|
||||
|
||||
triggerAction("actionEntrySetupTotp");
|
||||
|
||||
TotpSetupDialog* setupTotpDialog = m_dbWidget->findChild<TotpSetupDialog*>("TotpSetupDialog");
|
||||
auto* setupTotpDialog = m_dbWidget->findChild<TotpSetupDialog*>("TotpSetupDialog");
|
||||
|
||||
Tools::wait(100);
|
||||
QApplication::processEvents();
|
||||
|
||||
QLineEdit* seedEdit = setupTotpDialog->findChild<QLineEdit*>("seedEdit");
|
||||
auto* seedEdit = setupTotpDialog->findChild<QLineEdit*>("seedEdit");
|
||||
|
||||
QString exampleSeed = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
||||
QTest::keyClicks(seedEdit, exampleSeed);
|
||||
|
||||
QDialogButtonBox* setupTotpButtonBox = setupTotpDialog->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* setupTotpButtonBox = setupTotpDialog->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(setupTotpButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
auto* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
||||
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
|
||||
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
|
||||
editEntryWidget->setCurrentPage(1);
|
||||
QPlainTextEdit* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||
auto* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
|
||||
QTest::mouseClick(editEntryWidget->findChild<QAbstractButton*>("revealAttributeButton"), Qt::LeftButton);
|
||||
QCOMPARE(attrTextEdit->toPlainText(), exampleSeed);
|
||||
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
triggerAction("actionEntryTotp");
|
||||
|
||||
TotpDialog* totpDialog = m_dbWidget->findChild<TotpDialog*>("TotpDialog");
|
||||
QLabel* totpLabel = totpDialog->findChild<QLabel*>("totpLabel");
|
||||
auto* totpDialog = m_dbWidget->findChild<TotpDialog*>("TotpDialog");
|
||||
auto* totpLabel = totpDialog->findChild<QLabel*>("totpLabel");
|
||||
|
||||
QCOMPARE(totpLabel->text().replace(" ", ""), entry->totp());
|
||||
}
|
||||
@@ -755,16 +782,16 @@ void TestGui::testSearch()
|
||||
// Add canned entries for consistent testing
|
||||
Q_UNUSED(addCannedEntries());
|
||||
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
|
||||
SearchWidget* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
|
||||
auto* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
|
||||
QVERIFY(searchWidget->isEnabled());
|
||||
QLineEdit* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
|
||||
auto* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
|
||||
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
QVERIFY(entryView->isVisible());
|
||||
|
||||
QAction* clearButton = searchWidget->findChild<QAction*>("clearIcon");
|
||||
auto* clearButton = searchWidget->findChild<QAction*>("clearIcon");
|
||||
QVERIFY(!clearButton->isVisible());
|
||||
|
||||
// Enter search
|
||||
@@ -801,7 +828,7 @@ void TestGui::testSearch()
|
||||
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);
|
||||
QTest::keyClick(m_mainWindow.data(), 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);
|
||||
@@ -862,7 +889,7 @@ void TestGui::testSearch()
|
||||
QCOMPARE(entry->title(), origTitle.append("_edited"));
|
||||
|
||||
// Cancel search, should return to normal view
|
||||
QTest::keyClick(m_mainWindow, Qt::Key_Escape);
|
||||
QTest::keyClick(m_mainWindow.data(), Qt::Key_Escape);
|
||||
QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
}
|
||||
|
||||
@@ -871,10 +898,10 @@ void TestGui::testDeleteEntry()
|
||||
// Add canned entries for consistent testing
|
||||
Q_UNUSED(addCannedEntries());
|
||||
|
||||
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");
|
||||
auto* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete");
|
||||
QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction);
|
||||
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
@@ -934,7 +961,7 @@ void TestGui::testDeleteEntry()
|
||||
|
||||
void TestGui::testCloneEntry()
|
||||
{
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 1);
|
||||
|
||||
@@ -944,8 +971,8 @@ void TestGui::testCloneEntry()
|
||||
|
||||
triggerAction("actionEntryClone");
|
||||
|
||||
CloneDialog* cloneDialog = m_dbWidget->findChild<CloneDialog*>("CloneDialog");
|
||||
QDialogButtonBox* cloneButtonBox = cloneDialog->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* cloneDialog = m_dbWidget->findChild<CloneDialog*>("CloneDialog");
|
||||
auto* cloneButtonBox = cloneDialog->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(cloneButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 2);
|
||||
@@ -956,11 +983,11 @@ void TestGui::testCloneEntry()
|
||||
|
||||
void TestGui::testEntryPlaceholders()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
// Find the new entry action
|
||||
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
auto* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
QVERIFY(entryNewAction->isEnabled());
|
||||
|
||||
// Find the button associated with the new entry action
|
||||
@@ -973,14 +1000,14 @@ void TestGui::testEntryPlaceholders()
|
||||
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");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
QLineEdit* usernameEdit = editEntryWidget->findChild<QLineEdit*>("usernameEdit");
|
||||
QTest::keyClicks(usernameEdit, "john");
|
||||
QLineEdit* urlEdit = editEntryWidget->findChild<QLineEdit*>("urlEdit");
|
||||
QTest::keyClicks(urlEdit, "{TITLE}.{USERNAME}");
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 2);
|
||||
@@ -1000,8 +1027,8 @@ void TestGui::testEntryPlaceholders()
|
||||
|
||||
void TestGui::testDragAndDropEntry()
|
||||
{
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
||||
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
auto* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
||||
QAbstractItemModel* groupModel = groupView->model();
|
||||
|
||||
QModelIndex sourceIndex = entryView->model()->index(0, 1);
|
||||
@@ -1029,11 +1056,7 @@ void TestGui::testDragAndDropGroup()
|
||||
|
||||
// dropping parent on child is supposed to fail
|
||||
dragAndDropGroup(groupModel->index(0, 0, rootIndex),
|
||||
groupModel->index(0, 0, groupModel->index(0, 0, rootIndex)),
|
||||
-1,
|
||||
false,
|
||||
"NewDatabase",
|
||||
0);
|
||||
groupModel->index(0, 0, groupModel->index(0, 0, rootIndex)), -1, false, "NewDatabase", 0);
|
||||
|
||||
dragAndDropGroup(groupModel->index(1, 0, rootIndex), rootIndex, 0, true, "NewDatabase", 0);
|
||||
|
||||
@@ -1063,6 +1086,7 @@ void TestGui::testSaveAs()
|
||||
|
||||
fileInfo.refresh();
|
||||
QCOMPARE(fileInfo.lastModified(), lastModified);
|
||||
tmpFile.remove();
|
||||
}
|
||||
|
||||
void TestGui::testSave()
|
||||
@@ -1123,7 +1147,7 @@ void TestGui::testKeePass1Import()
|
||||
// Close the KeePass1 Database
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
triggerAction("actionDatabaseClose");
|
||||
Tools::wait(100);
|
||||
QApplication::processEvents();
|
||||
}
|
||||
|
||||
void TestGui::testDatabaseLocking()
|
||||
@@ -1135,13 +1159,13 @@ void TestGui::testDatabaseLocking()
|
||||
|
||||
QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName + " [locked]");
|
||||
|
||||
QAction* actionDatabaseMerge = m_mainWindow->findChild<QAction*>("actionDatabaseMerge", Qt::FindChildrenRecursively);
|
||||
auto* actionDatabaseMerge = m_mainWindow->findChild<QAction*>("actionDatabaseMerge", Qt::FindChildrenRecursively);
|
||||
QCOMPARE(actionDatabaseMerge->isEnabled(), false);
|
||||
QAction* actionDatabaseSave = m_mainWindow->findChild<QAction*>("actionDatabaseSave", Qt::FindChildrenRecursively);
|
||||
auto* actionDatabaseSave = m_mainWindow->findChild<QAction*>("actionDatabaseSave", Qt::FindChildrenRecursively);
|
||||
QCOMPARE(actionDatabaseSave->isEnabled(), false);
|
||||
|
||||
QWidget* dbWidget = m_tabWidget->currentDatabaseWidget();
|
||||
QWidget* unlockDatabaseWidget = dbWidget->findChild<QWidget*>("unlockDatabaseWidget");
|
||||
auto* unlockDatabaseWidget = dbWidget->findChild<QWidget*>("unlockDatabaseWidget");
|
||||
QWidget* editPassword = unlockDatabaseWidget->findChild<QLineEdit*>("editPassword");
|
||||
QVERIFY(editPassword);
|
||||
|
||||
@@ -1162,11 +1186,11 @@ void TestGui::testDragAndDropKdbxFiles()
|
||||
QMimeData badMimeData;
|
||||
badMimeData.setUrls({QUrl::fromLocalFile(badDatabaseFilePath)});
|
||||
QDragEnterEvent badDragEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &badDragEvent);
|
||||
qApp->notify(m_mainWindow.data(), &badDragEvent);
|
||||
QCOMPARE(badDragEvent.isAccepted(), false);
|
||||
|
||||
QDropEvent badDropEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &badDropEvent);
|
||||
qApp->notify(m_mainWindow.data(), &badDropEvent);
|
||||
QCOMPARE(badDropEvent.isAccepted(), false);
|
||||
|
||||
QCOMPARE(m_tabWidget->count(), openedDatabasesCount);
|
||||
@@ -1175,20 +1199,19 @@ void TestGui::testDragAndDropKdbxFiles()
|
||||
QMimeData goodMimeData;
|
||||
goodMimeData.setUrls({QUrl::fromLocalFile(goodDatabaseFilePath)});
|
||||
QDragEnterEvent goodDragEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &goodDragEvent);
|
||||
qApp->notify(m_mainWindow.data(), &goodDragEvent);
|
||||
QCOMPARE(goodDragEvent.isAccepted(), true);
|
||||
|
||||
QDropEvent goodDropEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &goodDropEvent);
|
||||
qApp->notify(m_mainWindow.data(), &goodDropEvent);
|
||||
QCOMPARE(goodDropEvent.isAccepted(), true);
|
||||
|
||||
QCOMPARE(m_tabWidget->count(), openedDatabasesCount + 1);
|
||||
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
triggerAction("actionDatabaseClose");
|
||||
Tools::wait(100);
|
||||
|
||||
QCOMPARE(m_tabWidget->count(), openedDatabasesCount);
|
||||
QTRY_COMPARE(m_tabWidget->count(), openedDatabasesCount);
|
||||
}
|
||||
|
||||
void TestGui::testTrayRestoreHide()
|
||||
@@ -1197,29 +1220,20 @@ void TestGui::testTrayRestoreHide()
|
||||
QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test...");
|
||||
}
|
||||
|
||||
QSystemTrayIcon* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>();
|
||||
auto* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>();
|
||||
QVERIFY(m_mainWindow->isVisible());
|
||||
|
||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||
Tools::wait(100);
|
||||
QVERIFY(!m_mainWindow->isVisible());
|
||||
QTRY_VERIFY(!m_mainWindow->isVisible());
|
||||
|
||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||
Tools::wait(100);
|
||||
QVERIFY(m_mainWindow->isVisible());
|
||||
QTRY_VERIFY(m_mainWindow->isVisible());
|
||||
|
||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||
Tools::wait(100);
|
||||
QVERIFY(!m_mainWindow->isVisible());
|
||||
QTRY_VERIFY(!m_mainWindow->isVisible());
|
||||
|
||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||
Tools::wait(100);
|
||||
QVERIFY(m_mainWindow->isVisible());
|
||||
}
|
||||
|
||||
void TestGui::cleanupTestCase()
|
||||
{
|
||||
delete m_mainWindow;
|
||||
QTRY_VERIFY(m_mainWindow->isVisible());
|
||||
}
|
||||
|
||||
int TestGui::addCannedEntries()
|
||||
@@ -1227,17 +1241,17 @@ int TestGui::addCannedEntries()
|
||||
int entries_added = 0;
|
||||
|
||||
// Find buttons
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild<QAction*>("actionEntryNew"));
|
||||
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
QLineEdit* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
QLineEdit* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
|
||||
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||
auto* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
|
||||
|
||||
// Add entry "test" and confirm added
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
QTest::keyClicks(titleEdit, "test");
|
||||
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
++entries_added;
|
||||
|
||||
@@ -1274,7 +1288,7 @@ void TestGui::checkDatabase(QString dbFileName)
|
||||
|
||||
void TestGui::triggerAction(const QString& name)
|
||||
{
|
||||
QAction* action = m_mainWindow->findChild<QAction*>(name);
|
||||
auto* action = m_mainWindow->findChild<QAction*>(name);
|
||||
QVERIFY(action);
|
||||
QVERIFY(action->isEnabled());
|
||||
action->trigger();
|
||||
@@ -1312,5 +1326,3 @@ void TestGui::clickIndex(const QModelIndex& index,
|
||||
{
|
||||
QTest::mouseClick(view->viewport(), button, stateKey, view->visualRect(index).center());
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestGui)
|
||||
|
||||
@@ -19,17 +19,18 @@
|
||||
#ifndef KEEPASSX_TESTGUI_H
|
||||
#define KEEPASSX_TESTGUI_H
|
||||
|
||||
#include "TemporaryFile.h"
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QScopedPointer>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
class Database;
|
||||
class DatabaseTabWidget;
|
||||
class DatabaseWidget;
|
||||
class QAbstractItemView;
|
||||
class MainWindow;
|
||||
|
||||
class TestGui : public QObject
|
||||
{
|
||||
@@ -84,12 +85,12 @@ private:
|
||||
Qt::MouseButton button,
|
||||
Qt::KeyboardModifiers stateKey = 0);
|
||||
|
||||
QPointer<MainWindow> m_mainWindow;
|
||||
QScopedPointer<MainWindow> m_mainWindow;
|
||||
QPointer<DatabaseTabWidget> m_tabWidget;
|
||||
QPointer<DatabaseWidget> m_dbWidget;
|
||||
QPointer<Database> m_db;
|
||||
QByteArray m_dbData;
|
||||
TemporaryFile m_dbFile;
|
||||
QScopedPointer<QTemporaryFile> m_dbFile;
|
||||
QString m_dbFileName;
|
||||
QString m_dbFilePath;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user