findEthics/Atlas
0
1"""2Unit tests for user authentication functionality3 4Consolidated from:5- test_user_authentication_comprehensive.py (unit test portions)6- test_user_id_validation.py7- Parts of test_chat_integration_user_auth.py (validation portions)8"""9 10import asyncio11 12from app import normalize_user_id, validate_user_id13import pytest14 15from analytics.collectors import create_session, track_message, track_search16from analytics.models import Message, SearchAnalytics, Session17from tests.utilities import MockDataGenerator, TestHelpers, ValidationHelpers, empty_user_ids, invalid_user_ids, valid_user_ids18 19 TestHelpers, ValidationHelpers, MockDataGenerator,20 valid_user_ids, invalid_user_ids, empty_user_ids21)22 23 24class TestUserIdValidation:25 """Test user_id validation functions and model validation"""26 27 def test_normalize_user_id_function(self):28 """Test normalize_user_id function handles various inputs correctly"""29 # Test None input30 assert normalize_user_id(None) is None31 32 # Test empty string33 assert normalize_user_id("") is None34 35 # Test whitespace-only strings36 assert normalize_user_id(" ") is None37 assert normalize_user_id("\t") is None38 assert normalize_user_id("\n") is None39 assert normalize_user_id(" \t\n ") is None40 41 # Test valid user_id42 assert normalize_user_id("user123") == "user123"43 assert normalize_user_id(" user123 ") == "user123"44 45 def test_validate_user_id_function(self):46 """Test validate_user_id function properly handles all cases"""47 # Test None input (anonymous)48 assert validate_user_id(None) is None49 50 # Test empty string (anonymous)51 assert validate_user_id("") is None52 assert validate_user_id(" ") is None53 54 # Test valid user_id55 assert validate_user_id("user123") == "user123"56 assert validate_user_id("user_123") == "user_123"57 assert validate_user_id("user-123") == "user-123"58 59 # Test invalid user_id raises exception60 with pytest.raises(Exception):61 validate_user_id("user@123")62 63 with pytest.raises(Exception):64 validate_user_id("a" * 256) # Too long65 66 def test_validate_user_id_with_fixtures(self, valid_user_ids, invalid_user_ids, empty_user_ids):67 """Test validate_user_id with fixture data"""68 # Test valid user IDs69 for user_id in valid_user_ids:70 result = validate_user_id(user_id)71 assert result == user_id.strip()72 73 # Test invalid user IDs74 for user_id in invalid_user_ids:75 with pytest.raises(Exception):76 validate_user_id(user_id)77 78 # Test empty user IDs (should return None)79 for user_id in empty_user_ids:80 result = validate_user_id(user_id)81 assert result is None82 83 84class TestSessionModelValidation:85 """Test Session model user_id validation"""86 87 def test_session_valid_user_id(self):88 """Test Session model with valid user_id values"""89 # Valid user_id90 session = Session(user_id="user_123")91 assert session.user_id == "user_123"92 93 # Valid user_id with hyphens and underscores94 session = Session(user_id="user-123_test")95 assert session.user_id == "user-123_test"96 97 # None user_id (anonymous)98 session = Session(user_id=None)99 assert session.user_id is None100 101 # Empty string becomes None102 session = Session(user_id="")103 assert session.user_id is None104 105 # Whitespace-only string becomes None106 session = Session(user_id=" ")107 assert session.user_id is None108 109 def test_session_invalid_user_id(self):110 """Test Session model with invalid user_id values"""111 # Non-string user_id112 with pytest.raises(Exception) as exc_info:113 Session(user_id=123)114 assert "string" in str(exc_info.value).lower()115 116 # Too long user_id117 with pytest.raises(Exception) as exc_info:118 Session(user_id="a" * 256)119 assert "255" in str(exc_info.value)120 121 # Invalid characters122 invalid_ids = ["user@123", "user 123", "user.123"]123 for invalid_id in invalid_ids:124 with pytest.raises(Exception) as exc_info:125 Session(user_id=invalid_id)126 assert "alphanumeric" in str(exc_info.value).lower()127 128 def test_session_to_dict_includes_user_id(self):129 """Test that Session.to_dict() includes user_id field"""130 # Session with user_id131 session = Session(user_id="user_123")132 session_dict = session.to_dict()133 assert "user_id" in session_dict134 assert session_dict["user_id"] == "user_123"135 136 # Anonymous session137 session = Session(user_id=None)138 session_dict = session.to_dict()139 assert "user_id" in session_dict140 assert session_dict["user_id"] is None141 142 143class TestMessageModelValidation:144 """Test Message model user_id validation"""145 146 def test_message_valid_user_id(self):147 """Test Message model with valid user_id values"""148 # Valid user_id149 message = Message(150 session_id="test_session",151 prompt_length=50,152 response_length=100,153 response_time_ms=1000,154 user_id="user_123"155 )156 assert message.user_id == "user_123"157 158 # None user_id (anonymous)159 message = Message(160 session_id="test_session",161 prompt_length=50,162 response_length=100,163 response_time_ms=1000,164 user_id=None165 )166 assert message.user_id is None167 168 def test_message_invalid_user_id(self):169 """Test Message model with invalid user_id values"""170 # Non-string user_id171 with pytest.raises(Exception) as exc_info:172 Message(173 session_id="test_session",174 prompt_length=50,175 response_length=100,176 response_time_ms=1000,177 user_id=123178 )179 assert "string" in str(exc_info.value).lower()180 181 # Invalid characters182 with pytest.raises(Exception):183 Message(184 session_id="test_session",185 prompt_length=50,186 response_length=100,187 response_time_ms=1000,188 user_id="user@123"189 )190 191 def test_message_to_dict_includes_user_id(self):192 """Test that Message.to_dict() includes user_id field"""193 message = Message(194 session_id="test_session",195 prompt_length=50,196 response_length=100,197 response_time_ms=1000,198 user_id="user_123"199 )200 message_dict = message.to_dict()201 assert "user_id" in message_dict202 assert message_dict["user_id"] == "user_123"203 204 205class TestSearchAnalyticsModelValidation:206 """Test SearchAnalytics model user_id validation"""207 208 def test_search_analytics_valid_user_id(self):209 """Test SearchAnalytics model with valid user_id values"""210 # Valid user_id211 search = SearchAnalytics(212 message_id="test_message",213 search_query="test query",214 user_id="user_123"215 )216 assert search.user_id == "user_123"217 218 # None user_id (anonymous)219 search = SearchAnalytics(220 message_id="test_message",221 search_query="test query",222 user_id=None223 )224 assert search.user_id is None225 226 def test_search_analytics_invalid_user_id(self):227 """Test SearchAnalytics model with invalid user_id values"""228 # Non-string user_id229 with pytest.raises(Exception) as exc_info:230 SearchAnalytics(231 message_id="test_message",232 search_query="test query",233 user_id=123234 )235 assert "string" in str(exc_info.value).lower()236 237 # Invalid characters238 with pytest.raises(Exception):239 SearchAnalytics(240 message_id="test_message",241 search_query="test query",242 user_id="user@123"243 )244 245 def test_search_analytics_to_dict_includes_user_id(self):246 """Test that SearchAnalytics.to_dict() includes user_id field"""247 search = SearchAnalytics(248 message_id="test_message",249 search_query="test query",250 user_id="user_123"251 )252 search_dict = search.to_dict()253 assert "user_id" in search_dict254 assert search_dict["user_id"] == "user_123"255 256 257class TestAnalyticsCollectors:258 """Test analytics collectors with user_id support"""259 260 @pytest.mark.asyncio261 async def test_create_session_with_user_id(self):262 """Test create_session function with user_id"""263 # Create session with user_id264 session = await create_session(user_agent="TestAgent", user_id="user_123")265 assert session.user_id == "user_123"266 assert session.user_agent == "TestAgent"267 assert session.session_id is not None268 269 # Create anonymous session270 session = await create_session(user_agent="TestAgent", user_id=None)271 assert session.user_id is None272 273 # Create session without user_id parameter274 session = await create_session(user_agent="TestAgent")275 assert session.user_id is None276 277 @pytest.mark.asyncio278 async def test_track_message_with_user_id(self):279 """Test track_message function with user_id"""280 # Create a session first281 session = await create_session(user_id="user_123")282 283 # Track message with user_id284 message = await track_message(285 session_id=session.session_id,286 prompt_length=50,287 response_length=100,288 response_time_ms=1000,289 user_id="user_123"290 )291 292 assert message is not None293 assert message.user_id == "user_123"294 assert message.session_id == session.session_id295 assert message.prompt_length == 50296 assert message.response_length == 100297 assert message.response_time_ms == 1000298 299 @pytest.mark.asyncio300 async def test_track_message_user_id_mismatch(self):301 """Test that user_id mismatch between session and message is handled"""302 # Create a session with one user_id303 session = await create_session(user_id="user_123")304 305 # Track message with different user_id306 message = await track_message(307 session_id=session.session_id,308 prompt_length=50,309 response_length=100,310 response_time_ms=1000,311 user_id="user_456" # Different user_id312 )313 314 assert message is not None315 assert message.user_id == "user_456" # Message should use provided user_id316 assert message.session_id == session.session_id317 318 @pytest.mark.asyncio319 async def test_track_search_with_user_id(self):320 """Test track_search function with user_id"""321 # Create session and message first322 session = await create_session(user_id="user_123")323 message = await track_message(324 session_id=session.session_id,325 prompt_length=50,326 response_length=100,327 response_time_ms=1000,328 user_id="user_123"329 )330 331 # Track search with user_id332 search = await track_search(333 message_id=message.message_id,334 search_query="test query",335 search_terms=["test", "query"],336 brave_results=5,337 duckduckgo_results=3,338 total_unique_results=7,339 user_id="user_123"340 )341 342 assert search is not None343 assert search.user_id == "user_123"344 assert search.message_id == message.message_id345 assert search.search_query == "test query"346 347 @pytest.mark.asyncio348 async def test_track_search_user_id_mismatch(self):349 """Test track_search with user_id mismatch"""350 # Create session and message with one user_id351 session = await create_session(user_id="user_123")352 message = await track_message(353 session_id=session.session_id,354 prompt_length=50,355 response_length=100,356 response_time_ms=1000,357 user_id="user_123"358 )359 360 # Track search with different user_id361 search = await track_search(362 message_id=message.message_id,363 search_query="test query",364 search_terms=["test", "query"],365 user_id="user_456" # Different user_id366 )367 368 assert search is not None369 assert search.user_id == "user_456" # Should use provided user_id370 assert search.message_id == message.message_id371 372 373class TestBackwardCompatibility:374 """Test backward compatibility for anonymous users"""375 376 @pytest.mark.asyncio377 async def test_create_session_without_user_id_parameter(self):378 """Test creating sessions without user_id parameter (old way)"""379 # Create session the old way (no user_id parameter)380 session = await create_session(user_agent="TestAgent")381 382 assert session.user_id is None383 assert session.user_agent == "TestAgent"384 assert session.session_id is not None385 assert session.status == "active"386 387 @pytest.mark.asyncio388 async def test_track_message_without_user_id_parameter(self):389 """Test tracking messages without user_id parameter (old way)"""390 # Create session first391 session = await create_session(user_agent="TestAgent")392 393 # Track message the old way (no user_id parameter)394 message = await track_message(395 session_id=session.session_id,396 prompt_length=50,397 response_length=100,398 response_time_ms=1000,399 used_search=True,400 max_tokens=500,401 temperature=0.7,402 success=True403 )404 405 assert message is not None406 assert message.user_id is None407 assert message.session_id == session.session_id408 assert message.prompt_length == 50409 assert message.used_search is True410 411 @pytest.mark.asyncio412 async def test_track_search_without_user_id_parameter(self):413 """Test tracking search without user_id parameter (old way)"""414 # Create session and message first415 session = await create_session()416 message = await track_message(417 session_id=session.session_id,418 prompt_length=50,419 response_length=100,420 response_time_ms=1000421 )422 423 # Track search the old way (no user_id parameter)424 search = await track_search(425 message_id=message.message_id,426 search_query="test query",427 search_terms=["test", "query"],428 brave_results=5,429 duckduckgo_results=3,430 total_unique_results=7431 )432 433 assert search is not None434 assert search.user_id is None435 assert search.message_id == message.message_id436 assert search.search_query == "test query"437 438 439class TestAuthenticationEdgeCases:440 """Test edge cases and error conditions"""441 442 def test_user_id_length_limits(self):443 """Test user_id length validation"""444 # Test maximum valid length445 max_valid = "a" * 255446 session = Session(user_id=max_valid)447 assert session.user_id == max_valid448 449 # Test too long450 too_long = "a" * 256451 with pytest.raises(Exception) as exc_info:452 Session(user_id=too_long)453 assert "255" in str(exc_info.value)454 455 def test_user_id_character_validation(self):456 """Test user_id character validation"""457 # Valid characters458 valid_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"459 session = Session(user_id=valid_chars)460 assert session.user_id == valid_chars461 462 # Invalid characters463 invalid_chars = ["@", " ", ".", "#", "$", "%", "&", "*", "+", "=", "[", "]", "{", "}", "|", "\\", "/", ":", ";", "<", ">", "?", ",", "'", '"', "`", "~", "!"]464 465 for char in invalid_chars:466 user_id_with_invalid = f"user{char}123"467 with pytest.raises(Exception):468 Session(user_id=user_id_with_invalid)469 470 def test_user_id_type_validation(self):471 """Test user_id type validation"""472 # Valid types473 Session(user_id="string_user_id")474 Session(user_id=None)475 476 # Invalid types477 invalid_types = [123, 45.67, True, [], {}, set()]478 479 for invalid_type in invalid_types:480 with pytest.raises(Exception):481 Session(user_id=invalid_type)482 483 @pytest.mark.asyncio484 async def test_collectors_with_invalid_user_id(self):485 """Test that collectors handle invalid user_id gracefully"""486 # Test create_session with invalid user_id487 with pytest.raises(Exception):488 await create_session(user_id="invalid@user")489 490 # Test track_message with invalid user_id491 session = await create_session(user_id="valid_user")492 with pytest.raises(Exception):493 await track_message(494 session_id=session.session_id,495 prompt_length=50,496 response_length=100,497 response_time_ms=1000,498 user_id="invalid@user"499 )500 501 502class TestAuthenticationDataConsistency:503 """Test data consistency across authentication features"""504 505 @pytest.mark.asyncio506 async def test_session_message_user_id_consistency(self):507 """Test that session and message user_id values are consistent"""508 user_id = "consistency_test_user"509 510 # Create session511 session = await create_session(user_id=user_id)512 assert session.user_id == user_id513 514 # Create message with same user_id515 message = await track_message(516 session_id=session.session_id,517 prompt_length=50,518 response_length=100,519 response_time_ms=1000,520 user_id=user_id521 )522 assert message.user_id == user_id523 assert message.session_id == session.session_id524 525 # Verify data persistence526 await TestHelpers.wait_for_data_persistence()527 528 # Verify session data529 session_valid = await TestHelpers.verify_session_data(530 session.session_id, user_id, expected_messages=1531 )532 assert session_valid533 534 # Verify message data535 message_valid = await TestHelpers.verify_message_data(536 message.message_id, user_id537 )538 assert message_valid539 540 @pytest.mark.asyncio541 async def test_anonymous_data_consistency(self):542 """Test that anonymous data is consistently handled"""543 # Create anonymous session544 session = await create_session(user_id=None)545 assert session.user_id is None546 547 # Create anonymous message548 message = await track_message(549 session_id=session.session_id,550 prompt_length=50,551 response_length=100,552 response_time_ms=1000,553 user_id=None554 )555 assert message.user_id is None556 assert message.session_id == session.session_id557 558 # Verify data persistence559 await TestHelpers.wait_for_data_persistence()560 561 # Verify session data562 session_valid = await TestHelpers.verify_session_data(563 session.session_id, None, expected_messages=1564 )565 assert session_valid566 567 # Verify message data568 message_valid = await TestHelpers.verify_message_data(569 message.message_id, None570 )571 assert message_valid572 573 574if __name__ == "__main__":575 # Run tests manually for debugging576 async def run_basic_tests():577 test_validation = TestUserIdValidation()578 test_validation.test_normalize_user_id_function()579 test_validation.test_validate_user_id_function()580 print("✅ User ID validation tests passed")581 582 test_session = TestSessionModelValidation()583 test_session.test_session_valid_user_id()584 test_session.test_session_to_dict_includes_user_id()585 print("✅ Session model validation tests passed")586 587 test_collectors = TestAnalyticsCollectors()588 await test_collectors.test_create_session_with_user_id()589 await test_collectors.test_track_message_with_user_id()590 print("✅ Analytics collectors tests passed")591 592 asyncio.run(run_basic_tests())