CLI: Add group commands

This commit is contained in:
louib 2019-09-12 11:08:28 -04:00 committed by Jonathan White
parent 964478e78f
commit 19f87ca057
12 changed files with 1066 additions and 497 deletions

View File

@ -10,6 +10,7 @@
- CLI: Add CSV export to the 'export' command [#3277]
- CLI: Add `-y --yubikey` option for YubiKey [#3416](https://github.com/keepassxreboot/keepassxc/issues/3416)
- Add 'Monospaced font' option to the Notes field [#3321](https://github.com/keepassxreboot/keepassxc/issues/3321)
- CLI: Add group commands (mv, mkdir and rmdir) [#3313].
### Changed

80
src/cli/AddGroup.cpp Normal file
View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <stdio.h>
#include "AddGroup.h"
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
AddGroup::AddGroup()
{
name = QString("mkdir");
description = QObject::tr("Adds a new group to a database.");
positionalArguments.append({QString("group"), QObject::tr("Path of the group to add."), QString("")});
}
AddGroup::~AddGroup()
{
}
int AddGroup::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments();
const QString& databasePath = args.at(0);
const QString& groupPath = args.at(1);
QStringList pathParts = groupPath.split("/");
QString groupName = pathParts.takeLast();
QString parentGroupPath = pathParts.join("/");
Group* group = database->rootGroup()->findGroupByPath(groupPath);
if (group) {
errorTextStream << QObject::tr("Group %1 already exists!").arg(groupPath) << endl;
return EXIT_FAILURE;
}
Group* parentGroup = database->rootGroup()->findGroupByPath(parentGroupPath);
if (!parentGroup) {
errorTextStream << QObject::tr("Group %1 not found.").arg(parentGroupPath) << endl;
return EXIT_FAILURE;
}
Group* newGroup = new Group();
newGroup->setUuid(QUuid::createUuid());
newGroup->setName(groupName);
newGroup->setParent(parentGroup);
QString errorMessage;
if (!database->save(databasePath, &errorMessage, true, false)) {
errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl;
return EXIT_FAILURE;
}
if (!parser->isSet(Command::QuietOption)) {
outputTextStream << QObject::tr("Successfully added group %1.").arg(groupName) << endl;
}
return EXIT_SUCCESS;
}

32
src/cli/AddGroup.h Normal file
View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_ADDGROUP_H
#define KEEPASSXC_ADDGROUP_H
#include "DatabaseCommand.h"
class AddGroup : public DatabaseCommand
{
public:
AddGroup();
~AddGroup();
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
};
#endif // KEEPASSXC_ADDGROUP_H

View File

@ -15,6 +15,7 @@
set(cli_SOURCES
Add.cpp
AddGroup.cpp
Analyze.cpp
Clip.cpp
Create.cpp
@ -28,7 +29,9 @@ set(cli_SOURCES
List.cpp
Locate.cpp
Merge.cpp
Move.cpp
Remove.cpp
RemoveGroup.cpp
Show.cpp)
add_library(cli STATIC ${cli_SOURCES})

View File

@ -23,6 +23,7 @@
#include "Command.h"
#include "Add.h"
#include "AddGroup.h"
#include "Analyze.h"
#include "Clip.h"
#include "Create.h"
@ -34,7 +35,9 @@
#include "List.h"
#include "Locate.h"
#include "Merge.h"
#include "Move.h"
#include "Remove.h"
#include "RemoveGroup.h"
#include "Show.h"
#include "TextStream.h"
#include "Utils.h"
@ -125,7 +128,10 @@ void populateCommands()
commands.insert(QString("locate"), new Locate());
commands.insert(QString("ls"), new List());
commands.insert(QString("merge"), new Merge());
commands.insert(QString("mkdir"), new AddGroup());
commands.insert(QString("mv"), new Move());
commands.insert(QString("rm"), new Remove());
commands.insert(QString("rmdir"), new RemoveGroup());
commands.insert(QString("show"), new Show());
}
}

81
src/cli/Move.cpp Normal file
View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <stdio.h>
#include "Move.h"
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
Move::Move()
{
name = QString("mv");
description = QObject::tr("Moves an entry to a new group.");
positionalArguments.append({QString("entry"), QObject::tr("Path of the entry to move."), QString("")});
positionalArguments.append({QString("group"), QObject::tr("Path of the destination group."), QString("")});
}
Move::~Move()
{
}
int Move::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments();
const QString& databasePath = args.at(0);
const QString& entryPath = args.at(1);
const QString& destinationPath = args.at(2);
Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) {
errorTextStream << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl;
return EXIT_FAILURE;
}
Group* destinationGroup = database->rootGroup()->findGroupByPath(destinationPath);
if (!destinationGroup) {
errorTextStream << QObject::tr("Could not find group with path %1.").arg(destinationPath) << endl;
return EXIT_FAILURE;
}
if (destinationGroup == entry->parent()) {
errorTextStream << QObject::tr("Entry is already in group %1.").arg(destinationPath) << endl;
return EXIT_FAILURE;
}
entry->beginUpdate();
entry->setGroup(destinationGroup);
entry->endUpdate();
QString errorMessage;
if (!database->save(databasePath, &errorMessage, true, false)) {
errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl;
return EXIT_FAILURE;
}
outputTextStream << QObject::tr("Successfully moved entry %1 to group %2.").arg(entry->title(), destinationPath)
<< endl;
return EXIT_SUCCESS;
}

32
src/cli/Move.h Normal file
View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_MOVE_H
#define KEEPASSXC_MOVE_H
#include "DatabaseCommand.h"
class Move : public DatabaseCommand
{
public:
Move();
~Move();
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
};
#endif // KEEPASSXC_MOVE_H

85
src/cli/RemoveGroup.cpp Normal file
View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <stdio.h>
#include "RemoveGroup.h"
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
RemoveGroup::RemoveGroup()
{
name = QString("rmdir");
description = QString("Removes a group from a database.");
positionalArguments.append({QString("group"), QObject::tr("Path of the group to remove."), QString("")});
}
RemoveGroup::~RemoveGroup()
{
}
int RemoveGroup::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
bool quiet = parser->isSet(Command::QuietOption);
QString databasePath = parser->positionalArguments().at(0);
QString groupPath = parser->positionalArguments().at(1);
TextStream outputTextStream(quiet ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
// Recursive option means were looking for a group to remove.
QPointer<Group> group = database->rootGroup()->findGroupByPath(groupPath);
if (!group) {
errorTextStream << QObject::tr("Group %1 not found.").arg(groupPath) << endl;
return EXIT_FAILURE;
}
if (group == database->rootGroup()) {
errorTextStream << QObject::tr("Cannot remove root group from database.") << endl;
return EXIT_FAILURE;
}
bool recycled = true;
auto* recycleBin = database->metadata()->recycleBin();
if (!database->metadata()->recycleBinEnabled() || (recycleBin && recycleBin->findGroupByUuid(group->uuid()))) {
delete group;
recycled = false;
} else {
database->recycleGroup(group);
};
QString errorMessage;
if (!database->save(databasePath, &errorMessage, true, false)) {
errorTextStream << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}
if (recycled) {
outputTextStream << QObject::tr("Successfully recycled group %1.").arg(groupPath) << endl;
} else {
outputTextStream << QObject::tr("Successfully deleted group %1.").arg(groupPath) << endl;
}
return EXIT_SUCCESS;
}

32
src/cli/RemoveGroup.h Normal file
View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_REMOVEGROUP_H
#define KEEPASSXC_REMOVEGROUP_H
#include "DatabaseCommand.h"
class RemoveGroup : public DatabaseCommand
{
public:
RemoveGroup();
~RemoveGroup();
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
};
#endif // KEEPASSXC_REMOVEGROUP_H

View File

@ -51,9 +51,18 @@ Lists the contents of a group in a database. If no group is specified, it will d
.IP "merge [options] <database1> <database2>"
Merges two databases together. The first database file is going to be replaced by the result of the merge, for that reason it is advisable to keep a backup of the two database files before attempting a merge. In the case that both databases make use of the same credentials, the \fI--same-credentials\fP or \fI-s\fP option can be used.
.IP "mkdir [options] <database> <group>"
Adds a new group to a database.
.IP "mv [options] <database> <entry> <group>"
Moves an entry to a new group.
.IP "rm [options] <database> <entry>"
Removes an entry from a database. If the database has a recycle bin, the entry will be moved there. If the entry is already in the recycle bin, it will be removed permanently.
.IP "rmdir [options] <database> <group>"
Removes a group from a database. If the database has a recycle bin, the group will be moved there. If the group is already in the recycle bin, it will be removed permanently.
.IP "show [options] <database> <entry>"
Shows the title, username, password, URL and notes of a database entry. Can also show the current TOTP. Regarding the occurrence of multiple entries with the same name in different groups, everything stated in the \fIclip\fP command section also applies here.

View File

@ -32,6 +32,7 @@
#include "format/KeePass2.h"
#include "cli/Add.h"
#include "cli/AddGroup.h"
#include "cli/Analyze.h"
#include "cli/Clip.h"
#include "cli/Command.h"
@ -44,7 +45,9 @@
#include "cli/List.h"
#include "cli/Locate.h"
#include "cli/Merge.h"
#include "cli/Move.h"
#include "cli/Remove.h"
#include "cli/RemoveGroup.h"
#include "cli/Show.h"
#include "cli/Utils.h"
@ -172,7 +175,7 @@ QSharedPointer<Database> TestCli::readTestDatabase() const
void TestCli::testCommand()
{
QCOMPARE(Command::getCommands().size(), 14);
QCOMPARE(Command::getCommands().size(), 17);
QVERIFY(Command::getCommand("add"));
QVERIFY(Command::getCommand("analyze"));
QVERIFY(Command::getCommand("clip"));
@ -295,20 +298,73 @@ void TestCli::testAdd()
QVERIFY(!defaultPasswordClassesRegex.match(entry->password()).hasMatch());
}
void TestCli::testAnalyze()
void TestCli::testAddGroup()
{
Analyze analyzeCmd;
QVERIFY(!analyzeCmd.name.isEmpty());
QVERIFY(analyzeCmd.getDescriptionLine().contains(analyzeCmd.name));
const QString hibpPath = QString(KEEPASSX_TEST_DATA_DIR).append("/hibp.txt");
AddGroup addGroupCmd;
QVERIFY(!addGroupCmd.name.isEmpty());
QVERIFY(addGroupCmd.getDescriptionLine().contains(addGroupCmd.name));
Utils::Test::setNextPassword("a");
analyzeCmd.execute({"analyze", "--hibp", hibpPath, m_dbFile->fileName()});
addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/new_group"});
m_stderrFile->reset();
m_stdoutFile->reset();
m_stdoutFile->readLine(); // skip password prompt
auto output = m_stdoutFile->readAll();
QVERIFY(output.contains("Sample Entry") && output.contains("123"));
QCOMPARE(m_stderrFile->readAll(), QByteArray(""));
QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added group new_group.\n"));
auto db = readTestDatabase();
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(""));
// 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();
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(""));
// 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)
@ -325,6 +381,22 @@ bool isTOTP(const QString& value)
return true;
}
void TestCli::testAnalyze()
{
Analyze analyzeCmd;
QVERIFY(!analyzeCmd.name.isEmpty());
QVERIFY(analyzeCmd.getDescriptionLine().contains(analyzeCmd.name));
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"));
}
void TestCli::testClip()
{
QClipboard* clipboard = QGuiApplication::clipboard();
@ -1245,6 +1317,63 @@ void TestCli::testMerge()
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
}
void TestCli::testMove()
{
Move moveCmd;
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();
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"));
// sanity check
db = readTestDatabase();
entry = db->rootGroup()->findEntryByPath("General/Sample Entry");
QVERIFY(entry);
}
void TestCli::testRemove()
{
Remove removeCmd;
@ -1264,6 +1393,7 @@ void TestCli::testRemove()
fileCopy.close();
qint64 pos = m_stdoutFile->pos();
qint64 posErr = m_stderrFile->pos();
// delete entry and verify
Utils::Test::setNextPassword("a");
@ -1271,6 +1401,7 @@ void TestCli::testRemove()
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(""));
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("a"));
@ -1283,6 +1414,7 @@ void TestCli::testRemove()
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
@ -1301,16 +1433,89 @@ void TestCli::testRemove()
QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry"));
QVERIFY(!readBackDb->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin"))));
pos = m_stdoutFile->pos();
// 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->reset();
m_stderrFile->seek(posErr);
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry /Sample Entry not found.\n"));
// 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"));
}
void TestCli::testRemoveGroup()
{
RemoveGroup removeGroupCmd;
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();
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();
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(""));
// 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(""));
}
void TestCli::testRemoveQuiet()

View File

@ -45,6 +45,7 @@ private slots:
void testCommand();
void testAdd();
void testAddGroup();
void testAnalyze();
void testClip();
void testCreate();
@ -60,7 +61,9 @@ private slots:
void testList();
void testLocate();
void testMerge();
void testMove();
void testRemove();
void testRemoveGroup();
void testRemoveQuiet();
void testShow();
void testInvalidDbFiles();