308 lines
9.3 KiB
Python
308 lines
9.3 KiB
Python
|
|
# =============================================================================
|
||
|
|
# 阶段5 自动化 - 会话管理服务单元测试
|
||
|
|
# =============================================================================
|
||
|
|
# 测试范围:session_manager.py - 会话生命周期、状态机、关单判定
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
|
||
|
|
from app.services.automation.session_manager import AutoSessionService
|
||
|
|
from app.models.automation import AutoSession, AutoAction
|
||
|
|
|
||
|
|
|
||
|
|
class TestAutoSessionService:
|
||
|
|
"""会话管理服务测试用例"""
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_create_session(self, db_session):
|
||
|
|
"""测试创建会话"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
|
||
|
|
# Act
|
||
|
|
session = await svc.create_session(
|
||
|
|
conversation_id="conv_001",
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="测试会话",
|
||
|
|
mode="real_exec"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert session.id is not None
|
||
|
|
assert session.conversation_id == "conv_001"
|
||
|
|
assert session.employee_id == "emp_001"
|
||
|
|
assert session.status == "created"
|
||
|
|
assert session.mode == "real_exec"
|
||
|
|
assert session.title == "测试会话"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_session(self, db_session):
|
||
|
|
"""测试获取会话"""
|
||
|
|
# Arrange - 先创建会话
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
created = await svc.create_session(
|
||
|
|
conversation_id="conv_002",
|
||
|
|
employee_id="emp_002",
|
||
|
|
description="查询测试"
|
||
|
|
)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Act
|
||
|
|
session = await svc.get_session(created.id)
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert session is not None
|
||
|
|
assert session.id == created.id
|
||
|
|
assert session.employee_id == "emp_002"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_session_not_found(self, db_session):
|
||
|
|
"""测试获取不存在的会话返回 None"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
|
||
|
|
# Act
|
||
|
|
session = await svc.get_session("nonexistent_id")
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert session is None
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_list_sessions_with_filters(self, db_session):
|
||
|
|
"""测试列出会话(支持过滤)"""
|
||
|
|
# Arrange - 创建多个会话
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
|
||
|
|
await svc.create_session(
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="会话1"
|
||
|
|
)
|
||
|
|
await svc.create_session(
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="会话2"
|
||
|
|
)
|
||
|
|
await svc.create_session(
|
||
|
|
employee_id="emp_002",
|
||
|
|
description="会话3"
|
||
|
|
)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Act - 按 employee_id 过滤
|
||
|
|
sessions = await svc.list_sessions(employee_id="emp_001")
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert len(sessions) == 2
|
||
|
|
assert all(s.employee_id == "emp_001" for s in sessions)
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_list_sessions_by_status(self, db_session):
|
||
|
|
"""测试按状态过滤会话"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
|
||
|
|
# 创建一个 running 会话
|
||
|
|
session = await svc.create_session(
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="测试"
|
||
|
|
)
|
||
|
|
session.status = "running"
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Act
|
||
|
|
running = await svc.list_sessions(status="running")
|
||
|
|
closed = await svc.list_sessions(status="closed")
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert len(running) >= 1
|
||
|
|
assert all(s.status == "running" for s in running)
|
||
|
|
assert len(closed) == 0
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_takeover(self, db_session):
|
||
|
|
"""测试转人工接管"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
session = await svc.create_session(
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="测试转人工"
|
||
|
|
)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Act
|
||
|
|
result = await svc.takeover(
|
||
|
|
session_id=session.id,
|
||
|
|
agent_id="agent_001",
|
||
|
|
note="我来接管处理"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert result.status == "handoff"
|
||
|
|
assert result.agent_id == "agent_001"
|
||
|
|
assert result.closed_by == "agent_001"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_resolve_feedback_satisfied(self, db_session):
|
||
|
|
"""测试反馈处理 - 满意则关单"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
session = await svc.create_session(
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="测试反馈"
|
||
|
|
)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Act - 满意
|
||
|
|
result = await svc.resolve_feedback(
|
||
|
|
session_id=session.id,
|
||
|
|
satisfied=True,
|
||
|
|
note="处理得很好"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert result.status == "closed"
|
||
|
|
assert result.closed_by == session.employee_id
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_resolve_feedback_unsatisfied(self, db_session):
|
||
|
|
"""测试反馈处理 - 不满意则转人工"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
session = await svc.create_session(
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="测试反馈"
|
||
|
|
)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Act - 不满意
|
||
|
|
result = await svc.resolve_feedback(
|
||
|
|
session_id=session.id,
|
||
|
|
satisfied=False,
|
||
|
|
note="没有解决我的问题"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert result.status == "handoff"
|
||
|
|
assert result.closed_by == "employee(reject)"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_auto_close_resolved_session(self, db_session):
|
||
|
|
"""测试静默关单 - 仅 resolved 态可关"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
session = await svc.create_session(
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="测试关单"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Case 1: 非 resolved 状态不应关单
|
||
|
|
session.status = "running"
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
await svc.auto_close(session.id)
|
||
|
|
|
||
|
|
await db_session.refresh(session)
|
||
|
|
assert session.status == "running" # 状态未变
|
||
|
|
|
||
|
|
# Case 2: resolved 状态应关单
|
||
|
|
session.status = "resolved"
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
await svc.auto_close(session.id)
|
||
|
|
|
||
|
|
await db_session.refresh(session)
|
||
|
|
assert session.status == "closed"
|
||
|
|
assert session.closed_by == "system(auto)"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_session_detail_with_actions(self, db_session):
|
||
|
|
"""测试获取会话详情(含动作列表)"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
session = await svc.create_session(
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="测试详情"
|
||
|
|
)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# 添加动作
|
||
|
|
action1 = AutoAction(
|
||
|
|
session_id=session.id,
|
||
|
|
action_index=0,
|
||
|
|
action_type="terminal_locate",
|
||
|
|
status="pending",
|
||
|
|
title="定位终端"
|
||
|
|
)
|
||
|
|
action2 = AutoAction(
|
||
|
|
session_id=session.id,
|
||
|
|
action_index=1,
|
||
|
|
action_type="virus_scan",
|
||
|
|
status="success",
|
||
|
|
title="病毒扫描"
|
||
|
|
)
|
||
|
|
db_session.add(action1)
|
||
|
|
db_session.add(action2)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Act
|
||
|
|
detail = await svc.get_session_detail(session.id)
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert detail is not None
|
||
|
|
assert detail["session"].id == session.id
|
||
|
|
assert len(detail["actions"]) == 2
|
||
|
|
# 验证动作按 action_index 排序
|
||
|
|
assert detail["actions"][0].action_type == "terminal_locate"
|
||
|
|
assert detail["actions"][1].action_type == "virus_scan"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_session_detail_not_found(self, db_session):
|
||
|
|
"""测试获取不存在的会话详情返回 None"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
|
||
|
|
# Act
|
||
|
|
detail = await svc.get_session_detail("nonexistent_id")
|
||
|
|
|
||
|
|
# Assert
|
||
|
|
assert detail is None
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_session_status_flow(self, db_session):
|
||
|
|
"""测试会话状态流转"""
|
||
|
|
# Arrange
|
||
|
|
svc = AutoSessionService(db_session)
|
||
|
|
session = await svc.create_session(
|
||
|
|
employee_id="emp_001",
|
||
|
|
description="状态流转测试"
|
||
|
|
)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
# Assert - 初始状态
|
||
|
|
assert session.status == "created"
|
||
|
|
|
||
|
|
# 模拟 start 后的状态变化
|
||
|
|
session.status = "running"
|
||
|
|
await db_session.flush()
|
||
|
|
await db_session.refresh(session)
|
||
|
|
assert session.status == "running"
|
||
|
|
|
||
|
|
# 审批暂停
|
||
|
|
session.status = "paused"
|
||
|
|
await db_session.flush()
|
||
|
|
await db_session.refresh(session)
|
||
|
|
assert session.status == "paused"
|
||
|
|
|
||
|
|
# 处置成功
|
||
|
|
session.status = "resolved"
|
||
|
|
await db_session.flush()
|
||
|
|
await db_session.refresh(session)
|
||
|
|
assert session.status == "resolved"
|
||
|
|
|
||
|
|
# 静默关单
|
||
|
|
session.status = "closed"
|
||
|
|
await db_session.flush()
|
||
|
|
await db_session.refresh(session)
|
||
|
|
assert session.status == "closed"
|