from __future__ import annotations import json import pytest from src.services.ai_agent_automation_backlog_snapshot import ( load_latest_ai_agent_automation_backlog_snapshot, ) def test_load_latest_backlog_snapshot_reads_newest_file(tmp_path): older = _snapshot(generated_at="2026-06-03T00:00:00+08:00", completion=72) newer = _snapshot(generated_at="2026-06-04T00:00:00+08:00", completion=76) (tmp_path / "ai_agent_automation_backlog_2026-06-03.json").write_text( json.dumps(older), encoding="utf-8", ) (tmp_path / "ai_agent_automation_backlog_2026-06-04.json").write_text( json.dumps(newer), encoding="utf-8", ) loaded = load_latest_ai_agent_automation_backlog_snapshot(tmp_path) assert loaded["generated_at"] == "2026-06-04T00:00:00+08:00" assert loaded["program_status"]["overall_completion_percent"] == 76 assert loaded["rollups"]["total_items"] == 1 assert loaded["progress_summary"]["overall_percent"] == 0 assert loaded["item_approval_boundary_rollup"]["total_items"] == 1 assert loaded["approval_boundaries"]["sdk_installation_allowed"] is False def test_load_backlog_snapshot_requires_read_only_mode(tmp_path): snapshot = _snapshot() snapshot["program_status"]["read_only_mode"] = False (tmp_path / "ai_agent_automation_backlog_2026-06-04.json").write_text( json.dumps(snapshot), encoding="utf-8", ) with pytest.raises(ValueError, match="read_only_mode"): load_latest_ai_agent_automation_backlog_snapshot(tmp_path) def test_load_backlog_snapshot_requires_blocked_approval_boundaries(tmp_path): snapshot = _snapshot() snapshot["approval_boundaries"]["paid_api_call_allowed"] = True (tmp_path / "ai_agent_automation_backlog_2026-06-04.json").write_text( json.dumps(snapshot), encoding="utf-8", ) with pytest.raises(ValueError, match="approval boundaries"): load_latest_ai_agent_automation_backlog_snapshot(tmp_path) def test_load_backlog_snapshot_requires_total_rollup_consistency(tmp_path): snapshot = _snapshot() snapshot["rollups"]["total_items"] = 2 (tmp_path / "ai_agent_automation_backlog_2026-06-04.json").write_text( json.dumps(snapshot), encoding="utf-8", ) with pytest.raises(ValueError, match="total_items"): load_latest_ai_agent_automation_backlog_snapshot(tmp_path) def test_load_backlog_snapshot_requires_item_approval_boundaries(tmp_path): snapshot = _snapshot() del snapshot["backlog_items"][0]["approval_boundary"] (tmp_path / "ai_agent_automation_backlog_2026-06-04.json").write_text( json.dumps(snapshot), encoding="utf-8", ) with pytest.raises(ValueError, match="approval_boundary"): load_latest_ai_agent_automation_backlog_snapshot(tmp_path) def test_load_backlog_snapshot_requires_progress_summary_consistency(tmp_path): snapshot = _snapshot() snapshot["progress_summary"]["overall_percent"] = 99 (tmp_path / "ai_agent_automation_backlog_2026-06-04.json").write_text( json.dumps(snapshot), encoding="utf-8", ) with pytest.raises(ValueError, match="overall_percent"): load_latest_ai_agent_automation_backlog_snapshot(tmp_path) def test_load_backlog_snapshot_fails_when_missing(tmp_path): with pytest.raises(FileNotFoundError): load_latest_ai_agent_automation_backlog_snapshot(tmp_path) def _snapshot( *, generated_at: str = "2026-06-04T00:00:00+08:00", completion: int = 76, ) -> dict: return { "schema_version": "ai_agent_automation_backlog_v1", "generated_at": generated_at, "source_inventory_snapshot_ref": "inventory.json", "program_status": { "overall_completion_percent": completion, "current_priority": "P1", "current_task_id": "P1-302", "next_task_id": "P1-303", "read_only_mode": True, }, "rollups": { "total_items": 1, "by_priority": {"P1": 1}, "by_status": {"planned": 1}, "by_gate_status": {"read_only_allowed": 1}, "by_owner_agent": {"hermes": 1}, }, "progress_summary": { "overall_percent": 0, "done_items": 0, "planned_items": 1, "total_items": 1, "formula": "round(done_items / total_items * 100),status=done 才計入完成。", "by_priority": [ { "priority": "P1", "completion_percent": 0, "done_items": 0, "total_items": 1, } ], "by_workstream": [ { "workstream_id": "WS2", "display_name": "自動化待辦", "completion_percent": 0, "done_items": 0, "total_items": 1, "next_task_id": "P1-303", } ], }, "backlog_items": [ { "item_id": "AUTO-P1-303", "priority": "P1", "status": "planned", "workstream_id": "WS2", "source_asset_id": "awoooi_api", "source_signal_kind": "inventory_gap", "title": "建立自動化待辦只讀 API", "owner_agent": "hermes", "recommended_action": "建立 read-only API。", "action_class": "execute_read_only", "gate_status": "read_only_allowed", "risk_level": "medium", "evidence_refs": ["docs/schemas/ai_agent_automation_backlog_v1.schema.json"], "acceptance_criteria": ["API 只讀"], "approval_boundary": { "mode": "read_only_allowed", "display_summary": "只讀呈現 committed snapshot,不授權任何寫入。", "allowed_actions": ["讀取 committed snapshot", "顯示治理 UI"], "blocked_actions": ["production_write", "paid_api_call", "destructive_operation"], "requires_operator_approval_for": ["任何非只讀操作"], }, "next_review": "P1-303", } ], "item_approval_boundary_rollup": { "total_items": 1, "by_mode": {"read_only_allowed": 1}, "items_requiring_explicit_approval": [], "items_with_blocked_operations": ["AUTO-P1-303"], }, "approval_boundaries": { "sdk_installation_allowed": False, "paid_api_call_allowed": False, "shadow_or_canary_allowed": False, "production_routing_allowed": False, "destructive_operation_allowed": False, }, }