aboutsummaryrefslogtreecommitdiffstats
path: root/assets
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-14 18:49:48 +0200
committerDanilo M. <danix@danix.xyz>2026-09-14 18:49:48 +0200
commitc2753771096e7c303da4b351986f7f313917dcf8 (patch)
tree2f96b6c85c4bd5bb74382a2cc3492eeba173e039 /assets
parentc1c196ebeef9a17defe8a3070e8f10d548ffdf40 (diff)
downloadqtmaildir-c2753771096e7c303da4b351986f7f313917dcf8.tar.gz
qtmaildir-c2753771096e7c303da4b351986f7f313917dcf8.zip
fix(hooks): spam is not an inbox arrival
notmuch's new.tags applies inbox to every newly indexed file and the Inbox filter is tag:inbox, so spam-folder mail appeared in the Inbox view. Add spam to the post-new hook's NOT_ARRIVALS set so the existing all-files carve-out covers it, and rename the sent_* helpers to not_arrival_* now that the list means more than sent mail. Trash stays out: measured 0 affected files and it is out of scope.
Diffstat (limited to 'assets')
-rwxr-xr-xassets/hooks/post-new68
-rwxr-xr-xassets/hooks/qtmaildirconf.py35
-rwxr-xr-xassets/hooks/test_post_new.py93
-rwxr-xr-xassets/hooks/test_qtmaildirconf.py70
4 files changed, 193 insertions, 73 deletions
diff --git a/assets/hooks/post-new b/assets/hooks/post-new
index fca31a1..cad566c 100755
--- a/assets/hooks/post-new
+++ b/assets/hooks/post-new
@@ -78,19 +78,19 @@ def _quote(value):
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
-def sent_only(query, folders):
+def not_arrival_only(query, folders):
"""The ids matching `query` whose files are ALL inside `folders`.
None on failure, so the caller leaves `tag:new` in place rather than
stripping on a half-read answer.
- **Every file has to be in a sent folder, not merely one of them.** notmuch
- deduplicates by Message-ID, so mail the user sends to another of their own
- accounts is ONE message with two files: the sender's Sent copy and the
- recipient's Inbox copy. The old query matched on a path and the tag applied
- to the message, so matching the Sent copy stripped `inbox` from the copy
- that had genuinely arrived, and the mail was missing from the account that
- received it (item 166).
+ **Every file has to be in a non-arrival folder, not merely one of them.**
+ notmuch deduplicates by Message-ID, so mail the user sends to another of
+ their own accounts is ONE message with two files: the sender's Sent copy
+ and the recipient's Inbox copy. The old query matched on a path and the tag
+ applied to the message, so matching the Sent copy stripped `inbox` from the
+ copy that had genuinely arrived, and the mail was missing from the account
+ that received it (item 166).
This cannot be expressed as a query, which is why it is a loop. Measured
against a two-file message: `not path:"Inbox/**"` does NOT exclude it, and
@@ -101,7 +101,7 @@ def sent_only(query, folders):
"""
root = mail_root()
if root is None:
- log("no mail root; leaving sent mail alone")
+ log("no mail root; leaving non-arrival mail alone")
return None
prefixes = [root / folder for folder in folders]
@@ -110,10 +110,11 @@ def sent_only(query, folders):
if matched is None:
return None
- # ponytail: one `notmuch search` per matched message. N is the sent mail
- # in tag:new, so an ordinary sync is a handful and a first-run reindex is
- # the whole corpus. Batch by parsing --format=json once if that ever
- # matters; it does not at this size, and the loop is the readable form.
+ # ponytail: one `notmuch search` per matched message. N is the
+ # non-arrival mail in tag:new, so an ordinary sync is a handful and a
+ # first-run reindex is the whole corpus. Batch by parsing --format=json
+ # once if that ever matters; it does not at this size, and the loop is the
+ # readable form.
ids = []
for message_id in matched:
paths = search(f"id:{_quote(message_id)}", "files")
@@ -176,20 +177,21 @@ def search(query, output):
return values
-def strip_inbox_from_sent(run):
- """Take `inbox` off mail the user SENT, and nothing else.
+def strip_inbox_from_non_arrivals(run):
+ """Take `inbox` off mail that did not ARRIVE, and nothing else.
`notmuch new` applies new.tags to every file it indexes, and it cannot
- tell an arrival from the copy qtmaildir files into a sent folder after a
- send. The result is sent mail carrying `inbox`, which puts it in an inbox
- view it never arrived in and in any hand-typed `tag:inbox` search.
+ tell an arrival from a copy this system filed itself: sent mail, a saved
+ draft, or mail a provider's filter dropped into a spam folder. The result
+ is such mail carrying `inbox`, which puts it in an inbox view it never
+ arrived in and in any hand-typed `tag:inbox` search.
This is NOT a relaxation of PROTECTED_REMOVALS below, and the difference
is the whole reason it can run unattended. That guard is about a RULE
removing `inbox` from mail whose provenance the hook cannot judge. Here
- the provenance is the file's own path: a message inside a configured sent
- folder is one this system sent, and `inbox` was never true of it. Nothing
- the user could act on is being hidden.
+ the provenance is the file's own path: a message whose files are ALL
+ inside a configured sent, drafts or spam folder never arrived, and `inbox`
+ was never true of it. Nothing the user could act on is being hidden.
Only `inbox`. `unread` is untouched, because maildir.synchronize_flags is
true and removing it rewrites Maildir filenames, which reaches the server
@@ -198,17 +200,17 @@ def strip_inbox_from_sent(run):
Scoped to tag:new like every rule, so a sync never rewrites tags across
the whole corpus. Mail already indexed keeps whatever it has.
"""
- folders = qtmaildirconf.sent_folders()
+ folders = qtmaildirconf.not_arrival_folders()
if not folders:
- # No config, or no account keeping sent mail locally. Nothing to
- # protect, and this must NOT fall through to an empty query: notmuch
- # reads that as "match everything", which would strip `inbox` from
- # every newly indexed message on the system.
+ # No config, or no account keeping non-arrival folders locally.
+ # Nothing to protect, and this must NOT fall through to an empty
+ # query: notmuch reads that as "match everything", which would strip
+ # `inbox` from every newly indexed message on the system.
return True
- query = f"{SCOPE} and ({qtmaildirconf.sent_query(folders)})"
+ query = f"{SCOPE} and ({qtmaildirconf.not_arrival_query(folders)})"
- ids = sent_only(query, folders)
+ ids = not_arrival_only(query, folders)
if ids is None:
return False
@@ -220,7 +222,7 @@ def strip_inbox_from_sent(run):
# "applied" whether it stripped four messages or none, and item 164 is
# exactly the case where that distinction is the whole question: a draft
# that kept `inbox` on a pass whose log claimed the carve-out had run.
- log(f"sent-folder carve-out applied over {len(folders)} folder(s), "
+ log(f"non-arrival carve-out applied over {len(folders)} folder(s), "
f"{len(ids)} message(s)")
return True
@@ -296,11 +298,11 @@ def main():
applied += 1
# AFTER the rules and BEFORE the marker is consumed. After, so a rule can
- # still see its own sent mail with `inbox` on it and match the way it
- # always did; before, because the marker is what scopes this to newly
+ # still see its own non-arrival mail with `inbox` on it and match the way
+ # it always did; before, because the marker is what scopes this to newly
# indexed mail and consuming it first would leave nothing to match.
- if not strip_inbox_from_sent(run_tag):
- log("sent-folder carve-out failed; leaving tag:new in place")
+ if not strip_inbox_from_non_arrivals(run_tag):
+ log("non-arrival carve-out failed; leaving tag:new in place")
return 1
# Only after every rule succeeded. A failure part way through leaves the
diff --git a/assets/hooks/qtmaildirconf.py b/assets/hooks/qtmaildirconf.py
index e709a28..882c94a 100755
--- a/assets/hooks/qtmaildirconf.py
+++ b/assets/hooks/qtmaildirconf.py
@@ -16,9 +16,9 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Reads the account layout out of qtmaildir.conf, for the post-new hook.
-Only the sent folders are read, and only so the hook can tell mail the user
-SENT from mail that arrived. Everything else in that file belongs to the
-application.
+Only the non-arrival folders are read, and only so the hook can tell mail
+this system filed itself (sent, drafts, spam) from mail that genuinely
+arrived. Everything else in that file belongs to the application.
Stdlib only: this is imported by a notmuch hook that runs on every sync.
@@ -52,7 +52,7 @@ def _accounts(path):
A file that will not parse yields NO accounts rather than raising. The
caller is a hook running after `notmuch new` has already indexed the
mail: failing the sync over a malformed application config is worse than
- not protecting sent mail for one cycle, and the hook logs the miss.
+ not protecting non-arrival mail for one cycle, and the hook logs the miss.
"""
parser = configparser.ConfigParser(
# QSettings writes `;` comments, and `#` appears inside values (a
@@ -77,20 +77,27 @@ def _accounts(path):
# Folders mail does not ARRIVE in: this system put the message there itself.
+# notmuch's `new.tags` applies `inbox` to every file it indexes, so a file in
+# any of these folders would otherwise appear in the tag:inbox Inbox view.
#
-# Trash is deliberately absent. qtmaildir's own Delete leaves `inbox` on a
-# trashed message so Restore can put it back where it came from, and stripping
-# it here would fight that.
-NOT_ARRIVALS = ("sent", "drafts")
-
-
-def sent_folders(path=None):
+# Trash is deliberately absent. This is scope, not a claim it can never
+# happen: measured 0 trashed files carried `inbox`, and the reported defect is
+# spam (item 202). qtmaildir's own Delete no longer leaves `inbox` on a
+# trashed message either: it strips `unread` and `inbox` (item 168), and
+# Restore re-adds `inbox` from the `moved-from:` origin tag once the message
+# returns to its origin folder. A file moved into the trash folder by another
+# client could still pick up `inbox` from `new.tags`; that is unmeasured and
+# out of scope for now, so it is not in this list.
+NOT_ARRIVALS = ("sent", "drafts", "spam")
+
+
+def not_arrival_folders(path=None):
"""Every folder mail does not arrive in, relative to the mail root.
An account contributes nothing unless it names a maildir: a bare `Sent`
would match every account's folder of that name at once. Each of the keys
in NOT_ARRIVALS is optional on its own, since an account may keep no sent
- mail or no drafts locally.
+ mail, no drafts or no spam folder locally.
"""
if path is None:
path = default_path()
@@ -107,12 +114,12 @@ def sent_folders(path=None):
return folders
-def sent_query(folders):
+def not_arrival_query(folders):
"""A notmuch query matching everything inside the given folders.
Empty for an empty list, and the caller MUST check: an empty query means
"match everything" to notmuch, so handing this straight to a tag command
- would treat the whole corpus as sent mail.
+ would treat the whole corpus as non-arrival mail.
`path:` is hierarchical, so `<folder>/**` covers `cur/` and `new/` and
any nesting a provider invents underneath.
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
diff --git a/assets/hooks/test_qtmaildirconf.py b/assets/hooks/test_qtmaildirconf.py
index c8aa78d..89565d9 100755
--- a/assets/hooks/test_qtmaildirconf.py
+++ b/assets/hooks/test_qtmaildirconf.py
@@ -15,7 +15,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Unit checks for the qtmaildir.conf reader the post-new hook uses to find
-the sent folders.
+the non-arrival folders.
The file is written by QSettings, not by configparser, so the cases that
matter are the ones where the two disagree: a section name carrying a dot, a
@@ -37,37 +37,51 @@ def write_config(tmp, text):
return path
-def test_sent_folders_are_read_per_account():
+def test_not_arrival_folders_are_read_per_account():
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, "[account.work]\n"
"maildir = work\n"
"sent = Sent\n"
"trash = Trash\n")
- assert qtmaildirconf.sent_folders(path) == ["work/Sent"]
+ assert qtmaildirconf.not_arrival_folders(path) == ["work/Sent"]
-def test_drafts_are_excluded_alongside_sent():
- """A draft never arrived either, so it must not carry `inbox`. Both keys
- feed one list: the hook asks a single question, "is this a folder mail
- arrives in", and sent and drafts answer it the same way.
+def test_drafts_and_spam_are_excluded_alongside_sent():
+ """Neither a draft nor spam ever arrived, so neither must carry `inbox`.
+ All three keys feed one list: the hook asks a single question, "is this a
+ folder mail arrives in", and sent, drafts and spam answer it the same way.
- Trash is deliberately NOT here. qtmaildir's own Delete leaves `inbox` on
- a trashed message so Restore can put it back where it came from, and
- stripping it here would fight that.
+ Trash is deliberately NOT here: the reported defect is spam and 0 trashed
+ files carried `inbox` (item 202), and qtmaildir's own Delete now strips
+ `unread` and `inbox` (item 168), so its own moves do not leave the tag
+ behind.
"""
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, "[account.work]\n"
"maildir = work\n"
"sent = Sent\n"
"drafts = Drafts\n"
+ "spam = Spam\n"
"trash = Trash\n")
- assert qtmaildirconf.sent_folders(path) == ["work/Sent", "work/Drafts"]
+ assert qtmaildirconf.not_arrival_folders(path) == [
+ "work/Sent", "work/Drafts", "work/Spam"]
+
+
+def test_spam_may_be_the_only_non_arrival_folder():
+ """An account need not keep sent mail or drafts locally, and Gmail's
+ spam folder is one the reader must still pick up on its own."""
+ with tempfile.TemporaryDirectory() as tmp:
+ path = write_config(tmp, "[account.g]\n"
+ "maildir = gmail\n"
+ "spam = [Gmail]/Spam\n")
+ assert qtmaildirconf.not_arrival_folders(path) == [
+ "gmail/[Gmail]/Spam"]
def test_an_account_with_only_drafts_still_contributes():
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, "[account.a]\nmaildir = a\ndrafts = Drafts\n")
- assert qtmaildirconf.sent_folders(path) == ["a/Drafts"]
+ assert qtmaildirconf.not_arrival_folders(path) == ["a/Drafts"]
def test_an_account_section_may_carry_a_dot():
@@ -78,7 +92,7 @@ def test_an_account_section_may_carry_a_dot():
path = write_config(tmp, "[account.provider.name]\n"
"maildir = provider-name\n"
"sent = Sent\n")
- assert qtmaildirconf.sent_folders(path) == ["provider-name/Sent"]
+ assert qtmaildirconf.not_arrival_folders(path) == ["provider-name/Sent"]
def test_a_folder_may_contain_spaces_and_brackets():
@@ -88,7 +102,7 @@ def test_a_folder_may_contain_spaces_and_brackets():
path = write_config(tmp, "[account.g]\n"
"maildir = gmail\n"
"sent = [Gmail]/Posta inviata\n")
- assert qtmaildirconf.sent_folders(path) == [
+ assert qtmaildirconf.not_arrival_folders(path) == [
"gmail/[Gmail]/Posta inviata"]
@@ -99,13 +113,23 @@ def test_an_account_without_a_sent_key_contributes_nothing():
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, "[account.a]\nmaildir = a\ntrash = Trash\n"
"[account.b]\nmaildir = b\nsent = Sent\n")
- assert qtmaildirconf.sent_folders(path) == ["b/Sent"]
+ assert qtmaildirconf.not_arrival_folders(path) == ["b/Sent"]
def test_an_empty_sent_value_contributes_nothing():
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, "[account.a]\nmaildir = a\nsent =\n")
- assert qtmaildirconf.sent_folders(path) == []
+ assert qtmaildirconf.not_arrival_folders(path) == []
+
+
+def test_an_account_without_a_spam_key_contributes_nothing():
+ """`spam` is optional like the others. A missing key must not append the
+ bare maildir, and an EMPTY value must not either: either would become an
+ empty-path prefix matching the whole account."""
+ with tempfile.TemporaryDirectory() as tmp:
+ path = write_config(tmp, "[account.a]\nmaildir = a\nsent = Sent\n"
+ "[account.b]\nmaildir = b\nspam =\n")
+ assert qtmaildirconf.not_arrival_folders(path) == ["a/Sent"]
def test_an_account_without_a_maildir_contributes_nothing():
@@ -113,7 +137,7 @@ def test_an_account_without_a_maildir_contributes_nothing():
and a bare `Sent` would match every account's sent folder at once."""
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, "[account.a]\nsent = Sent\n")
- assert qtmaildirconf.sent_folders(path) == []
+ assert qtmaildirconf.not_arrival_folders(path) == []
def test_comments_and_other_sections_are_ignored():
@@ -127,14 +151,14 @@ def test_comments_and_other_sections_are_ignored():
"; another comment\n"
"maildir = a\n"
"sent = Sent\n")
- assert qtmaildirconf.sent_folders(path) == ["a/Sent"]
+ assert qtmaildirconf.not_arrival_folders(path) == ["a/Sent"]
def test_a_missing_file_yields_no_folders():
"""The hook must run on a system with no qtmaildir config at all: it
then protects nothing, rather than failing the sync."""
with tempfile.TemporaryDirectory() as tmp:
- assert qtmaildirconf.sent_folders(Path(tmp) / "absent.conf") == []
+ assert qtmaildirconf.not_arrival_folders(Path(tmp) / "absent.conf") == []
def test_an_unreadable_file_yields_no_folders():
@@ -143,25 +167,25 @@ def test_an_unreadable_file_yields_no_folders():
mail for one cycle."""
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, "this is not an ini file\n[[[\n")
- assert qtmaildirconf.sent_folders(path) == []
+ assert qtmaildirconf.not_arrival_folders(path) == []
def test_the_query_scopes_every_folder():
folders = ["a/Sent", "g/[Gmail]/Posta inviata"]
- query = qtmaildirconf.sent_query(folders)
+ query = qtmaildirconf.not_arrival_query(folders)
assert query == ('path:"a/Sent/**" or path:"g/[Gmail]/Posta inviata/**"')
def test_the_query_is_empty_when_no_folder_is_configured():
"""An empty query means "match everything" to notmuch, so the caller
must be able to tell "nothing to protect" from "protect the world"."""
- assert qtmaildirconf.sent_query([]) == ""
+ assert qtmaildirconf.not_arrival_query([]) == ""
def test_a_folder_containing_a_quote_cannot_break_out_of_the_query():
"""The folder name reaches a notmuch query as a quoted string. A stray
double quote would end the term and let the rest be read as syntax."""
- query = qtmaildirconf.sent_query(['a/He said "hi"'])
+ query = qtmaildirconf.not_arrival_query(['a/He said "hi"'])
assert query.count('"') % 2 == 0
assert "\\\"" in query or '""' in query