feat(governance): 新增 Agent Gitea PR 草案 lane
This commit is contained in:
113
apps/api/tests/test_ai_agent_gitea_pr_draft_lane.py
Normal file
113
apps/api/tests/test_ai_agent_gitea_pr_draft_lane.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from src.services.ai_agent_gitea_pr_draft_lane import (
|
||||
load_latest_ai_agent_gitea_pr_draft_lane,
|
||||
)
|
||||
|
||||
|
||||
def test_load_latest_ai_agent_gitea_pr_draft_lane_reads_committed_snapshot():
|
||||
data = load_latest_ai_agent_gitea_pr_draft_lane()
|
||||
|
||||
assert data["schema_version"] == "ai_agent_gitea_pr_draft_lane_v1"
|
||||
assert data["program_status"]["overall_completion_percent"] == 78
|
||||
assert data["program_status"]["current_task_id"] == "P2-402E"
|
||||
assert data["program_status"]["next_task_id"] == "P2-402F"
|
||||
assert data["program_status"]["read_only_mode"] is True
|
||||
assert data["program_status"]["runtime_authority"] == (
|
||||
"draft_lane_only_no_pr_creation_or_branch_push"
|
||||
)
|
||||
assert data["branch_strategy"]["branch_push_allowed"] is False
|
||||
assert data["branch_strategy"]["pr_creation_allowed"] is False
|
||||
assert data["branch_strategy"]["automerge"] is False
|
||||
assert data["operation_boundaries"]["gitea_branch_push_allowed"] is False
|
||||
assert data["operation_boundaries"]["gitea_pr_creation_allowed"] is False
|
||||
assert data["operation_boundaries"]["workflow_trigger_allowed"] is False
|
||||
assert data["operation_boundaries"]["lockfile_write_allowed"] is False
|
||||
assert data["operation_boundaries"]["telegram_direct_send_allowed"] is False
|
||||
assert data["operation_boundaries"]["conversation_transcript_allowed"] is False
|
||||
assert data["approval_boundaries"]["gitea_pr_creation_approved"] is False
|
||||
assert data["rollups"]["grouping_rule_count"] == len(data["grouping_rules"]) == 6
|
||||
assert data["rollups"]["lane_step_count"] == len(data["lane_steps"]) == 7
|
||||
assert data["rollups"]["required_check_count"] == len(data["required_checks"]) == 9
|
||||
assert data["rollups"]["draft_template_count"] == len(data["draft_templates"]) == 4
|
||||
assert data["rollups"]["gitea_branch_push_allowed_count"] == 0
|
||||
assert data["rollups"]["gitea_pr_creation_allowed_count"] == 0
|
||||
assert data["rollups"]["auto_merge_allowed_count"] == 0
|
||||
assert data["rollups"]["workflow_trigger_allowed_count"] == 0
|
||||
assert data["rollups"]["lockfile_write_allowed_count"] == 0
|
||||
assert data["rollups"]["telegram_direct_send_allowed_count"] == 0
|
||||
assert data["rollups"]["conversation_transcript_allowed_count"] == 0
|
||||
assert all(rule["automerge"] is False for rule in data["grouping_rules"])
|
||||
assert all(template["branch_push_allowed"] is False for template in data["draft_templates"])
|
||||
|
||||
|
||||
def test_ai_agent_gitea_pr_draft_lane_blocks_pr_creation(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["operation_boundaries"]["gitea_pr_creation_allowed"] = True
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="operation boundaries"):
|
||||
load_latest_ai_agent_gitea_pr_draft_lane(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_gitea_pr_draft_lane_rejects_rollup_mismatch(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["rollups"]["grouping_rule_count"] = 99
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="rollup counts"):
|
||||
load_latest_ai_agent_gitea_pr_draft_lane(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_gitea_pr_draft_lane_rejects_automerge(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["grouping_rules"][0]["automerge"] = True
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="draft-only"):
|
||||
load_latest_ai_agent_gitea_pr_draft_lane(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_gitea_pr_draft_lane_rejects_non_blocking_check(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["required_checks"][0]["blocking"] = False
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="blocking evidence-only"):
|
||||
load_latest_ai_agent_gitea_pr_draft_lane(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_gitea_pr_draft_lane_requires_owner_response_fields(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["owner_response_requirements"][0]["required_fields"] = ["owner"]
|
||||
snapshot["owner_response_requirements"][1]["required_fields"] = ["owner"]
|
||||
snapshot["owner_response_requirements"][2]["required_fields"] = ["owner"]
|
||||
snapshot["owner_response_requirements"][3]["required_fields"] = ["owner"]
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="owner response requirements"):
|
||||
load_latest_ai_agent_gitea_pr_draft_lane(tmp_path)
|
||||
|
||||
|
||||
def test_ai_agent_gitea_pr_draft_lane_rejects_transcript_markers(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["lane_intent"]["purpose"] = "禁止放入 My request for Codex"
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="forbidden work-window conversation content"):
|
||||
load_latest_ai_agent_gitea_pr_draft_lane(tmp_path)
|
||||
|
||||
|
||||
def _write_snapshot(tmp_path, snapshot: dict) -> None:
|
||||
(tmp_path / "ai_agent_gitea_pr_draft_lane_2026-06-11.json").write_text(
|
||||
json.dumps(snapshot),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _snapshot() -> dict:
|
||||
return json.loads(json.dumps(load_latest_ai_agent_gitea_pr_draft_lane()))
|
||||
51
apps/api/tests/test_ai_agent_gitea_pr_draft_lane_api.py
Normal file
51
apps/api/tests/test_ai_agent_gitea_pr_draft_lane_api.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1.agents import router
|
||||
|
||||
|
||||
def test_agent_gitea_pr_draft_lane_endpoint_returns_committed_snapshot():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/agents/agent-gitea-pr-draft-lane")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "ai_agent_gitea_pr_draft_lane_v1"
|
||||
assert data["program_status"]["overall_completion_percent"] == 78
|
||||
assert data["program_status"]["current_task_id"] == "P2-402E"
|
||||
assert data["program_status"]["next_task_id"] == "P2-402F"
|
||||
assert data["program_status"]["read_only_mode"] is True
|
||||
assert data["branch_strategy"]["branch_push_allowed"] is False
|
||||
assert data["branch_strategy"]["pr_creation_allowed"] is False
|
||||
assert data["branch_strategy"]["automerge"] is False
|
||||
assert data["operation_boundaries"]["gitea_branch_push_allowed"] is False
|
||||
assert data["operation_boundaries"]["gitea_pr_creation_allowed"] is False
|
||||
assert data["operation_boundaries"]["workflow_trigger_allowed"] is False
|
||||
assert data["operation_boundaries"]["lockfile_write_allowed"] is False
|
||||
assert data["operation_boundaries"]["telegram_direct_send_allowed"] is False
|
||||
assert data["operation_boundaries"]["conversation_transcript_allowed"] is False
|
||||
assert data["approval_boundaries"]["gitea_pr_creation_approved"] is False
|
||||
assert data["rollups"]["grouping_rule_count"] == 6
|
||||
assert data["rollups"]["lane_step_count"] == 7
|
||||
assert data["rollups"]["required_check_count"] == 9
|
||||
assert data["rollups"]["draft_template_count"] == 4
|
||||
assert data["rollups"]["gitea_pr_creation_allowed_count"] == 0
|
||||
assert data["rollups"]["auto_merge_allowed_count"] == 0
|
||||
assert data["rollups"]["workflow_trigger_allowed_count"] == 0
|
||||
|
||||
serialized = json.dumps(data, ensure_ascii=False)
|
||||
for marker in [
|
||||
"批准!繼續",
|
||||
"My request for Codex",
|
||||
"Current URL:",
|
||||
"# In app browser",
|
||||
"AGENTS.md instructions",
|
||||
]:
|
||||
assert marker not in serialized
|
||||
@@ -13,9 +13,9 @@ def test_load_latest_ai_agent_proactive_operations_contract_reads_committed_snap
|
||||
data = load_latest_ai_agent_proactive_operations_contract()
|
||||
|
||||
assert data["schema_version"] == "ai_agent_proactive_operations_contract_v1"
|
||||
assert data["program_status"]["overall_completion_percent"] == 68
|
||||
assert data["program_status"]["current_task_id"] == "P2-402D"
|
||||
assert data["program_status"]["next_task_id"] == "P2-402E"
|
||||
assert data["program_status"]["overall_completion_percent"] == 78
|
||||
assert data["program_status"]["current_task_id"] == "P2-402E"
|
||||
assert data["program_status"]["next_task_id"] == "P2-402F"
|
||||
assert data["program_status"]["read_only_mode"] is True
|
||||
assert data["program_status"]["runtime_authority"] == "contract_only_no_version_or_runtime_update"
|
||||
assert data["approval_boundaries"]["runtime_version_update_allowed"] is False
|
||||
|
||||
@@ -16,9 +16,9 @@ def test_ai_agent_proactive_operations_contract_endpoint_returns_committed_snaps
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "ai_agent_proactive_operations_contract_v1"
|
||||
assert data["program_status"]["overall_completion_percent"] == 68
|
||||
assert data["program_status"]["current_task_id"] == "P2-402D"
|
||||
assert data["program_status"]["next_task_id"] == "P2-402E"
|
||||
assert data["program_status"]["overall_completion_percent"] == 78
|
||||
assert data["program_status"]["current_task_id"] == "P2-402E"
|
||||
assert data["program_status"]["next_task_id"] == "P2-402F"
|
||||
assert data["program_status"]["read_only_mode"] is True
|
||||
assert data["approval_boundaries"]["runtime_version_update_allowed"] is False
|
||||
assert data["approval_boundaries"]["package_upgrade_allowed"] is False
|
||||
|
||||
Reference in New Issue
Block a user