feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
306
backend/tests/test_coze_api.py
Normal file
306
backend/tests/test_coze_api.py
Normal file
@@ -0,0 +1,306 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user