Files
wecom_it_smart_desk/backend/tests/test_conversations.py
T

238 lines
8.5 KiB
Python
Raw Normal View History

# =============================================================================
# 企微IT智能服务台 — 会话状态流转测试
# =============================================================================
# 测试覆盖:
# 1. 会话创建默认状态为 queued
# 2. 坐席接单:queued → serving
# 3. 结单:serving → resolved
# 4. 重复接单处理
# 5. 已结单会话的操作限制
# 6. 置顶/取消置顶切换
# 7. 代办/取消代办切换
# 8. 会话列表过滤
# =============================================================================
import uuid
from datetime import datetime
import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession
from unittest.mock import patch
from app.models.agent import Agent
from app.models.conversation import Conversation
from app.services.session_service import SessionService
from tests.conftest import create_test_conversation, create_test_agent, MockRedis
class TestConversationStateFlow:
"""测试会话状态流转。"""
@pytest.mark.asyncio
async def test_new_conversation_default_status_queued(self, db_session):
"""验证新会话默认状态为 queued。"""
conv = create_test_conversation()
db_session.add(conv)
await db_session.flush()
assert conv.status == "queued"
@pytest.mark.asyncio
async def test_assign_conversation_to_serving(self, db_session):
"""验证坐席接单将会话状态改为 serving。"""
conv = create_test_conversation(status="queued")
agent = create_test_agent(user_id="agent001", name="坐席小王")
db_session.add_all([conv, agent])
await db_session.flush()
session_service = SessionService(db_session)
result = await session_service.assign_agent(conv.id, "agent001")
assert result.status == "serving"
assert result.assigned_agent_id == "agent001"
@pytest.mark.asyncio
async def test_resolve_conversation(self, db_session):
"""验证结单将会话状态改为 resolved。"""
conv = create_test_conversation(status="serving")
db_session.add(conv)
await db_session.flush()
session_service = SessionService(db_session)
result = await session_service.resolve_conversation(conv.id)
assert result.status == "resolved"
@pytest.mark.asyncio
async def test_resolve_queued_conversation_is_allowed(self, db_session):
"""验证 queued 状态的会话可以直接结单(员工问题自行解决)。"""
conv = create_test_conversation(status="queued")
db_session.add(conv)
await db_session.flush()
session_service = SessionService(db_session)
# queued → resolved 是合法的状态流转
result = await session_service.resolve_conversation(conv.id)
assert result.status == "resolved"
@pytest.mark.asyncio
async def test_cannot_resolve_already_resolved_conversation(self, db_session):
"""验证已结单的会话不能再结单。"""
conv = create_test_conversation(status="resolved")
db_session.add(conv)
await db_session.flush()
session_service = SessionService(db_session)
from app.utils.response import AppException
with pytest.raises(AppException):
await session_service.resolve_conversation(conv.id)
class TestConversationToggle:
"""测试会话标记切换。"""
@pytest.mark.asyncio
async def test_toggle_pin(self, db_session):
"""验证置顶切换:未置顶→置顶。"""
conv = create_test_conversation(is_pinned=False)
db_session.add(conv)
await db_session.flush()
session_service = SessionService(db_session)
result = await session_service.toggle_pin(conv.id)
assert result.is_pinned is True
@pytest.mark.asyncio
async def test_toggle_pin_off(self, db_session):
"""验证置顶切换:置顶→取消置顶。"""
conv = create_test_conversation(is_pinned=True)
db_session.add(conv)
await db_session.flush()
session_service = SessionService(db_session)
result = await session_service.toggle_pin(conv.id)
assert result.is_pinned is False
@pytest.mark.asyncio
async def test_toggle_todo(self, db_session):
"""验证代办切换:未代办→代办。"""
conv = create_test_conversation(is_todo=False)
db_session.add(conv)
await db_session.flush()
session_service = SessionService(db_session)
result = await session_service.toggle_todo(conv.id)
assert result.is_todo is True
@pytest.mark.asyncio
async def test_toggle_todo_off(self, db_session):
"""验证代办切换:代办→取消代办。"""
conv = create_test_conversation(is_todo=True)
db_session.add(conv)
await db_session.flush()
session_service = SessionService(db_session)
result = await session_service.toggle_todo(conv.id)
assert result.is_todo is False
class TestConversationList:
"""测试会话列表查询。"""
@pytest.mark.asyncio
async def test_list_all_conversations(self, db_session):
"""验证获取所有会话。"""
convs = [
create_test_conversation(employee_id=f"list_user_{i}", status="queued")
for i in range(3)
]
db_session.add_all(convs)
await db_session.flush()
session_service = SessionService(db_session)
result, total = await session_service.get_conversations()
assert total >= 3
@pytest.mark.asyncio
async def test_list_conversations_by_status(self, db_session):
"""验证按状态过滤会话。"""
db_session.add(create_test_conversation(employee_id="filter_queued", status="queued"))
db_session.add(create_test_conversation(employee_id="filter_serving", status="serving"))
await db_session.flush()
session_service = SessionService(db_session)
result, total = await session_service.get_conversations(status="queued")
for conv in result:
assert conv.status == "queued"
class TestConversationAPI:
"""测试会话管理 API 端点。"""
@pytest.mark.asyncio
async def test_get_conversations_endpoint(self, client, db_session, mock_redis):
"""验证 GET /api/conversations 返回正确格式。"""
conv = create_test_conversation(employee_id="api_list_user")
db_session.add(conv)
await db_session.flush()
# 登录坐席获取 token/api/conversations 需要 get_current_agent 认证)
login_resp = await client.post(
"/agents/login",
json={"user_id": "conv_list_agent", "name": "会话列表坐席"},
)
token = login_resp.json()["data"]["token"]
response = await client.get(
"/conversations",
headers={"Authorization": f"Bearer {token}"},
)
assert response.status_code == 200
data = response.json()
assert data["code"] == 0
assert "items" in data["data"]
@pytest.mark.asyncio
async def test_resolve_conversation_endpoint(self, client, db_session, mock_redis):
"""验证 POST /api/conversations/{id}/resolve 结单。
权限:只有主责坐席(assigned_agent_id)才能结单。
"""
# 先创建坐席
from app.models.agent import Agent as AgentModel
agent = AgentModel(
user_id="resolve_test_agent",
name="结单测试坐席",
status="online",
current_load=0,
max_load=5,
)
db_session.add(agent)
await db_session.flush()
# 创建分配给此坐席的会话
conv = create_test_conversation(employee_id="api_resolve_user", status="serving")
conv.assigned_agent_id = "resolve_test_agent"
db_session.add(conv)
await db_session.flush()
# 登录坐席获取 token
login_resp = await client.post(
"/agents/login",
json={"user_id": "resolve_test_agent", "name": "结单测试坐席"},
)
token = login_resp.json()["data"]["token"]
response = await client.post(
f"/conversations/{conv.id}/resolve",
headers={"Authorization": f"Bearer {token}"},
)
assert response.status_code == 200
data = response.json()
assert data["code"] == 0
assert data["data"]["status"] == "resolved"