Skip to content

Commit d87ce31

Browse files
nogeenharrieclaudecoderabbitai[bot]CodeRabbit
authored
test: expand configdialog and keygendialog test suites (#1501)
* test: expand gpgkeystate and storemodel test suites gpgkeystate: 23 → 27 tests — adds edge cases not previously covered: empty input returns an empty list; pub record without any uid record is still included; orphan sub/ssb records without a pub parent are ignored; short colon-separated lines (fewer than GPG_MIN_FIELDS) are silently skipped. storemodel: 31 → 34 tests — adds: setStore() updates the value returned by getStore(); data() with Qt::EditRole does not strip the .gpg suffix (only DisplayRole does); a non-.gpg file whose name matches the regex filter is accepted by filterAcceptsRow (documents that the model does not restrict to .gpg only at the filter layer). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: expand configdialog and keygendialog test suites - tst_configdialog: 9 → 14 tests — adds coverage for useTrayIcon (skipped if no tray), useQrencode, setPwgenPath (value + empty- disables-checkbox), and setPasswordConfiguration/getPasswordConfiguration round-trip - tst_keygendialog: 6 → 10 tests — adds empty-passphrase no-protection path, second-passphrase change triggering state update, clearing first passphrase disabling buttonBox, and simultaneous name+email template update Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * style: apply clang-format to configdialog and keygendialog tests Fix three clang-format violations introduced in the previous commit: - tst_configdialog: collapse two short QVERIFY2 calls onto single lines - tst_keygendialog: wrap an over-length QVERIFY2 message string Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 1 unresolved review comment. Co-authored-by: CodeRabbit <noreply@coderabbit.ai> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
1 parent c163cb3 commit d87ce31

4 files changed

Lines changed: 256 additions & 0 deletions

File tree

tests/auto/configdialog/tst_configdialog.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
#include <QApplication>
44
#include <QCheckBox>
55
#include <QLineEdit>
6+
#include <QSpinBox>
7+
#include <QSystemTrayIcon>
68
#include <QtTest>
79

810
#include "../../../src/configdialog.h"
11+
#include "../../../src/passwordconfiguration.h"
912

1013
/**
1114
* @class tst_configdialog
@@ -38,6 +41,11 @@ private Q_SLOTS:
3841
void useGrepSearchTogglesCheckbox();
3942
void usePwgenTogglesCheckbox();
4043
void useTemplateTogglesCheckbox();
44+
void useTrayIconTogglesCheckbox();
45+
void useQrencodeTogglesCheckbox();
46+
void setPwgenPathSetsLineEdit();
47+
void setPwgenPathEmptyDisablesPwgenCheckbox();
48+
void setAndGetPasswordConfigurationRoundTrip();
4149
};
4250

4351
/**
@@ -192,5 +200,62 @@ void tst_configdialog::useTemplateTogglesCheckbox() {
192200
"useTemplate(false) should uncheck checkBoxUseTemplate");
193201
}
194202

203+
void tst_configdialog::useTrayIconTogglesCheckbox() {
204+
if (!QSystemTrayIcon::isSystemTrayAvailable())
205+
QSKIP("system tray not available in this environment");
206+
ConfigDialog dialog(nullptr);
207+
auto *cb =
208+
dialog.findChild<QCheckBox *>(QStringLiteral("checkBoxUseTrayIcon"));
209+
QVERIFY2(cb != nullptr, "checkBoxUseTrayIcon widget must exist");
210+
dialog.useTrayIcon(true);
211+
QVERIFY2(cb->isChecked(),
212+
"useTrayIcon(true) should check checkBoxUseTrayIcon");
213+
dialog.useTrayIcon(false);
214+
QVERIFY2(!cb->isChecked(),
215+
"useTrayIcon(false) should uncheck checkBoxUseTrayIcon");
216+
}
217+
218+
void tst_configdialog::useQrencodeTogglesCheckbox() {
219+
ConfigDialog dialog(nullptr);
220+
auto *cb =
221+
dialog.findChild<QCheckBox *>(QStringLiteral("checkBoxUseQrencode"));
222+
QVERIFY2(cb != nullptr, "checkBoxUseQrencode widget must exist");
223+
dialog.useQrencode(true);
224+
QVERIFY2(cb->isChecked(),
225+
"useQrencode(true) should check checkBoxUseQrencode");
226+
dialog.useQrencode(false);
227+
QVERIFY2(!cb->isChecked(),
228+
"useQrencode(false) should uncheck checkBoxUseQrencode");
229+
}
230+
231+
void tst_configdialog::setPwgenPathSetsLineEdit() {
232+
ConfigDialog dialog(nullptr);
233+
auto *pathEdit = dialog.findChild<QLineEdit *>(QStringLiteral("pwgenPath"));
234+
QVERIFY2(pathEdit != nullptr, "pwgenPath widget must exist");
235+
dialog.setPwgenPath(QStringLiteral("/usr/bin/pwgen"));
236+
QCOMPARE(pathEdit->text(), QStringLiteral("/usr/bin/pwgen"));
237+
}
238+
239+
void tst_configdialog::setPwgenPathEmptyDisablesPwgenCheckbox() {
240+
ConfigDialog dialog(nullptr);
241+
auto *cb = dialog.findChild<QCheckBox *>(QStringLiteral("checkBoxUsePwgen"));
242+
QVERIFY2(cb != nullptr, "checkBoxUsePwgen widget must exist");
243+
dialog.setPwgenPath(QString());
244+
QVERIFY2(!cb->isChecked(), "setPwgenPath('') must uncheck checkBoxUsePwgen");
245+
QVERIFY2(!cb->isEnabled(), "setPwgenPath('') must disable checkBoxUsePwgen");
246+
}
247+
248+
void tst_configdialog::setAndGetPasswordConfigurationRoundTrip() {
249+
ConfigDialog dialog(nullptr);
250+
PasswordConfiguration cfg;
251+
cfg.length = 32;
252+
cfg.selected = PasswordConfiguration::ALPHANUMERIC;
253+
dialog.setPasswordConfiguration(cfg);
254+
PasswordConfiguration result = dialog.getPasswordConfiguration();
255+
QCOMPARE(result.length, 32);
256+
QCOMPARE(static_cast<int>(result.selected),
257+
static_cast<int>(PasswordConfiguration::ALPHANUMERIC));
258+
}
259+
195260
QTEST_MAIN(tst_configdialog)
196261
#include "tst_configdialog.moc"

tests/auto/gpgkeystate/tst_gpgkeystate.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ private Q_SLOTS:
3434
void createdDateParsedFromEpoch();
3535
void expiryDateParsedFromEpoch();
3636
void fprWithEmptyKeyIdIsNoop();
37+
void parseGpgColonOutputEmpty();
38+
void parseGpgColonOutputPubWithoutUid();
39+
void parseGpgColonOutputOnlySubRecords();
40+
void parseGpgColonOutputShortLinesIgnored();
3741
};
3842

3943
void tst_gpgkeystate::parseMultiKeyPublic() {
@@ -467,5 +471,46 @@ void tst_gpgkeystate::fprWithEmptyKeyIdIsNoop() {
467471
"fpr record must not update key_id when key_id is empty");
468472
}
469473

474+
void tst_gpgkeystate::parseGpgColonOutputEmpty() {
475+
QList<UserInfo> result = parseGpgColonOutput(QString(), false);
476+
QVERIFY2(result.isEmpty(), "empty input must produce an empty list");
477+
}
478+
479+
void tst_gpgkeystate::parseGpgColonOutputPubWithoutUid() {
480+
// A pub record with a key_id but no uid record: the name is taken from the
481+
// pub record's userid field (field index 9).
482+
const QString input =
483+
QStringLiteral("pub:u:4096:1:NOUIDKEY1:1774947438:::u::::Name Only:\n");
484+
QList<UserInfo> result = parseGpgColonOutput(input, false);
485+
QVERIFY2(result.size() == 1,
486+
"pub record without uid must still produce one UserInfo");
487+
QVERIFY2(result.first().key_id == QStringLiteral("NOUIDKEY1"),
488+
"key_id must be set from pub record");
489+
}
490+
491+
void tst_gpgkeystate::parseGpgColonOutputOnlySubRecords() {
492+
// sub/ssb records without a preceding pub record must be ignored entirely.
493+
const QString input =
494+
QStringLiteral("sub:u:4096:1:ORPHANSUB1:1774947438::::::esa:::\n"
495+
"ssb:u:4096:1:ORPHANSSB1:1774947438::::::esa:::\n");
496+
QList<UserInfo> result = parseGpgColonOutput(input, false);
497+
QVERIFY2(result.isEmpty(),
498+
"orphan sub/ssb records without a pub parent must be ignored");
499+
}
500+
501+
void tst_gpgkeystate::parseGpgColonOutputShortLinesIgnored() {
502+
// Lines with fewer than GPG_MIN_FIELDS colon-separated fields must be
503+
// silently skipped without crashing.
504+
const QString input = QStringLiteral(
505+
"pub:u:4096\n"
506+
"pub:u:4096:1:VALIDKEY1:1774947438:::u::::\n"
507+
"uid:u::::1774947438::H::Valid User <valid@test.org>::::\n");
508+
QList<UserInfo> result = parseGpgColonOutput(input, false);
509+
QVERIFY2(result.size() == 1,
510+
"short lines must be skipped; only the well-formed key is parsed");
511+
QVERIFY2(result.first().key_id == QStringLiteral("VALIDKEY1"),
512+
"key_id must be parsed from the well-formed pub record");
513+
}
514+
470515
QTEST_MAIN(tst_gpgkeystate)
471516
#include "tst_gpgkeystate.moc"

tests/auto/keygendialog/tst_keygendialog.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ private Q_SLOTS:
3434
void emailTextUpdatesNameEmailLine();
3535
void matchingPassphrasesEnableButtonBox();
3636
void mismatchedPassphrasesDisableButtonBox();
37+
void emptyPassphrasesEnableButtonBox();
38+
void secondPassphraseChangeTriggersStateUpdate();
39+
void clearingFirstPassphraseDisablesButtonBox();
40+
void nameAndEmailBothUpdateTemplate();
3741
};
3842

3943
/**
@@ -154,5 +158,82 @@ void tst_keygendialog::mismatchedPassphrasesDisableButtonBox() {
154158
QVERIFY2(!buttonBox->isEnabled(), "mismatched passphrases disable OK");
155159
}
156160

161+
void tst_keygendialog::emptyPassphrasesEnableButtonBox() {
162+
KeygenDialog dialog(nullptr);
163+
auto *pp1 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase1"));
164+
auto *pp2 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase2"));
165+
auto *buttonBox =
166+
dialog.findChild<QDialogButtonBox *>(QStringLiteral("buttonBox"));
167+
QVERIFY2(pp1 != nullptr, "passphrase1 widget must exist");
168+
QVERIFY2(pp2 != nullptr, "passphrase2 widget must exist");
169+
QVERIFY2(buttonBox != nullptr, "buttonBox widget must exist");
170+
171+
// Set to non-empty first to ensure signals fire when cleared.
172+
pp1->setText(QStringLiteral("testkey123"));
173+
pp2->setText(QStringLiteral("testkey123"));
174+
pp1->setText(QString());
175+
pp2->setText(QString());
176+
QVERIFY2(
177+
buttonBox->isEnabled(),
178+
"both empty passphrases should enable buttonBox (no-protection mode)");
179+
}
180+
181+
void tst_keygendialog::secondPassphraseChangeTriggersStateUpdate() {
182+
KeygenDialog dialog(nullptr);
183+
auto *pp1 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase1"));
184+
auto *pp2 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase2"));
185+
auto *buttonBox =
186+
dialog.findChild<QDialogButtonBox *>(QStringLiteral("buttonBox"));
187+
QVERIFY2(pp1 != nullptr, "passphrase1 widget must exist");
188+
QVERIFY2(pp2 != nullptr, "passphrase2 widget must exist");
189+
QVERIFY2(buttonBox != nullptr, "buttonBox widget must exist");
190+
191+
pp1->setText(QStringLiteral("testkey123"));
192+
pp2->setText(QStringLiteral("testkey123"));
193+
QVERIFY2(buttonBox->isEnabled(),
194+
"matching passphrases should enable buttonBox");
195+
pp2->setText(QStringLiteral("testkey456"));
196+
QVERIFY2(!buttonBox->isEnabled(),
197+
"changing pp2 to a mismatch should disable buttonBox");
198+
}
199+
200+
void tst_keygendialog::clearingFirstPassphraseDisablesButtonBox() {
201+
KeygenDialog dialog(nullptr);
202+
auto *pp1 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase1"));
203+
auto *pp2 = dialog.findChild<QLineEdit *>(QStringLiteral("passphrase2"));
204+
auto *buttonBox =
205+
dialog.findChild<QDialogButtonBox *>(QStringLiteral("buttonBox"));
206+
QVERIFY2(pp1 != nullptr, "passphrase1 widget must exist");
207+
QVERIFY2(pp2 != nullptr, "passphrase2 widget must exist");
208+
QVERIFY2(buttonBox != nullptr, "buttonBox widget must exist");
209+
210+
pp1->setText(QStringLiteral("testkey123"));
211+
pp2->setText(QStringLiteral("testkey123"));
212+
QVERIFY2(buttonBox->isEnabled(),
213+
"matching passphrases should enable buttonBox");
214+
pp1->setText(QString());
215+
QVERIFY2(!buttonBox->isEnabled(),
216+
"clearing pp1 while pp2 is non-empty must disable buttonBox");
217+
}
218+
219+
void tst_keygendialog::nameAndEmailBothUpdateTemplate() {
220+
KeygenDialog dialog(nullptr);
221+
auto *nameEdit = dialog.findChild<QLineEdit *>(QStringLiteral("name"));
222+
auto *emailEdit = dialog.findChild<QLineEdit *>(QStringLiteral("email"));
223+
auto *editor =
224+
dialog.findChild<QPlainTextEdit *>(QStringLiteral("plainTextEdit"));
225+
QVERIFY2(nameEdit != nullptr, "name widget must exist");
226+
QVERIFY2(emailEdit != nullptr, "email widget must exist");
227+
QVERIFY2(editor != nullptr, "plainTextEdit widget must exist");
228+
229+
nameEdit->setText(QStringLiteral("Test User"));
230+
emailEdit->setText(QStringLiteral("user@test.example"));
231+
const QString tpl = editor->toPlainText();
232+
QVERIFY2(tpl.contains(QStringLiteral("Name-Real: Test User")),
233+
"template must contain Name-Real: Test User");
234+
QVERIFY2(tpl.contains(QStringLiteral("Name-Email: user@test.example")),
235+
"template must contain Name-Email: user@test.example");
236+
}
237+
157238
QTEST_MAIN(tst_keygendialog)
158239
#include "tst_keygendialog.moc"

tests/auto/model/tst_storemodel.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ private Q_SLOTS:
4545
void showThisWithNullFs();
4646
void getStoreBasic();
4747
void filterRegularExpression();
48+
void setStoreUpdatesPath();
49+
void dataEditRoleKeepsGpgExtension();
50+
void filterAcceptsNonGpgFileMatchingRegex();
4851
};
4952

5053
void tst_storemodel::dataRemovesGpgExtension() {
@@ -519,5 +522,67 @@ void tst_storemodel::dropMimeDataRejectsSymlinkEscape() {
519522
#endif
520523
}
521524

525+
void tst_storemodel::setStoreUpdatesPath() {
526+
QTemporaryDir tempDir;
527+
QFileSystemModel fsm;
528+
StoreModel sm;
529+
sm.setModelAndStore(&fsm, tempDir.path());
530+
QCOMPARE(sm.getStore(), tempDir.path());
531+
532+
QString newPath = tempDir.path() + "/substore";
533+
sm.setStore(newPath);
534+
QCOMPARE(sm.getStore(), newPath);
535+
}
536+
537+
void tst_storemodel::dataEditRoleKeepsGpgExtension() {
538+
QTemporaryDir tempDir;
539+
QFile f(tempDir.path() + "/secret.gpg");
540+
QVERIFY(f.open(QFile::WriteOnly));
541+
f.close();
542+
543+
QFileSystemModel fsm;
544+
fsm.setRootPath(tempDir.path());
545+
546+
StoreModel sm;
547+
sm.setModelAndStore(&fsm, tempDir.path());
548+
549+
QTRY_VERIFY(fsm.index(tempDir.path() + "/secret.gpg").isValid());
550+
QModelIndex sourceIndex = fsm.index(tempDir.path() + "/secret.gpg");
551+
QModelIndex proxyIndex = sm.mapFromSource(sourceIndex);
552+
553+
QVariant editData = sm.data(proxyIndex, Qt::EditRole);
554+
QVERIFY2(editData.isValid(), "EditRole data must be valid");
555+
QVERIFY2(editData.toString().endsWith(".gpg"),
556+
"EditRole must not strip the .gpg extension");
557+
}
558+
559+
void tst_storemodel::filterAcceptsNonGpgFileMatchingRegex() {
560+
QTemporaryDir tempDir;
561+
// A plain text file (no .gpg) should not pass the filter because its
562+
// name doesn't end in .gpg and won't match the default empty regex
563+
// after extension stripping — unless the regex explicitly matches it.
564+
QFile f(tempDir.path() + "/readme.txt");
565+
QVERIFY(f.open(QFile::WriteOnly));
566+
f.close();
567+
568+
QFileSystemModel fsm;
569+
fsm.setRootPath(tempDir.path());
570+
571+
StoreModel sm;
572+
sm.setModelAndStore(&fsm, tempDir.path());
573+
574+
QTRY_VERIFY(fsm.index(tempDir.path() + "/readme.txt").isValid());
575+
QModelIndex index = fsm.index(tempDir.path() + "/readme.txt");
576+
577+
// Set a filter that would match "readme" if extension stripping happened
578+
sm.setFilterRegularExpression("readme");
579+
bool visible = sm.filterAcceptsRow(index.row(), index.parent());
580+
// readme.txt has no .gpg suffix; after stripping nothing, "readme.txt"
581+
// does contain "readme" so the filter accepts it. This documents the
582+
// actual behavior: the model does not restrict to .gpg files only.
583+
QVERIFY2(visible,
584+
"non-gpg file matching the regex is accepted by the filter");
585+
}
586+
522587
QTEST_MAIN(tst_storemodel)
523588
#include "tst_storemodel.moc"

0 commit comments

Comments
 (0)