from __future__ import annotations import os from dataclasses import replace from datetime import UTC, datetime, timedelta from uuid import UUID, uuid4 import pytest os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test") from src.services.paid_provider_canary_authorization import ( # noqa: E402 PAID_CANARY_AGENT_ID, PAID_CANARY_PROJECT_ID, PAID_CANARY_TRIGGER_REF, PAID_CANARY_TRIGGER_TYPE, PAID_CANARY_WORK_ITEM_ID, PaidProviderCanaryAuthorizationError, PaidProviderCanaryAuthorizationEvidence, paid_provider_canary_authorization_input, paid_provider_canary_authorization_input_sha256, require_paid_provider_canary_authorization, verify_paid_provider_canary_authorization, ) _SELECTED_RUN_ID = UUID("267a3d26-3c29-470f-8fe3-388a2f408f39") _TRACE_ID = "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" class _Reader: def __init__( self, evidence: PaidProviderCanaryAuthorizationEvidence | None, *, error: Exception | None = None, ) -> None: self.evidence = evidence self.error = error self.calls: list[tuple[UUID, str, str]] = [] async def read( self, *, run_id: UUID, project_id: str, work_item_id: str, ) -> PaidProviderCanaryAuthorizationEvidence | None: self.calls.append((run_id, project_id, work_item_id)) if self.error is not None: raise self.error return self.evidence def _evidence( *, now: datetime, run_id: UUID | None = None, ) -> PaidProviderCanaryAuthorizationEvidence: return PaidProviderCanaryAuthorizationEvidence( run_id=run_id or _SELECTED_RUN_ID, trace_id=_TRACE_ID, project_id=PAID_CANARY_PROJECT_ID, agent_id=PAID_CANARY_AGENT_ID, state="running", trigger_type=PAID_CANARY_TRIGGER_TYPE, trigger_ref=PAID_CANARY_TRIGGER_REF, is_shadow=False, input_sha256=paid_provider_canary_authorization_input_sha256(), timeout_at=now + timedelta(minutes=5), approval_step_count=1, approval_step_status="success", approval_step_was_blocked=False, approval_completed_at=now - timedelta(minutes=1), approval_audit_count=1, approval_audit_created_at=now - timedelta(seconds=50), prior_canary_start_count=0, ) def test_authorization_input_is_detached_and_hash_is_stable() -> None: first = paid_provider_canary_authorization_input() second = paid_provider_canary_authorization_input() first["provider_order"].append("unexpected") assert second["project_id"] == "awoooi" assert second["work_item_id"] == "AIA-SRE-013" assert "unexpected" not in second["provider_order"] assert len(paid_provider_canary_authorization_input_sha256()) == 64 assert ( paid_provider_canary_authorization_input_sha256() == paid_provider_canary_authorization_input_sha256() ) @pytest.mark.asyncio async def test_exact_gate5_evidence_returns_non_sensitive_authorized_receipt() -> None: now = datetime(2026, 7, 16, 8, 0, tzinfo=UTC) evidence = _evidence(now=now) reader = _Reader(evidence) receipt = await verify_paid_provider_canary_authorization( str(evidence.run_id), evidence_reader=reader, now=now, ) assert receipt["authorized"] is True assert receipt["status"] == "authorized" assert receipt["authorization_ref"] == str(evidence.run_id) assert receipt["run_id"] == str(evidence.run_id) assert receipt["trace_id"] == _TRACE_ID assert receipt["project_id"] == "awoooi" assert receipt["work_item_id"] == "AIA-SRE-013" assert receipt["valid_for_seconds"] == 300 assert receipt["checks"]["authorization_unused"] is True assert receipt["checks"]["authorization_run_selected"] is True assert receipt["contract"]["same_run_identity"] is True assert receipt["reuse_check"] == "clear_at_read_time" assert receipt["read_only_verification"] is True assert receipt["authorization_consumed_by_verifier"] is False assert receipt["operator_identity_returned"] is False assert receipt["secret_value_read_or_returned"] is False assert "approver_id" not in str(receipt) assert reader.calls == [ (evidence.run_id, PAID_CANARY_PROJECT_ID, PAID_CANARY_WORK_ITEM_ID) ] @pytest.mark.asyncio async def test_authorization_ref_must_be_canonical_uuid_without_db_read() -> None: reader = _Reader(None) malformed = await verify_paid_provider_canary_authorization( "AIA-SRE-013-user-approved-20260716", evidence_reader=reader, ) uppercase = await verify_paid_provider_canary_authorization( str(uuid4()).upper(), evidence_reader=reader, ) assert malformed["authorized"] is False assert malformed["error_code"] == ( "paid_canary_authorization_ref_must_be_uuid_run_id" ) assert uppercase["authorized"] is False assert uppercase["error_code"] == ( "paid_canary_authorization_ref_not_canonical_uuid" ) assert reader.calls == [] @pytest.mark.asyncio @pytest.mark.parametrize( ("field", "value", "failed_check"), [ ("project_id", "other", "project_exact"), ( "run_id", UUID("00000000-0000-4000-8000-000000000001"), "authorization_run_selected", ), ("agent_id", "generic-agent", "agent_exact"), ("state", "waiting_approval", "state_executable"), ("is_shadow", True, "non_shadow"), ("trigger_type", "schedule", "trigger_type_exact"), ("trigger_ref", "paid-provider-canary:OTHER", "trigger_ref_exact"), ("input_sha256", "0" * 64, "input_sha256_exact"), ("trace_id", None, "trace_id_present"), ("approval_step_count", 0, "approval_step_exactly_once"), ("approval_step_status", "failed", "approval_step_success"), ("approval_step_was_blocked", True, "approval_step_not_blocked"), ("approval_audit_count", 0, "approval_audit_exactly_once"), ("prior_canary_start_count", 1, "authorization_unused"), ], ) async def test_contract_drift_fails_closed( field: str, value: object, failed_check: str, ) -> None: now = datetime(2026, 7, 16, 8, 0, tzinfo=UTC) evidence = replace(_evidence(now=now), **{field: value}) receipt = await verify_paid_provider_canary_authorization( str(evidence.run_id), evidence_reader=_Reader(evidence), now=now, ) assert receipt["authorized"] is False assert failed_check in receipt["failed_checks"] assert receipt["checks"][failed_check] is False @pytest.mark.asyncio async def test_missing_timeout_or_expired_approval_fails_closed() -> None: now = datetime(2026, 7, 16, 8, 0, tzinfo=UTC) missing_timeout = replace(_evidence(now=now), timeout_at=None) expired = replace( _evidence(now=now), approval_completed_at=now - timedelta(minutes=16), timeout_at=now + timedelta(minutes=5), ) missing_receipt = await verify_paid_provider_canary_authorization( str(missing_timeout.run_id), evidence_reader=_Reader(missing_timeout), now=now, ) expired_receipt = await verify_paid_provider_canary_authorization( str(expired.run_id), evidence_reader=_Reader(expired), now=now, ) assert missing_receipt["authorized"] is False assert missing_receipt["checks"]["run_timeout_present"] is False assert expired_receipt["authorized"] is False assert expired_receipt["checks"]["authorization_not_expired"] is False @pytest.mark.asyncio async def test_db_error_and_missing_run_are_blocked_without_error_detail() -> None: run_id = uuid4() unavailable = await verify_paid_provider_canary_authorization( str(run_id), evidence_reader=_Reader(None, error=RuntimeError("database dsn secret")), ) missing = await verify_paid_provider_canary_authorization( str(run_id), evidence_reader=_Reader(None), ) assert unavailable["error_code"] == ( "paid_canary_authorization_evidence_unavailable" ) assert "database dsn secret" not in str(unavailable) assert missing["error_code"] == "paid_canary_authorization_run_not_found" @pytest.mark.asyncio async def test_require_helper_raises_only_safe_error_code() -> None: run_id = uuid4() with pytest.raises(PaidProviderCanaryAuthorizationError) as raised: await require_paid_provider_canary_authorization( str(run_id), evidence_reader=_Reader(None), ) assert raised.value.error_code == "paid_canary_authorization_run_not_found" assert str(raised.value) == raised.value.error_code assert raised.value.receipt["authorized"] is False