# allow: no-sut-import — black-box HTTP test; drives real routes through the Flask test client """ Tests for chat error handling and edge cases. Tests error responses, edge cases, and recovery scenarios. """ import json class TestErrorResponses: """Tests for API error response formats.""" def test_404_response_format(self, authenticated_client): """Test that 404 responses have consistent format.""" response = authenticated_client.get( "/api/chat/sessions/nonexistent-session-12345" ) assert response.status_code == 404 data = json.loads(response.data) assert data["success"] is False assert "error" in data def test_400_response_format(self, authenticated_client): """Test that 400 responses have consistent format.""" # Create session first create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Try invalid status update response = authenticated_client.patch( f"/api/chat/sessions/{session_id}", json={"status": "invalid_status"}, content_type="application/json", ) assert response.status_code == 400 data = json.loads(response.data) assert data["success"] is False assert "error" in data def test_error_messages_are_descriptive(self, authenticated_client): """Test that error messages provide useful information.""" # Create session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Try to send empty message response = authenticated_client.post( f"/api/chat/sessions/{session_id}/messages", json={"content": "", "trigger_research": False}, content_type="application/json", ) assert response.status_code == 400 data = json.loads(response.data) # Error message should mention the issue assert len(data["error"]) > 5 # Not just empty or cryptic class TestRobustness: """Tests for API robustness.""" def test_rapid_session_creation(self, authenticated_client): """Test creating many sessions rapidly.""" session_ids = [] for i in range(10): response = authenticated_client.post( "/api/chat/sessions", json={"initial_query": f"Rapid test {i}"}, content_type="application/json", ) assert response.status_code == 200 data = json.loads(response.data) session_ids.append(data["session_id"]) # All should be unique assert len(session_ids) == len(set(session_ids)) # All should be retrievable for sid in session_ids: get_resp = authenticated_client.get(f"/api/chat/sessions/{sid}") assert get_resp.status_code == 200 def test_rapid_message_creation(self, authenticated_client): """Test sending many messages rapidly (some may be rate-limited).""" # Create session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Send 20 messages rapidly - some may be rate-limited (429) success_count = 0 for i in range(20): response = authenticated_client.post( f"/api/chat/sessions/{session_id}/messages", json={ "content": f"Rapid message {i}", "trigger_research": False, }, content_type="application/json", ) assert response.status_code in (200, 429) if response.status_code == 200: success_count += 1 # At least some messages should succeed, and rate limiting should kick in assert success_count >= 1 # Successfully sent messages should be retrievable messages_resp = authenticated_client.get( f"/api/chat/sessions/{session_id}/messages?limit=100" ) data = json.loads(messages_resp.data) assert len(data["messages"]) == success_count def test_large_message_content(self, authenticated_client): """Test handling of large (but valid) message content.""" # Create session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Send exactly at limit (10000 chars) large_content = "A" * 10000 response = authenticated_client.post( f"/api/chat/sessions/{session_id}/messages", json={"content": large_content, "trigger_research": False}, content_type="application/json", ) assert response.status_code == 200 # Verify content is stored correctly messages_resp = authenticated_client.get( f"/api/chat/sessions/{session_id}/messages" ) data = json.loads(messages_resp.data) assert data["messages"][-1]["content"] == large_content class TestSessionStateTransitions: """Tests for session state transition edge cases.""" def test_multiple_status_changes(self, authenticated_client): """Test multiple sequential status changes.""" # Create session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Change to archived authenticated_client.patch( f"/api/chat/sessions/{session_id}", json={"status": "archived"}, content_type="application/json", ) # Verify final state is archived get_resp = authenticated_client.get(f"/api/chat/sessions/{session_id}") data = json.loads(get_resp.data) assert data["session"]["status"] == "archived" def test_delete_already_deleted_session(self, authenticated_client): """Test deleting an already deleted session returns 404.""" # Create and delete session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Delete once (hard delete) authenticated_client.delete(f"/api/chat/sessions/{session_id}") # Delete again - should return 404 (session no longer exists) response = authenticated_client.delete( f"/api/chat/sessions/{session_id}" ) assert response.status_code == 404 class TestMessageEdgeCases: """Tests for message edge cases.""" def test_get_messages_empty_response(self, authenticated_client): """Test getting messages when none exist.""" # Create session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Get messages (should be empty) response = authenticated_client.get( f"/api/chat/sessions/{session_id}/messages" ) assert response.status_code == 200 data = json.loads(response.data) assert data["success"] is True assert data["messages"] == [] def test_get_messages_with_large_offset(self, authenticated_client): """Test getting messages with offset beyond count.""" # Create session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Send a few messages for i in range(3): authenticated_client.post( f"/api/chat/sessions/{session_id}/messages", json={"content": f"Message {i}", "trigger_research": False}, content_type="application/json", ) # Get with offset beyond message count response = authenticated_client.get( f"/api/chat/sessions/{session_id}/messages?offset=100" ) assert response.status_code == 200 data = json.loads(response.data) assert data["messages"] == [] def test_message_with_null_bytes(self, authenticated_client): """Test message handling with null bytes.""" # Create session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Send message with null byte (may be rejected) response = authenticated_client.post( f"/api/chat/sessions/{session_id}/messages", json={ "content": "Message\x00with\x00nulls", "trigger_research": False, }, content_type="application/json", ) # Should handle gracefully - accept or reject without crashing. # 500 here would be an unhandled-exception regression. assert response.status_code in [200, 400] class TestListSessions: """Tests for session listing edge cases.""" def test_list_sessions_returns_list(self, authenticated_client): """Test that listing sessions returns a list.""" response = authenticated_client.get("/api/chat/sessions") assert response.status_code == 200 data = json.loads(response.data) assert data["success"] is True assert isinstance(data["sessions"], list) def test_list_with_zero_limit(self, authenticated_client): """Test listing with limit=0.""" response = authenticated_client.get("/api/chat/sessions?limit=0") assert response.status_code == 200 data = json.loads(response.data) # Should return empty or use default limit assert isinstance(data["sessions"], list) def test_list_ordering(self, authenticated_client): """Test that sessions are listed in order.""" # Create several sessions. Assertion is inclusion-only (not strict # ordering), so no sleep between creates is needed. session_ids = [] for i in range(3): create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": f"Order test {i}"}, content_type="application/json", ) session_ids.append(json.loads(create_resp.data)["session_id"]) # List sessions response = authenticated_client.get("/api/chat/sessions?limit=10") data = json.loads(response.data) # Recent sessions should be at the top (most recently updated first) listed_ids = [s["id"] for s in data["sessions"]] # At least our sessions should be in the list for sid in session_ids: assert sid in listed_ids class TestSessionRetrieval: """Tests for session retrieval edge cases.""" def test_get_session_includes_all_fields(self, authenticated_client): """Test that session retrieval includes all expected fields.""" # Create session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Field test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Get session response = authenticated_client.get(f"/api/chat/sessions/{session_id}") assert response.status_code == 200 data = json.loads(response.data) session = data["session"] expected_fields = [ "id", "title", "status", "created_at", "message_count", ] for field in expected_fields: assert field in session, f"Missing field: {field}" def test_get_session_with_messages(self, authenticated_client): """Test getting a session that has messages.""" # Create session create_resp = authenticated_client.post( "/api/chat/sessions", json={"initial_query": "Test"}, content_type="application/json", ) session_id = json.loads(create_resp.data)["session_id"] # Add some messages for i in range(5): authenticated_client.post( f"/api/chat/sessions/{session_id}/messages", json={"content": f"Message {i}", "trigger_research": False}, content_type="application/json", ) # Get session response = authenticated_client.get(f"/api/chat/sessions/{session_id}") data = json.loads(response.data) # Message count should be updated assert data["session"]["message_count"] == 5