test(tests): add fastmail client unit tests

This commit is contained in:
Prad Nukala committed 2026-08-26 11:59:05 -04:00
1 parent d97996e80e
commit a7b960a3fa
1 file changed
+165
+165
View File
@@ -0,0 +1,165 @@
"""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": ["<orig@acme.com>"],
"references": ["<root@acme.com>"],
"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"], ["<orig@acme.com>"])
self.assertEqual(d["references"], ["<root@acme.com>", "<orig@acme.com>"])
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()