from __future__ import annotations import json import pytest from src.services.service_health_failure_notification_policy import ( load_latest_service_health_failure_notification_policy, ) def test_load_latest_service_health_failure_notification_policy_reads_newest_file(tmp_path): older = _snapshot(generated_at="2026-06-04T00:00:00+08:00", completion=99) newer = _snapshot(generated_at="2026-06-05T00:00:00+08:00", completion=100) (tmp_path / "service_health_failure_notification_policy_2026-06-04.json").write_text( json.dumps(older), encoding="utf-8", ) (tmp_path / "service_health_failure_notification_policy_2026-06-05.json").write_text( json.dumps(newer), encoding="utf-8", ) loaded = load_latest_service_health_failure_notification_policy(tmp_path) assert loaded["generated_at"] == "2026-06-05T00:00:00+08:00" assert loaded["program_status"]["overall_completion_percent"] == 100 assert loaded["rollups"]["total_rules"] == 3 assert loaded["operation_boundaries"]["notification_send_allowed"] is False def test_service_health_failure_notification_policy_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="read_only_mode"): load_latest_service_health_failure_notification_policy(tmp_path) def test_service_health_failure_notification_policy_blocks_notification_send(tmp_path): snapshot = _snapshot() snapshot["operation_boundaries"]["notification_send_allowed"] = True _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="operation boundaries"): load_latest_service_health_failure_notification_policy(tmp_path) def test_service_health_failure_notification_policy_requires_success_suppression(tmp_path): snapshot = _snapshot() snapshot["policy_rules"][0]["decision"] = "escalate_immediate" snapshot["rollups"]["by_decision"] = { "escalate_immediate": 2, "create_action_required": 1, } snapshot["rollups"]["immediate_escalation_rule_ids"] = [ "service_health_verified", "production_api_health_failed", ] snapshot["rollups"]["suppressed_success_rule_ids"] = [] _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="verified service health rules"): load_latest_service_health_failure_notification_policy(tmp_path) def test_service_health_failure_notification_policy_requires_message_contract(tmp_path): snapshot = _snapshot() snapshot["message_template_contract"]["required_fields"].remove("blocked_reason") _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="message_template_contract"): load_latest_service_health_failure_notification_policy(tmp_path) def test_service_health_failure_notification_policy_requires_redaction_contract(tmp_path): snapshot = _snapshot() snapshot["display_redaction_contract"]["conversation_transcript_display_allowed"] = True _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="conversation transcript"): load_latest_service_health_failure_notification_policy(tmp_path) def test_service_health_failure_notification_policy_rejects_transcript_markers(tmp_path): snapshot = _snapshot() snapshot["message_template_contract"]["failure_message_policy"] = "禁止標記:" + "批准!" + "繼續" _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="forbidden work-window conversation content"): load_latest_service_health_failure_notification_policy(tmp_path) def test_service_health_failure_notification_policy_requires_forbidden_redaction_fields(tmp_path): snapshot = _snapshot() snapshot["message_template_contract"]["forbidden_fields"].remove("work_window_transcript") _write_snapshot(tmp_path, snapshot) with pytest.raises(ValueError, match="forbidden_fields"): load_latest_service_health_failure_notification_policy(tmp_path) def test_service_health_failure_notification_policy_fails_when_missing(tmp_path): with pytest.raises(FileNotFoundError): load_latest_service_health_failure_notification_policy(tmp_path) def _write_snapshot(tmp_path, snapshot: dict) -> None: (tmp_path / "service_health_failure_notification_policy_2026-06-05.json").write_text( json.dumps(snapshot), encoding="utf-8", ) def _snapshot( *, generated_at: str = "2026-06-05T00:00:00+08:00", completion: int = 100, ) -> dict: return { "schema_version": "service_health_failure_notification_policy_v1", "generated_at": generated_at, "source_service_health_matrix_ref": "docs/evaluations/service_health_gap_matrix_2026-06-05.json", "source_refs": ["docs/evaluations/service_health_gap_matrix_2026-06-05.json"], "program_status": { "overall_completion_percent": completion, "current_priority": "P1", "current_task_id": "P1-007", "next_task_id": "P2-004", "read_only_mode": True, }, "rollups": { "total_rules": 3, "by_decision": { "suppress_immediate_success": 1, "create_action_required": 1, "escalate_immediate": 1, }, "immediate_escalation_rule_ids": ["production_api_health_failed"], "suppressed_success_rule_ids": ["service_health_verified"], "action_required_rule_ids": ["stale_endpoint_detected"], "notification_send_allowed_count": 0, }, "notification_channels": [ _channel("telegram_ops", immediate_allowed=True, requires_operator_action=True), _channel("daily_status_summary", immediate_allowed=False, requires_operator_action=False), ], "policy_rules": [ _rule("service_health_verified", "verified", "info", "suppress_immediate_success"), _rule("stale_endpoint_detected", "action_required", "warning", "create_action_required"), _rule("production_api_health_failed", "failed", "critical", "escalate_immediate"), ], "message_template_contract": { "required_fields": [ "stage", "next_action", "blocked_reason", "auto_or_manual", "target_id", "severity", "evidence_ref", ], "forbidden_fields": [ "secret_value", "token", "authorization_header", "work_window_transcript", "codex_user_message", "prompt_text", "chain_of_thought", "session_id", "browser_context", ], "success_message_policy": "成功不得即時送 Telegram / AwoooP。", "failure_message_policy": "失敗才升級;此 snapshot 不授權實際發送。", }, "display_redaction_contract": { "frontend_display_policy": "前端只顯示 committed policy、rule summary、evidence ref、下一步與禁止事項。", "conversation_transcript_display_allowed": False, "redaction_required": True, "forbidden_frontend_content": [ "未核准內部內容", "未脫敏操作紀錄", "未核准決策細節", "工作階段脈絡", "機密 / 權杖 / 授權標頭", ], "allowed_frontend_fields": ["已提交證據參照", "政策規則摘要"], }, "agent_roles": [], "operation_boundaries": { "read_only_policy_allowed": True, "notification_send_allowed": False, "telegram_test_message_allowed": False, "awooop_event_write_allowed": False, "live_probe_allowed": False, "external_health_probe_allowed": False, "service_restart_allowed": False, "endpoint_change_allowed": False, "workflow_trigger_allowed": False, "runtime_execution_allowed": False, "secret_plaintext_allowed": False, }, "approval_boundaries": { "notification_send_approved": False, "telegram_test_message_approved": False, "awooop_event_write_approved": False, "live_probe_approved": False, "service_restart_approved": False, "endpoint_change_approved": False, "runtime_execution_approved": False, }, } def _channel(channel_id: str, *, immediate_allowed: bool, requires_operator_action: bool) -> dict: return { "channel_id": channel_id, "purpose": "test", "immediate_allowed": immediate_allowed, "success_immediate_allowed": False, "requires_operator_action": requires_operator_action, } def _rule(rule_id: str, state: str, severity: str, decision: str) -> dict: return { "rule_id": rule_id, "event_kind": rule_id, "service_state": state, "severity": severity, "decision": decision, "channels": ["daily_status_summary"], "owner_agent": "openclaw", "requires_incident": decision == "escalate_immediate", "requires_approval_record": False, "message_contract": "test", "evidence_refs": ["docs/evaluations/service_health_gap_matrix_2026-06-05.json"], }