diff options
author | Jonathan White <support@dmapps.us> | 2020-05-11 14:31:29 +0300 |
---|---|---|
committer | Jonathan White <support@dmapps.us> | 2020-05-14 23:33:25 +0300 |
commit | 485852c9dbc748055e1567f5bdad997905309c7e (patch) | |
tree | 991a13b549c8af667e63a027ef9cfc140221f3a0 /tests | |
parent | 612f8d2e5bb340efd881e092ff3d85aca79de122 (diff) |
CLI: Use stderr for password prompt
Fixes #3398.
Convert to QTextStream for all CLI IO and greatly improve CLI tests
* Completely overhaul CLI tests to be much more streamlined and easy to read. Removed unnecessary code blocks by using existing functions.
Co-authored-by: Emma Brooks <me@pluvano.com>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/TestCli.cpp | 1920 | ||||
-rw-r--r-- | tests/TestCli.h | 34 |
2 files changed, 767 insertions, 1187 deletions
diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index 23b7a007e..10bde7a08 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -23,12 +23,6 @@ #include "core/Global.h" #include "core/Tools.h" #include "crypto/Crypto.h" -#include "format/Kdbx3Reader.h" -#include "format/Kdbx3Writer.h" -#include "format/Kdbx4Reader.h" -#include "format/Kdbx4Writer.h" -#include "format/KdbxXmlReader.h" -#include "format/KeePass2.h" #include "keys/drivers/YubiKey.h" #include "cli/Add.h" @@ -56,14 +50,9 @@ #include "cli/Utils.h" #include <QClipboard> -#include <QFile> #include <QFuture> -#include <QSet> -#include <QTextStream> #include <QtConcurrent> -#include <cstdio> - QTEST_MAIN(TestCli) QSharedPointer<Database> globalCurrentDatabase; @@ -75,85 +64,48 @@ void TestCli::initTestCase() 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(); - - // Load the NewDatabase2.kdbx file into temporary storage - QFile sourceDbFile2(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase2.kdbx")); - QVERIFY(sourceDbFile2.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceDbFile2, m_dbData2)); - sourceDbFile2.close(); - - // Load the KeyFileProtected.kdbx file into temporary storage - QFile sourceDbFile3(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtected.kdbx")); - QVERIFY(sourceDbFile3.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceDbFile3, m_keyFileProtectedDbData)); - sourceDbFile3.close(); - - // Load the KeyFileProtectedNoPassword.kdbx file into temporary storage - QFile sourceDbFile4(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtectedNoPassword.kdbx")); - QVERIFY(sourceDbFile4.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceDbFile4, m_keyFileProtectedNoPasswordDbData)); - sourceDbFile4.close(); - - QFile sourceDbFileYubiKeyProtected(QString(KEEPASSX_TEST_DATA_DIR).append("/YubiKeyProtectedPasswords.kdbx")); - QVERIFY(sourceDbFileYubiKeyProtected.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceDbFileYubiKeyProtected, m_yubiKeyProtectedDbData)); - sourceDbFileYubiKeyProtected.close(); - - // Load the NewDatabase.xml file into temporary storage - QFile sourceXmlFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml")); - QVERIFY(sourceXmlFile.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceXmlFile, m_xmlData)); - sourceXmlFile.close(); + auto fd = new QFile(); +#ifdef Q_OS_WIN + fd->open(fopen("nul", "w"), QIODevice::WriteOnly); +#else + fd->open(fopen("/dev/null", "w"), QIODevice::WriteOnly); +#endif + Utils::DEVNULL.setDevice(fd); } void TestCli::init() { + const auto file = QString(KEEPASSX_TEST_DATA_DIR).append("/%1"); + m_dbFile.reset(new TemporaryFile()); - m_dbFile->open(); - m_dbFile->write(m_dbData); - m_dbFile->close(); + m_dbFile->copyFromFile(file.arg("NewDatabase.kdbx")); m_dbFile2.reset(new TemporaryFile()); - m_dbFile2->open(); - m_dbFile2->write(m_dbData2); - m_dbFile2->close(); + m_dbFile2->copyFromFile(file.arg("NewDatabase2.kdbx")); m_xmlFile.reset(new TemporaryFile()); - m_xmlFile->open(); - m_xmlFile->write(m_xmlData); - m_xmlFile->close(); + m_xmlFile->copyFromFile(file.arg("NewDatabase.xml")); m_keyFileProtectedDbFile.reset(new TemporaryFile()); - m_keyFileProtectedDbFile->open(); - m_keyFileProtectedDbFile->write(m_keyFileProtectedDbData); - m_keyFileProtectedDbFile->close(); + m_keyFileProtectedDbFile->copyFromFile(file.arg("KeyFileProtected.kdbx")); m_keyFileProtectedNoPasswordDbFile.reset(new TemporaryFile()); - m_keyFileProtectedNoPasswordDbFile->open(); - m_keyFileProtectedNoPasswordDbFile->write(m_keyFileProtectedNoPasswordDbData); - m_keyFileProtectedNoPasswordDbFile->close(); + m_keyFileProtectedNoPasswordDbFile->copyFromFile(file.arg("KeyFileProtectedNoPassword.kdbx")); m_yubiKeyProtectedDbFile.reset(new TemporaryFile()); - m_yubiKeyProtectedDbFile->open(); - m_yubiKeyProtectedDbFile->write(m_yubiKeyProtectedDbData); - m_yubiKeyProtectedDbFile->close(); + m_yubiKeyProtectedDbFile->copyFromFile(file.arg("YubiKeyProtectedPasswords.kdbx")); - m_stdinFile.reset(new TemporaryFile()); - m_stdinFile->open(); - Utils::STDIN = fdopen(m_stdinFile->handle(), "r+"); + m_stdout.reset(new QBuffer()); + m_stdout->open(QIODevice::ReadWrite); + Utils::STDOUT.setDevice(m_stdout.data()); - m_stdoutFile.reset(new TemporaryFile()); - m_stdoutFile->open(); - Utils::STDOUT = fdopen(m_stdoutFile->handle(), "r+"); + m_stderr.reset(new QBuffer()); + m_stderr->open(QIODevice::ReadWrite); + Utils::STDERR.setDevice(m_stderr.data()); - m_stderrFile.reset(new TemporaryFile()); - m_stderrFile->open(); - Utils::STDERR = fdopen(m_stderrFile->handle(), "r+"); + m_stdin.reset(new QBuffer()); + m_stdin->open(QIODevice::ReadWrite); + Utils::STDIN.setDevice(m_stdin.data()); } void TestCli::cleanup() @@ -164,26 +116,89 @@ void TestCli::cleanup() m_keyFileProtectedNoPasswordDbFile.reset(); m_yubiKeyProtectedDbFile.reset(); - m_stdinFile.reset(); - Utils::STDIN = stdin; + Utils::STDOUT.setDevice(nullptr); + Utils::STDERR.setDevice(nullptr); + Utils::STDIN.setDevice(nullptr); +} + +QSharedPointer<Database> TestCli::readDatabase(const QString& filename, const QString& pw, const QString& keyfile) +{ + auto db = QSharedPointer<Database>::create(); + auto key = QSharedPointer<CompositeKey>::create(); + + if (filename.isEmpty()) { + // Open the default test database + key->addKey(QSharedPointer<PasswordKey>::create("a")); + if (!db->open(m_dbFile->fileName(), key)) { + return {}; + } + } else { + // Open the specified database file using supplied credentials + key->addKey(QSharedPointer<PasswordKey>::create(pw)); + if (!keyfile.isEmpty()) { + auto filekey = QSharedPointer<FileKey>::create(); + filekey->load(keyfile); + key->addKey(filekey); + } + + if (!db->open(filename, key)) { + return {}; + } + } + + return db; +} + +int TestCli::execCmd(Command& cmd, const QStringList& args) const +{ + // Move to end of stream + m_stdout->readAll(); + m_stderr->readAll(); + + // Record stream position + auto outPos = m_stdout->pos(); + auto errPos = m_stderr->pos(); + + // Execute command + int ret = cmd.execute(args); - m_stdoutFile.reset(); - Utils::STDOUT = stdout; + // Move back to recorded position + m_stdout->seek(outPos); + m_stderr->seek(errPos); - m_stderrFile.reset(); - Utils::STDERR = stderr; + // Skip over blank lines + QByteArray newline("\n"); + while (m_stdout->peek(1) == newline) { + m_stdout->readLine(); + } + while (m_stderr->peek(1) == newline) { + m_stdout->readLine(); + } + + return ret; } -void TestCli::cleanupTestCase() +bool TestCli::isTotp(const QString& value) { + static const QRegularExpression totp("^\\d{6}$"); + return totp.match(value.trimmed()).hasMatch(); } -QSharedPointer<Database> TestCli::readTestDatabase() const +void TestCli::setInput(const QString& input) { - Utils::Test::setNextPassword("a"); - auto db = QSharedPointer<Database>(Utils::unlockDatabase(m_dbFile->fileName(), true, "", "", Utils::STDOUT)); - m_stdoutFile->seek(ftell(Utils::STDOUT)); // re-synchronize handles - return db; + setInput(QStringList(input)); +} + +void TestCli::setInput(const QStringList& input) +{ + auto ba = input.join("\n").toLatin1(); + // Always end in newline + if (!ba.endsWith("\n")) { + ba.append("\n"); + } + auto pos = m_stdin->pos(); + m_stdin->write(ba); + m_stdin->seek(pos); } void TestCli::testBatchCommands() @@ -250,24 +265,23 @@ void TestCli::testAdd() QVERIFY(!addCmd.name.isEmpty()); QVERIFY(addCmd.getDescriptionLine().contains(addCmd.name)); - Utils::Test::setNextPassword("a"); - addCmd.execute({"add", - "-u", - "newuser", - "--url", - "https://example.com/", - "-g", - "-L", - "20", - m_dbFile->fileName(), - "/newuser-entry"}); - m_stderrFile->reset(); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry.\n")); - - auto db = readTestDatabase(); + setInput("a"); + execCmd(addCmd, + {"add", + "-u", + "newuser", + "--url", + "https://example.com/", + "-g", + "-L", + "20", + m_dbFile->fileName(), + "/newuser-entry"}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QVERIFY(m_stdout->readAll().contains("Successfully added entry newuser-entry.")); + + auto db = readDatabase(); auto* entry = db->rootGroup()->findEntryByPath("/newuser-entry"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); @@ -275,33 +289,21 @@ void TestCli::testAdd() QCOMPARE(entry->password().size(), 20); // Quiet option - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addCmd.execute({"add", "-q", "-u", "newuser", "-g", "-L", "20", m_dbFile->fileName(), "/newentry-quiet"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - db = readTestDatabase(); + setInput("a"); + execCmd(addCmd, {"add", "-q", "-u", "newuser", "-g", "-L", "20", m_dbFile->fileName(), "/newentry-quiet"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newentry-quiet"); QVERIFY(entry); QCOMPARE(entry->password().size(), 20); - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - Utils::Test::setNextPassword("newpassword"); - addCmd.execute( - {"add", "-u", "newuser2", "--url", "https://example.net/", "-p", m_dbFile->fileName(), "/newuser-entry2"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip password prompt - m_stdoutFile->readLine(); // skip password input - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry2.\n")); - - db = readTestDatabase(); + setInput({"a", "newpassword"}); + execCmd(addCmd, + {"add", "-u", "newuser2", "--url", "https://example.net/", "-p", m_dbFile->fileName(), "/newuser-entry2"}); + QVERIFY(m_stdout->readAll().contains("Successfully added entry newuser-entry2.")); + + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newuser-entry2"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser2")); @@ -309,17 +311,11 @@ void TestCli::testAdd() QCOMPARE(entry->password(), QString("newpassword")); // Password generation options - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addCmd.execute({"add", "-u", "newuser3", "-g", "-L", "34", m_dbFile->fileName(), "/newuser-entry3"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry3.\n")); - - db = readTestDatabase(); + setInput("a"); + execCmd(addCmd, {"add", "-u", "newuser3", "-g", "-L", "34", m_dbFile->fileName(), "/newuser-entry3"}); + QVERIFY(m_stdout->readAll().contains("Successfully added entry newuser-entry3.")); + + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newuser-entry3"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser3")); @@ -327,29 +323,24 @@ void TestCli::testAdd() QRegularExpression defaultPasswordClassesRegex("^[a-zA-Z0-9]+$"); QVERIFY(defaultPasswordClassesRegex.match(entry->password()).hasMatch()); - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addCmd.execute({"add", - "-u", - "newuser4", - "-g", - "-L", - "20", - "--every-group", - "-s", - "-n", - "-U", - "-l", - m_dbFile->fileName(), - "/newuser-entry4"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry4.\n")); - - db = readTestDatabase(); + setInput("a"); + execCmd(addCmd, + {"add", + "-u", + "newuser4", + "-g", + "-L", + "20", + "--every-group", + "-s", + "-n", + "-U", + "-l", + m_dbFile->fileName(), + "/newuser-entry4"}); + QVERIFY(m_stdout->readAll().contains("Successfully added entry newuser-entry4.")); + + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newuser-entry4"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser4")); @@ -363,81 +354,44 @@ void TestCli::testAddGroup() QVERIFY(!addGroupCmd.name.isEmpty()); QVERIFY(addGroupCmd.getDescriptionLine().contains(addGroupCmd.name)); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/new_group"}); - m_stderrFile->reset(); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added group new_group.\n")); + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/new_group"}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully added group new_group.\n")); - auto db = readTestDatabase(); + auto db = readDatabase(); auto* group = db->rootGroup()->findGroupByPath("new_group"); QVERIFY(group); QCOMPARE(group->name(), QString("new_group")); // Trying to add the same group should fail. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/new_group"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Group /new_group already exists!\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/new_group"}); + QVERIFY(m_stderr->readAll().contains("Group /new_group already exists!")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should be able to add groups down the tree. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/new_group/newer_group"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added group newer_group.\n")); - - db = readTestDatabase(); + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/new_group/newer_group"}); + QVERIFY(m_stdout->readAll().contains("Successfully added group newer_group.")); + + db = readDatabase(); group = db->rootGroup()->findGroupByPath("new_group/newer_group"); QVERIFY(group); QCOMPARE(group->name(), QString("newer_group")); // Should fail if the path is invalid. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/invalid_group/newer_group"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Group /invalid_group not found.\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/invalid_group/newer_group"}); + QVERIFY(m_stderr->readAll().contains("Group /invalid_group not found.")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should fail to add the root group. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Group / already exists!\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); -} - -bool isTOTP(const QString& value) -{ - QString val = value.trimmed(); - if (val.length() < 5 || val.length() > 6) { - return false; - } - for (int i = 0; i < val.length(); ++i) { - if (!value[i].isDigit()) { - return false; - } - } - return true; + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/"}); + QVERIFY(m_stderr->readAll().contains("Group / already exists!")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testAnalyze() @@ -448,12 +402,13 @@ void TestCli::testAnalyze() const QString hibpPath = QString(KEEPASSX_TEST_DATA_DIR).append("/hibp.txt"); - Utils::Test::setNextPassword("a"); - analyzeCmd.execute({"analyze", "--hibp", hibpPath, m_dbFile->fileName()}); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - auto output = m_stdoutFile->readAll(); - QVERIFY(output.contains("Sample Entry") && output.contains("123")); + setInput("a"); + execCmd(analyzeCmd, {"analyze", "--hibp", hibpPath, m_dbFile->fileName()}); + auto output = m_stdout->readAll(); + QVERIFY(output.contains("Sample Entry")); + QVERIFY(output.contains("123")); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); } void TestCli::testClip() @@ -466,12 +421,9 @@ void TestCli::testClip() QVERIFY(clipCmd.getDescriptionLine().contains(clipCmd.name)); // Password - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry"}); - - m_stderrFile->reset(); - m_stdoutFile->reset(); - QString errorOutput(m_stderrFile->readAll()); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry"}); + QString errorOutput(m_stderr->readAll()); if (QProcessEnvironment::systemEnvironment().contains("WAYLAND_DISPLAY")) { QSKIP("Clip test skipped due to QClipboard and Wayland issues"); @@ -482,32 +434,31 @@ void TestCli::testClip() QSKIP("Clip test skipped due to missing clipboard tool"); } + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); QTRY_COMPARE(clipboard->text(), QString("Password")); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Entry's \"Password\" attribute copied to the clipboard!\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Entry's \"Password\" attribute copied to the clipboard!\n")); // Quiet option - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry", "-q"}); - m_stdoutFile->seek(pos); - // Output should be empty when quiet option is set. - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry", "-q"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); QTRY_COMPARE(clipboard->text(), QString("Password")); // Username - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry", "-a", "username"}); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry", "-a", "username"}); QTRY_COMPARE(clipboard->text(), QString("User Name")); // TOTP - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry", "--totp"}); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry", "--totp"}); - QTRY_VERIFY(isTOTP(clipboard->text())); + QTRY_VERIFY(isTotp(clipboard->text())); // Password with timeout - Utils::Test::setNextPassword("a"); + setInput("a"); // clang-format off QFuture<void> future = QtConcurrent::run(&clipCmd, static_cast<int(Clip::*)(const QStringList&)>(&DatabaseCommand::execute), @@ -520,47 +471,36 @@ void TestCli::testClip() future.waitForFinished(); // TOTP with timeout - Utils::Test::setNextPassword("a"); + setInput("a"); future = QtConcurrent::run(&clipCmd, static_cast<int (Clip::*)(const QStringList&)>(&DatabaseCommand::execute), QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1", "-t"}); - QTRY_VERIFY(isTOTP(clipboard->text())); + QTRY_VERIFY(isTotp(clipboard->text())); QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString(""), 2000); future.waitForFinished(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "--totp", "/Sample Entry", "0"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Invalid timeout value 0.\n")); - - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "--totp", "/Sample Entry", "bleuh"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Invalid timeout value bleuh.\n")); - - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile2->fileName(), "--totp", "/Sample Entry"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry with path /Sample Entry has no TOTP set up.\n")); - - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "-a", "TESTAttribute1", "/Sample Entry"}); - m_stderrFile->seek(posErr); - QCOMPARE( - m_stderrFile->readAll(), - QByteArray("ERROR: attribute TESTAttribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); - - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile2->fileName(), "--attribute", "Username", "--totp", "/Sample Entry"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("ERROR: Please specify one of --attribute or --totp, not both.\n")); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "--totp", "/Sample Entry", "0"}); + QVERIFY(m_stderr->readAll().contains("Invalid timeout value 0.\n")); + + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "--totp", "/Sample Entry", "bleuh"}); + QVERIFY(m_stderr->readAll().contains("Invalid timeout value bleuh.\n")); + + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile2->fileName(), "--totp", "/Sample Entry"}); + QVERIFY(m_stderr->readAll().contains("Entry with path /Sample Entry has no TOTP set up.\n")); + + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "-a", "TESTAttribute1", "/Sample Entry"}); + QVERIFY(m_stderr->readAll().contains( + "ERROR: attribute TESTAttribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); + + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile2->fileName(), "--attribute", "Username", "--totp", "/Sample Entry"}); + QVERIFY(m_stderr->readAll().contains("ERROR: Please specify one of --attribute or --totp, not both.\n")); } void TestCli::testCreate() @@ -574,165 +514,110 @@ void TestCli::testCreate() // Testing password option, password mismatch dbFilename = testDir->path() + "/testCreate_pw.kdbx"; - Utils::Test::setNextPassword("a"); - Utils::Test::setNextPassword("b"); - createCmd.execute({"db-create", dbFilename, "-p"}); - m_stderrFile->reset(); - m_stdoutFile->reset(); + setInput({"a", "b"}); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Error: Passwords do not match.\n")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Failed to set database password.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Error: Passwords do not match.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Failed to set database password.\n")); // Testing password option - Utils::Test::setNextPassword("a", true); - qint64 pos = m_stdoutFile->pos(); - qint64 errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n")); - - Utils::Test::setNextPassword("a"); - auto db = Utils::unlockDatabase(dbFilename, true, "", "", Utils::DEVNULL); + setInput({"a", "a"}); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); + + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully created new database.\n")); + + auto db = readDatabase(dbFilename, "a"); QVERIFY(db); // Testing with empty password (deny it) dbFilename = testDir->path() + "/testCreate_blankpw.kdbx"; - Utils::Test::setNextPassword(""); - m_stdinFile->reset(); - m_stdinFile->write("n\n"); - m_stdinFile->reset(); - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QVERIFY(m_stdoutFile->readLine().contains("empty password")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Failed to set database password.\n")); + setInput({"", "n"}); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); + + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QVERIFY(m_stderr->readLine().contains("empty password")); + QCOMPARE(m_stderr->readLine(), QByteArray("Failed to set database password.\n")); // Testing with empty password (accept it) - Utils::Test::setNextPassword(""); - m_stdinFile->reset(); - m_stdinFile->write("y\n"); - m_stdinFile->reset(); - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QVERIFY(m_stdoutFile->readLine().contains("empty password")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n")); - - Utils::Test::setNextPassword(""); - db = Utils::unlockDatabase(dbFilename, true, "", "", Utils::DEVNULL); + setInput({"", "y"}); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); + + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QVERIFY(m_stderr->readLine().contains("empty password")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully created new database.\n")); + + db = readDatabase(dbFilename, ""); QVERIFY(db); // Should refuse to create the database if it already exists. - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", "-p", dbFilename}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); // Output should be empty when there is an error. - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray()); QString errorMessage = QString("File " + dbFilename + " already exists.\n"); - QCOMPARE(m_stderrFile->readAll(), errorMessage.toUtf8()); + QCOMPARE(m_stderr->readAll(), errorMessage.toUtf8()); // Should refuse to create without any key provided. dbFilename = testDir->path() + "/testCreate_key.kdbx"; - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("No key is set. Aborting database creation.\n")); + execCmd(createCmd, {"db-create", dbFilename}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QCOMPARE(m_stderr->readLine(), QByteArray("No key is set. Aborting database creation.\n")); // Testing with keyfile creation dbFilename = testDir->path() + "/testCreate_key2.kdbx"; QString keyfilePath = testDir->path() + "/keyfile.txt"; - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - Utils::Test::setNextPassword("a", true); - createCmd.execute({"db-create", dbFilename, "-p", "-k", keyfilePath}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n")); - - Utils::Test::setNextPassword("a"); - db = Utils::unlockDatabase(dbFilename, true, keyfilePath, "", Utils::DEVNULL); + setInput({"a", "a"}); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-k", keyfilePath}); + + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully created new database.\n")); + + db = readDatabase(dbFilename, "a", keyfilePath); QVERIFY(db); // Testing with existing keyfile dbFilename = testDir->path() + "/testCreate_key3.kdbx"; - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - Utils::Test::setNextPassword("a", true); - createCmd.execute({"db-create", dbFilename, "-p", "-k", keyfilePath}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n")); - - Utils::Test::setNextPassword("a"); - db = Utils::unlockDatabase(dbFilename, true, keyfilePath, "", Utils::DEVNULL); + setInput({"a", "a"}); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-k", keyfilePath}); + + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully created new database.\n")); + + db = readDatabase(dbFilename, "a", keyfilePath); QVERIFY(db); // Invalid decryption time (format). dbFilename = testDir->path() + "/testCreate_time.kdbx"; - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p", "-t", "NAN"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-t", "NAN"}); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Invalid decryption time NAN.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QCOMPARE(m_stderr->readAll(), QByteArray("Invalid decryption time NAN.\n")); // Invalid decryption time (range). - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p", "-t", "10"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-t", "10"}); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QVERIFY(m_stderrFile->readAll().contains(QByteArray("Target decryption time must be between"))); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains(QByteArray("Target decryption time must be between"))); int encryptionTime = 500; // Custom encryption time - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - Utils::Test::setNextPassword("a", true); + setInput({"a", "a"}); int epochBefore = QDateTime::currentMSecsSinceEpoch(); - createCmd.execute({"db-create", dbFilename, "-p", "-t", QString::number(encryptionTime)}); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-t", QString::number(encryptionTime)}); // Removing 100ms to make sure we account for changes in computation time. QVERIFY(QDateTime::currentMSecsSinceEpoch() > (epochBefore + encryptionTime - 100)); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Benchmarking key derivation function for 500ms delay.\n")); - QVERIFY(m_stdoutFile->readLine().contains(QByteArray("rounds for key derivation function.\n"))); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Benchmarking key derivation function for 500ms delay.\n")); + QVERIFY(m_stdout->readLine().contains(QByteArray("rounds for key derivation function.\n"))); - Utils::Test::setNextPassword("a"); - db = Utils::unlockDatabase(dbFilename, true, "", "", Utils::DEVNULL); + db = readDatabase(dbFilename, "a"); QVERIFY(db); } @@ -742,33 +627,27 @@ void TestCli::testInfo() QVERIFY(!infoCmd.name.isEmpty()); QVERIFY(infoCmd.getDescriptionLine().contains(infoCmd.name)); - Utils::Test::setNextPassword("a"); - infoCmd.execute({"db-info", m_dbFile->fileName()}); - m_stdoutFile->reset(); - m_stderrFile->reset(); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QVERIFY(m_stdoutFile->readLine().contains(QByteArray("UUID: "))); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Name: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Description: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Cipher: AES 256-bit\n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("KDF: AES (6000 rounds)\n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Recycle bin is enabled.\n")); + setInput("a"); + execCmd(infoCmd, {"db-info", m_dbFile->fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QVERIFY(m_stdout->readLine().contains(QByteArray("UUID: "))); + QCOMPARE(m_stdout->readLine(), QByteArray("Name: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Description: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Cipher: AES 256-bit\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("KDF: AES (6000 rounds)\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Recycle bin is enabled.\n")); // Test with quiet option. - qint64 pos = m_stdoutFile->pos(); - qint64 errPos = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - infoCmd.execute({"db-info", "-q", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QVERIFY(m_stdoutFile->readLine().contains(QByteArray("UUID: "))); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Name: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Description: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Cipher: AES 256-bit\n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("KDF: AES (6000 rounds)\n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Recycle bin is enabled.\n")); + setInput("a"); + execCmd(infoCmd, {"db-info", "-q", m_dbFile->fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QVERIFY(m_stdout->readLine().contains(QByteArray("UUID: "))); + QCOMPARE(m_stdout->readLine(), QByteArray("Name: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Description: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Cipher: AES 256-bit\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("KDF: AES (6000 rounds)\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Recycle bin is enabled.\n")); } void TestCli::testDiceware() @@ -777,33 +656,25 @@ void TestCli::testDiceware() QVERIFY(!dicewareCmd.name.isEmpty()); QVERIFY(dicewareCmd.getDescriptionLine().contains(dicewareCmd.name)); - dicewareCmd.execute({"diceware"}); - m_stdoutFile->reset(); - QString passphrase(m_stdoutFile->readLine()); + execCmd(dicewareCmd, {"diceware"}); + QString passphrase(m_stdout->readLine()); QVERIFY(!passphrase.isEmpty()); - dicewareCmd.execute({"diceware", "-W", "2"}); - m_stdoutFile->seek(passphrase.toLatin1().size()); - passphrase = m_stdoutFile->readLine(); + execCmd(dicewareCmd, {"diceware", "-W", "2"}); + passphrase = m_stdout->readLine(); QCOMPARE(passphrase.split(" ").size(), 2); - auto pos = m_stdoutFile->pos(); - dicewareCmd.execute({"diceware", "-W", "10"}); - m_stdoutFile->seek(pos); - passphrase = m_stdoutFile->readLine(); + execCmd(dicewareCmd, {"diceware", "-W", "10"}); + passphrase = m_stdout->readLine(); QCOMPARE(passphrase.split(" ").size(), 10); // Testing with invalid word count - auto posErr = m_stderrFile->pos(); - dicewareCmd.execute({"diceware", "-W", "-10"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid word count -10\n")); + execCmd(dicewareCmd, {"diceware", "-W", "-10"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid word count -10\n")); // Testing with invalid word count format - posErr = m_stderrFile->pos(); - dicewareCmd.execute({"diceware", "-W", "bleuh"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid word count bleuh\n")); + execCmd(dicewareCmd, {"diceware", "-W", "bleuh"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid word count bleuh\n")); TemporaryFile wordFile; wordFile.open(); @@ -812,10 +683,8 @@ void TestCli::testDiceware() } wordFile.close(); - pos = m_stdoutFile->pos(); - dicewareCmd.execute({"diceware", "-W", "11", "-w", wordFile.fileName()}); - m_stdoutFile->seek(pos); - passphrase = m_stdoutFile->readLine(); + execCmd(dicewareCmd, {"diceware", "-W", "11", "-w", wordFile.fileName()}); + passphrase = m_stdout->readLine(); const auto words = passphrase.split(" "); QCOMPARE(words.size(), 11); QRegularExpression regex("^word\\d+$"); @@ -830,10 +699,8 @@ void TestCli::testDiceware() } smallWordFile.close(); - posErr = m_stderrFile->pos(); - dicewareCmd.execute({"diceware", "-W", "11", "-w", smallWordFile.fileName()}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("The word list is too small (< 1000 items)\n")); + execCmd(dicewareCmd, {"diceware", "-W", "11", "-w", smallWordFile.fileName()}); + QCOMPARE(m_stderr->readLine(), QByteArray("The word list is too small (< 1000 items)\n")); } void TestCli::testEdit() @@ -842,15 +709,20 @@ void TestCli::testEdit() QVERIFY(!editCmd.name.isEmpty()); QVERIFY(editCmd.getDescriptionLine().contains(editCmd.name)); - Utils::Test::setNextPassword("a"); - // clang-format off - editCmd.execute({"edit", "-u", "newuser", "--url", "https://otherurl.example.com/", "-t", "newtitle", m_dbFile->fileName(), "/Sample Entry"}); - // clang-format on - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully edited entry newtitle.\n")); - - auto db = readTestDatabase(); + setInput("a"); + execCmd(editCmd, + {"edit", + "-u", + "newuser", + "--url", + "https://otherurl.example.com/", + "-t", + "newtitle", + m_dbFile->fileName(), + "/Sample Entry"}); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully edited entry newtitle.\n")); + + auto db = readDatabase(); auto* entry = db->rootGroup()->findEntryByPath("/newtitle"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); @@ -858,18 +730,14 @@ void TestCli::testEdit() QCOMPARE(entry->password(), QString("Password")); // Quiet option - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", m_dbFile->fileName(), "-q", "-t", "newertitle", "/newtitle"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - - Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", "-g", m_dbFile->fileName(), "/newertitle"}); - db = readTestDatabase(); + setInput("a"); + execCmd(editCmd, {"edit", m_dbFile->fileName(), "-q", "-t", "newertitle", "/newtitle"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); + + setInput("a"); + execCmd(editCmd, {"edit", "-g", m_dbFile->fileName(), "/newertitle"}); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newertitle"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); @@ -877,9 +745,9 @@ void TestCli::testEdit() QVERIFY(!entry->password().isEmpty()); QVERIFY(entry->password() != QString("Password")); - Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", "-g", "-L", "34", "-t", "evennewertitle", m_dbFile->fileName(), "/newertitle"}); - db = readTestDatabase(); + setInput("a"); + execCmd(editCmd, {"edit", "-g", "-L", "34", "-t", "evennewertitle", m_dbFile->fileName(), "/newertitle"}); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/evennewertitle"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); @@ -889,36 +757,31 @@ void TestCli::testEdit() QRegularExpression defaultPasswordClassesRegex("^[a-zA-Z0-9]+$"); QVERIFY(defaultPasswordClassesRegex.match(entry->password()).hasMatch()); - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", - "-g", - "-L", - "20", - "--every-group", - "-s", - "-n", - "--upper", - "-l", - m_dbFile->fileName(), - "/evennewertitle"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully edited entry evennewertitle.\n")); - - db = readTestDatabase(); + setInput("a"); + execCmd(editCmd, + {"edit", + "-g", + "-L", + "20", + "--every-group", + "-s", + "-n", + "--upper", + "-l", + m_dbFile->fileName(), + "/evennewertitle"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully edited entry evennewertitle.\n")); + + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/evennewertitle"); QVERIFY(entry); QCOMPARE(entry->password().size(), 20); QVERIFY(!defaultPasswordClassesRegex.match(entry->password()).hasMatch()); - Utils::Test::setNextPassword("a"); - Utils::Test::setNextPassword("newpassword"); - editCmd.execute({"edit", "-p", m_dbFile->fileName(), "/evennewertitle"}); - db = readTestDatabase(); + setInput({"a", "newpassword"}); + execCmd(editCmd, {"edit", "-p", m_dbFile->fileName(), "/evennewertitle"}); + db = readDatabase(); + QVERIFY(db); entry = db->rootGroup()->findEntryByPath("/evennewertitle"); QVERIFY(entry); QCOMPARE(entry->password(), QString("newpassword")); @@ -1014,14 +877,9 @@ void TestCli::testEstimate() QVERIFY(!estimateCmd.name.isEmpty()); QVERIFY(estimateCmd.getDescriptionLine().contains(estimateCmd.name)); - QTextStream in(m_stdinFile.data()); - QTextStream out(m_stdoutFile.data()); - - in << input << endl; - in.seek(0); - estimateCmd.execute({"estimate", "-a"}); - out.seek(0); - auto result = out.readAll(); + setInput(input); + execCmd(estimateCmd, {"estimate", "-a"}); + auto result = QString(m_stdout->readAll()); QVERIFY(result.contains("Length " + length)); QVERIFY(result.contains("Entropy " + entropy)); QVERIFY(result.contains("Log10 " + log10)); @@ -1036,61 +894,47 @@ void TestCli::testExport() QVERIFY(!exportCmd.name.isEmpty()); QVERIFY(exportCmd.getDescriptionLine().contains(exportCmd.name)); - Utils::Test::setNextPassword("a"); - exportCmd.execute({"export", m_dbFile->fileName()}); + setInput("a"); + execCmd(exportCmd, {"export", m_dbFile->fileName()}); - m_stdoutFile->seek(0); - m_stdoutFile->readLine(); // skip prompt line + TemporaryFile xmlOutput; + xmlOutput.open(QIODevice::WriteOnly); + xmlOutput.write(m_stdout->readAll()); + xmlOutput.close(); - 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()); + QVERIFY(db->import(xmlOutput.fileName())); + auto* entry = db->rootGroup()->findEntryByPath("/Sample Entry"); QVERIFY(entry); QCOMPARE(entry->password(), QString("Password")); - m_stdoutFile->reset(); - // Quiet option QScopedPointer<Database> dbQuiet(new Database()); - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - exportCmd.execute({"export", "-f", "xml", "-q", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - reader.readDatabase(m_stdoutFile.data(), dbQuiet.data()); - QVERIFY(!reader.hasError()); - QVERIFY(db.data()); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(exportCmd, {"export", "-f", "xml", "-q", m_dbFile->fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + + xmlOutput.open(QIODevice::WriteOnly); + xmlOutput.write(m_stdout->readAll()); + xmlOutput.close(); + + QVERIFY(db->import(xmlOutput.fileName())); // CSV exporting - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - exportCmd.execute({"export", "-f", "csv", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip prompt line - m_stderrFile->seek(posErr); - QByteArray csvHeader = m_stdoutFile->readLine(); + setInput("a"); + execCmd(exportCmd, {"export", "-f", "csv", m_dbFile->fileName()}); + QByteArray csvHeader = m_stdout->readLine(); QCOMPARE(csvHeader, QByteArray("\"Group\",\"Title\",\"Username\",\"Password\",\"URL\",\"Notes\"\n")); - QByteArray csvData = m_stdoutFile->readAll(); + QByteArray csvData = m_stdout->readAll(); QVERIFY(csvData.contains(QByteArray( "\"NewDatabase\",\"Sample Entry\",\"User Name\",\"Password\",\"http://www.somesite.com/\",\"Notes\"\n"))); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); // test invalid format - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - exportCmd.execute({"export", "-f", "yaml", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip prompt line - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Unsupported format yaml\n")); + setInput("a"); + execCmd(exportCmd, {"export", "-f", "yaml", m_dbFile->fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Unsupported format yaml\n")); } void TestCli::testGenerate_data() @@ -1130,35 +974,30 @@ void TestCli::testGenerate() QVERIFY(!generateCmd.name.isEmpty()); QVERIFY(generateCmd.getDescriptionLine().contains(generateCmd.name)); - qint64 pos = 0; - // run multiple times to make accidental passes unlikely - TextStream stream(m_stdoutFile.data()); for (int i = 0; i < 10; ++i) { - generateCmd.execute(parameters); - stream.seek(pos); + execCmd(generateCmd, parameters); QRegularExpression regex(pattern); - QString password = stream.readLine(); - pos = stream.pos(); +#ifdef Q_OS_UNIX + QString password = QString::fromUtf8(m_stdout->readLine()); +#else + QString password = QString::fromLatin1(m_stdout->readLine()); +#endif + QVERIFY2(regex.match(password).hasMatch(), qPrintable("Password " + password + " does not match pattern " + pattern)); + QCOMPARE(m_stderr->readAll(), QByteArray()); } // Testing with invalid password length - auto posErr = m_stderrFile->pos(); - generateCmd.execute({"generate", "-L", "-10"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid password length -10\n")); + execCmd(generateCmd, {"generate", "-L", "-10"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid password length -10\n")); - posErr = m_stderrFile->pos(); - generateCmd.execute({"generate", "-L", "0"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid password length 0\n")); + execCmd(generateCmd, {"generate", "-L", "0"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid password length 0\n")); // Testing with invalid word count format - posErr = m_stderrFile->pos(); - generateCmd.execute({"generate", "-L", "bleuh"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid password length bleuh\n")); + execCmd(generateCmd, {"generate", "-L", "bleuh"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid password length bleuh\n")); } void TestCli::testImport() @@ -1168,52 +1007,41 @@ void TestCli::testImport() QVERIFY(importCmd.getDescriptionLine().contains(importCmd.name)); QScopedPointer<QTemporaryDir> testDir(new QTemporaryDir()); - QString databaseFilename = testDir->path() + "testImport1.kdbx"; - - Utils::Test::setNextPassword("a", true); - importCmd.execute({"import", m_xmlFile->fileName(), databaseFilename}); + QString databaseFilename = testDir->path() + "/testImport1.kdbx"; - m_stderrFile->reset(); - m_stdoutFile->reset(); + setInput({"a", "a"}); + execCmd(importCmd, {"import", m_xmlFile->fileName(), databaseFilename}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully imported database.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully imported database.\n")); - Utils::Test::setNextPassword("a"); - auto db = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename, true, "", "", Utils::DEVNULL)); + auto db = readDatabase(databaseFilename, "a"); QVERIFY(db); auto* entry = db->rootGroup()->findEntryByPath("/Sample Entry 1"); QVERIFY(entry); QCOMPARE(entry->username(), QString("User Name")); // Should refuse to create the database if it already exists. - qint64 pos = m_stdoutFile->pos(); - qint64 errPos = m_stderrFile->pos(); - importCmd.execute({"import", m_xmlFile->fileName(), databaseFilename}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + execCmd(importCmd, {"import", m_xmlFile->fileName(), databaseFilename}); // Output should be empty when there is an error. - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray()); QString errorMessage = QString("File " + databaseFilename + " already exists.\n"); - QCOMPARE(m_stderrFile->readAll(), errorMessage.toUtf8()); + QCOMPARE(m_stderr->readAll(), errorMessage.toUtf8()); // Quiet option QScopedPointer<QTemporaryDir> testDirQuiet(new QTemporaryDir()); - QString databaseFilenameQuiet = testDirQuiet->path() + "testImport2.kdbx"; + QString databaseFilenameQuiet = testDirQuiet->path() + "/testImport2.kdbx"; - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a", true); - importCmd.execute({"import", "-q", m_xmlFile->fileName(), databaseFilenameQuiet}); - m_stdoutFile->seek(pos); + setInput({"a", "a"}); + execCmd(importCmd, {"import", "-q", m_xmlFile->fileName(), databaseFilenameQuiet}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray()); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray()); - Utils::Test::setNextPassword("a"); - auto dbQuiet = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilenameQuiet, true, "", "", Utils::DEVNULL)); - QVERIFY(dbQuiet); + db = readDatabase(databaseFilenameQuiet, "a"); + QVERIFY(db); } void TestCli::testKeyFileOption() @@ -1221,35 +1049,26 @@ void TestCli::testKeyFileOption() List listCmd; QString keyFilePath(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtected.key")); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-k", keyFilePath, m_keyFileProtectedDbFile->fileName()}); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-k", keyFilePath, m_keyFileProtectedDbFile->fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("entry1\n" "entry2\n")); // Should raise an error with no key file. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", m_keyFileProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QVERIFY(m_stderrFile->readAll().contains("Invalid credentials were provided")); + setInput("a"); + execCmd(listCmd, {"ls", m_keyFileProtectedDbFile->fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains("Invalid credentials were provided")); // Should raise an error if key file path is invalid. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-k", "invalidpath", m_keyFileProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll().split(':').at(0), QByteArray("Failed to load key file invalidpath")); + setInput("a"); + execCmd(listCmd, {"ls", "-k", "invalidpath", m_keyFileProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stdout->readAll(), QByteArray()); + QCOMPARE(m_stderr->readAll().split(':').at(0), QByteArray("Failed to load key file invalidpath")); } void TestCli::testNoPasswordOption() @@ -1257,21 +1076,17 @@ void TestCli::testNoPasswordOption() List listCmd; QString keyFilePath(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtectedNoPassword.key")); - listCmd.execute({"ls", "-k", keyFilePath, "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()}); - m_stdoutFile->reset(); - QCOMPARE(m_stdoutFile->readAll(), + execCmd(listCmd, {"ls", "-k", keyFilePath, "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()}); + // Expecting no password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("entry1\n" "entry2\n")); // Should raise an error with no key file. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - listCmd.execute({"ls", "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QVERIFY(m_stderrFile->readAll().contains("Invalid credentials were provided")); + execCmd(listCmd, {"ls", "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains("Invalid credentials were provided")); } void TestCli::testList() @@ -1280,11 +1095,11 @@ void TestCli::testList() QVERIFY(!listCmd.name.isEmpty()); QVERIFY(listCmd.getDescriptionLine().contains(listCmd.name)); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", m_dbFile->fileName()}); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", m_dbFile->fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "General/\n" "Windows/\n" @@ -1294,11 +1109,10 @@ void TestCli::testList() "Homebanking/\n")); // Quiet option - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-q", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-q", m_dbFile->fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "General/\n" "Windows/\n" @@ -1307,12 +1121,9 @@ void TestCli::testList() "eMail/\n" "Homebanking/\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-R", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-R", m_dbFile->fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "General/\n" " [empty]\n" @@ -1328,12 +1139,9 @@ void TestCli::testList() " Subgroup/\n" " Subgroup Entry\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-R", "-f", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-R", "-f", m_dbFile->fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "General/\n" "General/[empty]\n" @@ -1349,30 +1157,21 @@ void TestCli::testList() "Homebanking/Subgroup/\n" "Homebanking/Subgroup/Subgroup Entry\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-R", "-f", m_dbFile->fileName(), "/Homebanking"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-R", "-f", m_dbFile->fileName(), "/Homebanking"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Subgroup/\n" "Subgroup/Subgroup Entry\n")); - pos = m_stdoutFile->pos(); - Utils::Test::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::Test::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")); + setInput("a"); + execCmd(listCmd, {"ls", m_dbFile->fileName(), "/General/"}); + QCOMPARE(m_stdout->readAll(), QByteArray("[empty]\n")); + + setInput("a"); + execCmd(listCmd, {"ls", m_dbFile->fileName(), "/DoesNotExist/"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Cannot find group /DoesNotExist/.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testLocate() @@ -1381,30 +1180,26 @@ void TestCli::testLocate() QVERIFY(!locateCmd.name.isEmpty()); QVERIFY(locateCmd.getDescriptionLine().contains(locateCmd.name)); - Utils::Test::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")); + setInput("a"); + execCmd(locateCmd, {"locate", m_dbFile->fileName(), "Sample"}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("/Sample Entry\n")); // Quiet option - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - locateCmd.execute({"locate", m_dbFile->fileName(), "-q", "Sample"}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n")); - - pos = m_stdoutFile->pos(); - Utils::Test::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")); + setInput("a"); + execCmd(locateCmd, {"locate", m_dbFile->fileName(), "-q", "Sample"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("/Sample Entry\n")); + + setInput("a"); + execCmd(locateCmd, {"locate", m_dbFile->fileName(), "Does Not Exist"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("No results for that search term.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // write a modified database - auto db = readTestDatabase(); + auto db = readDatabase(); QVERIFY(db); auto* group = db->rootGroup()->findGroupByPath("/General/"); QVERIFY(group); @@ -1412,25 +1207,19 @@ void TestCli::testLocate() entry->setUuid(QUuid::createUuid()); entry->setTitle("New Entry"); group->addEntry(entry); + TemporaryFile tmpFile; tmpFile.open(); - Kdbx4Writer writer; - writer.writeDatabase(&tmpFile, db.data()); tmpFile.close(); + db->saveAs(tmpFile.fileName()); - pos = m_stdoutFile->pos(); - Utils::Test::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::Test::setNextPassword("a"); - locateCmd.execute({"locate", tmpFile.fileName(), "Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(locateCmd, {"locate", tmpFile.fileName(), "New"}); + QCOMPARE(m_stdout->readAll(), QByteArray("/General/New Entry\n")); + + setInput("a"); + execCmd(locateCmd, {"locate", tmpFile.fileName(), "Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("/Sample Entry\n/General/New Entry\n/Homebanking/Subgroup/Subgroup Entry\n")); } @@ -1440,30 +1229,32 @@ void TestCli::testMerge() QVERIFY(!mergeCmd.name.isEmpty()); QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name)); - Kdbx4Writer writer; - Kdbx4Reader reader; - // load test database and save copies - auto db = readTestDatabase(); + auto db = readDatabase(); QVERIFY(db); TemporaryFile targetFile1; targetFile1.open(); - writer.writeDatabase(&targetFile1, db.data()); targetFile1.close(); + TemporaryFile targetFile2; targetFile2.open(); - writer.writeDatabase(&targetFile2, db.data()); targetFile2.close(); - // save another copy with a different password TemporaryFile targetFile3; targetFile3.open(); + targetFile3.close(); + + db->saveAs(targetFile1.fileName()); + db->saveAs(targetFile2.fileName()); + + // save another copy with a different password auto oldKey = db->key(); auto key = QSharedPointer<CompositeKey>::create(); key->addKey(QSharedPointer<PasswordKey>::create("b")); db->setKey(key); - writer.writeDatabase(&targetFile3, db.data()); - targetFile3.close(); + db->saveAs(targetFile3.fileName()); + + // Restore the original password db->setKey(oldKey); // then add a new entry to the in-memory database and save another copy @@ -1474,91 +1265,64 @@ void TestCli::testMerge() auto* group = db->rootGroup()->findGroupByPath("/Internet/"); QVERIFY(group); group->addEntry(entry); + TemporaryFile sourceFile; sourceFile.open(); - writer.writeDatabase(&sourceFile, db.data()); sourceFile.close(); - - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "-s", targetFile1.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - m_stderrFile->reset(); - QList<QByteArray> outLines1 = m_stdoutFile->readAll().split('\n'); - QCOMPARE(outLines1.at(0).split('[').at(0), QByteArray("\tOverwriting Internet ")); - QCOMPARE(outLines1.at(1).split('[').at(0), QByteArray("\tCreating missing Some Website ")); + db->saveAs(sourceFile.fileName()); + + setInput("a"); + execCmd(mergeCmd, {"merge", "-s", targetFile1.fileName(), sourceFile.fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QList<QByteArray> outLines1 = m_stdout->readAll().split('\n'); + QVERIFY(outLines1.at(0).contains("Overwriting Internet")); + QVERIFY(outLines1.at(1).contains("Creating missing Some Website")); QCOMPARE(outLines1.at(2), QString("Successfully merged %1 into %2.").arg(sourceFile.fileName(), targetFile1.fileName()).toUtf8()); - QFile readBack(targetFile1.fileName()); - readBack.open(QIODevice::ReadOnly); auto mergedDb = QSharedPointer<Database>::create(); - reader.readDatabase(&readBack, oldKey, mergedDb.data()); - readBack.close(); - QVERIFY(mergedDb); + QVERIFY(mergedDb->open(targetFile1.fileName(), oldKey)); + auto* entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); QVERIFY(entry1); QCOMPARE(entry1->title(), QString("Some Website")); QCOMPARE(entry1->password(), QString("secretsecretsecret")); // the dry run option should not modify the target database. - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "--dry-run", "-s", targetFile2.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - m_stderrFile->reset(); - QList<QByteArray> outLines2 = m_stdoutFile->readAll().split('\n'); - QCOMPARE(outLines2.at(0).split('[').at(0), QByteArray("\tOverwriting Internet ")); - QCOMPARE(outLines2.at(1).split('[').at(0), QByteArray("\tCreating missing Some Website ")); + setInput("a"); + execCmd(mergeCmd, {"merge", "--dry-run", "-s", targetFile2.fileName(), sourceFile.fileName()}); + QList<QByteArray> outLines2 = m_stdout->readAll().split('\n'); + QVERIFY(outLines2.at(0).contains("Overwriting Internet")); + QVERIFY(outLines2.at(1).contains("Creating missing Some Website")); QCOMPARE(outLines2.at(2), QByteArray("Database was not modified by merge operation.")); - QFile readBack2(targetFile2.fileName()); - readBack2.open(QIODevice::ReadOnly); mergedDb = QSharedPointer<Database>::create(); - reader.readDatabase(&readBack2, oldKey, mergedDb.data()); - readBack2.close(); - QVERIFY(mergedDb); + QVERIFY(mergedDb->open(targetFile2.fileName(), oldKey)); entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); QVERIFY(!entry1); // the dry run option can be used with the quiet option - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "--dry-run", "-s", "-q", targetFile2.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - m_stderrFile->reset(); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - - readBack2.setFileName(targetFile2.fileName()); - readBack2.open(QIODevice::ReadOnly); + setInput("a"); + execCmd(mergeCmd, {"merge", "--dry-run", "-s", "-q", targetFile2.fileName(), sourceFile.fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); + mergedDb = QSharedPointer<Database>::create(); - reader.readDatabase(&readBack2, oldKey, mergedDb.data()); - readBack2.close(); - QVERIFY(mergedDb); + QVERIFY(mergedDb->open(targetFile2.fileName(), oldKey)); entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); QVERIFY(!entry1); // try again with different passwords for both files - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("b"); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", targetFile3.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - m_stdoutFile->readLine(); - QList<QByteArray> outLines3 = m_stdoutFile->readAll().split('\n'); + setInput({"b", "a"}); + execCmd(mergeCmd, {"merge", targetFile3.fileName(), sourceFile.fileName()}); + QList<QByteArray> outLines3 = m_stdout->readAll().split('\n'); QCOMPARE(outLines3.at(2), QString("Successfully merged %1 into %2.").arg(sourceFile.fileName(), targetFile3.fileName()).toUtf8()); - readBack.setFileName(targetFile3.fileName()); - readBack.open(QIODevice::ReadOnly); mergedDb = QSharedPointer<Database>::create(); - reader.readDatabase(&readBack, key, mergedDb.data()); - readBack.close(); - QVERIFY(mergedDb); + QVERIFY(mergedDb->open(targetFile3.fileName(), key)); + entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); QVERIFY(entry1); QCOMPARE(entry1->title(), QString("Some Website")); @@ -1566,27 +1330,21 @@ void TestCli::testMerge() // making sure that the message is different if the database was not // modified by the merge operation. - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "-s", sourceFile.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Database was not modified by merge operation.\n")); + setInput("a"); + execCmd(mergeCmd, {"merge", "-s", sourceFile.fileName(), sourceFile.fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray("Database was not modified by merge operation.\n")); // Quiet option - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "-q", "-s", sourceFile.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(mergeCmd, {"merge", "-q", "-s", sourceFile.fileName(), sourceFile.fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Quiet option without the -s option - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "-q", sourceFile.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput({"a", "a"}); + execCmd(mergeCmd, {"merge", "-q", sourceFile.fileName(), sourceFile.fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testMergeWithKeys() @@ -1599,9 +1357,6 @@ void TestCli::testMergeWithKeys() QVERIFY(!mergeCmd.name.isEmpty()); QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name)); - Kdbx4Writer writer; - Kdbx4Reader reader; - QScopedPointer<QTemporaryDir> testDir(new QTemporaryDir()); QString sourceDatabaseFilename = testDir->path() + "/testSourceDatabase.kdbx"; @@ -1610,22 +1365,16 @@ void TestCli::testMergeWithKeys() QString targetDatabaseFilename = testDir->path() + "/testTargetDatabase.kdbx"; QString targetKeyfilePath = testDir->path() + "/testTargetKeyfile.txt"; - qint64 pos = m_stdoutFile->pos(); + setInput({"a", "a"}); + execCmd(createCmd, {"db-create", sourceDatabaseFilename, "-p", "-k", sourceKeyfilePath}); - Utils::Test::setNextPassword("a", true); - createCmd.execute({"db-create", sourceDatabaseFilename, "-p", "-k", sourceKeyfilePath}); + setInput({"b", "b"}); + execCmd(createCmd, {"db-create", targetDatabaseFilename, "-p", "-k", targetKeyfilePath}); - Utils::Test::setNextPassword("b", true); - createCmd.execute({"db-create", targetDatabaseFilename, "-p", "-k", targetKeyfilePath}); - - Utils::Test::setNextPassword("a"); - auto sourceDatabase = QSharedPointer<Database>( - Utils::unlockDatabase(sourceDatabaseFilename, true, sourceKeyfilePath, "", Utils::STDOUT)); + auto sourceDatabase = readDatabase(sourceDatabaseFilename, "a", sourceKeyfilePath); QVERIFY(sourceDatabase); - Utils::Test::setNextPassword("b"); - auto targetDatabase = QSharedPointer<Database>( - Utils::unlockDatabase(targetDatabaseFilename, true, targetKeyfilePath, "", Utils::STDOUT)); + auto targetDatabase = readDatabase(targetDatabaseFilename, "b", targetKeyfilePath); QVERIFY(targetDatabase); auto* rootGroup = new Group(); @@ -1660,34 +1409,22 @@ void TestCli::testMergeWithKeys() targetDatabase->setRootGroup(otherRootGroup); - QFile sourceDatabaseFile(sourceDatabaseFilename); - sourceDatabaseFile.open(QIODevice::WriteOnly); - QVERIFY(writer.writeDatabase(&sourceDatabaseFile, sourceDatabase.data())); - sourceDatabaseFile.flush(); - sourceDatabaseFile.close(); - - QFile targetDatabaseFile(targetDatabaseFilename); - targetDatabaseFile.open(QIODevice::WriteOnly); - QVERIFY(writer.writeDatabase(&targetDatabaseFile, targetDatabase.data())); - targetDatabaseFile.flush(); - targetDatabaseFile.close(); - - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("b"); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", - "-k", - targetKeyfilePath, - "--key-file-from", - sourceKeyfilePath, - targetDatabaseFile.fileName(), - sourceDatabaseFile.fileName()}); - - m_stdoutFile->seek(pos); - QList<QByteArray> lines = m_stdoutFile->readAll().split('\n'); - QVERIFY(lines.contains(QString("Successfully merged %1 into %2.") - .arg(sourceDatabaseFile.fileName(), targetDatabaseFile.fileName()) - .toUtf8())); + sourceDatabase->saveAs(sourceDatabaseFilename); + targetDatabase->saveAs(targetDatabaseFilename); + + setInput({"b", "a"}); + execCmd(mergeCmd, + {"merge", + "-k", + targetKeyfilePath, + "--key-file-from", + sourceKeyfilePath, + targetDatabaseFilename, + sourceDatabaseFilename}); + + QList<QByteArray> lines = m_stdout->readAll().split('\n'); + QVERIFY(lines.contains( + QString("Successfully merged %1 into %2.").arg(sourceDatabaseFilename, targetDatabaseFilename).toUtf8())); } void TestCli::testMove() @@ -1696,53 +1433,37 @@ void TestCli::testMove() QVERIFY(!moveCmd.name.isEmpty()); QVERIFY(moveCmd.getDescriptionLine().contains(moveCmd.name)); - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - moveCmd.execute({"mv", m_dbFile->fileName(), "invalid_entry_path", "invalid_group_path"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Could not find entry with path invalid_entry_path.\n")); - - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - moveCmd.execute({"mv", m_dbFile->fileName(), "Sample Entry", "invalid_group_path"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Could not find group with path invalid_group_path.\n")); - - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - moveCmd.execute({"mv", m_dbFile->fileName(), "Sample Entry", "General/"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully moved entry Sample Entry to group General/.\n")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("")); - - auto db = readTestDatabase(); + setInput("a"); + execCmd(moveCmd, {"mv", m_dbFile->fileName(), "invalid_entry_path", "invalid_group_path"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Could not find entry with path invalid_entry_path.\n")); + QCOMPARE(m_stdout->readLine(), QByteArray()); + + setInput("a"); + execCmd(moveCmd, {"mv", m_dbFile->fileName(), "Sample Entry", "invalid_group_path"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Could not find group with path invalid_group_path.\n")); + QCOMPARE(m_stdout->readLine(), QByteArray()); + + setInput("a"); + execCmd(moveCmd, {"mv", m_dbFile->fileName(), "Sample Entry", "General/"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray()); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully moved entry Sample Entry to group General/.\n")); + + auto db = readDatabase(); auto* entry = db->rootGroup()->findEntryByPath("General/Sample Entry"); QVERIFY(entry); // Test that not modified if the same group is destination. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - moveCmd.execute({"mv", m_dbFile->fileName(), "General/Sample Entry", "General/"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Entry is already in group General/.\n")); + setInput("a"); + execCmd(moveCmd, {"mv", m_dbFile->fileName(), "General/Sample Entry", "General/"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Entry is already in group General/.\n")); + QCOMPARE(m_stdout->readLine(), QByteArray()); // sanity check - db = readTestDatabase(); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("General/Sample Entry"); QVERIFY(entry); } @@ -1753,80 +1474,52 @@ void TestCli::testRemove() 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(); + auto db = readDatabase(); QVERIFY(db); TemporaryFile fileCopy; fileCopy.open(); - db->metadata()->setRecycleBinEnabled(false); - writer.writeDatabase(&fileCopy, db.data()); fileCopy.close(); - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); + db->metadata()->setRecycleBinEnabled(false); + db->saveAs(fileCopy.fileName()); // delete entry and verify - Utils::Test::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")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeCmd, {"rm", m_dbFile->fileName(), "/Sample Entry"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->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); - auto readBackDb = QSharedPointer<Database>::create(); - reader.readDatabase(&readBack, key, readBackDb.data()); - readBack.close(); + auto readBackDb = readDatabase(); QVERIFY(readBackDb); QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); QVERIFY(readBackDb->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); - pos = m_stdoutFile->pos(); - pos = m_stdoutFile->pos(); - // try again, this time without recycle bin - Utils::Test::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 = QSharedPointer<Database>::create(); - reader.readDatabase(&readBack, key, readBackDb.data()); - readBack.close(); + setInput("a"); + execCmd(removeCmd, {"rm", fileCopy.fileName(), "/Sample Entry"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully deleted entry Sample Entry.\n")); + + readBackDb = readDatabase(fileCopy.fileName(), "a"); QVERIFY(readBackDb); QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); QVERIFY(!readBackDb->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); // finally, try deleting a non-existent entry - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry /Sample Entry not found.\n")); + setInput("a"); + execCmd(removeCmd, {"rm", fileCopy.fileName(), "/Sample Entry"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Entry /Sample Entry not found.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // try deleting a directory, should fail - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeCmd.execute({"rm", fileCopy.fileName(), "/General"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry /General not found.\n")); + setInput("a"); + execCmd(removeCmd, {"rm", fileCopy.fileName(), "/General"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Entry /General not found.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testRemoveGroup() @@ -1835,60 +1528,41 @@ void TestCli::testRemoveGroup() QVERIFY(!removeGroupCmd.name.isEmpty()); QVERIFY(removeGroupCmd.getDescriptionLine().contains(removeGroupCmd.name)); - Kdbx3Reader reader; - Kdbx3Writer writer; - // try deleting a directory, should recycle it first. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeGroupCmd.execute({"rmdir", m_dbFile->fileName(), "/General"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully recycled group /General.\n")); - - auto db = readTestDatabase(); + setInput("a"); + execCmd(removeGroupCmd, {"rmdir", m_dbFile->fileName(), "/General"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully recycled group /General.\n")); + + auto db = readDatabase(); auto* group = db->rootGroup()->findGroupByPath("General"); QVERIFY(!group); // try deleting a directory again, should delete it permanently. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeGroupCmd.execute({"rmdir", m_dbFile->fileName(), "Recycle Bin/General"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully deleted group Recycle Bin/General.\n")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - - db = readTestDatabase(); + setInput("a"); + execCmd(removeGroupCmd, {"rmdir", m_dbFile->fileName(), "Recycle Bin/General"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully deleted group Recycle Bin/General.\n")); + + db = readDatabase(); group = db->rootGroup()->findGroupByPath("Recycle Bin/General"); QVERIFY(!group); // try deleting an invalid group, should fail. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeGroupCmd.execute({"rmdir", m_dbFile->fileName(), "invalid"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Group invalid not found.\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeGroupCmd, {"rmdir", m_dbFile->fileName(), "invalid"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Group invalid not found.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should fail to remove the root group. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeGroupCmd.execute({"rmdir", m_dbFile->fileName(), "/"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Cannot remove root group from database.\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeGroupCmd, {"rmdir", m_dbFile->fileName(), "/"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Cannot remove root group from database.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testRemoveQuiet() @@ -1897,44 +1571,27 @@ void TestCli::testRemoveQuiet() QVERIFY(!removeCmd.name.isEmpty()); QVERIFY(removeCmd.getDescriptionLine().contains(removeCmd.name)); - Kdbx3Reader reader; - Kdbx3Writer writer; - - qint64 pos = m_stdoutFile->pos(); - // delete entry and verify - Utils::Test::setNextPassword("a"); - removeCmd.execute({"rm", "-q", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeCmd, {"rm", "-q", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); - auto key = QSharedPointer<CompositeKey>::create(); - key->addKey(QSharedPointer<PasswordKey>::create("a")); - QFile readBack(m_dbFile->fileName()); - readBack.open(QIODevice::ReadOnly); - auto readBackDb = QSharedPointer<Database>::create(); - reader.readDatabase(&readBack, key, readBackDb.data()); - readBack.close(); - QVERIFY(readBackDb); - QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); - QVERIFY(readBackDb->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); + auto db = readDatabase(); + QVERIFY(db); - pos = m_stdoutFile->pos(); + QVERIFY(!db->rootGroup()->findEntryByPath("/Sample Entry")); + QVERIFY(db->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); // remove the entry completely - Utils::Test::setNextPassword("a"); - removeCmd.execute({"rm", "-q", m_dbFile->fileName(), QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin"))}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - - readBack.setFileName(m_dbFile->fileName()); - readBack.open(QIODevice::ReadOnly); - readBackDb = QSharedPointer<Database>::create(); - reader.readDatabase(&readBack, key, readBackDb.data()); - readBack.close(); - QVERIFY(readBackDb); - QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); - QVERIFY(!readBackDb->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); + setInput("a"); + execCmd(removeCmd, {"rm", "-q", m_dbFile->fileName(), QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin"))}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); + + db = readDatabase(); + QVERIFY(!db->rootGroup()->findEntryByPath("/Sample Entry")); + QVERIFY(!db->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); } void TestCli::testShow() @@ -1943,119 +1600,82 @@ void TestCli::testShow() QVERIFY(!showCmd.name.isEmpty()); QVERIFY(showCmd.getDescriptionLine().contains(showCmd.name)); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(showCmd, {"show", m_dbFile->fileName(), "/Sample Entry"}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Title: Sample Entry\n" "UserName: User Name\n" "Password: PROTECTED\n" "URL: http://www.somesite.com/\n" "Notes: Notes\n")); - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", "-s", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(showCmd, {"show", "-s", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Title: Sample Entry\n" "UserName: User Name\n" "Password: Password\n" "URL: http://www.somesite.com/\n" "Notes: Notes\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", m_dbFile->fileName(), "-q", "/Sample Entry"}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(showCmd, {"show", m_dbFile->fileName(), "-q", "/Sample Entry"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Title: Sample Entry\n" "UserName: User Name\n" "Password: PROTECTED\n" "URL: http://www.somesite.com/\n" "Notes: Notes\n")); - pos = m_stdoutFile->pos(); - Utils::Test::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::Test::setNextPassword("a"); - showCmd.execute({"show", "-a", "Password", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Password\n")); - - pos = m_stdoutFile->pos(); - Utils::Test::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(), + setInput("a"); + execCmd(showCmd, {"show", "-a", "Title", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n")); + + setInput("a"); + execCmd(showCmd, {"show", "-a", "Password", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Password\n")); + + setInput("a"); + execCmd(showCmd, {"show", "-a", "Title", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "http://www.somesite.com/\n")); // Test case insensitivity - pos = m_stdoutFile->pos(); - Utils::Test::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(), + setInput("a"); + execCmd(showCmd, {"show", "-a", "TITLE", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "http://www.somesite.com/\n")); - pos = m_stdoutFile->pos(); - Utils::Test::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")); - - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", "-t", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QVERIFY(isTOTP(m_stdoutFile->readAll())); - - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", "-a", "Title", m_dbFile->fileName(), "--totp", "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Sample Entry\n")); - QVERIFY(isTOTP(m_stdoutFile->readAll())); - - pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", m_dbFile2->fileName(), "--totp", "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry with path /Sample Entry has no TOTP set up.\n")); + setInput("a"); + execCmd(showCmd, {"show", "-a", "DoesNotExist", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains("ERROR: unknown attribute DoesNotExist.\n")); + + setInput("a"); + execCmd(showCmd, {"show", "-t", m_dbFile->fileName(), "/Sample Entry"}); + QVERIFY(isTotp(m_stdout->readAll())); + + setInput("a"); + execCmd(showCmd, {"show", "-a", "Title", m_dbFile->fileName(), "--totp", "/Sample Entry"}); + QCOMPARE(m_stdout->readLine(), QByteArray("Sample Entry\n")); + QVERIFY(isTotp(m_stdout->readAll())); + + setInput("a"); + execCmd(showCmd, {"show", m_dbFile2->fileName(), "--totp", "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains("Entry with path /Sample Entry has no TOTP set up.\n")); // Show with ambiguous attributes - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", m_dbFile->fileName(), "-a", "Testattribute1", "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE( - m_stderrFile->readAll(), - QByteArray("ERROR: attribute Testattribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); + setInput("a"); + execCmd(showCmd, {"show", m_dbFile->fileName(), "-a", "Testattribute1", "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains( + "ERROR: attribute Testattribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); } void TestCli::testInvalidDbFiles() @@ -2064,16 +1684,13 @@ void TestCli::testInvalidDbFiles() QString nonExistentDbPath("/foo/bar/baz"); QString directoryName("/"); - qint64 pos = m_stderrFile->pos(); - showCmd.execute({"show", nonExistentDbPath, "-q", "/Sample Entry"}); - m_stderrFile->seek(pos); - QCOMPARE(QString(m_stderrFile->readAll()), + execCmd(showCmd, {"show", nonExistentDbPath, "/Sample Entry"}); + QCOMPARE(QString(m_stderr->readAll()), QObject::tr("Failed to open database file %1: not found").arg(nonExistentDbPath) + "\n"); + QCOMPARE(m_stdout->readAll(), QByteArray()); - pos = m_stderrFile->pos(); - showCmd.execute({"show", directoryName, "-q", "whatever"}); - m_stderrFile->seek(pos); - QCOMPARE(QString(m_stderrFile->readAll()), + execCmd(showCmd, {"show", directoryName, "whatever"}); + QCOMPARE(QString(m_stderr->readAll()), QObject::tr("Failed to open database file %1: not a plain file").arg(directoryName) + "\n"); // Create a write-only file and try to open it. @@ -2085,10 +1702,8 @@ void TestCli::testInvalidDbFiles() QVERIFY(tempFile.open()); QString path = QFileInfo(tempFile).absoluteFilePath(); QVERIFY(tempFile.setPermissions(QFileDevice::WriteOwner)); - pos = m_stderrFile->pos(); - showCmd.execute({"show", path, "some entry"}); - m_stderrFile->seek(pos); - QCOMPARE(QString(m_stderrFile->readAll()), + execCmd(showCmd, {"show", path, "some entry"}); + QCOMPARE(QString(m_stderr->readAll()), QObject::tr("Failed to open database file %1: not readable").arg(path) + "\n"); #endif // Q_OS_WIN } @@ -2120,66 +1735,39 @@ void TestCli::testYubiKeyOption() List listCmd; Add addCmd; - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-y", "2", m_yubiKeyProtectedDbFile->fileName()}); - m_stdoutFile->reset(); - m_stderrFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-y", "2", m_yubiKeyProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("entry1\n" "entry2\n")); // Should raise an error with no yubikey slot. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", m_yubiKeyProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), + setInput("a"); + execCmd(listCmd, {"ls", m_yubiKeyProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Error while reading the database: Invalid credentials were provided, please try again.\n")); - QCOMPARE(m_stderrFile->readLine(), + QCOMPARE(m_stderr->readLine(), QByteArray("If this reoccurs, then your database file may be corrupt. (HMAC mismatch)\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should raise an error if yubikey slot is not a string - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-y", "invalidslot", m_yubiKeyProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll().split(':').at(0), QByteArray("Invalid YubiKey slot invalidslot\n")); + setInput("a"); + execCmd(listCmd, {"ls", "-y", "invalidslot", m_yubiKeyProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll().split(':').at(0), QByteArray("Invalid YubiKey slot invalidslot\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should raise an error if yubikey slot is invalid. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-y", "3", m_yubiKeyProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll().split(':').at(0), QByteArray("Invalid YubiKey slot 3\n")); + setInput("a"); + execCmd(listCmd, {"ls", "-y", "3", m_yubiKeyProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll().split(':').at(0), QByteArray("Invalid YubiKey slot 3\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } -namespace -{ - - void expectParseResult(const QString& input, const QStringList& expectedOutput) - { - QStringList result = Utils::splitCommandString(input); - QCOMPARE(result.size(), expectedOutput.size()); - for (int i = 0; i < expectedOutput.size(); ++i) { - QCOMPARE(result[i], expectedOutput[i]); - } - } - -} // namespace - void TestCli::testCommandParsing_data() { QTest::addColumn<QString>("input"); @@ -2201,23 +1789,25 @@ void TestCli::testCommandParsing() QFETCH(QString, input); QFETCH(QStringList, expectedOutput); - expectParseResult(input, expectedOutput); + QStringList result = Utils::splitCommandString(input); + QCOMPARE(result.size(), expectedOutput.size()); + for (int i = 0; i < expectedOutput.size(); ++i) { + QCOMPARE(result[i], expectedOutput[i]); + } } void TestCli::testOpen() { - Open o; + Open openCmd; - Utils::Test::setNextPassword("a"); - o.execute({"open", m_dbFile->fileName()}); - m_stdoutFile->reset(); - QVERIFY(o.currentDatabase); + setInput("a"); + execCmd(openCmd, {"open", m_dbFile->fileName()}); + QVERIFY(openCmd.currentDatabase); - List l; + List listCmd; // Set a current database, simulating interactive mode. - l.currentDatabase = o.currentDatabase; - l.execute({"ls"}); - m_stdoutFile->reset(); + listCmd.currentDatabase = openCmd.currentDatabase; + execCmd(listCmd, {"ls"}); QByteArray expectedOutput("Sample Entry\n" "General/\n" "Windows/\n" @@ -2225,28 +1815,20 @@ void TestCli::testOpen() "Internet/\n" "eMail/\n" "Homebanking/\n"); - QByteArray actualOutput = m_stdoutFile->readAll(); + QByteArray actualOutput = m_stdout->readAll(); actualOutput.truncate(expectedOutput.length()); QCOMPARE(actualOutput, expectedOutput); } void TestCli::testHelp() { - Help h; + Help helpCmd; Commands::setupCommands(false); - { - h.execute({"help"}); - m_stderrFile->reset(); - QString output(m_stderrFile->readAll()); - QVERIFY(output.contains(QObject::tr("Available commands"))); - } + execCmd(helpCmd, {"help"}); + QVERIFY(m_stdout->readAll().contains("Available commands")); - { - List l; - h.execute({"help", "ls"}); - m_stderrFile->reset(); - QString output(m_stderrFile->readAll()); - QVERIFY(output.contains(l.description)); - } + List listCmd; + execCmd(helpCmd, {"help", "ls"}); + QVERIFY(m_stdout->readAll().contains(listCmd.description.toLatin1())); } diff --git a/tests/TestCli.h b/tests/TestCli.h index 44420d580..a8e6eabbb 100644 --- a/tests/TestCli.h +++ b/tests/TestCli.h @@ -18,30 +18,33 @@ #ifndef KEEPASSXC_TESTCLI_H #define KEEPASSXC_TESTCLI_H -#include "core/Database.h" -#include "util/TemporaryFile.h" - -#include <QByteArray> -#include <QFile> +#include <QBuffer> #include <QScopedPointer> #include <QSharedPointer> -#include <QTemporaryFile> +#include <QStringList> #include <QTest> -#include <stdio.h> +#include "util/TemporaryFile.h" + +class Command; +class Database; class TestCli : public QObject { Q_OBJECT private: - QSharedPointer<Database> readTestDatabase() const; + QSharedPointer<Database> + readDatabase(const QString& filename = {}, const QString& pw = {}, const QString& keyfile = {}); + int execCmd(Command& cmd, const QStringList& args) const; + bool isTotp(const QString& value); + void setInput(const QString& input); + void setInput(const QStringList& input); private slots: void initTestCase(); void init(); void cleanup(); - void cleanupTestCase(); void testBatchCommands(); void testAdd(); @@ -78,21 +81,16 @@ private slots: void testYubiKeyOption(); private: - QByteArray m_dbData; - QByteArray m_dbData2; - QByteArray m_xmlData; - QByteArray m_yubiKeyProtectedDbData; - QByteArray m_keyFileProtectedDbData; - QByteArray m_keyFileProtectedNoPasswordDbData; QScopedPointer<TemporaryFile> m_dbFile; QScopedPointer<TemporaryFile> m_dbFile2; QScopedPointer<TemporaryFile> m_xmlFile; QScopedPointer<TemporaryFile> m_keyFileProtectedDbFile; QScopedPointer<TemporaryFile> m_keyFileProtectedNoPasswordDbFile; QScopedPointer<TemporaryFile> m_yubiKeyProtectedDbFile; - QScopedPointer<TemporaryFile> m_stdoutFile; - QScopedPointer<TemporaryFile> m_stderrFile; - QScopedPointer<TemporaryFile> m_stdinFile; + + QScopedPointer<QBuffer> m_stdout; + QScopedPointer<QBuffer> m_stderr; + QScopedPointer<QBuffer> m_stdin; }; #endif // KEEPASSXC_TESTCLI_H |