5e53146a9a
新增自动化审批状态机/执行器/意图路由/会话管理、OTP 绑定流程、neo4j 客户端、响应契约、置信度门禁、环境门控、Tier1 API 等测试。
250 lines
9.1 KiB
Python
250 lines
9.1 KiB
Python
# =============================================================================
|
||
# 企微IT智能服务台 — 审批状态机测试(D7 / P0-6)
|
||
# =============================================================================
|
||
# 说明:测试五态流转 + 合法转换校验 + 非法转换拦截。
|
||
# 状态机路径:
|
||
# pending → approved → applied → graph_synced
|
||
# pending → queued → approved → applied → graph_synced
|
||
# pending → rejected
|
||
# pending → expired
|
||
# (改写) → pending
|
||
# =============================================================================
|
||
|
||
import pytest
|
||
|
||
from app.schemas.enums import (
|
||
SuggestionStatusEnum,
|
||
is_valid_transition,
|
||
)
|
||
|
||
|
||
class TestApprovalStateMachine:
|
||
"""审批状态机 — 合法转换测试。"""
|
||
|
||
# ── 合法转换 ──
|
||
|
||
def test_pending_to_approved_valid(self):
|
||
"""pending → approved 合法。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.pending, SuggestionStatusEnum.approved
|
||
) is True
|
||
|
||
def test_pending_to_queued_valid(self):
|
||
"""pending → queued 合法。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.pending, SuggestionStatusEnum.queued
|
||
) is True
|
||
|
||
def test_pending_to_rejected_valid(self):
|
||
"""pending → rejected 合法。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.pending, SuggestionStatusEnum.rejected
|
||
) is True
|
||
|
||
def test_pending_to_expired_valid(self):
|
||
"""pending → expired 合法。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.pending, SuggestionStatusEnum.expired
|
||
) is True
|
||
|
||
def test_queued_to_approved_valid(self):
|
||
"""queued → approved 合法。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.queued, SuggestionStatusEnum.approved
|
||
) is True
|
||
|
||
def test_approved_to_applied_valid(self):
|
||
"""approved → applied 合法。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.approved, SuggestionStatusEnum.applied
|
||
) is True
|
||
|
||
def test_applied_to_graph_synced_valid(self):
|
||
"""applied → graph_synced 合法(终态)。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.applied, SuggestionStatusEnum.graph_synced
|
||
) is True
|
||
|
||
# ── 非法转换 ──
|
||
|
||
def test_graph_synced_is_terminal(self):
|
||
"""graph_synced 是终态,不可再转换。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.graph_synced, SuggestionStatusEnum.pending
|
||
) is False
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.graph_synced, SuggestionStatusEnum.applied
|
||
) is False
|
||
|
||
def test_rejected_is_terminal(self):
|
||
"""rejected 是终态,不可再转换。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.rejected, SuggestionStatusEnum.pending
|
||
) is False
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.rejected, SuggestionStatusEnum.approved
|
||
) is False
|
||
|
||
def test_expired_is_terminal(self):
|
||
"""expired 是终态,不可再转换。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.expired, SuggestionStatusEnum.pending
|
||
) is False
|
||
|
||
def test_pending_direct_to_graph_synced_invalid(self):
|
||
"""pending 不能直接跳到 graph_synced(必经 approved→applied)。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.pending, SuggestionStatusEnum.graph_synced
|
||
) is False
|
||
|
||
def test_queued_direct_to_applied_invalid(self):
|
||
"""queued 不能直接跳到 applied(必经 approved)。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.queued, SuggestionStatusEnum.applied
|
||
) is False
|
||
|
||
def test_applied_cannot_go_back_to_pending(self):
|
||
"""applied 不能回退到 pending。"""
|
||
assert is_valid_transition(
|
||
SuggestionStatusEnum.applied, SuggestionStatusEnum.pending
|
||
) is False
|
||
|
||
|
||
class TestApprovalServiceFlows:
|
||
"""审批服务层流程测试(需要数据库)。"""
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_approve_suggestion_not_found(self, db_session):
|
||
"""验证审批不存在的建议返回 None。"""
|
||
from app.services.knowledge_iteration_service import KnowledgeIterationService
|
||
|
||
service = KnowledgeIterationService()
|
||
result = await service.approve_suggestion(
|
||
db_session, "nonexistent-id", "reviewer-001"
|
||
)
|
||
assert result is None
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_reject_suggestion_not_found(self, db_session):
|
||
"""验证拒绝不存在的建议返回 None。"""
|
||
from app.services.knowledge_iteration_service import KnowledgeIterationService
|
||
|
||
service = KnowledgeIterationService()
|
||
result = await service.reject_suggestion(
|
||
db_session, "nonexistent-id", "reviewer-001", "不需要"
|
||
)
|
||
assert result is None
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_rewrite_suggestion_not_found(self, db_session):
|
||
"""验证改写不存在的建议返回 None。"""
|
||
from app.services.knowledge_iteration_service import KnowledgeIterationService
|
||
|
||
service = KnowledgeIterationService()
|
||
result = await service.rewrite_suggestion(
|
||
db_session, "nonexistent-id", "reviewer-001", {"title": "新标题"}
|
||
)
|
||
assert result is None
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_rewrite_resets_to_pending(self, db_session):
|
||
"""验证改写后状态重置为 pending。"""
|
||
from app.services.knowledge_iteration_service import KnowledgeIterationService
|
||
from app.models.knowledge_suggestion import KnowledgeSuggestion
|
||
|
||
# 创建测试建议
|
||
suggestion = KnowledgeSuggestion(
|
||
suggestion_type="new_faq",
|
||
status="approved",
|
||
title="测试标题",
|
||
content="测试内容",
|
||
category="网络",
|
||
tags=[],
|
||
source_type="conversation",
|
||
source_data=["conv-001"],
|
||
reason="测试",
|
||
confidence=0.8,
|
||
audience="employee_quick_reply",
|
||
)
|
||
db_session.add(suggestion)
|
||
await db_session.commit()
|
||
await db_session.refresh(suggestion)
|
||
|
||
# 改写
|
||
service = KnowledgeIterationService()
|
||
result = await service.rewrite_suggestion(
|
||
db_session, suggestion.id, "reviewer-001", {"title": "改写后的标题"}
|
||
)
|
||
|
||
assert result is not None
|
||
assert result.status == "pending"
|
||
assert result.title == "改写后的标题"
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_approve_suggestion_full_flow(self, db_session):
|
||
"""验证完整审批流程:pending→approved→applied。"""
|
||
from app.services.knowledge_iteration_service import KnowledgeIterationService
|
||
from app.models.knowledge_suggestion import KnowledgeSuggestion
|
||
|
||
# 创建测试建议
|
||
suggestion = KnowledgeSuggestion(
|
||
suggestion_type="new_faq",
|
||
status="pending",
|
||
title="VPN连接测试",
|
||
content="1.检查网络 2.重启客户端",
|
||
category="网络",
|
||
tags=["VPN"],
|
||
source_type="conversation",
|
||
source_data=["conv-002"],
|
||
reason="测试流程",
|
||
confidence=0.86,
|
||
audience="employee_quick_reply",
|
||
issue="VPN问题",
|
||
action="VPN连接修复",
|
||
relation_type="LEADS_TO",
|
||
)
|
||
db_session.add(suggestion)
|
||
await db_session.commit()
|
||
await db_session.refresh(suggestion)
|
||
|
||
# 审批通过(不传 neo4j_client,只到 applied)
|
||
service = KnowledgeIterationService()
|
||
result = await service.approve_suggestion(
|
||
db_session, suggestion.id, "reviewer-001", neo4j_client=None
|
||
)
|
||
|
||
assert result is not None
|
||
assert result.status in ("applied", "graph_synced")
|
||
assert result.reviewer_id == "reviewer-001"
|
||
assert result.reviewed_at is not None
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_queue_suggestion_flow(self, db_session):
|
||
"""验证入队列流程:pending→queued。"""
|
||
from app.services.knowledge_iteration_service import KnowledgeIterationService
|
||
from app.models.knowledge_suggestion import KnowledgeSuggestion
|
||
|
||
suggestion = KnowledgeSuggestion(
|
||
suggestion_type="new_faq",
|
||
status="pending",
|
||
title="队列测试",
|
||
content="测试内容",
|
||
category="软件",
|
||
tags=[],
|
||
source_type="conversation",
|
||
source_data=["conv-003"],
|
||
reason="测试入队列",
|
||
confidence=0.5,
|
||
audience="employee_quick_reply",
|
||
)
|
||
db_session.add(suggestion)
|
||
await db_session.commit()
|
||
await db_session.refresh(suggestion)
|
||
|
||
service = KnowledgeIterationService()
|
||
result = await service.queue_suggestion(db_session, suggestion.id)
|
||
|
||
assert result is not None
|
||
assert result.status == "queued"
|
||
assert result.queued_at is not None
|