aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_rulequery.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-13 11:15:19 +0200
committerDanilo M. <danix@danix.xyz>2026-08-13 11:15:19 +0200
commite2ec7263a65a44beb807374e8ec1a136bf5c3d88 (patch)
tree1d6c0a3cb0a9bf7f09cdd116536e147b1b62e005 /tests/test_rulequery.cpp
parent26dd50700305d613b86fe8789f13c8253f75984a (diff)
downloadqtmaildir-e2ec7263a65a44beb807374e8ec1a136bf5c3d88.tar.gz
qtmaildir-e2ec7263a65a44beb807374e8ec1a136bf5c3d88.zip
test(rulequery): pin whole-query rejection
Diffstat (limited to 'tests/test_rulequery.cpp')
-rw-r--r--tests/test_rulequery.cpp48
1 files changed, 48 insertions, 0 deletions
diff --git a/tests/test_rulequery.cpp b/tests/test_rulequery.cpp
index 88485f3..7a34403 100644
--- a/tests/test_rulequery.cpp
+++ b/tests/test_rulequery.cpp
@@ -48,6 +48,8 @@ private slots:
void aNegatedTermParsesAsANegatedOperator();
void whatParsesCompilesBackUnchanged();
void anOrGroupWithExclusionsParses();
+ void anUnrepresentableQueryRejectsWhole();
+ void aParenBearingValueIsARowNotAShape();
};
void TestRuleQuery::aSingleContainsTermCompiles()
@@ -324,5 +326,51 @@ void TestRuleQuery::anOrGroupWithExclusionsParses()
"and not subject:receipt and not subject:refund"));
}
+void TestRuleQuery::anUnrepresentableQueryRejectsWhole()
+{
+ const QStringList unrepresentable = {
+ QStringLiteral("from:a.example.org or (from:b.example.org "
+ "or from:c.example.org)"), // nested or inside or
+ QStringLiteral("from:a.example.org and subject:x "
+ "or subject:y"), // mixed, unparenthesised
+ QStringLiteral("body:receipt"), // unrecognised prefix
+ QStringLiteral("folder:Inbox"), // not the prefix we emit
+ QStringLiteral("receipt"), // bare word, no prefix
+ QStringLiteral("path:\"account-one\""), // no /** suffix
+ QStringLiteral("from:a.example.org and"), // trailing operator
+ QStringLiteral("date:2026-01-01..2026-02-01"), // two-sided range
+ QStringLiteral("from:a.example.org xor subject:x"),
+ QStringLiteral("subject:\"unterminated"), // open quote
+ QStringLiteral("(from:a.example.org or from:b.example.org)"),
+ // group, nothing after it
+ QStringLiteral("(from:a.example.org and from:b.example.org) "
+ "and not subject:x"), // group joined by and
+ };
+
+ for (const QString &query : unrepresentable) {
+ const RuleQuery q = RuleQuery::parse(query);
+ QVERIFY2(!q.parsed, qPrintable(QStringLiteral("parsed: ") + query));
+ // Rejecting whole means keeping nothing: a half-parse is how a
+ // negation goes missing and a rule quietly matches more mail.
+ QVERIFY2(q.terms.isEmpty() && q.exclusions.isEmpty(),
+ qPrintable(QStringLiteral("kept rows: ") + query));
+ }
+}
+
+void TestRuleQuery::aParenBearingValueIsARowNotAShape()
+{
+ // notmuch reads these parens as characters to search for, not as
+ // grouping, so the query is meaningful and matches nothing. It parses
+ // here as a From row holding that literal text, which is what it means.
+ // The property to hold is the round trip, NOT a rejection: a test
+ // expecting an error here fails against correct code.
+ const RuleQuery q = RuleQuery::parse(QStringLiteral("from:(((("));
+
+ QVERIFY(q.parsed);
+ QCOMPARE(q.terms.size(), 1);
+ QCOMPARE(q.terms.at(0).value, QStringLiteral("(((("));
+ QCOMPARE(q.compile(), QStringLiteral("from:(((("));
+}
+
QTEST_MAIN(TestRuleQuery)
#include "test_rulequery.moc"