digital-employee-platform/backend/tests/test_conversations.py
root a0c2586487 feat(backend): B3 Task 3.1 - 对话与消息基础 API
- 添加 Conversation CRUD 端点(创建/列表/获取/删除)
- 添加 Message 操作端点(发送/列表)
- 注册 conversations 路由到 API v1
- 修复测试 fixture 的 API 路径前缀
- 添加 async_client fixture alias

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 02:24:06 +08:00

189 lines
6.6 KiB
Python

import pytest
from httpx import AsyncClient
from app.main import app
@pytest.fixture
async def tenant_id(async_client: AsyncClient) -> str:
"""Create a tenant and return its ID."""
resp = await async_client.post("/api/v1/tenants", json={"name": "Test Tenant", "slug": "test-tenant"})
return resp.json()["id"]
@pytest.fixture
async def employee_id(async_client: AsyncClient, tenant_id: str) -> str:
"""Create an employee and return its ID."""
resp = await async_client.post(
"/api/v1/employees",
json={
"tenant_id": tenant_id,
"name": "Test Employee",
"role": "assistant",
"system_prompt": "You are a helpful assistant.",
},
)
return resp.json()["id"]
class TestConversationCRUD:
"""Test conversation CRUD operations."""
@pytest.mark.asyncio
async def test_create_conversation(self, async_client: AsyncClient, tenant_id: str, employee_id: str):
"""Create a new conversation."""
resp = await async_client.post(
"/api/v1/conversations",
json={
"tenant_id": tenant_id,
"employee_id": employee_id,
"user_id": "user-001",
"title": "Test Conversation",
},
)
assert resp.status_code == 201
data = resp.json()
assert data["tenant_id"] == tenant_id
assert data["employee_id"] == employee_id
assert data["user_id"] == "user-001"
assert data["title"] == "Test Conversation"
assert "id" in data
assert "created_at" in data
@pytest.mark.asyncio
async def test_list_conversations(self, async_client: AsyncClient, tenant_id: str, employee_id: str):
"""List all conversations."""
# Create two conversations
await async_client.post(
"/api/v1/conversations",
json={
"tenant_id": tenant_id,
"employee_id": employee_id,
"user_id": "user-001",
"title": "Conv 1",
},
)
await async_client.post(
"/api/v1/conversations",
json={
"tenant_id": tenant_id,
"employee_id": employee_id,
"user_id": "user-002",
"title": "Conv 2",
},
)
resp = await async_client.get("/api/v1/conversations", params={"tenant_id": tenant_id})
assert resp.status_code == 200
data = resp.json()
assert len(data) == 2
@pytest.mark.asyncio
async def test_get_conversation(self, async_client: AsyncClient, tenant_id: str, employee_id: str):
"""Get a single conversation by ID."""
create_resp = await async_client.post(
"/api/v1/conversations",
json={
"tenant_id": tenant_id,
"employee_id": employee_id,
"user_id": "user-001",
"title": "Test Conv",
},
)
conv_id = create_resp.json()["id"]
resp = await async_client.get(f"/api/v1/conversations/{conv_id}")
assert resp.status_code == 200
data = resp.json()
assert data["id"] == conv_id
assert data["title"] == "Test Conv"
@pytest.mark.asyncio
async def test_delete_conversation(self, async_client: AsyncClient, tenant_id: str, employee_id: str):
"""Delete a conversation."""
create_resp = await async_client.post(
"/api/v1/conversations",
json={
"tenant_id": tenant_id,
"employee_id": employee_id,
"user_id": "user-001",
},
)
conv_id = create_resp.json()["id"]
resp = await async_client.delete(f"/api/v1/conversations/{conv_id}")
assert resp.status_code == 204
# Verify it's deleted
get_resp = await async_client.get(f"/api/v1/conversations/{conv_id}")
assert get_resp.status_code == 404
class TestMessageOperations:
"""Test message operations within a conversation."""
@pytest.fixture
async def conversation_id(self, async_client: AsyncClient, tenant_id: str, employee_id: str) -> str:
"""Create a conversation and return its ID."""
resp = await async_client.post(
"/api/v1/conversations",
json={
"tenant_id": tenant_id,
"employee_id": employee_id,
"user_id": "user-001",
},
)
return resp.json()["id"]
@pytest.mark.asyncio
async def test_send_message(self, async_client: AsyncClient, conversation_id: str):
"""Send a message to a conversation."""
resp = await async_client.post(
f"/api/v1/conversations/{conversation_id}/messages",
json={"content": "Hello, how are you?"},
)
# For now, we expect 200 with the created user message
# Later with SSE streaming, this will change
assert resp.status_code == 200
data = resp.json()
assert data["role"] == "user"
assert data["content"] == "Hello, how are you?"
assert "id" in data
assert data["conversation_id"] == conversation_id
@pytest.mark.asyncio
async def test_list_messages(self, async_client: AsyncClient, conversation_id: str):
"""List all messages in a conversation."""
# Send a message first
await async_client.post(
f"/api/v1/conversations/{conversation_id}/messages",
json={"content": "First message"},
)
await async_client.post(
f"/api/v1/conversations/{conversation_id}/messages",
json={"content": "Second message"},
)
resp = await async_client.get(f"/api/v1/conversations/{conversation_id}/messages")
assert resp.status_code == 200
data = resp.json()
assert len(data) >= 2
# Messages should be ordered by created_at
assert data[0]["content"] == "First message"
assert data[1]["content"] == "Second message"
@pytest.mark.asyncio
async def test_get_conversation_with_messages(
self, async_client: AsyncClient, conversation_id: str
):
"""Get conversation details with messages."""
await async_client.post(
f"/api/v1/conversations/{conversation_id}/messages",
json={"content": "Test message"},
)
resp = await async_client.get(f"/api/v1/conversations/{conversation_id}")
assert resp.status_code == 200
# The response may or may not include messages depending on schema
# For now, we just verify the conversation is accessible