Skip to content

Commit b2df519

Browse files
nogeenharrieclaude
andauthored
test: add coverage for readTemplates, writeTemplates, getFolderTemplate (#1496)
* test: add coverage for readTemplates, writeTemplates, getFolderTemplate readTemplates (4 tests): - missing .templates file returns empty hash - single section with multiple fields parsed correctly - multiple sections parsed into separate hash entries - empty section name [] is ignored with a warning - comment lines (#) are skipped writeTemplates (3 tests): - round-trip: write then read yields identical data - empty hash writes successfully and reads back empty - sections are written in alphabetical key order getFolderTemplate (4 tests): - .default_template in the current directory is found - .default_template in a parent directory is found - no .default_template anywhere returns empty string - .default_template containing only a comment returns empty string Total util tests: 123 → 136 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: fix two fragile assertions in util template tests writeTemplatesSortedKeys: indexOf returns -1 for missing markers, so -1 < positivePos would silently pass. Add explicit presence checks for both [alpha] and [zebra] before the ordering assertion. readTemplatesEmptySectionIgnored: add QTest::ignoreMessage(QtWarningMsg) before calling readTemplates so the expected qWarning is asserted as a test side-effect rather than just leaking to stderr. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use forward slash in getFolderTemplate path prefix check QDir::cleanPath() normalizes separators to '/' on all platforms, but QDir::separator() returns '\' on Windows, causing the startsWith guard to never match on Windows and preventing parent-directory traversal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: avoid double slash in getFolderTemplate ancestry check Appending "/" to cleanStoreAbs produces "//" when the store is at the filesystem root ("/"), causing startsWith to never match. Check the character at cleanStoreAbs.length() instead to safely distinguish a proper subdirectory from a sibling with a longer name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: correct linker order in mainwindow test for MinGW All other test .pro files use 'LIBS = ... $$LIBS' so that -lqtpass comes before the Windows system libs (-lmpr, -lbcrypt) accumulated from qtpass.pri. mainwindow.pro used 'LIBS +=' which placed the system libs first, causing 'undefined reference to WNetUseConnectionA' on MinGW. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2bc3bd0 commit b2df519

3 files changed

Lines changed: 195 additions & 3 deletions

File tree

src/util.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,6 @@ auto Util::getFolderTemplate(const QString &folderPath,
659659
const QString &storePath) -> QString {
660660
QDir storeDir(storePath);
661661
QString cleanStoreAbs = QDir::cleanPath(storeDir.absolutePath());
662-
QString sep = QDir::separator();
663662
QDir dir(folderPath);
664663
while (true) {
665664
if (dir.exists(".default_template")) {
@@ -682,7 +681,9 @@ auto Util::getFolderTemplate(const QString &folderPath,
682681
if (currentPath == cleanStoreAbs) {
683682
break;
684683
}
685-
if (!currentPath.startsWith(cleanStoreAbs + sep)) {
684+
if (!currentPath.startsWith(cleanStoreAbs) ||
685+
currentPath.length() <= cleanStoreAbs.length() ||
686+
currentPath.at(cleanStoreAbs.length()) != QChar('/')) {
686687
break;
687688
}
688689
if (!dir.cdUp()) {

tests/auto/mainwindow/mainwindow.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
SOURCES += tst_mainwindow.cpp
44

5-
LIBS += -L"$$OUT_PWD/../../../src/$(OBJECTS_DIR)" -lqtpass
5+
LIBS = -L"$$OUT_PWD/../../../src/$(OBJECTS_DIR)" -lqtpass $$LIBS
66
clang|gcc:PRE_TARGETDEPS += "$$OUT_PWD/../../../src/$(OBJECTS_DIR)/libqtpass.a"
77

88
HEADERS += ../../../src/mainwindow.h

tests/auto/util/tst_util.cpp

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,18 @@ private Q_SLOTS:
236236
void sshAuthSockOverrideStatusRegularFileRejected();
237237
void sshAuthSockOverrideStatusNotReadable();
238238
void sshAuthSockOverrideStatusValid();
239+
void readTemplatesNoFile();
240+
void readTemplatesSingleSection();
241+
void readTemplatesMultipleSections();
242+
void readTemplatesEmptySectionIgnored();
243+
void readTemplatesCommentsIgnored();
244+
void writeTemplatesRoundTrip();
245+
void writeTemplatesEmptyHash();
246+
void writeTemplatesSortedKeys();
247+
void getFolderTemplateInCurrent();
248+
void getFolderTemplateInParent();
249+
void getFolderTemplateNoneFound();
250+
void getFolderTemplateCommentIgnored();
239251
};
240252

241253
/**
@@ -2447,5 +2459,184 @@ void tst_util::sshAuthSockOverrideStatusValid() {
24472459
#endif
24482460
}
24492461

2462+
void tst_util::readTemplatesNoFile() {
2463+
QTemporaryDir tmp;
2464+
QVERIFY2(tmp.isValid(), "tmp dir must be valid");
2465+
auto result = Util::readTemplates(tmp.path());
2466+
QVERIFY2(result.isEmpty(), "missing .templates file must return empty hash");
2467+
}
2468+
2469+
void tst_util::readTemplatesSingleSection() {
2470+
QTemporaryDir tmp;
2471+
QVERIFY2(tmp.isValid(), "tmp dir must be valid");
2472+
QFile f(QDir(tmp.path()).filePath(".templates"));
2473+
QVERIFY2(f.open(QIODevice::WriteOnly | QIODevice::Text),
2474+
".templates must open");
2475+
QTextStream out(&f);
2476+
out << "[login]\nusername\npassword\n";
2477+
f.close();
2478+
2479+
auto result = Util::readTemplates(tmp.path());
2480+
QVERIFY2(result.size() == 1, "one section expected");
2481+
QVERIFY2(result.contains("login"), "section 'login' must be present");
2482+
QCOMPARE(result.value("login"), QStringList({"username", "password"}));
2483+
}
2484+
2485+
void tst_util::readTemplatesMultipleSections() {
2486+
QTemporaryDir tmp;
2487+
QVERIFY2(tmp.isValid(), "tmp dir must be valid");
2488+
QFile f(QDir(tmp.path()).filePath(".templates"));
2489+
QVERIFY2(f.open(QIODevice::WriteOnly | QIODevice::Text),
2490+
".templates must open");
2491+
QTextStream out(&f);
2492+
out << "[web]\nurl\nusername\n\n[ssh]\nhost\nport\n";
2493+
f.close();
2494+
2495+
auto result = Util::readTemplates(tmp.path());
2496+
QVERIFY2(result.size() == 2, "two sections expected");
2497+
QCOMPARE(result.value("web"), QStringList({"url", "username"}));
2498+
QCOMPARE(result.value("ssh"), QStringList({"host", "port"}));
2499+
}
2500+
2501+
void tst_util::readTemplatesEmptySectionIgnored() {
2502+
QTemporaryDir tmp;
2503+
QVERIFY2(tmp.isValid(), "tmp dir must be valid");
2504+
QFile f(QDir(tmp.path()).filePath(".templates"));
2505+
QVERIFY2(f.open(QIODevice::WriteOnly | QIODevice::Text),
2506+
".templates must open");
2507+
QTextStream out(&f);
2508+
out << "[]\nfield\n[valid]\nkey\n";
2509+
f.close();
2510+
2511+
QTest::ignoreMessage(
2512+
QtWarningMsg, QRegularExpression("Empty template section in "
2513+
"\\.templates file, ignoring fields"));
2514+
auto result = Util::readTemplates(tmp.path());
2515+
QVERIFY2(!result.contains(""), "empty section name must be ignored");
2516+
QVERIFY2(result.contains("valid"), "valid section must still be parsed");
2517+
}
2518+
2519+
void tst_util::readTemplatesCommentsIgnored() {
2520+
QTemporaryDir tmp;
2521+
QVERIFY2(tmp.isValid(), "tmp dir must be valid");
2522+
QFile f(QDir(tmp.path()).filePath(".templates"));
2523+
QVERIFY2(f.open(QIODevice::WriteOnly | QIODevice::Text),
2524+
".templates must open");
2525+
QTextStream out(&f);
2526+
out << "# top-level comment\n[section]\n# inline comment\nfield\n";
2527+
f.close();
2528+
2529+
auto result = Util::readTemplates(tmp.path());
2530+
QVERIFY2(result.contains("section"), "section must be parsed");
2531+
QCOMPARE(result.value("section"), QStringList({"field"}));
2532+
}
2533+
2534+
void tst_util::writeTemplatesRoundTrip() {
2535+
QTemporaryDir tmp;
2536+
QVERIFY2(tmp.isValid(), "tmp dir must be valid");
2537+
QHash<QString, QStringList> original;
2538+
original.insert("login", {"username", "password"});
2539+
original.insert("ssh", {"host", "port"});
2540+
2541+
QVERIFY2(Util::writeTemplates(tmp.path(), original), "write must succeed");
2542+
auto roundTripped = Util::readTemplates(tmp.path());
2543+
QCOMPARE(roundTripped.value("login"), original.value("login"));
2544+
QCOMPARE(roundTripped.value("ssh"), original.value("ssh"));
2545+
}
2546+
2547+
void tst_util::writeTemplatesEmptyHash() {
2548+
QTemporaryDir tmp;
2549+
QVERIFY2(tmp.isValid(), "tmp dir must be valid");
2550+
QHash<QString, QStringList> empty;
2551+
QVERIFY2(Util::writeTemplates(tmp.path(), empty),
2552+
"write of empty hash must succeed");
2553+
auto result = Util::readTemplates(tmp.path());
2554+
QVERIFY2(result.isEmpty(), "reading back empty write must yield empty hash");
2555+
}
2556+
2557+
void tst_util::writeTemplatesSortedKeys() {
2558+
QTemporaryDir tmp;
2559+
QVERIFY2(tmp.isValid(), "tmp dir must be valid");
2560+
QHash<QString, QStringList> tmpl;
2561+
tmpl.insert("zebra", {"z"});
2562+
tmpl.insert("alpha", {"a"});
2563+
QVERIFY2(Util::writeTemplates(tmp.path(), tmpl), "write must succeed");
2564+
2565+
QFile f(QDir(tmp.path()).filePath(".templates"));
2566+
QVERIFY2(f.open(QIODevice::ReadOnly | QIODevice::Text),
2567+
".templates must open");
2568+
QString content = QString::fromUtf8(f.readAll());
2569+
f.close();
2570+
QVERIFY2(content.indexOf("[alpha]") != -1,
2571+
"[alpha] section must be present in written file");
2572+
QVERIFY2(content.indexOf("[zebra]") != -1,
2573+
"[zebra] section must be present in written file");
2574+
QVERIFY2(content.indexOf("[alpha]") < content.indexOf("[zebra]"),
2575+
"sections must be written in alphabetical order");
2576+
}
2577+
2578+
void tst_util::getFolderTemplateInCurrent() {
2579+
QTemporaryDir store;
2580+
QVERIFY2(store.isValid(), "store dir must be valid");
2581+
QDir storeDir(store.path());
2582+
storeDir.mkdir("sub");
2583+
const QString subPath = storeDir.filePath("sub");
2584+
2585+
QFile f(QDir(subPath).filePath(".default_template"));
2586+
QVERIFY2(f.open(QIODevice::WriteOnly | QIODevice::Text), "file must open");
2587+
QTextStream out(&f);
2588+
out << "mytemplate\n";
2589+
f.close();
2590+
2591+
QString result = Util::getFolderTemplate(subPath, store.path());
2592+
QCOMPARE(result, QStringLiteral("mytemplate"));
2593+
}
2594+
2595+
void tst_util::getFolderTemplateInParent() {
2596+
QTemporaryDir store;
2597+
QVERIFY2(store.isValid(), "store dir must be valid");
2598+
QDir storeDir(store.path());
2599+
storeDir.mkpath("a/b");
2600+
const QString deepPath = storeDir.filePath("a/b");
2601+
2602+
QFile f(QDir(storeDir.filePath("a")).filePath(".default_template"));
2603+
QVERIFY2(f.open(QIODevice::WriteOnly | QIODevice::Text), "file must open");
2604+
QTextStream out(&f);
2605+
out << "parenttemplate\n";
2606+
f.close();
2607+
2608+
QString result = Util::getFolderTemplate(deepPath, store.path());
2609+
QCOMPARE(result, QStringLiteral("parenttemplate"));
2610+
}
2611+
2612+
void tst_util::getFolderTemplateNoneFound() {
2613+
QTemporaryDir store;
2614+
QVERIFY2(store.isValid(), "store dir must be valid");
2615+
QDir storeDir(store.path());
2616+
storeDir.mkdir("sub");
2617+
const QString subPath = storeDir.filePath("sub");
2618+
2619+
QString result = Util::getFolderTemplate(subPath, store.path());
2620+
QVERIFY2(result.isEmpty(), "no .default_template must return empty string");
2621+
}
2622+
2623+
void tst_util::getFolderTemplateCommentIgnored() {
2624+
QTemporaryDir store;
2625+
QVERIFY2(store.isValid(), "store dir must be valid");
2626+
QDir storeDir(store.path());
2627+
storeDir.mkdir("sub");
2628+
const QString subPath = storeDir.filePath("sub");
2629+
2630+
QFile f(QDir(subPath).filePath(".default_template"));
2631+
QVERIFY2(f.open(QIODevice::WriteOnly | QIODevice::Text), "file must open");
2632+
QTextStream out(&f);
2633+
out << "# this is a comment\n";
2634+
f.close();
2635+
2636+
QString result = Util::getFolderTemplate(subPath, store.path());
2637+
QVERIFY2(result.isEmpty(),
2638+
".default_template with only a comment must return empty string");
2639+
}
2640+
24502641
QTEST_MAIN(tst_util)
24512642
#include "tst_util.moc"

0 commit comments

Comments
 (0)