Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Integration tests for chat requests with user authentication | |
| This test file focuses on end-to-end testing of the chat API with user_id support, | |
| including request validation, response handling, and data persistence. | |
| """ | |
| import sys | |
| import os | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| # Load environment variables | |
| try: | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| except ImportError: | |
| pass # dotenv not available, continue without it | |
| import asyncio | |
| import httpx | |
| import json | |
| import time | |
| from datetime import datetime | |
| from typing import Optional, Dict, Any | |
| class TestChatRequestValidation: | |
| """Test chat request validation with user_id""" | |
| async def test_valid_user_id_formats(self): | |
| """Test chat requests with various valid user_id formats""" | |
| valid_user_ids = [ | |
| "user123", | |
| "user_123", | |
| "user-123", | |
| "user_123-test", | |
| "123user", | |
| "a", # Single character | |
| "a" * 255, # Maximum length | |
| ] | |
| for user_id in valid_user_ids: | |
| chat_data = { | |
| "prompt": f"Test message for user {user_id}", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7, | |
| "user_id": user_id | |
| } | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| response = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| assert response.status_code == 200, f"Failed for user_id: {user_id}" | |
| result = response.json() | |
| assert "response" in result | |
| # Check session ID in headers | |
| session_id = response.headers.get('X-Session-ID') | |
| assert session_id is not None | |
| print(f"β Valid user_id '{user_id}' accepted") | |
| except httpx.ConnectError: | |
| print("β οΈ Server not running - skipping integration test") | |
| return | |
| async def test_invalid_user_id_formats(self): | |
| """Test chat requests with invalid user_id formats""" | |
| invalid_user_ids = [ | |
| "user@123", # @ symbol | |
| "user 123", # space | |
| "user.123", # period | |
| "user#123", # hash | |
| "user$123", # dollar sign | |
| "user%123", # percent | |
| "user&123", # ampersand | |
| "user*123", # asterisk | |
| "user+123", # plus | |
| "user=123", # equals | |
| "user[123]", # brackets | |
| "user{123}", # braces | |
| "user|123", # pipe | |
| "user\\123", # backslash | |
| "user/123", # forward slash | |
| "user:123", # colon | |
| "user;123", # semicolon | |
| "user<123>", # angle brackets | |
| "user?123", # question mark | |
| "user,123", # comma | |
| "user'123", # single quote | |
| 'user"123', # double quote | |
| "user`123", # backtick | |
| "user~123", # tilde | |
| "user!123", # exclamation | |
| "a" * 256, # Too long | |
| ] | |
| for user_id in invalid_user_ids: | |
| chat_data = { | |
| "prompt": f"Test message for invalid user {user_id}", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7, | |
| "user_id": user_id | |
| } | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| response = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| assert response.status_code == 400, f"Should have failed for user_id: {user_id}" | |
| result = response.json() | |
| assert "detail" in result | |
| print(f"β Invalid user_id '{user_id}' correctly rejected") | |
| except httpx.ConnectError: | |
| print("β οΈ Server not running - skipping integration test") | |
| return | |
| async def test_empty_user_id_handling(self): | |
| """Test that empty user_id is treated as anonymous""" | |
| empty_user_ids = ["", " ", "\t", "\n"] | |
| for empty_user_id in empty_user_ids: | |
| chat_data = { | |
| "prompt": "Test message with empty user_id", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7, | |
| "user_id": empty_user_id | |
| } | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| response = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| assert response.status_code == 200 | |
| result = response.json() | |
| assert "response" in result | |
| print(f"β Empty user_id '{repr(empty_user_id)}' treated as anonymous") | |
| except httpx.ConnectError: | |
| print("β οΈ Server not running - skipping integration test") | |
| return | |
| async def test_missing_user_id_field(self): | |
| """Test that missing user_id field works (backward compatibility)""" | |
| chat_data = { | |
| "prompt": "Test message without user_id field", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7 | |
| # No user_id field | |
| } | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| response = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| assert response.status_code == 200 | |
| result = response.json() | |
| assert "response" in result | |
| # Check session ID in headers | |
| session_id = response.headers.get('X-Session-ID') | |
| assert session_id is not None | |
| print("β Missing user_id field handled correctly") | |
| except httpx.ConnectError: | |
| print("β οΈ Server not running - skipping integration test") | |
| return | |
| class TestChatRequestFlow: | |
| """Test complete chat request flow with user authentication""" | |
| async def test_authenticated_user_session_flow(self): | |
| """Test complete flow for authenticated user""" | |
| user_id = "test_flow_user" | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| # First request - creates new session | |
| chat_data1 = { | |
| "prompt": "First message from authenticated user", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7, | |
| "user_id": user_id | |
| } | |
| response1 = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data1, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| assert response1.status_code == 200 | |
| result1 = response1.json() | |
| assert "response" in result1 | |
| session_id = response1.headers.get('X-Session-ID') | |
| assert session_id is not None | |
| print(f"β First request created session: {session_id}") | |
| # Second request - uses existing session | |
| chat_data2 = { | |
| "prompt": "Second message from same user", | |
| "max_new_tokens": 50, | |
| "use_search": True, # Enable search this time | |
| "temperature": 0.7, | |
| "user_id": user_id | |
| } | |
| response2 = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data2, | |
| headers={ | |
| "Content-Type": "application/json", | |
| "X-Session-ID": session_id # Provide session ID | |
| } | |
| ) | |
| assert response2.status_code == 200 | |
| result2 = response2.json() | |
| assert "response" in result2 | |
| # Should return same session ID | |
| session_id2 = response2.headers.get('X-Session-ID') | |
| assert session_id2 == session_id | |
| print(f"β Second request used same session: {session_id2}") | |
| # Wait for data to be written | |
| await asyncio.sleep(2) | |
| # Verify data was stored correctly | |
| await self._verify_session_data(session_id, user_id, expected_messages=2) | |
| except httpx.ConnectError: | |
| print("β οΈ Server not running - skipping integration test") | |
| return | |
| async def test_anonymous_user_session_flow(self): | |
| """Test complete flow for anonymous user""" | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| # First request - anonymous user | |
| chat_data1 = { | |
| "prompt": "First message from anonymous user", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7 | |
| # No user_id field | |
| } | |
| response1 = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data1, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| assert response1.status_code == 200 | |
| result1 = response1.json() | |
| assert "response" in result1 | |
| session_id = response1.headers.get('X-Session-ID') | |
| assert session_id is not None | |
| print(f"β Anonymous request created session: {session_id}") | |
| # Second request - same anonymous user | |
| chat_data2 = { | |
| "prompt": "Second message from anonymous user", | |
| "max_new_tokens": 50, | |
| "use_search": True, | |
| "temperature": 0.7 | |
| # No user_id field | |
| } | |
| response2 = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data2, | |
| headers={ | |
| "Content-Type": "application/json", | |
| "X-Session-ID": session_id | |
| } | |
| ) | |
| assert response2.status_code == 200 | |
| result2 = response2.json() | |
| assert "response" in result2 | |
| session_id2 = response2.headers.get('X-Session-ID') | |
| assert session_id2 == session_id | |
| print(f"β Anonymous second request used same session: {session_id2}") | |
| # Wait for data to be written | |
| await asyncio.sleep(2) | |
| # Verify data was stored correctly (user_id should be None) | |
| await self._verify_session_data(session_id, None, expected_messages=2) | |
| except httpx.ConnectError: | |
| print("β οΈ Server not running - skipping integration test") | |
| return | |
| async def test_mixed_user_sessions(self): | |
| """Test that different users get different sessions""" | |
| user_id1 = "test_user_1" | |
| user_id2 = "test_user_2" | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| # Request from user 1 | |
| chat_data1 = { | |
| "prompt": "Message from user 1", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7, | |
| "user_id": user_id1 | |
| } | |
| response1 = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data1, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| assert response1.status_code == 200 | |
| session_id1 = response1.headers.get('X-Session-ID') | |
| assert session_id1 is not None | |
| # Request from user 2 | |
| chat_data2 = { | |
| "prompt": "Message from user 2", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7, | |
| "user_id": user_id2 | |
| } | |
| response2 = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data2, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| assert response2.status_code == 200 | |
| session_id2 = response2.headers.get('X-Session-ID') | |
| assert session_id2 is not None | |
| # Sessions should be different | |
| assert session_id1 != session_id2 | |
| print(f"β User 1 session: {session_id1}") | |
| print(f"β User 2 session: {session_id2}") | |
| print("β Different users got different sessions") | |
| except httpx.ConnectError: | |
| print("β οΈ Server not running - skipping integration test") | |
| return | |
| async def _verify_session_data(self, session_id: str, expected_user_id: Optional[str], expected_messages: int): | |
| """Verify that session data was stored correctly""" | |
| try: | |
| from analytics.database import get_sessions_collection, get_messages_collection | |
| sessions_collection = await get_sessions_collection() | |
| messages_collection = await get_messages_collection() | |
| if sessions_collection is None or messages_collection is None: | |
| print("β οΈ Database not available - skipping data verification") | |
| return | |
| # Check session data | |
| session_doc = await sessions_collection.find_one({"_id": session_id}) | |
| assert session_doc is not None, f"Session {session_id} not found in database" | |
| assert session_doc.get("user_id") == expected_user_id, f"Expected user_id {expected_user_id}, got {session_doc.get('user_id')}" | |
| # Check message data | |
| message_docs = await messages_collection.find({"session_id": session_id}).to_list(None) | |
| assert len(message_docs) == expected_messages, f"Expected {expected_messages} messages, got {len(message_docs)}" | |
| for message_doc in message_docs: | |
| assert message_doc.get("user_id") == expected_user_id, f"Message user_id mismatch: expected {expected_user_id}, got {message_doc.get('user_id')}" | |
| print(f"β Session data verified: user_id={expected_user_id}, messages={len(message_docs)}") | |
| except Exception as e: | |
| print(f"β οΈ Could not verify session data: {e}") | |
| class TestChatRequestPerformance: | |
| """Test performance of chat requests with user authentication""" | |
| async def test_authenticated_request_performance(self): | |
| """Test performance of authenticated chat requests""" | |
| user_id = "perf_test_user" | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| # Warm up | |
| chat_data = { | |
| "prompt": "Warmup message", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7, | |
| "user_id": user_id | |
| } | |
| await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| # Performance test | |
| num_requests = 5 | |
| total_time = 0 | |
| for i in range(num_requests): | |
| chat_data = { | |
| "prompt": f"Performance test message {i}", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7, | |
| "user_id": user_id | |
| } | |
| start_time = time.time() | |
| response = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| end_time = time.time() | |
| assert response.status_code == 200 | |
| request_time = end_time - start_time | |
| total_time += request_time | |
| print(f"Request {i+1}: {request_time:.2f}s") | |
| avg_time = total_time / num_requests | |
| print(f"β Average request time: {avg_time:.2f}s") | |
| # Performance assertion (requests should be reasonably fast) | |
| assert avg_time < 10.0, f"Requests too slow: {avg_time:.2f}s average" | |
| except httpx.ConnectError: | |
| print("β οΈ Server not running - skipping performance test") | |
| return | |
| async def test_anonymous_vs_authenticated_performance(self): | |
| """Compare performance between anonymous and authenticated requests""" | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| # Test anonymous requests | |
| anonymous_times = [] | |
| for i in range(3): | |
| chat_data = { | |
| "prompt": f"Anonymous performance test {i}", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7 | |
| } | |
| start_time = time.time() | |
| response = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| end_time = time.time() | |
| assert response.status_code == 200 | |
| anonymous_times.append(end_time - start_time) | |
| # Test authenticated requests | |
| authenticated_times = [] | |
| for i in range(3): | |
| chat_data = { | |
| "prompt": f"Authenticated performance test {i}", | |
| "max_new_tokens": 50, | |
| "use_search": False, | |
| "temperature": 0.7, | |
| "user_id": "perf_auth_user" | |
| } | |
| start_time = time.time() | |
| response = await client.post( | |
| "http://localhost:7860/chat", | |
| json=chat_data, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| end_time = time.time() | |
| assert response.status_code == 200 | |
| authenticated_times.append(end_time - start_time) | |
| avg_anonymous = sum(anonymous_times) / len(anonymous_times) | |
| avg_authenticated = sum(authenticated_times) / len(authenticated_times) | |
| print(f"β Average anonymous request time: {avg_anonymous:.2f}s") | |
| print(f"β Average authenticated request time: {avg_authenticated:.2f}s") | |
| # Performance should be similar (user authentication shouldn't add significant overhead) | |
| time_difference = abs(avg_authenticated - avg_anonymous) | |
| assert time_difference < 2.0, f"Too much performance difference: {time_difference:.2f}s" | |
| except httpx.ConnectError: | |
| print("β οΈ Server not running - skipping performance comparison") | |
| return | |
| async def run_integration_tests(): | |
| """Run all integration tests""" | |
| print("π Running Chat Integration Tests with User Authentication") | |
| print("=" * 60) | |
| # Test request validation | |
| validation_test = TestChatRequestValidation() | |
| await validation_test.test_valid_user_id_formats() | |
| await validation_test.test_invalid_user_id_formats() | |
| await validation_test.test_empty_user_id_handling() | |
| await validation_test.test_missing_user_id_field() | |
| print("β Request validation tests completed") | |
| # Test request flow | |
| flow_test = TestChatRequestFlow() | |
| await flow_test.test_authenticated_user_session_flow() | |
| await flow_test.test_anonymous_user_session_flow() | |
| await flow_test.test_mixed_user_sessions() | |
| print("β Request flow tests completed") | |
| # Test performance | |
| perf_test = TestChatRequestPerformance() | |
| await perf_test.test_authenticated_request_performance() | |
| await perf_test.test_anonymous_vs_authenticated_performance() | |
| print("β Performance tests completed") | |
| print("\nπ ALL INTEGRATION TESTS COMPLETED!") | |
| if __name__ == "__main__": | |
| asyncio.run(run_integration_tests()) |