#!/usr/bin/env python3 """Tests for gmail_send (DWD Gmail-API send) + digest_mailer (transport routing). No network: the credential provider and urllib.request.urlopen are monkeypatched. Run directly or via backend/run_tests.py. """ import base64 import json import os import sys import urllib.request sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from email_integration import gmail_send import digest_mailer import smtp_send FAILS = [] def check(cond, msg): print((" ok " if cond else " XX ") + msg) if not cond: FAILS.append(msg) class _Tok: def __init__(self, t): self.token = t class _Provider: def __init__(self): self.calls = [] def access_token_for(self, email, scope): self.calls.append((email, scope)) return _Tok("tok-" + email) class _Resp: def __init__(self, payload): self._p = payload def read(self): return json.dumps(self._p).encode() def __enter__(self): return self def __exit__(self, *a): return False def main(): # 1. _build_raw round-trips raw = gmail_send._build_raw("a@x", ["b@x", "c@x"], "Subj", "Hello body") decoded = base64.urlsafe_b64decode(raw).decode() check("From: a@x" in decoded and "Subject: Subj" in decoded and "b@x, c@x" in decoded and "Hello body" in decoded, "_build_raw round-trips From/To/Subject/body") # 2. send_via_gmail posts correctly (fake provider + fake urlopen) captured = {} orig_open = urllib.request.urlopen orig_build = gmail_send._creds.build_provider prov = _Provider() gmail_send._creds.build_provider = lambda factory: prov def fake_open(req, timeout=None): captured["url"] = req.full_url captured["auth"] = req.headers.get("Authorization") captured["body"] = json.loads(req.data.decode()) return _Resp({"id": "msg123", "threadId": "thr123"}) urllib.request.urlopen = fake_open try: res = gmail_send.send_via_gmail("grant@ten31.xyz", ["a@ten31.xyz", "b@ten31.xyz"], "S", "B") finally: urllib.request.urlopen = orig_open gmail_send._creds.build_provider = orig_build check(res["message_id"] == "msg123", "send returns message_id") check(res["from"] == "grant@ten31.xyz", "send reports from") check(res["sent_to"] == ["a@ten31.xyz", "b@ten31.xyz"], "send reports recipients") check(captured["url"].endswith("/users/grant%40ten31.xyz/messages/send"), "posts to messages/send for the impersonated user") check(captured["auth"] == "Bearer tok-grant@ten31.xyz", "uses the compose-scoped token") check("raw" in captured["body"] and "message" not in captured["body"], "send body is {raw:...} (not nested under message, unlike drafts)") check(prov.calls and prov.calls[0][1] == gmail_send._creds.GMAIL_COMPOSE_SCOPE, "requests the gmail.compose scope") # 3. validation try: gmail_send.send_via_gmail("x@x", [], "s", "b"); ok = False except ValueError: ok = True check(ok, "empty recipients -> ValueError") try: gmail_send.send_via_gmail("", ["a@x"], "s", "b"); ok = False except ValueError: ok = True check(ok, "missing sender -> ValueError") # 4. transport() selection orig_avail = gmail_send.gmail_available orig_smtp_cfg = smtp_send.smtp_configured try: gmail_send.gmail_available = lambda: True check(digest_mailer.transport() == "gmail-dwd", "transport prefers gmail-dwd") gmail_send.gmail_available = lambda: False smtp_send.smtp_configured = lambda: True check(digest_mailer.transport() == "smtp", "transport falls back to smtp") smtp_send.smtp_configured = lambda: False check(digest_mailer.transport() is None, "transport None when neither configured") finally: gmail_send.gmail_available = orig_avail smtp_send.smtp_configured = orig_smtp_cfg # 5. send_digest routes to gmail + honors CRM_DIGEST_SENDER orig_send = gmail_send.send_via_gmail sent = {} try: gmail_send.gmail_available = lambda: True def fake_send(sender, to, subj, body, conn=None): sent.update(sender=sender, to=to) return {"sent_to": to, "from": sender, "message_id": "m"} gmail_send.send_via_gmail = fake_send os.environ["CRM_DIGEST_SENDER"] = "digest@ten31.xyz" res = digest_mailer.send_digest(None, ["a@ten31.xyz"], "S", "B") check(res["transport"] == "gmail-dwd", "send_digest tags transport gmail-dwd") check(sent["sender"] == "digest@ten31.xyz", "send_digest uses CRM_DIGEST_SENDER") finally: gmail_send.send_via_gmail = orig_send gmail_send.gmail_available = orig_avail os.environ.pop("CRM_DIGEST_SENDER", None) # 6. send_digest raises NoTransport when neither is available try: gmail_send.gmail_available = lambda: False smtp_send.smtp_configured = lambda: False try: digest_mailer.send_digest(None, ["a@x"], "S", "B"); ok = False except digest_mailer.NoTransport: ok = True check(ok, "send_digest raises NoTransport when no transport") finally: gmail_send.gmail_available = orig_avail smtp_send.smtp_configured = orig_smtp_cfg print() if FAILS: print(f"FAILED ({len(FAILS)}):") for f in FAILS: print(" -", f) raise SystemExit(1) print("ALL PASS (gmail_send + digest_mailer)") if __name__ == "__main__": main()