5e53146a9a
新增自动化审批状态机/执行器/意图路由/会话管理、OTP 绑定流程、neo4j 客户端、响应契约、置信度门禁、环境门控、Tier1 API 等测试。
209 lines
6.4 KiB
Python
209 lines
6.4 KiB
Python
# =============================================================================
|
|
# 阶段5 自动化 - 审批单服务单元测试
|
|
# =============================================================================
|
|
# 测试范围:approval.py - 审批单创建/流转/审计
|
|
# =============================================================================
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
from datetime import datetime, timezone
|
|
|
|
from app.services.automation.approval import ApprovalService
|
|
from app.models.automation import ApprovalTicket
|
|
|
|
|
|
class TestApprovalService:
|
|
"""审批单服务测试用例"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ensure_ticket_creates_new_ticket(self, db_session):
|
|
"""测试 ensure_ticket 创建新审批单"""
|
|
# Arrange
|
|
approval_svc = ApprovalService(db_session)
|
|
|
|
# 创建 mock action
|
|
action = MagicMock()
|
|
action.id = "action_001"
|
|
action.session_id = "session_001"
|
|
|
|
# Act
|
|
ticket = await approval_svc.ensure_ticket(
|
|
action=action,
|
|
channel="agent",
|
|
reason="测试审批单"
|
|
)
|
|
|
|
# Assert
|
|
assert ticket is not None
|
|
assert ticket.action_id == "action_001"
|
|
assert ticket.session_id == "session_001"
|
|
assert ticket.channel == "agent"
|
|
assert ticket.status == "pending"
|
|
assert ticket.reason == "测试审批单"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ensure_ticket_idempotent(self, db_session):
|
|
"""测试 ensure_ticket 幂等性 - 相同动作只创建一张待决审批单"""
|
|
# Arrange
|
|
approval_svc = ApprovalService(db_session)
|
|
|
|
action = MagicMock()
|
|
action.id = "action_002"
|
|
action.session_id = "session_002"
|
|
|
|
# Act - 第一次调用
|
|
ticket1 = await approval_svc.ensure_ticket(
|
|
action=action,
|
|
channel="agent",
|
|
reason="第一次创建"
|
|
)
|
|
|
|
# Act - 第二次调用(幂等)
|
|
ticket2 = await approval_svc.ensure_ticket(
|
|
action=action,
|
|
channel="h5",
|
|
reason="第二次创建(应返回已存在的)"
|
|
)
|
|
|
|
# Assert - 两次返回同一张审批单
|
|
assert ticket1.id == ticket2.id
|
|
assert ticket1.status == "pending"
|
|
# 保持原有信息不变
|
|
assert ticket1.reason == "第一次创建"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_decide_approve(self, db_session):
|
|
"""测试 decide 审批通过"""
|
|
# Arrange
|
|
approval_svc = ApprovalService(db_session)
|
|
|
|
# 先创建审批单
|
|
action = MagicMock()
|
|
action.id = "action_003"
|
|
action.session_id = "session_003"
|
|
|
|
ticket = await approval_svc.ensure_ticket(
|
|
action=action,
|
|
channel="agent",
|
|
reason="测试审批"
|
|
)
|
|
|
|
# Act - 审批通过
|
|
result = await approval_svc.decide(
|
|
ticket_id=ticket.id,
|
|
decision="approve",
|
|
note="同意执行",
|
|
approver_id="agent_001"
|
|
)
|
|
|
|
# Assert
|
|
assert result.status == "approved"
|
|
assert result.decision_note == "同意执行"
|
|
assert result.approver_id == "agent_001"
|
|
assert result.decided_at is not None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_decide_reject(self, db_session):
|
|
"""测试 decide 审批驳回"""
|
|
# Arrange
|
|
approval_svc = ApprovalService(db_session)
|
|
|
|
action = MagicMock()
|
|
action.id = "action_004"
|
|
action.session_id = "session_004"
|
|
|
|
ticket = await approval_svc.ensure_ticket(
|
|
action=action,
|
|
channel="agent",
|
|
reason="测试审批"
|
|
)
|
|
|
|
# Act - 审批驳回
|
|
result = await approval_svc.decide(
|
|
ticket_id=ticket.id,
|
|
decision="reject",
|
|
note="风险太高,驳回",
|
|
approver_id="agent_002"
|
|
)
|
|
|
|
# Assert
|
|
assert result.status == "rejected"
|
|
assert result.decision_note == "风险太高,驳回"
|
|
assert result.approver_id == "agent_002"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_decide_nonexistent_ticket(self, db_session):
|
|
"""测试 decide 对不存在的审批单抛出异常"""
|
|
# Arrange
|
|
approval_svc = ApprovalService(db_session)
|
|
|
|
# Act & Assert
|
|
with pytest.raises(ValueError, match="审批单不存在"):
|
|
await approval_svc.decide(
|
|
ticket_id="nonexistent_id",
|
|
decision="approve",
|
|
note="test",
|
|
approver_id="agent_001"
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_decide_already_decided(self, db_session):
|
|
"""测试 decide 对已决审批单不重复处理"""
|
|
# Arrange
|
|
approval_svc = ApprovalService(db_session)
|
|
|
|
action = MagicMock()
|
|
action.id = "action_005"
|
|
action.session_id = "session_005"
|
|
|
|
ticket = await approval_svc.ensure_ticket(
|
|
action=action,
|
|
channel="agent",
|
|
reason="测试"
|
|
)
|
|
|
|
# 第一次审批通过
|
|
await approval_svc.decide(
|
|
ticket_id=ticket.id,
|
|
decision="approve",
|
|
note="通过",
|
|
approver_id="agent_001"
|
|
)
|
|
|
|
# 第二次尝试审批(应该直接返回,不处理)
|
|
result = await approval_svc.decide(
|
|
ticket_id=ticket.id,
|
|
decision="reject",
|
|
note="试图驳回",
|
|
approver_id="agent_002"
|
|
)
|
|
|
|
# Assert - 状态应保持为 approved,不受第二次影响
|
|
assert result.status == "approved"
|
|
assert result.approver_id == "agent_001" # 保持原审批人
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_pending_for_action(self, db_session):
|
|
"""测试 get_pending_for_action 查询待决审批单"""
|
|
# Arrange
|
|
approval_svc = ApprovalService(db_session)
|
|
|
|
action = MagicMock()
|
|
action.id = "action_006"
|
|
action.session_id = "session_006"
|
|
|
|
# 创建审批单
|
|
await approval_svc.ensure_ticket(
|
|
action=action,
|
|
channel="agent",
|
|
reason="待决审批单"
|
|
)
|
|
|
|
# Act
|
|
pending = await approval_svc.get_pending_for_action(action.id)
|
|
|
|
# Assert
|
|
assert pending is not None
|
|
assert pending.action_id == action.id
|
|
assert pending.status == "pending"
|