feat(agents): 新增高風險 owner review queue
Some checks failed
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / tests (push) Successful in 1m54s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / tests (push) Successful in 1m54s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
155
apps/api/tests/test_ai_agent_high_risk_owner_review_queue.py
Normal file
155
apps/api/tests/test_ai_agent_high_risk_owner_review_queue.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from src.services.ai_agent_high_risk_owner_review_queue import (
|
||||
load_latest_ai_agent_high_risk_owner_review_queue,
|
||||
)
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
_COMMITTED_SNAPSHOT = (
|
||||
_REPO_ROOT
|
||||
/ "docs"
|
||||
/ "evaluations"
|
||||
/ "ai_agent_high_risk_owner_review_queue_2026-06-19.json"
|
||||
)
|
||||
|
||||
|
||||
def test_load_latest_ai_agent_high_risk_owner_review_queue_reads_newest_file(tmp_path):
|
||||
older = _snapshot(generated_at="2026-06-18T00:00:00+08:00")
|
||||
newer = _snapshot(generated_at="2026-06-19T03:20:00+08:00")
|
||||
(tmp_path / "ai_agent_high_risk_owner_review_queue_2026-06-18.json").write_text(
|
||||
json.dumps(older),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(tmp_path / "ai_agent_high_risk_owner_review_queue_2026-06-19.json").write_text(
|
||||
json.dumps(newer),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
loaded = load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
assert loaded["generated_at"] == "2026-06-19T03:20:00+08:00"
|
||||
assert loaded["program_status"]["current_task_id"] == "P2-409"
|
||||
assert loaded["program_status"]["next_task_id"] == "P2-410"
|
||||
assert loaded["program_status"]["read_only_mode"] is True
|
||||
assert loaded["rollups"]["queue_item_count"] == 7
|
||||
assert loaded["rollups"]["approval_packet_count"] == 7
|
||||
assert loaded["rollups"]["rejection_guard_count"] == 8
|
||||
assert loaded["rollups"]["telegram_send_count"] == 0
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_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_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_blocks_live_execution(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["owner_review_queue_items"][0]["live_execution_allowed"] = True
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="live_execution_allowed"):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_blocks_telegram_send(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["telegram_policy"]["telegram_send_allowed"] = True
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="telegram_policy"):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_requires_high_and_critical(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
for item in snapshot["owner_review_queue_items"]:
|
||||
item["risk_tier"] = "high"
|
||||
snapshot["rollups"]["critical_queue_count"] = 0
|
||||
snapshot["rollups"]["high_risk_queue_count"] = len(snapshot["owner_review_queue_items"])
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="must include high and critical"):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_requires_approval_packet(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["owner_review_queue_items"][0]["approval_packet_id"] = "missing_packet"
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="missing approval packets"):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_requires_rejection_guard(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["owner_review_queue_items"][0]["rejection_guard_ids"] = ["missing_guard"]
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="missing rejection guards"):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_requires_reviewer_checklist(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["owner_review_queue_items"][0]["reviewer_checklist_ids"] = ["missing_checklist"]
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="missing reviewer checklists"):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_requires_rollback_owner(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["owner_review_queue_items"][0]["rollback_owner_required"] = False
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="rollback_owner_required"):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_requires_rollup_consistency(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["rollups"]["queue_item_count"] = 99
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="rollup counts"):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_rejects_private_terms(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["owner_review_queue_items"][0]["display_name"] = "請把 In app browser 內容放進前端"
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="forbidden public terms"):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_fails_when_missing(tmp_path):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
load_latest_ai_agent_high_risk_owner_review_queue(tmp_path)
|
||||
|
||||
|
||||
def _snapshot(*, generated_at: str = "2026-06-19T03:20: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_high_risk_owner_review_queue_2026-06-19.json").write_text(
|
||||
json.dumps(payload),
|
||||
encoding="utf-8",
|
||||
)
|
||||
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1.agents import router
|
||||
|
||||
|
||||
def test_ai_agent_high_risk_owner_review_queue_endpoint_returns_committed_snapshot():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/agents/agent-high-risk-owner-review-queue")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "ai_agent_high_risk_owner_review_queue_v1"
|
||||
assert data["program_status"]["current_task_id"] == "P2-409"
|
||||
assert data["program_status"]["next_task_id"] == "P2-410"
|
||||
assert data["program_status"]["read_only_mode"] is True
|
||||
assert (
|
||||
data["program_status"]["runtime_authority"]
|
||||
== "high_risk_owner_review_queue_no_live_execution_committed_snapshot"
|
||||
)
|
||||
assert data["rollups"]["source_readback_count"] == len(data["source_readbacks"]) == 6
|
||||
assert data["rollups"]["queue_item_count"] == len(data["owner_review_queue_items"]) == 7
|
||||
assert data["rollups"]["high_risk_queue_count"] == 5
|
||||
assert data["rollups"]["critical_queue_count"] == 2
|
||||
assert data["rollups"]["approval_packet_count"] == len(data["approval_packets"]) == 7
|
||||
assert data["rollups"]["rejection_guard_count"] == len(data["rejection_guards"]) == 8
|
||||
assert data["rollups"]["reviewer_checklist_count"] == len(data["reviewer_checklists"]) == 7
|
||||
assert data["rollups"]["approval_packet_required_count"] == 7
|
||||
assert data["rollups"]["rollback_owner_required_count"] == 7
|
||||
assert data["rollups"]["postcheck_required_count"] == 7
|
||||
assert data["rollups"]["blocked_runtime_action_count"] == 42
|
||||
assert data["rollups"]["owner_response_received_count"] == 0
|
||||
assert data["rollups"]["owner_response_accepted_count"] == 0
|
||||
assert data["rollups"]["auto_worker_run_count"] == 0
|
||||
assert data["rollups"]["live_execution_count"] == 0
|
||||
assert data["rollups"]["gateway_queue_write_count"] == 0
|
||||
assert data["rollups"]["telegram_send_count"] == 0
|
||||
assert data["rollups"]["bot_api_call_count"] == 0
|
||||
assert data["rollups"]["receipt_production_write_count"] == 0
|
||||
assert data["rollups"]["production_write_count"] == 0
|
||||
assert data["rollups"]["secret_read_count"] == 0
|
||||
assert data["rollups"]["paid_api_call_count"] == 0
|
||||
assert data["rollups"]["host_write_count"] == 0
|
||||
assert data["rollups"]["kubectl_action_count"] == 0
|
||||
assert data["telegram_policy"]["canonical_room"] == "AwoooI SRE 戰情室"
|
||||
assert data["telegram_policy"]["canonical_room_env"] == "SRE_GROUP_CHAT_ID"
|
||||
assert data["telegram_policy"]["telegram_send_allowed"] is False
|
||||
assert data["telegram_policy"]["gateway_queue_write_allowed"] is False
|
||||
assert data["telegram_policy"]["direct_bot_api_allowed"] is False
|
||||
assert data["routing_policy"]["high_risk_default_route"] == "pause_to_owner_review_queue"
|
||||
assert data["routing_policy"]["critical_risk_default_route"] == "pause_to_owner_review_queue"
|
||||
assert data["routing_policy"]["verbal_approval_accepted"] is False
|
||||
assert data["activation_boundaries"]["live_execution_enabled"] is False
|
||||
assert data["activation_boundaries"]["openclaw_replacement_allowed"] is False
|
||||
Reference in New Issue
Block a user