Files
awoooi/apps/api/tests/test_service_health_gap_matrix.py
Your Name 1007a1bc04
All checks were successful
CD Pipeline / tests (push) Successful in 1m28s
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / build-and-deploy (push) Successful in 3m50s
CD Pipeline / post-deploy-checks (push) Successful in 1m55s
feat(governance): 新增服務健康缺口矩陣
2026-06-05 14:22:30 +08:00

302 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import json
import pytest
from src.services.service_health_gap_matrix import load_latest_service_health_gap_matrix
def test_load_latest_service_health_gap_matrix_reads_newest_file(tmp_path):
older = _snapshot(generated_at="2026-06-04T00:00:00+08:00", completion=40)
newer = _snapshot(generated_at="2026-06-05T00:00:00+08:00", completion=100)
(tmp_path / "service_health_gap_matrix_2026-06-04.json").write_text(
json.dumps(older),
encoding="utf-8",
)
(tmp_path / "service_health_gap_matrix_2026-06-05.json").write_text(
json.dumps(newer),
encoding="utf-8",
)
loaded = load_latest_service_health_gap_matrix(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_targets"] == 7
assert loaded["operation_boundaries"]["active_probe_allowed"] is False
def test_service_health_gap_matrix_requires_read_only_mode(tmp_path):
snapshot = _snapshot()
snapshot["program_status"]["read_only_mode"] = False
(tmp_path / "service_health_gap_matrix_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="read_only_mode"):
load_latest_service_health_gap_matrix(tmp_path)
def test_service_health_gap_matrix_blocks_probe_restart_endpoint_and_notifications(tmp_path):
snapshot = _snapshot()
snapshot["operation_boundaries"]["active_probe_allowed"] = True
snapshot["operation_boundaries"]["service_restart_allowed"] = True
snapshot["operation_boundaries"]["endpoint_change_allowed"] = True
snapshot["operation_boundaries"]["notification_send_allowed"] = True
(tmp_path / "service_health_gap_matrix_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="operation boundaries"):
load_latest_service_health_gap_matrix(tmp_path)
def test_service_health_gap_matrix_requires_rollup_consistency(tmp_path):
snapshot = _snapshot()
snapshot["rollups"]["target_ids_requiring_action"] = []
(tmp_path / "service_health_gap_matrix_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="target_ids_requiring_action"):
load_latest_service_health_gap_matrix(tmp_path)
def test_service_health_gap_matrix_requires_required_gaps_and_stale_endpoints(tmp_path):
snapshot = _snapshot()
snapshot["health_gaps"] = snapshot["health_gaps"][:-1]
snapshot["stale_endpoints"] = snapshot["stale_endpoints"][:-1]
_refresh_rollups(snapshot)
(tmp_path / "service_health_gap_matrix_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="missing required health gaps"):
load_latest_service_health_gap_matrix(tmp_path)
def test_service_health_gap_matrix_requires_operator_denials(tmp_path):
snapshot = _snapshot()
snapshot["operator_contract"]["must_not_interpret_as"].remove("active probe 批准")
(tmp_path / "service_health_gap_matrix_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="operator_contract"):
load_latest_service_health_gap_matrix(tmp_path)
def test_service_health_gap_matrix_requires_endpoint_and_notification_policies(tmp_path):
snapshot = _snapshot()
snapshot["operator_contract"]["endpoint_policy"] = "可修改端點"
(tmp_path / "service_health_gap_matrix_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="endpoint_policy"):
load_latest_service_health_gap_matrix(tmp_path)
def test_service_health_gap_matrix_rejects_secret_payload_keys(tmp_path):
snapshot = _snapshot()
snapshot["latest_observations"][0]["authorization_header"] = "redacted"
(tmp_path / "service_health_gap_matrix_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="forbidden secret payload key"):
load_latest_service_health_gap_matrix(tmp_path)
def test_service_health_gap_matrix_fails_when_missing(tmp_path):
with pytest.raises(FileNotFoundError):
load_latest_service_health_gap_matrix(tmp_path)
def _snapshot(
*,
generated_at: str = "2026-06-05T00:00:00+08:00",
completion: int = 100,
) -> dict:
targets = [
_target("production_api_health_public", "api_health", "verified", "critical", "fresh_readback"),
_target("awoooi_web_health_manifest", "web_health", "verified", "high", "manifest_mapped"),
_target("ollama_three_layer_health_contract", "ai_provider_health", "action_required", "critical", "source_mismatch"),
_target("openclaw_health_endpoint_contract", "ai_provider_health", "action_required", "critical", "source_mismatch"),
_target("prometheus_alertmanager_endpoint_reference", "observability_health", "action_required", "high", "source_mismatch"),
_target("gitea_workflow_runner_health_contract", "devops_health", "action_required", "critical", "stale_evidence"),
_target("kali_scanner_health_reference", "security_edge_health", "action_required", "medium", "manifest_only"),
]
gaps = [
_gap("endpoint_reference_stale_hosts", ["ollama_three_layer_health_contract"]),
_gap("gitea_runner_attestation_health_gap", ["gitea_workflow_runner_health_contract"]),
_gap("health_check_script_not_authoritative", ["production_api_health_public"]),
_gap("openclaw_nemo_rca_health_review", ["openclaw_health_endpoint_contract"]),
_gap("security_scanner_health_evidence_gap", ["kali_scanner_health_reference"]),
]
endpoints = [
_endpoint("legacy_188_ollama_provider_endpoint"),
_endpoint("prometheus_alertmanager_110_188_split"),
_endpoint("openclaw_8088_comment_vs_8089_contract"),
]
payload = {
"schema_version": "service_health_gap_matrix_v1",
"generated_at": generated_at,
"program_status": {
"overall_completion_percent": completion,
"current_priority": "P1",
"current_task_id": "P1-005",
"next_task_id": "P1-006",
"read_only_mode": True,
},
"source_refs": ["docs/schemas/service_health_gap_matrix_v1.schema.json"],
"rollups": {},
"service_health_targets": targets,
"health_gaps": gaps,
"stale_endpoints": endpoints,
"latest_observations": [
{
"observation_id": "service_health_gap_matrix_seed",
"status": "verified",
"summary": "只讀 health gap seed。",
"evidence_refs": ["docs/reference/SERVICE-ENDPOINTS.md"],
}
],
"operator_contract": {
"display_mode": "read_only_service_health_gap_matrix",
"must_not_interpret_as": [
"服務重啟批准",
"endpoint 修改批准",
"active probe 批准",
"live health check 已執行",
"Secret payload 已讀取或可輸出",
"Telegram 成功通知批准",
"workflow / deploy / reload 觸發批准",
"provider 切換批准",
"runtime execution 授權",
"OpenClaw 取代或降級批准",
],
"secret_display_policy": "只顯示來源檔案與 env var 名稱。",
"restart_policy": "P1-005 只能列缺口;任何 restart 都需另行批准。",
"endpoint_policy": "P1-005 不修改端點,只提出 source truth cleanup。",
"notification_policy": "成功 smoke 不通知;失敗或 stale 才進 P1-007 通知合約。",
},
"operation_boundaries": {
"read_only_api_allowed": True,
"service_restart_allowed": False,
"pod_restart_allowed": False,
"host_restart_allowed": False,
"rollout_restart_allowed": False,
"endpoint_change_allowed": False,
"configmap_patch_allowed": False,
"active_probe_allowed": False,
"external_health_probe_allowed": False,
"live_benchmark_allowed": False,
"provider_switch_allowed": False,
"paid_api_call_allowed": False,
"secret_read_allowed": False,
"secret_plaintext_allowed": False,
"notification_send_allowed": False,
"workflow_trigger_allowed": False,
"deploy_trigger_allowed": False,
"reload_trigger_allowed": False,
"runtime_execution_allowed": False,
},
"approval_boundaries": {
"service_restart_approved": False,
"endpoint_change_approved": False,
"configmap_patch_approved": False,
"active_probe_approved": False,
"secret_read_approved": False,
"notification_send_approved": False,
"workflow_trigger_approved": False,
"provider_switch_approved": False,
"cost_change_approved": False,
},
}
_refresh_rollups(payload)
return payload
def _target(target_id: str, kind: str, status: str, risk_level: str, freshness_status: str) -> dict:
return {
"target_id": target_id,
"display_name": target_id,
"kind": kind,
"status": status,
"risk_level": risk_level,
"freshness_status": freshness_status,
"health_contract": "只讀健康合約。",
"endpoint_contract": "不修改 endpoint。",
"evidence_refs": ["docs/reference/SERVICE-ENDPOINTS.md"],
"next_action": "只補證據,不執行。",
}
def _gap(gap_id: str, target_ids: list[str]) -> dict:
return {
"gap_id": gap_id,
"display_name": gap_id,
"status": "action_required",
"severity": "high",
"summary": "只讀缺口。",
"target_ids": target_ids,
"evidence_refs": ["docs/reference/SERVICE-ENDPOINTS.md"],
"next_action": "只整理 owner attestation。",
}
def _endpoint(endpoint_id: str) -> dict:
return {
"endpoint_id": endpoint_id,
"display_name": endpoint_id,
"status": "action_required",
"severity": "medium",
"stale_ref": "舊端點參考。",
"current_truth": "只標記 source drift不判定 live down。",
"evidence_refs": ["docs/reference/SERVICE-ENDPOINTS.md"],
"next_action": "只整理 source cleanup。",
}
def _refresh_rollups(payload: dict) -> None:
targets = payload["service_health_targets"]
gaps = payload["health_gaps"]
endpoints = payload["stale_endpoints"]
payload["rollups"] = {
"total_targets": len(targets),
"by_kind": _count_by(targets, "kind"),
"by_status": _count_by(targets, "status"),
"by_freshness_status": _count_by(targets, "freshness_status"),
"target_ids_requiring_action": sorted(
target["target_id"] for target in targets if target["status"] == "action_required"
),
"health_gap_ids": sorted(gap["gap_id"] for gap in gaps),
"stale_endpoint_ids": sorted(endpoint["endpoint_id"] for endpoint in endpoints),
"critical_target_ids": sorted(
target["target_id"] for target in targets if target["risk_level"] == "critical"
),
"read_only_denials_total": 10,
"service_restart_allowed_count": 0,
"endpoint_change_allowed_count": 0,
"active_probe_allowed_count": 0,
"notification_send_allowed_count": 0,
"runtime_execution_allowed_count": 0,
}
def _count_by(items: list[dict], key: str) -> dict[str, int]:
counts: dict[str, int] = {}
for item in items:
value = item[key]
counts[value] = counts.get(value, 0) + 1
return counts