""" 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