# ============================================================ # Plan 批准/驳回审计黄金测试(plan.md §3.1 / 矩阵 110 行剩余项) # 覆盖:DRAFT->APPROVED 批准(追加版本、regenCount 不变、证据引用)、 # DRAFT->FAILED 驳回、非法流转拒绝、历史不可变。 # ============================================================ from __future__ import annotations from pathlib import Path import pytest from server.agent_core.plan_runtime import ( PlanStore, PlanTransitionError, ) def _store(tmp_path: Path) -> PlanStore: return PlanStore(str(tmp_path / "plans.json")) def test_approve_appends_status_version(tmp_path: Path): """批准:DRAFT->APPROVED 追加新版本,regenCount 不变,证据引用记录 status。""" store = _store(tmp_path) plan = store.create(plan_id="p-approve", layer="L0", parent_id=None, inputs={"prompt": "x"}, payload={"goal": "g"}) assert plan.status == "DRAFT" and plan.version == 1 and plan.regenCount == 0 approved = store.transition_status("p-approve", new_status="APPROVED", created_by="USER", note="计划员批准") assert approved.status == "APPROVED" assert approved.version == 2 assert approved.regenCount == 0 # 状态流转不消耗重生额度 assert approved.evidenceRefs[-1] == "status:APPROVED" # 历史不可变:v1 仍为 DRAFT assert store.node_at("p-approve", 1).status == "DRAFT" def test_reject_appends_failed_version(tmp_path: Path): """驳回:DRAFT->FAILED 追加新版本,携带 note 入 payload。""" store = _store(tmp_path) store.create(plan_id="p-reject", layer="L0", parent_id=None, inputs={"prompt": "x"}, payload={"goal": "g"}) rejected = store.transition_status("p-reject", new_status="FAILED", created_by="USER", note="驳回:数据不足") assert rejected.status == "FAILED" assert rejected.version == 2 assert rejected.payload["_note"][-1]["status"] == "FAILED" assert rejected.payload["_note"][-1]["note"] == "驳回:数据不足" def test_illegal_transition_rejected(tmp_path: Path): """非法流转:APPROVED->DRAFT 必须拒绝(PlanTransitionError)。""" store = _store(tmp_path) store.create(plan_id="p-illegal", layer="L0", parent_id=None, inputs={"prompt": "x"}, payload={"goal": "g"}) store.transition_status("p-illegal", new_status="APPROVED") with pytest.raises(PlanTransitionError): store.transition_status("p-illegal", new_status="DRAFT") def test_transition_chain_approve_then_done(tmp_path: Path): """连续流转:DRAFT->APPROVED->DONE 合法,每步追加版本且历史不变量保持。""" store = _store(tmp_path) store.create(plan_id="p-chain", layer="L0", parent_id=None, inputs={"prompt": "x"}, payload={"goal": "g"}) store.transition_status("p-chain", new_status="APPROVED") done = store.transition_status("p-chain", new_status="DONE") assert done.status == "DONE" assert done.version == 3 versions = store.versions("p-chain") assert [v.status for v in versions] == ["DRAFT", "APPROVED", "DONE"] assert [v.regenCount for v in versions] == [0, 0, 0]