aboutsummaryrefslogtreecommitdiffstats
path: root/assets/hooks/test_post_new.py
diff options
context:
space:
mode:
Diffstat (limited to 'assets/hooks/test_post_new.py')
-rwxr-xr-xassets/hooks/test_post_new.py93
1 files changed, 90 insertions, 3 deletions
diff --git a/assets/hooks/test_post_new.py b/assets/hooks/test_post_new.py
index 07728a6..074bf24 100755
--- a/assets/hooks/test_post_new.py
+++ b/assets/hooks/test_post_new.py
@@ -212,7 +212,7 @@ def test_a_protected_removal_is_skipped_whole_and_the_run_continues():
assert count(env, "tag:new") == 0
-def setup_accounts(tmp, sent_config=True, split_index=False):
+def setup_accounts(tmp, sent_config=True, split_index=False, with_spam=False):
"""A maildir laid out as qtmaildir configures it: two accounts, each with
an Inbox and a Sent folder, one message in each.
@@ -220,6 +220,9 @@ def setup_accounts(tmp, sent_config=True, split_index=False):
thing that cares where a file sits. The folder names are the awkward
ones deliberately: a bracketed, spaced provider folder is what the real
config carries, and a flat `Sent` is what the other half carries.
+
+ `with_spam` adds each account's spam folder and its `spam` key, for the
+ test that a non-arrival folder added later is picked up the same way.
"""
root = Path(tmp) / "Mail"
folders = {
@@ -231,6 +234,10 @@ def setup_accounts(tmp, sent_config=True, split_index=False):
"acct-two/Inbox", "acct-two/[Provider]/Posta inviata"):
for part in ("new", "cur", "tmp"):
(root / sub / part).mkdir(parents=True)
+ if with_spam:
+ for sub in ("acct-one/Spam", "acct-two/[Provider]/Spam"):
+ for part in ("new", "cur", "tmp"):
+ (root / sub / part).mkdir(parents=True)
make_message(root / "acct-one/Inbox", "arrived-one",
"friend@example.org", "an arrival")
@@ -269,8 +276,10 @@ def setup_accounts(tmp, sent_config=True, split_index=False):
conf.parent.mkdir(parents=True, exist_ok=True)
conf.write_text(
"[account.one]\nmaildir = acct-one\nsent = Sent\n"
- "[account.two]\nmaildir = acct-two\n"
- "sent = [Provider]/Posta inviata\n")
+ + ("spam = Spam\n" if with_spam else "")
+ + "[account.two]\nmaildir = acct-two\n"
+ "sent = [Provider]/Posta inviata\n"
+ + ("spam = [Provider]/Spam\n" if with_spam else ""))
subprocess.run(["notmuch", "new"], env=env, capture_output=True,
check=True)
@@ -346,6 +355,84 @@ def test_a_message_that_was_sent_AND_received_keeps_inbox():
assert count(env, "tag:inbox and id:sent-one@example.org") == 0
+def test_spam_mail_does_not_keep_the_inbox_tag():
+ """Spam is not an arrival either. A provider's filter files it into a spam
+ folder, notmuch's new.tags still applies `inbox`, and without the carve-out
+ it shows up in the Inbox view (item 202)."""
+ with tempfile.TemporaryDirectory() as tmp:
+ env = setup_accounts(tmp, with_spam=True)
+ root = Path(tmp) / "Mail"
+ make_message(root / "acct-one/Spam", "spam-one",
+ "spammer@example.net", "cheap stuff")
+ subprocess.run(["notmuch", "new"], env=env, capture_output=True,
+ check=True)
+
+ write_rules(env, [])
+ assert count(env, 'tag:inbox and path:"acct-one/Spam/**"') == 1
+
+ result = subprocess.run([str(HOOK)], env=env, capture_output=True,
+ text=True)
+ assert result.returncode == 0, result.stderr
+ assert count(env, 'tag:inbox and path:"acct-one/Spam/**"') == 0
+ # The arrival is untouched, which fails if the spam prefix is wrong.
+ assert count(env, 'tag:inbox and path:"acct-one/Inbox/**"') == 1
+
+
+def test_a_message_that_is_spam_AND_arrived_keeps_inbox():
+ """The all-files rule, for spam. notmuch deduplicates by Message-ID, so a
+ message with one file in a spam folder and one in an inbox DID arrive and
+ must keep `inbox` (item 202, same constraint as item 166 for sent)."""
+ with tempfile.TemporaryDirectory() as tmp:
+ env = setup_accounts(tmp, with_spam=True)
+ root = Path(tmp) / "Mail"
+ make_message(root / "acct-one/Spam", "spam-and-inbox",
+ "spammer@example.net", "came twice")
+ make_message(root / "acct-one/Inbox", "spam-and-inbox",
+ "spammer@example.net", "came twice")
+ subprocess.run(["notmuch", "new"], env=env, capture_output=True,
+ check=True)
+ assert count(env, "id:spam-and-inbox@example.org") == 1
+ assert files(env, "id:spam-and-inbox@example.org") == 2
+
+ write_rules(env, [])
+ result = subprocess.run([str(HOOK)], env=env, capture_output=True,
+ text=True)
+ assert result.returncode == 0, result.stderr
+ assert count(env, "tag:inbox and id:spam-and-inbox@example.org") == 1
+
+
+def test_an_account_without_a_spam_key_contributes_nothing():
+ """`spam` is per account. One account names its spam folder; the other
+ does not. Mail in the unconfigured account's Spam folder is an arrival by
+ the only rule the hook has and keeps `inbox`, which is the empty-path
+ guard: a missing key must not fall back to the account maildir, matching
+ everything under it."""
+ with tempfile.TemporaryDirectory() as tmp:
+ env = setup_accounts(tmp, with_spam=True)
+ root = Path(tmp) / "Mail"
+ make_message(root / "acct-one/Spam", "one-spam",
+ "spammer@example.net", "spam for one")
+ make_message(root / "acct-two/[Provider]/Spam", "two-spam",
+ "spammer@example.net", "spam for two")
+ subprocess.run(["notmuch", "new"], env=env, capture_output=True,
+ check=True)
+
+ # Rewrite the config so only account one carries a spam key.
+ conf = Path(env["XDG_CONFIG_HOME"]) / "qtmaildir" / "qtmaildir.conf"
+ conf.write_text(
+ "[account.one]\nmaildir = acct-one\nsent = Sent\nspam = Spam\n"
+ "[account.two]\nmaildir = acct-two\n"
+ "sent = [Provider]/Posta inviata\n")
+
+ write_rules(env, [])
+ result = subprocess.run([str(HOOK)], env=env, capture_output=True,
+ text=True)
+ assert result.returncode == 0, result.stderr
+ assert count(env, 'tag:inbox and path:"acct-one/Spam/**"') == 0
+ assert count(
+ env, 'tag:inbox and path:"acct-two/[Provider]/Spam/**"') == 1
+
+
def test_the_carve_out_works_with_the_index_split_from_the_mail():
"""notmuch can hold the Xapian index outside the Maildir, and this user's
does. `database.path` is then the index directory, so a carve-out reading