Files
wecom_it_smart_desk/backend/tests/test_conversations.py
T

238 lines
8.5 KiB
Python
Raw 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.
# =============================================================================
# 企微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"