74 lines
3.3 KiB
Python
74 lines
3.3 KiB
Python
|
|
# ============================================================
|
|||
|
|
# 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]
|