"""Guards for tools/fastmail_client.py (JMAP email/calendar/draft client). The transport is stdlib urllib; everything above it is pure or mockable. These tests pin: - filter builders emit only the conditions actually supplied (an empty condition would over-match the whole mailbox); - reply drafts carry RFC 5322 threading (inReplyTo/references from the original's messageId), a single "Re:" prefix, and default recipients from Reply-To before From; - drafts always land in the Drafts mailbox with $draft set - the client has no send path at all; - the client surfaces JMAP method-level errors instead of returning partial results, and resolves method responses by name. """ import unittest from datetime import UTC, datetime from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "tools")) from fastmail_client import ( # noqa: E402 FastmailClient, FastmailError, build_draft, build_email_filter, build_event_filter, iso_days_ago, ) class FilterBuilderTests(unittest.TestCase): def test_only_supplied_conditions_appear(self): self.assertEqual(build_email_filter(query="interview"), {"text": "interview"}) self.assertEqual(build_email_filter(), {}) self.assertEqual(build_event_filter(query="onsite"), {"text": "onsite"}) def test_iso_days_ago_is_utc_zulu(self): fixed = datetime(2026, 8, 26, 12, 0, 0, tzinfo=UTC) self.assertEqual(iso_days_ago(14, now=fixed), "2026-08-12T12:00:00Z") class DraftBuilderTests(unittest.TestCase): ORIGINAL = { "messageId": [""], "references": [""], "subject": "Interview availability", "from": [{"email": "recruiter@acme.com", "name": "R"}], "replyTo": [{"email": "talent@acme.com"}], } def test_plain_draft_shape(self): d = build_draft("mb1", "me@example.com", ["a@b.com"], "Hi", "body text") self.assertEqual(d["mailboxIds"], {"mb1": True}) self.assertEqual(d["keywords"], {"$draft": True, "$seen": True}) self.assertEqual(d["bodyValues"]["body"]["value"], "body text") self.assertNotIn("inReplyTo", d) def test_reply_threads_and_prefixes_subject_once(self): d = build_draft("mb1", "me@example.com", [], "", "thanks", in_reply_to=self.ORIGINAL) self.assertEqual(d["inReplyTo"], [""]) self.assertEqual(d["references"], ["", ""]) self.assertEqual(d["subject"], "Re: Interview availability") # Reply-To wins over From for the default recipient. self.assertEqual(d["to"], [{"email": "talent@acme.com"}]) def test_re_subject_not_doubled(self): orig = dict(self.ORIGINAL, subject="Re: Interview availability") d = build_draft("mb1", "me@example.com", [], "", "x", in_reply_to=orig) self.assertEqual(d["subject"], "Re: Interview availability") def test_no_send_path_exists(self): # The client must never submit mail: no EmailSubmission anywhere. source = (Path(__file__).resolve().parent.parent / "tools" / "fastmail_client.py").read_text(encoding="utf-8") self.assertNotIn("EmailSubmission/set", source) class MockedClient(FastmailClient): """Transport replaced: records requests, replays canned JMAP responses.""" def __init__(self, responses): super().__init__(token="test-token") self._canned = responses self.requests = [] self._session = { "apiUrl": "https://api.test/jmap", "username": "me@example.com", "primaryAccounts": { "urn:ietf:params:jmap:mail": "acct-mail", "urn:ietf:params:jmap:calendars": "acct-cal", }, } def _http(self, url, body=None): self.requests.append((url, body)) return self._canned.pop(0) class ClientWiringTests(unittest.TestCase): def test_search_email_uses_backreference_and_returns_list(self): client = MockedClient([{ "methodResponses": [ ["Email/query", {"ids": ["e1"]}, "q"], ["Email/get", {"list": [{"id": "e1", "subject": "s"}]}, "g"], ] }]) out = client.search_email(query="interview", days=14) self.assertEqual(out, [{"id": "e1", "subject": "s"}]) _url, body = client.requests[0] query_call, get_call = body["methodCalls"] self.assertEqual(query_call[1]["filter"]["text"], "interview") self.assertIn("after", query_call[1]["filter"]) self.assertEqual(get_call[1]["#ids"], {"resultOf": "q", "name": "Email/query", "path": "/ids"}) def test_jmap_method_error_raises_instead_of_partial_result(self): client = MockedClient([{ "methodResponses": [["error", {"type": "unknownMethod"}, "q"]] }]) with self.assertRaises(FastmailError): client.search_email(query="x") def test_events_use_calendar_account(self): client = MockedClient([{ "methodResponses": [ ["CalendarEvent/query", {"ids": ["ev1"]}, "q"], ["CalendarEvent/get", {"list": [{"id": "ev1", "title": "Onsite"}]}, "g"], ] }]) out = client.search_events(days=7) self.assertEqual(out[0]["title"], "Onsite") _url, body = client.requests[0] self.assertEqual(body["methodCalls"][0][1]["accountId"], "acct-cal") def test_create_draft_rejection_surfaces(self): client = MockedClient([ # Mailbox/query for drafts {"methodResponses": [["Mailbox/query", {"ids": ["mb-drafts"]}, "q"]]}, # Email/set rejection {"methodResponses": [["Email/set", {"notCreated": {"draft": {"type": "invalidProperties"}}, "created": {}}, "s"]]}, ]) with self.assertRaises(FastmailError): client.create_draft(["a@b.com"], "Hi", "body") def test_create_draft_happy_path_lands_in_drafts(self): client = MockedClient([ {"methodResponses": [["Mailbox/query", {"ids": ["mb-drafts"]}, "q"]]}, {"methodResponses": [["Email/set", {"created": {"draft": {"id": "new-draft"}}}, "s"]]}, ]) out = client.create_draft(["a@b.com"], "Hi", "body") self.assertEqual(out, {"id": "new-draft"}) _url, body = client.requests[1] create = body["methodCalls"][0][1]["create"]["draft"] self.assertEqual(create["mailboxIds"], {"mb-drafts": True}) self.assertEqual(create["from"], [{"email": "me@example.com"}]) if __name__ == "__main__": unittest.main()