from app.platform import mailer def test_logmailer_captures_to_outbox(): m = mailer.LogMailer() assert m.outbox == [] m.send("merchant@example.com", "Your ecomm code: 123456", "Code: 123456 (valid 10 min)") assert len(m.outbox) == 1 msg = m.outbox[-1] assert msg.to == "merchant@example.com" assert msg.subject == "Your ecomm code: 123456" assert "123456" in msg.body def test_build_mailer_log_kind(): m = mailer.build_mailer("log") assert isinstance(m, mailer.LogMailer) class _FakeSMTP: """Stand-in for smtplib.SMTP capturing the call sequence (no network).""" instances: list["_FakeSMTP"] = [] fail_on_send = False def __init__(self, host, port, timeout=None): self.host, self.port, self.timeout = host, port, timeout self.calls: list[str] = [] self.message = None _FakeSMTP.instances.append(self) def __enter__(self): return self def __exit__(self, *exc): return False def starttls(self): self.calls.append("starttls") def login(self, user, password): self.calls.append(f"login:{user}") def send_message(self, msg): if _FakeSMTP.fail_on_send: raise RuntimeError("relay refused") self.calls.append("send_message") self.message = msg def _smtp_env(monkeypatch): monkeypatch.setattr(mailer.smtplib, "SMTP", _FakeSMTP) _FakeSMTP.instances.clear() _FakeSMTP.fail_on_send = False monkeypatch.setenv("ECOMM_SMTP_HOST", "smtp.example.com") monkeypatch.setenv("ECOMM_SMTP_PORT", "587") monkeypatch.setenv("ECOMM_SMTP_USER", "sender@example.com") monkeypatch.setenv("ECOMM_SMTP_PASSWORD", "not-a-real-password") monkeypatch.setenv("ECOMM_SMTP_FROM", "ecomm ") def test_build_mailer_smtp_builds_from_config(monkeypatch): _smtp_env(monkeypatch) m = mailer.build_mailer("smtp") assert isinstance(m, mailer.SmtpMailer) def test_smtpmailer_sends_via_starttls_login(monkeypatch): _smtp_env(monkeypatch) m = mailer.build_mailer("smtp") m.send("merchant@example.com", "Your ecomm code: 123456", "Code: 123456") smtp = _FakeSMTP.instances[-1] assert (smtp.host, smtp.port) == ("smtp.example.com", 587) assert smtp.calls == ["starttls", "login:sender@example.com", "send_message"] assert smtp.message["To"] == "merchant@example.com" assert smtp.message["Subject"] == "Your ecomm code: 123456" assert smtp.message["From"] == "ecomm " assert "123456" in smtp.message.get_content() def test_smtpmailer_failure_raises_mailer_error(monkeypatch): _smtp_env(monkeypatch) _FakeSMTP.fail_on_send = True import pytest m = mailer.build_mailer("smtp") with pytest.raises(mailer.MailerError): m.send("merchant@example.com", "subject", "body") def test_smtpmailer_never_logs_the_body(monkeypatch): # §6.6 log hygiene: codes never appear in deployed logs. LogMailer logging the body # is dev-only by configuration; the deployed adapter must not. The handler attaches # directly to the ecomm.mailer logger (it doesn't propagate to root). import logging _smtp_env(monkeypatch) records: list[str] = [] handler = logging.Handler() handler.emit = lambda r: records.append(r.getMessage()) # type: ignore[method-assign] lg = logging.getLogger("ecomm.mailer") lg.addHandler(handler) lg.setLevel(logging.DEBUG) try: mailer.build_mailer("smtp").send( "merchant@example.com", "Your ecomm code: 123456", "Code: 123456" ) finally: lg.removeHandler(handler) assert records, "the deployed adapter should log the send event (observability §6.6)" assert all("123456" not in r and "merchant@example.com" not in r for r in records)