from __future__ import annotations import copy import json from pathlib import Path import pytest from src.services.ai_agent_action_audit_ledger import ( load_latest_ai_agent_action_audit_ledger, ) _REPO_ROOT = Path(__file__).resolve().parents[3] _COMMITTED_SNAPSHOT = ( _REPO_ROOT / "docs" / "evaluations" / "ai_agent_action_audit_ledger_2026-06-19.json" ) def test_load_latest_ai_agent_action_audit_ledger_reads_newest_file(tmp_path): older = _snapshot(generated_at="2026-06-18T23:00:00+08:00") newer = _snapshot(generated_at="2026-06-19T04:10:00+08:00") (tmp_path / "ai_agent_action_audit_ledger_2026-06-18.json").write_text( json.dumps(older), encoding="utf-8", ) (tmp_path / "ai_agent_action_audit_ledger_2026-06-19.json").write_text( json.dumps(newer), encoding="utf-8", ) loaded = load_latest_ai_agent_action_audit_ledger(tmp_path) assert loaded["generated_at"] == "2026-06-19T04:10:00+08:00" assert loaded["program_status"]["current_task_id"] == "P2-410" assert loaded["program_status"]["next_task_id"] == "P2-411" assert loaded["rollups"]["audit_event_template_count"] == 8 assert loaded["rollups"]["verifier_receipt_gate_count"] == 5 assert loaded["rollups"]["telegram_send_count"] == 0 def test_ai_agent_action_audit_ledger_requires_read_only_mode(tmp_path): snapshot = _snapshot() snapshot["program_status"]["read_only_mode"] = False _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="program_status"): load_latest_ai_agent_action_audit_ledger(tmp_path) def test_ai_agent_action_audit_ledger_blocks_audit_db_write(tmp_path): snapshot = _snapshot() snapshot["audit_truth"]["audit_db_write_enabled"] = True _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="audit_db_write_enabled"): load_latest_ai_agent_action_audit_ledger(tmp_path) def test_ai_agent_action_audit_ledger_blocks_event_gateway_write(tmp_path): snapshot = _snapshot() snapshot["audit_event_templates"][0]["gateway_queue_write_allowed"] = True _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="gateway_queue_write_allowed"): load_latest_ai_agent_action_audit_ledger(tmp_path) def test_ai_agent_action_audit_ledger_requires_all_risk_tiers(tmp_path): snapshot = _snapshot() for event in snapshot["audit_event_templates"]: event["risk_tier"] = "medium" _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="low, medium, high, and critical"): load_latest_ai_agent_action_audit_ledger(tmp_path) def test_ai_agent_action_audit_ledger_rejects_missing_source_ref(tmp_path): snapshot = _snapshot() snapshot["audit_event_templates"][0]["source_readback_ids"] = ["missing_source"] _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="missing source readbacks"): load_latest_ai_agent_action_audit_ledger(tmp_path) def test_ai_agent_action_audit_ledger_blocks_verifier_live_mode(tmp_path): snapshot = _snapshot() snapshot["verifier_receipt_gates"][0]["live_verifier_allowed"] = True _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="live_verifier_allowed"): load_latest_ai_agent_action_audit_ledger(tmp_path) def test_ai_agent_action_audit_ledger_requires_rollup_consistency(tmp_path): snapshot = _snapshot() snapshot["rollups"]["audit_event_template_count"] = 99 _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="rollup counts"): load_latest_ai_agent_action_audit_ledger(tmp_path) def test_ai_agent_action_audit_ledger_rejects_private_terms(tmp_path): snapshot = _snapshot() snapshot["audit_event_templates"][0]["display_name"] = "請把 " + "In app " + "browser" + " 內容放進前端" _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="forbidden public terms"): load_latest_ai_agent_action_audit_ledger(tmp_path) def test_ai_agent_action_audit_ledger_fails_when_missing(tmp_path): with pytest.raises(FileNotFoundError): load_latest_ai_agent_action_audit_ledger(tmp_path) def _snapshot(*, generated_at: str = "2026-06-19T04:10:00+08:00") -> dict: payload = json.loads(_COMMITTED_SNAPSHOT.read_text(encoding="utf-8")) cloned = copy.deepcopy(payload) cloned["generated_at"] = generated_at return cloned def _write_snapshot(tmp_path, payload: dict) -> None: (tmp_path / "ai_agent_action_audit_ledger_2026-06-19.json").write_text( json.dumps(payload), encoding="utf-8", )