1b8c60fcb5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
112 lines
3.7 KiB
Python
112 lines
3.7 KiB
Python
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 <sender@example.com>")
|
|
|
|
|
|
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 <sender@example.com>"
|
|
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)
|