Skip to content

Commit 2b2d6d0

Browse files
annejanclaude
andauthored
test(keygendialog): widget tests for the GPG keygen dialog (#1476)
* chore: clang-format two over-long QVERIFY2 lines in tst_integration.cpp CodeRabbit's auto-fix on #1471 captured the QFile::write() return value and asserted it equals the payload size — sensible nit. The resulting QVERIFY2 calls busted the 80-col limit and the super-linter is rejecting unrelated PRs (#1474 head was 7430a35) because clang-format runs over the whole tree. Reformat both calls — break after the condition, matches the style of all the other QVERIFY2 sites added in earlier security PRs. No semantic change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(keygendialog): widget tests for the GPG keygen dialog KeygenDialog (the modal that fronts GPG key-pair generation) had zero test coverage. Added six widget-level tests in a new tests/auto/ keygendialog/ subdirectory, modelled on the existing tests/auto/ importkeydialog/ pattern: - constructionLoadsNonEmptyTemplate: dialog instantiates, plainTextEdit receives the default GPG batch template (covers both the ed25519 and RSA fallback paths via the shared Name-Real:/Name-Email: substring check). - expertCheckboxTogglesTemplateEditor: toggling the "Expert" checkbox flips plainTextEdit between read-only/disabled and editable/enabled. - nameTextUpdatesNameRealLine: typing in the Name field replaces the Name-Real: line in the template. - emailTextUpdatesNameEmailLine: same for Name-Email:. - matchingPassphrasesEnableButtonBox: identical text in both passphrase fields enables the OK button. - mismatchedPassphrasesDisableButtonBox: differing passphrases disable OK. The dialog is normally parented to ConfigDialog; tests pass nullptr to sidestep ConfigDialog construction. The protected done() slot isn't exercised — it dispatches into dialog->genKey() which dereferences the ConfigDialog parent. Build clean, 8/8 keygendialog tests pass, 46/46 ui tests still pass, doxygen unchanged. Wired into tests/auto/auto.pro SUBDIRS and .gitignore. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(keygendialog): QVERIFY2 + scanner-safe passphrase fixtures Two reviewer nits on #1476: - Replace bare QVERIFY(obj != nullptr) with QVERIFY2(obj != nullptr, "<name> != nullptr") at every findChild site so a future test failure reports which specific widget couldn't be located. - Swap the "secret" passphrase literals in the two passphrase-matching tests for "testkey123" / "testkey456" — gitleaks-friendly fixtures that still exercise the equality / inequality paths. No behavioural change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(keygendialog): descriptive QVERIFY2 messages instead of tautological ones CodeRabbit nit on the previous follow-up: "checkBox != nullptr" as a failure message just restates the condition; QVERIFY2 already prints the condition. Switch to the existing "<widget-name> widget must exist" style already used on line 47 for plainTextEdit. No behavioural change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e845f01 commit 2b2d6d0

5 files changed

Lines changed: 209 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ tests/auto/gpgkeystate/tst_gpgkeystate
3737
tests/auto/integration/tst_integration
3838
tests/auto/exportpublickeydialog/tst_exportpublickeydialog
3939
tests/auto/importkeydialog/tst_importkeydialog
40+
tests/auto/keygendialog/tst_keygendialog
4041
tests/auto/locale/tst_locale
4142
tests/auto/locale/.qm/
4243
tests/auto/locale/qmake_qmake_qm_files.qrc

tests/auto/auto.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
TEMPLATE = subdirs
2-
SUBDIRS += util ui model settings passwordconfig filecontent simpletransaction gpgkeystate exportpublickeydialog importkeydialog locale
2+
SUBDIRS += util ui model settings passwordconfig filecontent simpletransaction gpgkeystate exportpublickeydialog importkeydialog keygendialog locale
33
win32: SUBDIRS -= executor
44
!win32: SUBDIRS += executor
55
!win32: SUBDIRS += integration
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
!include(../auto.pri) { error("Couldn't find the auto.pri file!") }
2+
3+
SOURCES += tst_keygendialog.cpp
4+
5+
LIBS = -L"$$OUT_PWD/../../../src/$(OBJECTS_DIR)" -lqtpass $$LIBS
6+
clang|gcc:PRE_TARGETDEPS += "$$OUT_PWD/../../../src/$(OBJECTS_DIR)/libqtpass.a"
7+
8+
HEADERS += keygendialog.h
9+
10+
OBJ_PATH += ../../../src/$(OBJECTS_DIR)
11+
12+
VPATH += ../../../src
13+
INCLUDEPATH += ../../../src
14+
15+
win32 {
16+
RC_FILE = ../../../windows.rc
17+
QMAKE_LINK_OBJECT_MAX = 24
18+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDisplayName</key>
6+
<string>QtPass @SHORT_VERSION@</string>
7+
<key>CFBundleExecutable</key>
8+
<string>@EXECUTABLE@</string>
9+
<key>CFBundleGetInfoString</key>
10+
<string>@SHORT_VERSION@</string>
11+
<key>CFBundleIconFile</key>
12+
<string>icon.icns</string>
13+
<key>CFBundleIdentifier</key>
14+
<string>org.qtpass</string>
15+
<key>CFBundlePackageType</key>
16+
<string>APPL</string>
17+
<key>CFBundleShortVersionString</key>
18+
<string>@SHORT_VERSION@</string>
19+
<key>CFBundleSignature</key>
20+
<string>@TYPEINFO@</string>
21+
<key>NOTE</key>
22+
<string>QtPass is a multi-platform GUI for pass</string>
23+
<key>NSHumanReadableCopyright</key>
24+
<string>Copyright © 2014-2026 IJhack
25+
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 3 of the License, or (at your option) any later version.</string>
26+
<key>NSPrincipalClass</key>
27+
<string>NSApplication</string>
28+
<key>NSHighResolutionCapable</key>
29+
<string>True</string>
30+
</dict>
31+
</plist>
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2+
// SPDX-License-Identifier: GPL-3.0-or-later
3+
#include <QApplication>
4+
#include <QCheckBox>
5+
#include <QDialogButtonBox>
6+
#include <QLineEdit>
7+
#include <QPlainTextEdit>
8+
#include <QPushButton>
9+
#include <QtTest>
10+
11+
#include "../../../src/keygendialog.h"
12+
13+
/**
14+
* @class tst_keygendialog
15+
* @brief Widget-level tests for KeygenDialog.
16+
*
17+
* KeygenDialog is the modal that fronts GPG key-pair generation. These tests
18+
* cover construction and the input-driven UI state transitions; they don't
19+
* drive a full key generation (that needs a real gpg + several seconds of
20+
* entropy gathering).
21+
*
22+
* The dialog is normally parented to a ConfigDialog; tests pass nullptr to
23+
* sidestep ConfigDialog construction entirely. The protected `done()` slot
24+
* isn't exercised here for the same reason (it dispatches into
25+
* dialog->genKey(), which dereferences the ConfigDialog parent).
26+
*/
27+
class tst_keygendialog : public QObject {
28+
Q_OBJECT
29+
30+
private Q_SLOTS:
31+
void constructionLoadsNonEmptyTemplate();
32+
void expertCheckboxTogglesTemplateEditor();
33+
void nameTextUpdatesNameRealLine();
34+
void emailTextUpdatesNameEmailLine();
35+
void matchingPassphrasesEnableButtonBox();
36+
void mismatchedPassphrasesDisableButtonBox();
37+
};
38+
39+
/**
40+
* @brief The default GPG batch template gets loaded into the editor on
41+
* construction, regardless of whether ed25519 is supported.
42+
*/
43+
void tst_keygendialog::constructionLoadsNonEmptyTemplate() {
44+
KeygenDialog dialog(nullptr);
45+
auto *editor =
46+
dialog.findChild<QPlainTextEdit *>(QStringLiteral("plainTextEdit"));
47+
QVERIFY2(editor != nullptr, "plainTextEdit widget must exist");
48+
const QString tpl = editor->toPlainText();
49+
QVERIFY2(!tpl.isEmpty(), "default key template must be loaded");
50+
// Both the ed25519 and RSA fallback templates contain Name-Real and
51+
// Name-Email placeholders we rely on in the other tests.
52+
QVERIFY2(tpl.contains(QStringLiteral("Name-Real:")),
53+
"template must contain a Name-Real: line");
54+
QVERIFY2(tpl.contains(QStringLiteral("Name-Email:")),
55+
"template must contain a Name-Email: line");
56+
}
57+
58+
/**
59+
* @brief Toggling the "Expert" checkbox enables/disables direct editing of
60+
* the GPG batch template.
61+
*/
62+
void tst_keygendialog::expertCheckboxTogglesTemplateEditor() {
63+
KeygenDialog dialog(nullptr);
64+
auto *checkBox = dialog.findChild<QCheckBox *>(QStringLiteral("checkBox"));
65+
auto *editor =
66+
dialog.findChild<QPlainTextEdit *>(QStringLiteral("plainTextEdit"));
67+
QVERIFY2(checkBox != nullptr, "checkBox widget must exist");
68+
QVERIFY2(editor != nullptr, "plainTextEdit widget must exist");
69+
70+
// Default state: checkbox unchecked, editor read-only / disabled.
71+
checkBox->setChecked(false);
72+
QVERIFY2(editor->isReadOnly(), "editor should start read-only");
73+
QVERIFY2(!editor->isEnabled(), "editor should start disabled");
74+
75+
checkBox->setChecked(true);
76+
QVERIFY2(!editor->isReadOnly(), "expert mode should drop read-only");
77+
QVERIFY2(editor->isEnabled(), "expert mode should enable editor");
78+
79+
checkBox->setChecked(false);
80+
QVERIFY2(editor->isReadOnly(), "unchecking expert restores read-only");
81+
QVERIFY2(!editor->isEnabled(), "unchecking expert disables editor");
82+
}
83+
84+
/**
85+
* @brief Typing in the Name field replaces the Name-Real: line in the
86+
* template.
87+
*/
88+
void tst_keygendialog::nameTextUpdatesNameRealLine() {
89+
KeygenDialog dialog(nullptr);
90+
auto *nameEdit = dialog.findChild<QLineEdit *>(QStringLiteral("name"));
91+
auto *editor =
92+
dialog.findChild<QPlainTextEdit *>(QStringLiteral("plainTextEdit"));
93+
QVERIFY2(nameEdit != nullptr, "name widget must exist");
94+
QVERIFY2(editor != nullptr, "plainTextEdit widget must exist");
95+
96+
// The slot fires on textChanged(); setText() is enough to trigger it.
97+
nameEdit->setText(QStringLiteral("QtPass Tester"));
98+
QVERIFY2(editor->toPlainText().contains(
99+
QStringLiteral("Name-Real: QtPass Tester")),
100+
"template should reflect typed name");
101+
}
102+
103+
/**
104+
* @brief Typing in the Email field replaces the Name-Email: line.
105+
*/
106+
void tst_keygendialog::emailTextUpdatesNameEmailLine() {
107+
KeygenDialog dialog(nullptr);
108+
auto *emailEdit = dialog.findChild<QLineEdit *>(QStringLiteral("email"));
109+
auto *editor =
110+
dialog.findChild<QPlainTextEdit *>(QStringLiteral("plainTextEdit"));
111+
QVERIFY2(emailEdit != nullptr, "email widget must exist");
112+
QVERIFY2(editor != nullptr, "plainTextEdit widget must exist");
113+
114+
emailEdit->setText(QStringLiteral("tester@qtpass.example"));
115+
QVERIFY2(editor->toPlainText().contains(
116+
QStringLiteral("Name-Email: tester@qtpass.example")),
117+
"template should reflect typed email");
118+
}
119+
120+
/**
121+
* @brief Matching passphrases in both passphrase fields enable the
122+
* DialogButtonBox so OK can be clicked.
123+
*/
124+
void tst_keygendialog::matchingPassphrasesEnableButtonBox() {
125+
KeygenDialog dialog(nullptr);
126+
auto *pp1 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase1"));
127+
auto *pp2 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase2"));
128+
auto *buttonBox =
129+
dialog.findChild<QDialogButtonBox *>(QStringLiteral("buttonBox"));
130+
QVERIFY2(pp1 != nullptr, "passphrase1 widget must exist");
131+
QVERIFY2(pp2 != nullptr, "passphrase2 widget must exist");
132+
QVERIFY2(buttonBox != nullptr, "buttonBox widget must exist");
133+
134+
pp1->setText(QStringLiteral("testkey123"));
135+
pp2->setText(QStringLiteral("testkey123"));
136+
QVERIFY2(buttonBox->isEnabled(), "matching passphrases enable OK");
137+
}
138+
139+
/**
140+
* @brief Mismatched passphrases disable the DialogButtonBox.
141+
*/
142+
void tst_keygendialog::mismatchedPassphrasesDisableButtonBox() {
143+
KeygenDialog dialog(nullptr);
144+
auto *pp1 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase1"));
145+
auto *pp2 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase2"));
146+
auto *buttonBox =
147+
dialog.findChild<QDialogButtonBox *>(QStringLiteral("buttonBox"));
148+
QVERIFY2(pp1 != nullptr, "passphrase1 widget must exist");
149+
QVERIFY2(pp2 != nullptr, "passphrase2 widget must exist");
150+
QVERIFY2(buttonBox != nullptr, "buttonBox widget must exist");
151+
152+
pp1->setText(QStringLiteral("testkey123"));
153+
pp2->setText(QStringLiteral("testkey456"));
154+
QVERIFY2(!buttonBox->isEnabled(), "mismatched passphrases disable OK");
155+
}
156+
157+
QTEST_MAIN(tst_keygendialog)
158+
#include "tst_keygendialog.moc"

0 commit comments

Comments
 (0)