"""REQ-107: route_halt_artifact is a real implementation (SNS + outbox fallback).""" import json import os import sys from pathlib import Path from unittest import mock import pytest ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) from core.separation_of_duties import route_halt_artifact def test_route_halt_publishes_to_sns_when_arn_set(monkeypatch): """With ACDL_SOD_HALT_TOPIC_ARN set, the SNS client receives the publish.""" monkeypatch.setenv("ACDL_SOD_HALT_TOPIC_ARN", "arn:aws:sns:us-east-1:000000000000:acdl-sod-halt") sns_client = mock.MagicMock() route_halt_artifact("contract-123", "SEPARATION_OF_DUTIES_VIOLATION: x==y", oncall_client=sns_client) sns_client.publish.assert_called_once() call = sns_client.publish.call_args assert call.kwargs["TopicArn"] == "arn:aws:sns:us-east-1:000000000000:acdl-sod-halt" assert "contract-123" in call.kwargs["Message"] assert "SEPARATION_OF_DUTIES_VIOLATION" in call.kwargs["Message"] assert call.kwargs["Subject"] == "ACDL SoD halt" def test_route_halt_falls_back_to_stderr_when_arn_unset(monkeypatch, capsys): """Without ACDL_SOD_HALT_TOPIC_ARN, a stderr emission occurs.""" monkeypatch.delenv("ACDL_SOD_HALT_TOPIC_ARN", raising=False) # Mock outbox_writer.write_event to avoid AWS calls. with mock.patch("core.outbox_writer.write_event", return_value=None): route_halt_artifact("contract-456", "violation", oncall_client=None) captured = capsys.readouterr() assert "contract-456" in captured.err assert "violation" in captured.err def test_route_halt_outbox_fallback_writes_event(monkeypatch): """Without the SNS ARN, the outbox fallback writes a SEPARATION_OF_DUTIES_VIOLATION event.""" monkeypatch.delenv("ACDL_SOD_HALT_TOPIC_ARN", raising=False) with mock.patch("core.outbox_writer.write_event") as mock_write: route_halt_artifact("contract-789", "sod violation", oncall_client=None) mock_write.assert_called_once() event = mock_write.call_args[0][0] assert event["contractId"] == "contract-789" assert event["eventType"] == "SEPARATION_OF_DUTIES_VIOLATION" assert "sod violation" in event["reason"] def test_route_halt_sns_failure_falls_back_to_outbox(monkeypatch): """If SNS publish raises, the outbox fallback is used.""" monkeypatch.setenv("ACDL_SOD_HALT_TOPIC_ARN", "arn:aws:sns:us-east-1:000000000000:acdl-sod-halt") sns_client = mock.MagicMock() sns_client.publish.side_effect = Exception("SNS down") with mock.patch("core.outbox_writer.write_event") as mock_write: route_halt_artifact("contract-fail", "violation", oncall_client=sns_client) mock_write.assert_called_once() def test_sns_topic_defined_in_terraform(): """terraform/platform/main.tf defines the acdl-sod-halt SNS topic.""" tf = (ROOT / "terraform" / "platform" / "main.tf").read_text() assert "aws_sns_topic" in tf assert "acdl-sod-halt" in tf assert "acdl_sod_halt_topic_arn" in tf