- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
307 lines
10 KiB
Python
307 lines
10 KiB
Python
"""
|
|
Coze API 网关单元测试
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, AsyncMock, patch
|
|
from fastapi.testclient import TestClient
|
|
from fastapi import FastAPI
|
|
from sse_starlette.sse import ServerSentEvent
|
|
|
|
from app.api.v1.coze_gateway import router
|
|
from app.services.ai.coze.models import (
|
|
CreateSessionResponse, EndSessionResponse,
|
|
StreamEvent, StreamEventType, ContentType, MessageRole
|
|
)
|
|
from app.services.ai.coze.exceptions import CozeAPIError, CozeAuthError
|
|
|
|
|
|
# 创建测试应用
|
|
app = FastAPI()
|
|
app.include_router(router)
|
|
|
|
client = TestClient(app)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_user():
|
|
"""模拟已登录用户"""
|
|
with patch("app.api.v1.coze_gateway.get_current_user") as mock_get_user:
|
|
mock_get_user.return_value = {
|
|
"user_id": "test-user-123",
|
|
"username": "test_user"
|
|
}
|
|
yield mock_get_user
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_coze_service():
|
|
"""模拟 Coze 服务"""
|
|
with patch("app.api.v1.coze_gateway.get_coze_service") as mock_get_service:
|
|
mock_service = Mock()
|
|
mock_get_service.return_value = mock_service
|
|
yield mock_service
|
|
|
|
|
|
class TestCourseChat:
|
|
"""测试课程对话 API"""
|
|
|
|
def test_create_course_chat_session_success(self, mock_user, mock_coze_service):
|
|
"""测试成功创建课程对话会话"""
|
|
# Mock 服务响应
|
|
mock_coze_service.create_session = AsyncMock(
|
|
return_value=CreateSessionResponse(
|
|
session_id="session-123",
|
|
conversation_id="conv-123",
|
|
bot_id="bot-123",
|
|
created_at="2024-01-01T10:00:00"
|
|
)
|
|
)
|
|
|
|
response = client.post(
|
|
"/api/v1/course-chat/sessions",
|
|
json={"course_id": "course-456"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["code"] == 200
|
|
assert data["message"] == "success"
|
|
assert data["data"]["session_id"] == "session-123"
|
|
assert data["data"]["conversation_id"] == "conv-123"
|
|
|
|
def test_create_course_chat_session_auth_error(self, mock_user, mock_coze_service):
|
|
"""测试认证错误"""
|
|
mock_coze_service.create_session = AsyncMock(
|
|
side_effect=CozeAuthError(
|
|
message="认证失败",
|
|
code="AUTH_ERROR",
|
|
status_code=401
|
|
)
|
|
)
|
|
|
|
response = client.post(
|
|
"/api/v1/course-chat/sessions",
|
|
json={"course_id": "course-456"}
|
|
)
|
|
|
|
assert response.status_code == 401
|
|
data = response.json()
|
|
assert data["detail"]["code"] == "AUTH_ERROR"
|
|
assert data["detail"]["message"] == "认证失败"
|
|
|
|
def test_create_course_chat_session_server_error(self, mock_user, mock_coze_service):
|
|
"""测试服务器错误"""
|
|
mock_coze_service.create_session = AsyncMock(
|
|
side_effect=Exception("Unexpected error")
|
|
)
|
|
|
|
response = client.post(
|
|
"/api/v1/course-chat/sessions",
|
|
json={"course_id": "course-456"}
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = response.json()
|
|
assert data["detail"]["code"] == "INTERNAL_ERROR"
|
|
|
|
|
|
class TestTraining:
|
|
"""测试陪练 API"""
|
|
|
|
def test_create_training_session_with_topic(self, mock_user, mock_coze_service):
|
|
"""测试创建带主题的陪练会话"""
|
|
mock_coze_service.create_session = AsyncMock(
|
|
return_value=CreateSessionResponse(
|
|
session_id="training-123",
|
|
conversation_id="conv-456",
|
|
bot_id="training-bot",
|
|
created_at="2024-01-01T11:00:00"
|
|
)
|
|
)
|
|
|
|
response = client.post(
|
|
"/api/v1/training/sessions",
|
|
json={"training_topic": "客诉处理技巧"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["data"]["session_id"] == "training-123"
|
|
|
|
# 验证服务调用
|
|
call_args = mock_coze_service.create_session.call_args[0][0]
|
|
assert call_args.training_topic == "客诉处理技巧"
|
|
|
|
def test_create_training_session_without_topic(self, mock_user, mock_coze_service):
|
|
"""测试创建不带主题的陪练会话"""
|
|
mock_coze_service.create_session = AsyncMock(
|
|
return_value=CreateSessionResponse(
|
|
session_id="training-456",
|
|
conversation_id="conv-789",
|
|
bot_id="training-bot",
|
|
created_at="2024-01-01T12:00:00"
|
|
)
|
|
)
|
|
|
|
response = client.post("/api/v1/training/sessions", json={})
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["data"]["session_id"] == "training-456"
|
|
|
|
def test_end_training_session_success(self, mock_user, mock_coze_service):
|
|
"""测试成功结束陪练会话"""
|
|
mock_coze_service.end_session = AsyncMock(
|
|
return_value=EndSessionResponse(
|
|
session_id="training-123",
|
|
ended_at="2024-01-01T13:00:00",
|
|
duration_seconds=1800,
|
|
message_count=25
|
|
)
|
|
)
|
|
|
|
response = client.post(
|
|
"/api/v1/training/sessions/training-123/end",
|
|
json={
|
|
"reason": "练习完成",
|
|
"feedback": {"rating": 5, "helpful": True}
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["data"]["duration_seconds"] == 1800
|
|
assert data["data"]["message_count"] == 25
|
|
|
|
def test_end_nonexistent_session(self, mock_user, mock_coze_service):
|
|
"""测试结束不存在的会话"""
|
|
mock_coze_service.end_session = AsyncMock(
|
|
side_effect=CozeAPIError(
|
|
message="会话不存在",
|
|
code="SESSION_NOT_FOUND",
|
|
status_code=404
|
|
)
|
|
)
|
|
|
|
response = client.post(
|
|
"/api/v1/training/sessions/nonexistent/end",
|
|
json={}
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestChatMessages:
|
|
"""测试消息发送 API"""
|
|
|
|
def test_send_message_non_stream(self, mock_user, mock_coze_service):
|
|
"""测试非流式消息发送"""
|
|
# Mock 异步生成器
|
|
async def mock_generator():
|
|
yield StreamEvent(
|
|
event=StreamEventType.MESSAGE_DELTA,
|
|
data={},
|
|
content="Hello",
|
|
content_type=ContentType.TEXT,
|
|
role=MessageRole.ASSISTANT
|
|
)
|
|
yield StreamEvent(
|
|
event=StreamEventType.MESSAGE_COMPLETED,
|
|
data={"usage": {"tokens": 10}},
|
|
message_id="msg-123",
|
|
content="Hello, how can I help you?",
|
|
content_type=ContentType.TEXT,
|
|
role=MessageRole.ASSISTANT
|
|
)
|
|
yield StreamEvent(
|
|
event=StreamEventType.DONE,
|
|
data={"session_id": "session-123"}
|
|
)
|
|
|
|
mock_coze_service.send_message = AsyncMock(return_value=mock_generator())
|
|
|
|
response = client.post(
|
|
"/api/v1/chat/messages",
|
|
json={
|
|
"session_id": "session-123",
|
|
"content": "Hello",
|
|
"stream": False
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["data"]["content"] == "Hello, how can I help you?"
|
|
assert data["data"]["content_type"] == "text"
|
|
assert data["data"]["role"] == "assistant"
|
|
|
|
def test_send_message_with_files(self, mock_user, mock_coze_service):
|
|
"""测试带附件的消息发送"""
|
|
async def mock_generator():
|
|
yield StreamEvent(
|
|
event=StreamEventType.MESSAGE_COMPLETED,
|
|
data={},
|
|
message_id="msg-456",
|
|
content="File received",
|
|
content_type=ContentType.TEXT,
|
|
role=MessageRole.ASSISTANT
|
|
)
|
|
yield StreamEvent(
|
|
event=StreamEventType.DONE,
|
|
data={"session_id": "session-123"}
|
|
)
|
|
|
|
mock_coze_service.send_message = AsyncMock(return_value=mock_generator())
|
|
|
|
response = client.post(
|
|
"/api/v1/chat/messages",
|
|
json={
|
|
"session_id": "session-123",
|
|
"content": "Please analyze this file",
|
|
"file_ids": ["file-123", "file-456"],
|
|
"stream": False
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
# 验证服务调用
|
|
call_args = mock_coze_service.send_message.call_args[0][0]
|
|
assert call_args.file_ids == ["file-123", "file-456"]
|
|
|
|
def test_get_message_history(self, mock_user, mock_coze_service):
|
|
"""测试获取消息历史"""
|
|
from app.services.ai.coze.models import CozeMessage
|
|
|
|
mock_messages = [
|
|
CozeMessage(
|
|
message_id="msg-1",
|
|
session_id="session-123",
|
|
role=MessageRole.USER,
|
|
content="Hello"
|
|
),
|
|
CozeMessage(
|
|
message_id="msg-2",
|
|
session_id="session-123",
|
|
role=MessageRole.ASSISTANT,
|
|
content="Hi there!"
|
|
)
|
|
]
|
|
|
|
mock_coze_service.get_session_messages = AsyncMock(
|
|
return_value=mock_messages
|
|
)
|
|
|
|
response = client.get(
|
|
"/api/v1/sessions/session-123/messages?limit=10&offset=0"
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert len(data["data"]["messages"]) == 2
|
|
assert data["data"]["messages"][0]["content"] == "Hello"
|
|
assert data["data"]["messages"][1]["content"] == "Hi there!"
|
|
assert data["data"]["limit"] == 10
|
|
assert data["data"]["offset"] == 0
|