import json from datetime import timedelta import pytest from approval_engine.audit import AuditCoreSink, AuditDeliveryError, OutboxWorker from approval_engine.store import Engine from tests.conftest import FROZEN, approve class Response: def __init__(self, status): self.status = status def getcode(self): return self.status def close(self): pass def test_audit_sender_adapts_envelope_and_rereads_token(tmp_path): token = tmp_path / "token" token.write_text("first") seen = [] def open_request(request, timeout): seen.append((request, timeout)) return Response(202) engine = Engine(":memory:", clock=lambda: FROZEN) approve(engine) sink = AuditCoreSink("http://audit-core:8080", token, opener=open_request) sink(engine.undrained()[0]["payload"]) token.write_text("second") sink(engine.undrained()[0]["payload"]) first, second = (item[0] for item in seen) assert first.get_header("Authorization") == "Bearer first" assert second.get_header("Authorization") == "Bearer second" assert first.get_header("Idempotency-key") == engine.undrained()[0]["event_id"] body = json.loads(first.data) assert body["id"] == body["correlation_id"] or body["correlation_id"] assert body["type"] == "approval.issuance" assert body["source"] == "approval-engine" engine.close() def test_nonaccepted_audit_status_remains_pending(tmp_path): token = tmp_path / "token" token.write_text("token") engine = Engine(":memory:", clock=lambda: FROZEN) approve(engine) sink = AuditCoreSink( "http://audit-core:8080", token, opener=lambda *_args, **_kwargs: Response(503) ) result = engine.drain(sink) assert result == {"delivered": 0, "failed": 1} assert engine.undrained()[0]["last_error"] == "AuditDeliveryError" engine.close() def test_worker_emits_due_heartbeat_and_drains(): now = [FROZEN] engine = Engine(":memory:", clock=lambda: now[0]) delivered = [] worker = OutboxWorker(engine, delivered.append, heartbeat_interval_seconds=300) first = worker.run_once() assert first["delivered"] == 1 assert delivered[0]["action"] == "approval.heartbeat" now[0] += timedelta(seconds=301) second = worker.run_once() assert second["delivered"] == 1 assert len(delivered) == 2 engine.close() def test_sender_rejects_empty_token(tmp_path): token = tmp_path / "token" token.write_text("") sink = AuditCoreSink("http://audit-core", token) with pytest.raises(AuditDeliveryError, match="credential"): sink({})