mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2026-03-11 08:54:48 +00:00
- The copy action (Control+C) when no text is selected copies the password of the current entry. This should be reasonable when Control+B copies the username. - Down at EOL moves the focus to the entry view. Enter and Tab should do that, but it would be handy for user to be able to get to the third entry by hitting Down three times.
743 lines
28 KiB
C++
743 lines
28 KiB
C++
/*
|
|
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
|
*
|
|
* 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 "TestGui.h"
|
|
|
|
#include <QAction>
|
|
#include <QApplication>
|
|
#include <QDialogButtonBox>
|
|
#include <QLineEdit>
|
|
#include <QLabel>
|
|
#include <QMimeData>
|
|
#include <QPushButton>
|
|
#include <QSpinBox>
|
|
#include <QTemporaryFile>
|
|
#include <QTest>
|
|
#include <QToolBar>
|
|
#include <QToolButton>
|
|
#include <QTimer>
|
|
#include <QSignalSpy>
|
|
|
|
#include "config-keepassx-tests.h"
|
|
#include "core/Config.h"
|
|
#include "core/Database.h"
|
|
#include "core/Entry.h"
|
|
#include "core/Group.h"
|
|
#include "core/Metadata.h"
|
|
#include "core/Tools.h"
|
|
#include "crypto/Crypto.h"
|
|
#include "format/KeePass2Reader.h"
|
|
#include "gui/DatabaseTabWidget.h"
|
|
#include "gui/DatabaseWidget.h"
|
|
#include "gui/FileDialog.h"
|
|
#include "gui/MainWindow.h"
|
|
#include "gui/MessageBox.h"
|
|
#include "gui/SearchWidget.h"
|
|
#include "gui/entry/EditEntryWidget.h"
|
|
#include "gui/entry/EntryView.h"
|
|
#include "gui/group/GroupModel.h"
|
|
#include "gui/group/GroupView.h"
|
|
#include "keys/PasswordKey.h"
|
|
|
|
void TestGui::initTestCase()
|
|
{
|
|
QVERIFY(Crypto::init());
|
|
Config::createTempFileInstance();
|
|
m_mainWindow = new MainWindow();
|
|
m_tabWidget = m_mainWindow->findChild<DatabaseTabWidget*>("tabWidget");
|
|
m_mainWindow->show();
|
|
m_mainWindow->activateWindow();
|
|
Tools::wait(50);
|
|
|
|
// Load the NewDatabase.kdbx file into temporary storage
|
|
QByteArray tmpData;
|
|
QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx"));
|
|
QVERIFY(sourceDbFile.open(QIODevice::ReadOnly));
|
|
QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData));
|
|
sourceDbFile.close();
|
|
}
|
|
|
|
// Every test starts with opening the temp database
|
|
void TestGui::init()
|
|
{
|
|
// Write the temp storage to a temp database file for use in our tests
|
|
QVERIFY(m_dbFile.open());
|
|
QCOMPARE(m_dbFile.write(m_dbData), static_cast<qint64>((m_dbData.size())));
|
|
m_dbFile.close();
|
|
|
|
m_dbFileName = QFileInfo(m_dbFile).fileName();
|
|
|
|
fileDialog()->setNextFileName(m_dbFile.fileName());
|
|
triggerAction("actionDatabaseOpen");
|
|
|
|
QWidget* databaseOpenWidget = m_mainWindow->findChild<QWidget*>("databaseOpenWidget");
|
|
QLineEdit* editPassword = databaseOpenWidget->findChild<QLineEdit*>("editPassword");
|
|
QVERIFY(editPassword);
|
|
|
|
QTest::keyClicks(editPassword, "a");
|
|
QTest::keyClick(editPassword, Qt::Key_Enter);
|
|
Tools::wait(100);
|
|
|
|
QVERIFY(m_tabWidget->currentDatabaseWidget());
|
|
|
|
m_dbWidget = m_tabWidget->currentDatabaseWidget();
|
|
m_db = m_dbWidget->database();
|
|
}
|
|
|
|
// Every test ends with closing the temp database without saving
|
|
void TestGui::cleanup()
|
|
{
|
|
// DO NOT save the database
|
|
MessageBox::setNextAnswer(QMessageBox::No);
|
|
triggerAction("actionDatabaseClose");
|
|
Tools::wait(100);
|
|
|
|
m_db = nullptr;
|
|
m_dbWidget = nullptr;
|
|
}
|
|
|
|
void TestGui::testMergeDatabase()
|
|
{
|
|
// It is safe to ignore the warning this line produces
|
|
QSignalSpy dbMergeSpy(m_dbWidget, SIGNAL(databaseMerged(Database*)));
|
|
|
|
// set file to merge from
|
|
fileDialog()->setNextFileName(QString(KEEPASSX_TEST_DATA_DIR).append("/MergeDatabase.kdbx"));
|
|
triggerAction("actionDatabaseMerge");
|
|
|
|
QWidget* databaseOpenMergeWidget = m_mainWindow->findChild<QWidget*>("databaseOpenMergeWidget");
|
|
QLineEdit* editPasswordMerge = databaseOpenMergeWidget->findChild<QLineEdit*>("editPassword");
|
|
QVERIFY(editPasswordMerge->isVisible());
|
|
|
|
m_tabWidget->currentDatabaseWidget()->setCurrentWidget(databaseOpenMergeWidget);
|
|
|
|
QTest::keyClicks(editPasswordMerge, "a");
|
|
QTest::keyClick(editPasswordMerge, Qt::Key_Enter);
|
|
|
|
QTRY_COMPARE(dbMergeSpy.count(), 1);
|
|
QTRY_VERIFY(m_tabWidget->tabText(m_tabWidget->currentIndex()).contains("*"));
|
|
|
|
m_db = m_tabWidget->currentDatabaseWidget()->database();
|
|
|
|
// there are seven child groups of the root group
|
|
QCOMPARE(m_db->rootGroup()->children().size(), 7);
|
|
// the merged group should contain an entry
|
|
QCOMPARE(m_db->rootGroup()->children().at(6)->entries().size(), 1);
|
|
// the General group contains one entry merged from the other db
|
|
QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 1);
|
|
}
|
|
|
|
void TestGui::testAutoreloadDatabase()
|
|
{
|
|
config()->set("AutoReloadOnChange", false);
|
|
|
|
// Load the MergeDatabase.kdbx file into temporary storage
|
|
QByteArray tmpData;
|
|
QFile mergeDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/MergeDatabase.kdbx"));
|
|
QVERIFY(mergeDbFile.open(QIODevice::ReadOnly));
|
|
QVERIFY(Tools::readAllFromDevice(&mergeDbFile, tmpData));
|
|
mergeDbFile.close();
|
|
|
|
// Test accepting new file in autoreload
|
|
MessageBox::setNextAnswer(QMessageBox::Yes);
|
|
// Overwrite the current database with the temp data
|
|
QVERIFY(m_dbFile.open());
|
|
QVERIFY(m_dbFile.write(tmpData, static_cast<qint64>(tmpData.size())));
|
|
m_dbFile.close();
|
|
Tools::wait(1500);
|
|
|
|
m_db = m_dbWidget->database();
|
|
|
|
// the General group contains one entry from the new db data
|
|
QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 1);
|
|
QVERIFY(! m_tabWidget->tabText(m_tabWidget->currentIndex()).endsWith("*"));
|
|
|
|
// Reset the state
|
|
cleanup();
|
|
init();
|
|
|
|
// Test rejecting new file in autoreload
|
|
MessageBox::setNextAnswer(QMessageBox::No);
|
|
// Overwrite the current temp database with a new file
|
|
m_dbFile.open();
|
|
QVERIFY(m_dbFile.write(tmpData, static_cast<qint64>(tmpData.size())));
|
|
m_dbFile.close();
|
|
Tools::wait(1500);
|
|
|
|
m_db = m_dbWidget->database();
|
|
|
|
// Ensure the merge did not take place
|
|
QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 0);
|
|
QVERIFY(m_tabWidget->tabText(m_tabWidget->currentIndex()).endsWith("*"));
|
|
|
|
// Reset the state
|
|
cleanup();
|
|
init();
|
|
|
|
// Test accepting a merge of edits into autoreload
|
|
// Turn on autoload so we only get one messagebox (for the merge)
|
|
config()->set("AutoReloadOnChange", true);
|
|
|
|
// Modify some entries
|
|
testEditEntry();
|
|
|
|
// This is saying yes to merging the entries
|
|
MessageBox::setNextAnswer(QMessageBox::Yes);
|
|
// Overwrite the current database with the temp data
|
|
QVERIFY(m_dbFile.open());
|
|
QVERIFY(m_dbFile.write(tmpData, static_cast<qint64>(tmpData.size())));
|
|
m_dbFile.close();
|
|
Tools::wait(1500);
|
|
|
|
m_db = m_dbWidget->database();
|
|
|
|
QCOMPARE(m_db->rootGroup()->findChildByName("General")->entries().size(), 1);
|
|
QVERIFY(m_tabWidget->tabText(m_tabWidget->currentIndex()).endsWith("*"));
|
|
}
|
|
|
|
void TestGui::testTabs()
|
|
{
|
|
QCOMPARE(m_tabWidget->count(), 1);
|
|
QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), m_dbFileName);
|
|
}
|
|
|
|
void TestGui::testEditEntry()
|
|
{
|
|
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
|
|
|
// Select the first entry in the database
|
|
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
|
QModelIndex entryItem = entryView->model()->index(0, 1);
|
|
clickIndex(entryItem, entryView, Qt::LeftButton);
|
|
|
|
// Confirm the edit action button is enabled
|
|
QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
|
QVERIFY(entryEditAction->isEnabled());
|
|
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
|
|
QVERIFY(entryEditWidget->isVisible());
|
|
QVERIFY(entryEditWidget->isEnabled());
|
|
|
|
// Edit the first entry ("Sample Entry")
|
|
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
|
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
|
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
|
QTest::keyClicks(titleEdit, "_test");
|
|
|
|
// Save the edit
|
|
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
|
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
|
|
|
// Confirm edit was made
|
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
|
Entry* entry = entryView->entryFromIndex(entryItem);
|
|
QCOMPARE(entry->title(), QString("Sample Entry_test"));
|
|
QCOMPARE(entry->historyItems().size(), 1);
|
|
|
|
// Confirm modified indicator is showing
|
|
QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("%1*").arg(m_dbFileName));
|
|
}
|
|
|
|
void TestGui::testAddEntry()
|
|
{
|
|
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
|
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
|
|
|
// Find the new entry action
|
|
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
|
QVERIFY(entryNewAction->isEnabled());
|
|
|
|
// Find the button associated with the new entry action
|
|
QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction);
|
|
QVERIFY(entryNewWidget->isVisible());
|
|
QVERIFY(entryNewWidget->isEnabled());
|
|
|
|
// Click the new entry button and check that we enter edit mode
|
|
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
|
|
|
// Add entry "test" and confirm added
|
|
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
|
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
|
QTest::keyClicks(titleEdit, "test");
|
|
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
|
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
|
|
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
|
QModelIndex item = entryView->model()->index(1, 1);
|
|
Entry* entry = entryView->entryFromIndex(item);
|
|
|
|
QCOMPARE(entry->title(), QString("test"));
|
|
QCOMPARE(entry->historyItems().size(), 0);
|
|
|
|
// Add entry "something 2"
|
|
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
|
QTest::keyClicks(titleEdit, "something 2");
|
|
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
|
|
|
// Add entry "something 3"
|
|
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
|
QTest::keyClicks(titleEdit, "something 3");
|
|
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
|
|
|
// Confirm that 4 entries now exist
|
|
QTRY_COMPARE(entryView->model()->rowCount(), 4);
|
|
}
|
|
|
|
void TestGui::testEntryEntropy()
|
|
{
|
|
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
|
|
|
// Find the new entry action
|
|
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
|
QVERIFY(entryNewAction->isEnabled());
|
|
|
|
// Find the button associated with the new entry action
|
|
QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction);
|
|
QVERIFY(entryNewWidget->isVisible());
|
|
QVERIFY(entryNewWidget->isEnabled());
|
|
|
|
// Click the new entry button and check that we enter edit mode
|
|
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
|
|
|
// Add entry "test" and confirm added
|
|
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
|
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
|
QTest::keyClicks(titleEdit, "test");
|
|
|
|
// Open the password generator
|
|
QToolButton* generatorButton = editEntryWidget->findChild<QToolButton*>("tooglePasswordGeneratorButton");
|
|
QTest::mouseClick(generatorButton, Qt::LeftButton);
|
|
|
|
// Type in some password
|
|
QLineEdit* editNewPassword = editEntryWidget->findChild<QLineEdit*>("editNewPassword");
|
|
QLabel* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
|
|
QLabel* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
|
|
|
|
editNewPassword->setText("");
|
|
QTest::keyClicks(editNewPassword, "hello");
|
|
QCOMPARE(entropyLabel->text(), QString("Entropy: 6.38 bit"));
|
|
QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor"));
|
|
|
|
editNewPassword->setText("");
|
|
QTest::keyClicks(editNewPassword, "helloworld");
|
|
QCOMPARE(entropyLabel->text(), QString("Entropy: 13.10 bit"));
|
|
QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor"));
|
|
|
|
editNewPassword->setText("");
|
|
QTest::keyClicks(editNewPassword, "password1");
|
|
QCOMPARE(entropyLabel->text(), QString("Entropy: 4.00 bit"));
|
|
QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor"));
|
|
|
|
editNewPassword->setText("");
|
|
QTest::keyClicks(editNewPassword, "D0g..................");
|
|
QCOMPARE(entropyLabel->text(), QString("Entropy: 19.02 bit"));
|
|
QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor"));
|
|
|
|
editNewPassword->setText("");
|
|
QTest::keyClicks(editNewPassword, "Tr0ub4dour&3");
|
|
QCOMPARE(entropyLabel->text(), QString("Entropy: 30.87 bit"));
|
|
QCOMPARE(strengthLabel->text(), QString("Password Quality: Poor"));
|
|
|
|
editNewPassword->setText("");
|
|
QTest::keyClicks(editNewPassword, "correcthorsebatterystaple");
|
|
QCOMPARE(entropyLabel->text(), QString("Entropy: 47.98 bit"));
|
|
QCOMPARE(strengthLabel->text(), QString("Password Quality: Weak"));
|
|
|
|
editNewPassword->setText("");
|
|
QTest::keyClicks(editNewPassword, "YQC3kbXbjC652dTDH");
|
|
QCOMPARE(entropyLabel->text(), QString("Entropy: 96.07 bit"));
|
|
QCOMPARE(strengthLabel->text(), QString("Password Quality: Good"));
|
|
|
|
editNewPassword->setText("");
|
|
QTest::keyClicks(editNewPassword, "Bs5ZFfthWzR8DGFEjaCM6bGqhmCT4km");
|
|
QCOMPARE(entropyLabel->text(), QString("Entropy: 174.59 bit"));
|
|
QCOMPARE(strengthLabel->text(), QString("Password Quality: Excellent"));
|
|
|
|
// We are done
|
|
}
|
|
|
|
void TestGui::testSearch()
|
|
{
|
|
// Add canned entries for consistent testing
|
|
testAddEntry();
|
|
|
|
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
|
|
|
SearchWidget* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
|
|
QVERIFY(searchWidget->isEnabled());
|
|
QLineEdit* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
|
|
|
|
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
|
QVERIFY(entryView->isVisible());
|
|
|
|
// Enter search
|
|
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
|
QTRY_VERIFY(searchTextEdit->hasFocus());
|
|
// Search for "ZZZ"
|
|
QTest::keyClicks(searchTextEdit, "ZZZ");
|
|
QTRY_COMPARE(searchTextEdit->text(), QString("ZZZ"));
|
|
QTRY_VERIFY(m_dbWidget->isInSearchMode());
|
|
QTRY_COMPARE(entryView->model()->rowCount(), 0);
|
|
// Escape clears searchedit and retains focus
|
|
QTest::keyClick(searchTextEdit, Qt::Key_Escape);
|
|
QTRY_VERIFY(searchTextEdit->text().isEmpty());
|
|
QTRY_VERIFY(searchTextEdit->hasFocus());
|
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
|
// Search for "some"
|
|
QTest::keyClicks(searchTextEdit, "some");
|
|
QTRY_VERIFY(m_dbWidget->isInSearchMode());
|
|
QTRY_COMPARE(entryView->model()->rowCount(), 3);
|
|
// Search for "someTHING"
|
|
QTest::keyClicks(searchTextEdit, "THING");
|
|
QTRY_COMPARE(entryView->model()->rowCount(), 2);
|
|
// Press Down to focus on the entry view if at EOL
|
|
QTest::keyClick(searchTextEdit, Qt::Key_Left);
|
|
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
|
QTRY_VERIFY(searchTextEdit->hasFocus());
|
|
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
|
QTRY_VERIFY(entryView->hasFocus());
|
|
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
|
|
|
// Test case sensitive search
|
|
searchWidget->setCaseSensitive(true);
|
|
QTRY_COMPARE(entryView->model()->rowCount(), 0);
|
|
searchWidget->setCaseSensitive(false);
|
|
QTRY_COMPARE(entryView->model()->rowCount(), 2);
|
|
|
|
// Test group search
|
|
GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
|
QCOMPARE(groupView->currentGroup(), m_db->rootGroup());
|
|
QModelIndex rootGroupIndex = groupView->model()->index(0, 0);
|
|
clickIndex(groupView->model()->index(0, 0, rootGroupIndex), groupView, Qt::LeftButton);
|
|
QCOMPARE(groupView->currentGroup()->name(), QString("General"));
|
|
QTRY_COMPARE(entryView->model()->rowCount(), 0);
|
|
// reset
|
|
clickIndex(rootGroupIndex, groupView, Qt::LeftButton);
|
|
QCOMPARE(groupView->currentGroup(), m_db->rootGroup());
|
|
|
|
// Try to edit the first entry from the search view
|
|
QModelIndex item = entryView->model()->index(0, 1);
|
|
Entry* entry = entryView->entryFromIndex(item);
|
|
QVERIFY(m_dbWidget->isInSearchMode());
|
|
clickIndex(item, entryView, Qt::LeftButton);
|
|
QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
|
|
QVERIFY(entryEditAction->isEnabled());
|
|
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
|
|
QVERIFY(entryEditWidget->isVisible());
|
|
QVERIFY(entryEditWidget->isEnabled());
|
|
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
|
|
|
|
// Perform the edit and save it
|
|
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
|
|
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
|
QString origTitle = titleEdit->text();
|
|
QTest::keyClicks(titleEdit, "_edited");
|
|
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
|
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
|
|
|
// Confirm the edit was made and we are back in view mode
|
|
QTRY_VERIFY(m_dbWidget->isInSearchMode());
|
|
QCOMPARE(entry->title(), origTitle.append("_edited"));
|
|
|
|
// Cancel search, should return to normal view
|
|
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
|
QTest::keyClick(searchTextEdit, Qt::Key_Escape);
|
|
QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
|
}
|
|
|
|
void TestGui::testDeleteEntry()
|
|
{
|
|
// Add canned entries for consistent testing
|
|
testAddEntry();
|
|
|
|
GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
|
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
|
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
|
QAction* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete");
|
|
QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction);
|
|
|
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
|
clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton);
|
|
QVERIFY(entryDeleteWidget->isVisible());
|
|
QVERIFY(entryDeleteWidget->isEnabled());
|
|
QVERIFY(!m_db->metadata()->recycleBin());
|
|
|
|
MessageBox::setNextAnswer(QMessageBox::Yes);
|
|
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
|
|
|
|
QCOMPARE(entryView->model()->rowCount(), 3);
|
|
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 1);
|
|
|
|
clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton);
|
|
clickIndex(entryView->model()->index(2, 0), entryView, Qt::LeftButton, Qt::ControlModifier);
|
|
QCOMPARE(entryView->selectionModel()->selectedRows().size(), 2);
|
|
|
|
MessageBox::setNextAnswer(QMessageBox::No);
|
|
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
|
|
QCOMPARE(entryView->model()->rowCount(), 3);
|
|
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 1);
|
|
|
|
MessageBox::setNextAnswer(QMessageBox::Yes);
|
|
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
|
|
QCOMPARE(entryView->model()->rowCount(), 1);
|
|
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 3);
|
|
|
|
QCOMPARE(groupView->currentGroup(), m_db->rootGroup());
|
|
QModelIndex rootGroupIndex = groupView->model()->index(0, 0);
|
|
clickIndex(groupView->model()->index(groupView->model()->rowCount(rootGroupIndex) - 1, 0, rootGroupIndex),
|
|
groupView, Qt::LeftButton);
|
|
QCOMPARE(groupView->currentGroup()->name(), m_db->metadata()->recycleBin()->name());
|
|
|
|
clickIndex(entryView->model()->index(0, 0), entryView, Qt::LeftButton);
|
|
MessageBox::setNextAnswer(QMessageBox::No);
|
|
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
|
|
QCOMPARE(entryView->model()->rowCount(), 3);
|
|
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 3);
|
|
|
|
MessageBox::setNextAnswer(QMessageBox::Yes);
|
|
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
|
|
QCOMPARE(entryView->model()->rowCount(), 2);
|
|
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 2);
|
|
|
|
clickIndex(entryView->model()->index(0, 0), entryView, Qt::LeftButton);
|
|
clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton, Qt::ControlModifier);
|
|
MessageBox::setNextAnswer(QMessageBox::Yes);
|
|
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
|
|
QCOMPARE(entryView->model()->rowCount(), 0);
|
|
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 0);
|
|
|
|
clickIndex(groupView->model()->index(0, 0),
|
|
groupView, Qt::LeftButton);
|
|
QCOMPARE(groupView->currentGroup(), m_db->rootGroup());
|
|
}
|
|
|
|
void TestGui::testCloneEntry()
|
|
{
|
|
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
|
|
|
QCOMPARE(entryView->model()->rowCount(), 1);
|
|
|
|
QModelIndex item = entryView->model()->index(0, 1);
|
|
Entry* entryOrg = entryView->entryFromIndex(item);
|
|
clickIndex(item, entryView, Qt::LeftButton);
|
|
|
|
triggerAction("actionEntryClone");
|
|
|
|
QCOMPARE(entryView->model()->rowCount(), 2);
|
|
Entry* entryClone = entryView->entryFromIndex(entryView->model()->index(1, 1));
|
|
QVERIFY(entryOrg->uuid() != entryClone->uuid());
|
|
QCOMPARE(entryClone->title(), entryOrg->title());
|
|
}
|
|
|
|
void TestGui::testDragAndDropEntry()
|
|
{
|
|
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
|
GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView");
|
|
QAbstractItemModel* groupModel = groupView->model();
|
|
|
|
QModelIndex sourceIndex = entryView->model()->index(0, 1);
|
|
QModelIndex targetIndex = groupModel->index(0, 0, groupModel->index(0, 0));
|
|
QVERIFY(sourceIndex.isValid());
|
|
QVERIFY(targetIndex.isValid());
|
|
|
|
QMimeData mimeData;
|
|
QByteArray encoded;
|
|
QDataStream stream(&encoded, QIODevice::WriteOnly);
|
|
Entry* entry = entryView->entryFromIndex(sourceIndex);
|
|
stream << entry->group()->database()->uuid() << entry->uuid();
|
|
mimeData.setData("application/x-keepassx-entry", encoded);
|
|
|
|
QVERIFY(groupModel->dropMimeData(&mimeData, Qt::MoveAction, -1, 0, targetIndex));
|
|
QCOMPARE(entry->group()->name(), QString("General"));
|
|
}
|
|
|
|
void TestGui::testDragAndDropGroup()
|
|
{
|
|
QAbstractItemModel* groupModel = m_dbWidget->findChild<GroupView*>("groupView")->model();
|
|
QModelIndex rootIndex = groupModel->index(0, 0);
|
|
|
|
dragAndDropGroup(groupModel->index(0, 0, rootIndex),
|
|
groupModel->index(1, 0, rootIndex),
|
|
-1, true, "Windows", 0);
|
|
|
|
// dropping parent on child is supposed to fail
|
|
dragAndDropGroup(groupModel->index(0, 0, rootIndex),
|
|
groupModel->index(0, 0, groupModel->index(0, 0, rootIndex)),
|
|
-1, false, "NewDatabase", 0);
|
|
|
|
dragAndDropGroup(groupModel->index(1, 0, rootIndex),
|
|
rootIndex,
|
|
0, true, "NewDatabase", 0);
|
|
|
|
dragAndDropGroup(groupModel->index(0, 0, rootIndex),
|
|
rootIndex,
|
|
-1, true, "NewDatabase", 4);
|
|
}
|
|
|
|
void TestGui::testSaveAs()
|
|
{
|
|
QFileInfo fileInfo(m_dbFile.fileName());
|
|
QDateTime lastModified = fileInfo.lastModified();
|
|
|
|
m_db->metadata()->setName("SaveAs");
|
|
|
|
// open temporary file so it creates a filename
|
|
QTemporaryFile tmpFile;
|
|
QVERIFY(tmpFile.open());
|
|
QString tmpFileName = tmpFile.fileName();
|
|
tmpFile.remove();
|
|
|
|
fileDialog()->setNextFileName(tmpFileName);
|
|
|
|
triggerAction("actionDatabaseSaveAs");
|
|
|
|
QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("SaveAs"));
|
|
|
|
checkDatabase(tmpFileName);
|
|
|
|
fileInfo.refresh();
|
|
QCOMPARE(fileInfo.lastModified(), lastModified);
|
|
}
|
|
|
|
void TestGui::testSave()
|
|
{
|
|
m_db->metadata()->setName("Save");
|
|
// wait for modified timer
|
|
QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save*"));
|
|
|
|
triggerAction("actionDatabaseSave");
|
|
QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save"));
|
|
|
|
checkDatabase();
|
|
}
|
|
|
|
void TestGui::testDatabaseSettings()
|
|
{
|
|
triggerAction("actionChangeDatabaseSettings");
|
|
QWidget* dbSettingsWidget = m_dbWidget->findChild<QWidget*>("databaseSettingsWidget");
|
|
QSpinBox* transformRoundsSpinBox = dbSettingsWidget->findChild<QSpinBox*>("transformRoundsSpinBox");
|
|
transformRoundsSpinBox->setValue(100);
|
|
QTest::keyClick(transformRoundsSpinBox, Qt::Key_Enter);
|
|
// wait for modified timer
|
|
QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save*"));
|
|
QCOMPARE(m_db->transformRounds(), Q_UINT64_C(100));
|
|
|
|
triggerAction("actionDatabaseSave");
|
|
QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save"));
|
|
|
|
checkDatabase();
|
|
}
|
|
|
|
void TestGui::testKeePass1Import()
|
|
{
|
|
fileDialog()->setNextFileName(QString(KEEPASSX_TEST_DATA_DIR).append("/basic.kdb"));
|
|
triggerAction("actionImportKeePass1");
|
|
|
|
QWidget* keepass1OpenWidget = m_mainWindow->findChild<QWidget*>("keepass1OpenWidget");
|
|
QLineEdit* editPassword = keepass1OpenWidget->findChild<QLineEdit*>("editPassword");
|
|
QVERIFY(editPassword);
|
|
|
|
QTest::keyClicks(editPassword, "masterpw");
|
|
QTest::keyClick(editPassword, Qt::Key_Enter);
|
|
|
|
QCOMPARE(m_tabWidget->count(), 2);
|
|
QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("basic [New database]*"));
|
|
|
|
// Close the KeePass1 Database
|
|
MessageBox::setNextAnswer(QMessageBox::No);
|
|
triggerAction("actionDatabaseClose");
|
|
Tools::wait(100);
|
|
|
|
}
|
|
|
|
void TestGui::testDatabaseLocking()
|
|
{
|
|
QString origDbName = m_tabWidget->tabText(0);
|
|
|
|
MessageBox::setNextAnswer(QMessageBox::Cancel);
|
|
triggerAction("actionLockDatabases");
|
|
|
|
QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName + " [locked]");
|
|
|
|
QWidget* dbWidget = m_tabWidget->currentDatabaseWidget();
|
|
QWidget* unlockDatabaseWidget = dbWidget->findChild<QWidget*>("unlockDatabaseWidget");
|
|
QWidget* editPassword = unlockDatabaseWidget->findChild<QLineEdit*>("editPassword");
|
|
QVERIFY(editPassword);
|
|
|
|
QTest::keyClicks(editPassword, "a");
|
|
QTest::keyClick(editPassword, Qt::Key_Enter);
|
|
|
|
QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName);
|
|
}
|
|
|
|
void TestGui::cleanupTestCase()
|
|
{
|
|
delete m_mainWindow;
|
|
}
|
|
|
|
void TestGui::checkDatabase(QString dbFileName)
|
|
{
|
|
if (dbFileName.isEmpty())
|
|
dbFileName = m_dbFile.fileName();
|
|
|
|
CompositeKey key;
|
|
key.addKey(PasswordKey("a"));
|
|
KeePass2Reader reader;
|
|
QScopedPointer<Database> dbSaved(reader.readDatabase(dbFileName, key));
|
|
QVERIFY(dbSaved);
|
|
QVERIFY(!reader.hasError());
|
|
QCOMPARE(dbSaved->metadata()->name(), m_db->metadata()->name());
|
|
}
|
|
|
|
void TestGui::triggerAction(const QString& name)
|
|
{
|
|
QAction* action = m_mainWindow->findChild<QAction*>(name);
|
|
QVERIFY(action);
|
|
QVERIFY(action->isEnabled());
|
|
action->trigger();
|
|
}
|
|
|
|
void TestGui::dragAndDropGroup(const QModelIndex& sourceIndex, const QModelIndex& targetIndex, int row,
|
|
bool expectedResult, const QString& expectedParentName, int expectedPos)
|
|
{
|
|
QVERIFY(sourceIndex.isValid());
|
|
QVERIFY(targetIndex.isValid());
|
|
|
|
GroupModel* groupModel = qobject_cast<GroupModel*>(m_dbWidget->findChild<GroupView*>("groupView")->model());
|
|
|
|
QMimeData mimeData;
|
|
QByteArray encoded;
|
|
QDataStream stream(&encoded, QIODevice::WriteOnly);
|
|
Group* group = groupModel->groupFromIndex(sourceIndex);
|
|
stream << group->database()->uuid() << group->uuid();
|
|
mimeData.setData("application/x-keepassx-group", encoded);
|
|
|
|
QCOMPARE(groupModel->dropMimeData(&mimeData, Qt::MoveAction, row, 0, targetIndex), expectedResult);
|
|
QCOMPARE(group->parentGroup()->name(), expectedParentName);
|
|
QCOMPARE(group->parentGroup()->children().indexOf(group), expectedPos);
|
|
}
|
|
|
|
void TestGui::clickIndex(const QModelIndex& index, QAbstractItemView* view, Qt::MouseButton button,
|
|
Qt::KeyboardModifiers stateKey)
|
|
{
|
|
QTest::mouseClick(view->viewport(), button, stateKey, view->visualRect(index).center());
|
|
}
|
|
|
|
QTEST_MAIN(TestGui)
|