aps-agent/tests/golden/test_approval_transfer.py

119 lines
4.8 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.

# 审批转派(transfer)黄金测试(plan.md §3.3 / 矩阵 P3)
# ============================================================
# 覆盖:owner 转派、非 owner 拒绝、P3 拒绝、转派后目标可批原 owner 不能、DB 持久化。
from __future__ import annotations
from pathlib import Path
import pytest
from server.agent_core import harness
from server.auth.context import IdentityContext, bind_identity, reset_identity
def _identity(user_id: int, tenant: str = "platform") -> IdentityContext:
return IdentityContext(user_id, f"user-{user_id}", f"User {user_id}", tenant,
roles=("planner", "approver", "admin"))
def _as(identity: IdentityContext, callback):
token = bind_identity(identity)
try:
return callback()
finally:
reset_identity(token)
def _stage(owner: IdentityContext, action: str = "schedule.publish") -> str:
params = {"versionId": 91}
token = bind_identity(owner)
try:
block = harness.stage_confirmation(
"transfer-test", action, params, title="转派测试", summary_lines=["t"],
)
finally:
reset_identity(token)
return str(block.props["confirmId"])
@pytest.fixture(autouse=True)
def _clear_pending():
if getattr(harness._approval_store, "backend", "file") == "file":
harness._approval_store.clear()
yield
if getattr(harness._approval_store, "backend", "file") == "file":
harness._approval_store.clear()
@pytest.fixture
def database_backend(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
from server.db.database import reset_engine
monkeypatch.setenv("APS_APPROVAL_BACKEND", "database")
monkeypatch.delenv("APS_APPROVAL_PATH", raising=False)
db_path = (tmp_path / "approval.db").as_posix()
monkeypatch.setenv("APS_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("APS_APPROVAL_DATABASE_ALLOW_SQLITE", "1")
reset_engine()
store = harness.configure_approval_store()
yield store
store.clear()
reset_engine()
def test_owner_transfers_to_target(tmp_path: Path):
"""owner 可转派;转派后目标用户可审批,原 owner 不能。"""
harness.configure_approval_store(str(tmp_path / "approvals.json"))
owner, target = _identity(4001), _identity(4002)
confirm_id = _stage(owner)
transferred = _as(owner, lambda: harness.transfer_confirmation(confirm_id, 4002))
assert transferred is not None
assert transferred["ownerUserId"] == 4002
# 原 owner 不可审批
assert _as(owner, lambda: harness.take_confirmation(confirm_id, approve=True)) is None
# 目标用户可审批
approved = _as(target, lambda: harness.take_confirmation(confirm_id, approve=True))
assert approved is not None and approved["needsSecondConfirm"] is False
# 历史含 TRANSFERRED(转派后 owner 是 target,用 target 身份查看)
history = _as(target, harness.list_approval_history)
assert any(h["status"] == "TRANSFERRED" for h in history)
def test_non_owner_cannot_transfer(tmp_path: Path):
"""非 owner 非 delegate 的第三人不可转派。"""
harness.configure_approval_store(str(tmp_path / "approvals.json"))
owner, stranger = _identity(4001), _identity(4003)
confirm_id = _stage(owner)
result = _as(stranger, lambda: harness.transfer_confirmation(confirm_id, 4002))
assert result is None
def test_p3_transfer_rejected(tmp_path: Path):
"""P3 转派拒绝(保双人职责分离)。"""
harness.configure_approval_store(str(tmp_path / "approvals.json"))
owner = _identity(4001)
confirm_id = _stage(owner, action="mes.dispatch")
result = _as(owner, lambda: harness.transfer_confirmation(confirm_id, 4002))
assert result is None
def test_transfer_to_self_is_noop(tmp_path: Path):
"""转派给自己 = no-op(仍可审批)。"""
harness.configure_approval_store(str(tmp_path / "approvals.json"))
owner = _identity(4001)
confirm_id = _stage(owner)
result = _as(owner, lambda: harness.transfer_confirmation(confirm_id, 4001))
assert result is not None
approved = _as(owner, lambda: harness.take_confirmation(confirm_id, approve=True))
assert approved is not None
def test_database_backend_transfer_persists(database_backend):
"""共享 database 后端:转派持久化并生效。"""
owner, target = _identity(7201), _identity(7202)
confirm_id = _as(owner, lambda: _stage(owner))
transferred = _as(owner, lambda: harness.transfer_confirmation(confirm_id, 7202))
assert transferred is not None and transferred["ownerUserId"] == 7202
assert _as(owner, lambda: harness.take_confirmation(confirm_id, approve=True)) is None
approved = _as(target, lambda: harness.take_confirmation(confirm_id, approve=True))
assert approved is not None and approved["needsSecondConfirm"] is False