"""Handler-level SES-auth reject seam (constraint 5b), per pipeline. The sender-auth gate sits AFTER ``get_object`` in both handlers, and the allowlist is read from the environment at CALL time. With an empty ``ALLOWED_DKIM_DOMAINS`` and NO auth monkeypatch, every email must be rejected: ZERO Bedrock calls, ZERO DynamoDB writes, and NO raise (rejected mail is skipped, never DLQ'd). This closes the hole where deleting the gate line still passed every other test (those bypass auth via monkeypatch). An accept-path companion per pipeline proves the gate lets good mail THROUGH when the allowlist is set to the fixture's SES-stamped domain. Parameterized over both handlers via the root-conftest ``email_handler`` fixture; all wiring goes through the shared loader + support package (no bare imports; PO web_ui lacks __init__.py -- the loader, never package imports). """ import sys from tests.support import FakeDynamoResource, FakeS3, load_raw # Per-pipeline reject fixture: any committed .eml works (the gate fails at the # empty-allowlist check, before the DKIM verdict even matters). _REJECT_FIXTURE = { "po": ("po", "ai-fallback", "comment-01"), "wo": ("wo", "ai-fallback", "unknown-subject"), } class _RecordingBedrock: """Records every invoke_model call so the test can assert ZERO were made.""" def __init__(self): self.calls = [] def invoke_model(self, modelId, body): # noqa: N803 (boto3 kwarg name) self.calls.append((modelId, body)) raise AssertionError("bedrock must not be called on a rejected email") def _siblings(handler_module): name = handler_module.__name__ # "po_email_processor_handler" / "wo_..." pipeline = name.split("_", 1)[0] return ( pipeline, sys.modules[f"{name}__extraction"], sys.modules[f"{name}__persistence"], ) def _event(key="inbound/o1"): return { "Records": [ {"s3": {"bucket": {"name": "ingest-emails-x"}, "object": {"key": key}}} ] } def test_empty_allowlist_rejects_before_bedrock_and_writes(email_handler, monkeypatch): pipeline, extraction, persistence = _siblings(email_handler) raw = load_raw(*_REJECT_FIXTURE[pipeline]) # Env read at call time; the gate sits after get_object, so a fake S3 is # still needed to reach it. monkeypatch.setenv("ALLOWED_DKIM_DOMAINS", "") monkeypatch.setattr(email_handler, "s3", FakeS3({"inbound/o1": raw})) bedrock = _RecordingBedrock() monkeypatch.setattr(extraction, "bedrock", bedrock) fake = FakeDynamoResource() monkeypatch.setattr(persistence, "dynamodb", fake) # NO raise: rejected mail is skipped, and the invocation returns normally. result = email_handler.handler(_event(), None) assert result == {"statusCode": 200, "body": "OK"} # ZERO bedrock calls, ZERO writes. assert bedrock.calls == [] assert not any(table.updates or table.puts for table in fake.tables.values()) # --- Accept-path companions: the gate lets good mail THROUGH ----------------- # A full-contract PO AI payload (valid -> a write lands once past the gate). _PO_AI_PAYLOAD = { "email_type": "new_po", "po_number": "2D-70000001", "po_status": "Issued - Created", "source_system": "coupa", "submitted_by": None, "on_behalf_of": None, "order_date": None, "revision_date": None, "last_opened": None, "acknowledged_at": None, "payment_terms": None, "requisition_number": None, "department": None, "view_order_url": None, "supplier": {"name": None}, "site_code": None, "ship_to": { "name": None, "address": None, "street": None, "city": None, "state": None, "zip": None, "location_code": None, "attn": None, }, "total_amount": 123.45, "currency": "USD", "fiscal_year": None, "trade": None, "coupa_category": None, "line_items": [], } _WO_AI_PAYLOAD = { "email_type": "comment", "work_order_id": "77777777777", "description": None, "status": None, "site_code": None, "building": None, "address": None, "severity": None, "priority": None, "date_reported": None, "scheduled_start": None, "due_date": None, "assigned_to": None, "commenter": None, "comment_text": "ai extracted", "comment_time": None, } class _FakeBody: def __init__(self, data): self._data = data def read(self): return self._data class _JsonBedrock: def __init__(self, payload): self.calls = [] self._payload = payload def invoke_model(self, modelId, body): # noqa: N803 import json self.calls.append((modelId, body)) text = json.dumps(self._payload) return {"body": _FakeBody(json.dumps({"content": [{"text": text}]}).encode())} def test_po_accept_path_passes_gate_and_writes(po_handler, monkeypatch): """PO: a scrubbed Coupa fixture (dkim=pass amazon.coupahost.com) authenticates when the allowlist names its stamped domain -- processing reaches Bedrock and a purchase-order write lands. NO auth monkeypatch (the real gate runs).""" extraction = sys.modules["po_email_processor_handler__extraction"] persistence = sys.modules["po_email_processor_handler__persistence"] monkeypatch.setenv("ALLOWED_DKIM_DOMAINS", "amazon.coupahost.com") monkeypatch.setattr( po_handler, "s3", FakeS3({"inbound/o1": load_raw("po", "ai-fallback", "new-po-05")}), ) bedrock = _JsonBedrock(_PO_AI_PAYLOAD) monkeypatch.setattr(extraction, "bedrock", bedrock) fake = FakeDynamoResource() monkeypatch.setattr(persistence, "dynamodb", fake) result = po_handler.handler(_event(), None) assert result == {"statusCode": 200, "body": "OK"} assert bedrock.calls, "authenticated mail must reach the Bedrock fallback" assert fake.tables[persistence.PO_TABLE].updates def test_wo_accept_path_passes_gate_and_writes(wo_handler, monkeypatch): """WO: the one permitted new fixture (dkim=pass seahaven.com) authenticates when the allowlist names seahaven.com -- processing reaches Bedrock and a work-order write lands. NO auth monkeypatch (the real gate runs).""" extraction = sys.modules["wo_email_processor_handler__extraction"] persistence = sys.modules["wo_email_processor_handler__persistence"] monkeypatch.setenv("ALLOWED_DKIM_DOMAINS", "seahaven.com") monkeypatch.setattr( wo_handler, "s3", FakeS3({"inbound/o1": load_raw("wo", "ses-stamped", "auth-pass-01")}), ) bedrock = _JsonBedrock(_WO_AI_PAYLOAD) monkeypatch.setattr(extraction, "bedrock", bedrock) fake = FakeDynamoResource() monkeypatch.setattr(persistence, "dynamodb", fake) result = wo_handler.handler(_event(), None) assert result == {"statusCode": 200, "body": "OK"} assert bedrock.calls, "authenticated mail must reach the Bedrock fallback" assert fake.tables[persistence.WORK_ORDERS_TABLE].updates