"""Tests for chat streaming endpoint.""" import pytest from fastapi.testclient import TestClient from unittest.mock import Mock, patch, AsyncMock import asyncio @pytest.mark.unit class TestChatStream: """Tests for chat streaming endpoint.""" @pytest.fixture def client(self): """Create test client for chat routes.""" from fastapi import FastAPI from agentic_rag.api.routes.chat import router app = FastAPI() app.include_router(router) return TestClient(app) def test_chat_stream_endpoint_exists(self, client): """Test chat stream endpoint exists and returns 200.""" response = client.post("/chat/stream", json={"message": "Hello"}) assert response.status_code == 200 def test_chat_stream_returns_streaming_response(self, client): """Test chat stream returns streaming response.""" response = client.post("/chat/stream", json={"message": "Hello"}) # Should be event stream assert "text/event-stream" in response.headers.get("content-type", "") def test_chat_stream_accepts_valid_message(self, client): """Test chat stream accepts valid message.""" response = client.post("/chat/stream", json={"message": "Test message"}) assert response.status_code == 200 def test_chat_stream_response_content(self, client): """Test chat stream response contains expected content.""" response = client.post("/chat/stream", json={"message": "Hello"}) content = response.content.decode("utf-8") assert "data: Hello from DocuMente!" in content assert "data: Streaming not fully implemented yet." in content assert "data: [DONE]" in content def test_chat_stream_multiple_messages(self, client): """Test chat stream generates multiple messages.""" response = client.post("/chat/stream", json={"message": "Hello"}) content = response.content.decode("utf-8") # Should have 3 data messages assert content.count("data:") == 3 def test_chat_stream_sse_format(self, client): """Test chat stream uses Server-Sent Events format.""" response = client.post("/chat/stream", json={"message": "Hello"}) content = response.content.decode("utf-8") # Each line should end with \n\n lines = content.strip().split("\n\n") for line in lines: assert line.startswith("data:") @pytest.mark.unit class TestChatMessageModel: """Tests for ChatMessage Pydantic model.""" def test_chat_message_creation(self): """Test ChatMessage can be created with message field.""" from agentic_rag.api.routes.chat import ChatMessage message = ChatMessage(message="Hello world") assert message.message == "Hello world" def test_chat_message_empty_string(self): """Test ChatMessage accepts empty string.""" from agentic_rag.api.routes.chat import ChatMessage message = ChatMessage(message="") assert message.message == "" def test_chat_message_long_message(self): """Test ChatMessage accepts long message.""" from agentic_rag.api.routes.chat import ChatMessage long_message = "A" * 10000 message = ChatMessage(message=long_message) assert message.message == long_message def test_chat_message_special_characters(self): """Test ChatMessage accepts special characters.""" from agentic_rag.api.routes.chat import ChatMessage special = "Hello! @#$%^&*()_+-=[]{}|;':\",./<>?" message = ChatMessage(message=special) assert message.message == special def test_chat_message_unicode(self): """Test ChatMessage accepts unicode characters.""" from agentic_rag.api.routes.chat import ChatMessage unicode_msg = "Hello 世界 🌍 Привет" message = ChatMessage(message=unicode_msg) assert message.message == unicode_msg def test_chat_message_serialization(self): """Test ChatMessage serializes correctly.""" from agentic_rag.api.routes.chat import ChatMessage message = ChatMessage(message="Test") data = message.model_dump() assert data == {"message": "Test"} def test_chat_message_json_serialization(self): """Test ChatMessage JSON serialization.""" from agentic_rag.api.routes.chat import ChatMessage message = ChatMessage(message="Test") json_str = message.model_dump_json() assert '"message":"Test"' in json_str @pytest.mark.unit class TestChatStreamValidation: """Tests for chat stream request validation.""" @pytest.fixture def client(self): """Create test client for chat routes.""" from fastapi import FastAPI from agentic_rag.api.routes.chat import router app = FastAPI() app.include_router(router) return TestClient(app) def test_chat_stream_rejects_empty_body(self, client): """Test chat stream rejects empty body.""" response = client.post("/chat/stream", json={}) assert response.status_code == 422 # Validation error def test_chat_stream_rejects_missing_message(self, client): """Test chat stream rejects request without message field.""" response = client.post("/chat/stream", json={"other_field": "value"}) assert response.status_code == 422 def test_chat_stream_rejects_invalid_json(self, client): """Test chat stream rejects invalid JSON.""" response = client.post( "/chat/stream", data="not valid json", headers={"Content-Type": "application/json"} ) assert response.status_code == 422 def test_chat_stream_accepts_extra_fields(self, client): """Test chat stream accepts extra fields (if configured).""" response = client.post("/chat/stream", json={"message": "Hello", "extra": "field"}) # FastAPI/Pydantic v2 ignores extra fields by default assert response.status_code == 200 @pytest.mark.unit class TestChatStreamAsync: """Tests for chat stream async behavior.""" def test_chat_stream_is_async(self): """Test chat stream endpoint is async function.""" from agentic_rag.api.routes.chat import chat_stream import asyncio assert asyncio.iscoroutinefunction(chat_stream) @pytest.mark.asyncio async def test_generate_function_yields_bytes(self): """Test generate function yields bytes.""" from agentic_rag.api.routes.chat import chat_stream # Access the inner generate function # We need to inspect the function behavior response_mock = Mock() request = Mock() request.message = "Hello" # The generate function is defined inside chat_stream # Let's test the streaming response directly from agentic_rag.api.routes.chat import ChatMessage # Create the streaming response from fastapi.responses import StreamingResponse # Check that chat_stream returns StreamingResponse result = await chat_stream(ChatMessage(message="Hello")) assert isinstance(result, StreamingResponse) @pytest.mark.unit class TestChatStreamEdgeCases: """Tests for chat stream edge cases.""" @pytest.fixture def client(self): """Create test client for chat routes.""" from fastapi import FastAPI from agentic_rag.api.routes.chat import router app = FastAPI() app.include_router(router) return TestClient(app) def test_chat_stream_with_whitespace_message(self, client): """Test chat stream with whitespace-only message.""" response = client.post("/chat/stream", json={"message": " "}) assert response.status_code == 200 def test_chat_stream_with_newline_message(self, client): """Test chat stream with message containing newlines.""" response = client.post("/chat/stream", json={"message": "Line 1\nLine 2\nLine 3"}) assert response.status_code == 200 def test_chat_stream_with_null_bytes(self, client): """Test chat stream with message containing null bytes.""" response = client.post("/chat/stream", json={"message": "Hello\x00World"}) assert response.status_code == 200 @pytest.mark.unit class TestChatRouterConfiguration: """Tests for chat router configuration.""" def test_router_exists(self): """Test router module exports router.""" from agentic_rag.api.routes.chat import router assert router is not None def test_router_is_api_router(self): """Test router is FastAPI APIRouter.""" from agentic_rag.api.routes.chat import router from fastapi import APIRouter assert isinstance(router, APIRouter) def test_chat_stream_endpoint_configured(self): """Test chat stream endpoint is configured.""" from agentic_rag.api.routes.chat import router routes = [route for route in router.routes if hasattr(route, "path")] paths = [route.path for route in routes] assert "/chat/stream" in paths def test_chat_stream_endpoint_methods(self): """Test chat stream endpoint accepts POST.""" from agentic_rag.api.routes.chat import router stream_route = None for route in router.routes: if hasattr(route, "path") and route.path == "/chat/stream": stream_route = route break assert stream_route is not None assert "POST" in stream_route.methods @pytest.mark.unit class TestChatStreamResponseFormat: """Tests for chat stream response format.""" @pytest.fixture def client(self): """Create test client for chat routes.""" from fastapi import FastAPI from agentic_rag.api.routes.chat import router app = FastAPI() app.include_router(router) return TestClient(app) def test_chat_stream_content_type_header(self, client): """Test chat stream has correct content-type header.""" response = client.post("/chat/stream", json={"message": "Hello"}) content_type = response.headers.get("content-type", "") assert "text/event-stream" in content_type def test_chat_stream_cache_control(self, client): """Test chat stream has cache control headers.""" response = client.post("/chat/stream", json={"message": "Hello"}) # Streaming responses typically have no-cache headers # This is set by FastAPI/Starlette for streaming responses assert response.status_code == 200 def test_chat_stream_response_chunks(self, client): """Test chat stream response is chunked.""" response = client.post("/chat/stream", json={"message": "Hello"}) # Response body should contain multiple chunks content = response.content.decode("utf-8") chunks = content.split("\n\n") # Should have multiple data chunks assert len(chunks) >= 3