diff options
Diffstat (limited to 'tests/test_notmuchworker.cpp')
| -rw-r--r-- | tests/test_notmuchworker.cpp | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/tests/test_notmuchworker.cpp b/tests/test_notmuchworker.cpp index 3f75898..9a0896d 100644 --- a/tests/test_notmuchworker.cpp +++ b/tests/test_notmuchworker.cpp @@ -65,6 +65,8 @@ private slots: void loadMessageOnAnUnknownIdReturnsNothing(); void aQueryCarriesEachThreadsFirstMessageId(); void aSentQueryCarriesTheMatchedMessageNotTheThreadsFirst(); + void queryCarriesTheFirstMessageSender(); + void sendersAreCountedForTheCandidateList(); void loadThreadTreeReportsReplyDepth(); void loadThreadTreeCarriesTheFactsARowNeeds(); @@ -74,6 +76,7 @@ private slots: void recipientsAreAbsentUnlessAskedFor(); void recipientsAreFoldedWhenAskedFor(); void recipientsCrossAQueuedCall(); + void theFirstRecipientsAddressCrossesForTheAvatar(); void requestCountsAnswersOneCountPerQuery(); void requestCountsKeepsPositionOnAnInvalidQuery(); @@ -515,6 +518,79 @@ void TestNotmuchWorker::aSentQueryCarriesTheMatchedMessageNotTheThreadsFirst() } } +void TestNotmuchWorker::queryCarriesTheFirstMessageSender() +{ + // Item 169. The card has no address to hash: `authors` is notmuch's own + // summarised string and carries display names only, measured on the real + // index as 'Ryanair' and 'The Hacker News tramite LinkedIn', with no `@` + // anywhere. firstMessageSender is the BARE address of the one message the + // root card stands for. + // + // The From header carries a display name on purpose: a wrong + // implementation returning the display name or the authors string fails + // this test. + NotmuchFixture fixture; + QVERIFY(fixture.isValid()); + QVERIFY(fixture.addMessage(QStringLiteral("inbox"), + QStringLiteral("sender-probe@example.org"), + QStringLiteral("Probe subject"), + QStringLiteral("Probe <sender-probe@example.org>"), + QStringLiteral("Mon, 8 Jun 2026 10:00:00 +0000"), + QStringLiteral("body"))); + QVERIFY2(fixture.index(), qPrintable(fixture.error())); + + NotmuchWorker worker(fixture.configPath()); + + QSignalSpy spy(&worker, &NotmuchWorker::threadsReady); + worker.runQuery(QStringLiteral("subject:\"Probe subject\""), 1, + NotmuchWorker::NewestFirst, false); + QVERIFY(spy.count() > 0); + + const auto threads = spy.first().at(0).value<QVector<ThreadSummary>>(); + QCOMPARE(threads.size(), 1); + // The bare address, not the display name and not notmuch's authors string. + QCOMPARE(threads.first().firstMessageSender, + QStringLiteral("sender-probe@example.org")); +} + +void TestNotmuchWorker::sendersAreCountedForTheCandidateList() +{ + // The counts that BusinessSenders::appendCandidates() consumes: per-sender + // totals over a query, keyed by the lower-cased BARE address (a display + // name would defeat the bulk-sender guess). Two messages from one sender + // must count twice. + NotmuchFixture fixture; + QVERIFY(fixture.isValid()); + QVERIFY(fixture.addMessage(QStringLiteral("inbox"), + QStringLiteral("n1@example.org"), + QStringLiteral("Receipt one"), + QStringLiteral("noreply@shop.example"), + QStringLiteral("Mon, 8 Jun 2026 10:00:00 +0000"), + QStringLiteral("body"))); + QVERIFY(fixture.addMessage(QStringLiteral("inbox"), + QStringLiteral("n2@example.org"), + QStringLiteral("Receipt two"), + QStringLiteral("noreply@shop.example"), + QStringLiteral("Tue, 9 Jun 2026 10:00:00 +0000"), + QStringLiteral("body"))); + QVERIFY(fixture.addMessage(QStringLiteral("inbox"), + QStringLiteral("j1@example.org"), + QStringLiteral("Hello"), + QStringLiteral("john@example.org"), + QStringLiteral("Wed, 10 Jun 2026 10:00:00 +0000"), + QStringLiteral("body"))); + QVERIFY2(fixture.index(), qPrintable(fixture.error())); + + NotmuchWorker worker(fixture.configPath()); + QSignalSpy spy(&worker, &NotmuchWorker::senderCountsReady); + worker.countSenders(QStringLiteral("*")); + QVERIFY(spy.count() > 0); + + const auto counts = spy.first().at(0).value<QHash<QString, int>>(); + QCOMPARE(counts.value(QStringLiteral("noreply@shop.example")), 2); + QCOMPARE(counts.value(QStringLiteral("john@example.org")), 1); +} + void TestNotmuchWorker::loadThreadTreeReportsReplyDepth() { // Thread A is a root plus one reply carrying In-Reply-To, which is what @@ -1003,6 +1079,35 @@ void TestNotmuchWorker::recipientsAreFoldedWhenAskedFor() "two plus one: %1").arg(summary))); } +void TestNotmuchWorker::theFirstRecipientsAddressCrossesForTheAvatar() +{ + // Item 169's flat-view avatar. `recipients` is a DISPLAY summary and + // carries no address at all when every recipient has a name, so the hash + // needs the bare one; it rides the same fold, so it costs nothing extra. + const QVector<ThreadSummary> one = + runQuery(QStringLiteral("subject:Preventivo"), + NotmuchWorker::NewestFirst, true); + QCOMPARE(one.size(), 1); + QCOMPARE(one.at(0).firstMessageRecipient, + QStringLiteral("mario@example.org")); + + // A quoted display name containing a comma must not defeat the parse, for + // the same reason it must not defeat the summary. + const QVector<ThreadSummary> many = + runQuery(QStringLiteral("subject:Riunione"), + NotmuchWorker::NewestFirst, true); + QCOMPARE(many.size(), 1); + QCOMPARE(many.at(0).firstMessageRecipient, + QStringLiteral("mario@example.org")); + + // And it stays empty when the query never asked, exactly as `recipients` + // does: it is behind the same performance contract. + const QVector<ThreadSummary> unasked = + runQuery(QStringLiteral("subject:Preventivo")); + QCOMPARE(unasked.size(), 1); + QVERIFY(unasked.at(0).firstMessageRecipient.isEmpty()); +} + void TestNotmuchWorker::recipientsCrossAQueuedCall() { // The trap CLAUDE.md records for SortOrder, in the shape it takes for this |
