""" Deep behavioral tests for safe_error_message and flask_api helpers. Tests exception type mapping, context formatting, and field mapping patterns. """ from local_deep_research.news.flask_api import safe_error_message # --- Exception type mapping --- class TestSafeErrorMessageTypes: """Tests for exception type → message mapping.""" def test_value_error_returns_invalid_input(self): result = safe_error_message(ValueError("bad value"), "test") assert result == "Invalid input provided" def test_key_error_returns_required_data_missing(self): result = safe_error_message(KeyError("missing_key"), "test") assert result == "Required data missing" def test_type_error_returns_invalid_data_format(self): result = safe_error_message(TypeError("wrong type"), "test") assert result == "Invalid data format" def test_runtime_error_returns_generic(self): result = safe_error_message(RuntimeError("oops"), "test") assert "An error occurred" in result def test_exception_returns_generic(self): result = safe_error_message(Exception("generic"), "test") assert "An error occurred" in result def test_os_error_returns_generic(self): result = safe_error_message(OSError("disk full"), "test") assert "An error occurred" in result def test_attribute_error_returns_generic(self): result = safe_error_message(AttributeError("no attr"), "test") assert "An error occurred" in result def test_index_error_returns_generic(self): result = safe_error_message(IndexError("out of range"), "test") assert "An error occurred" in result # --- Context formatting --- class TestSafeErrorMessageContext: """Tests for context string formatting.""" def test_context_included_in_generic_message(self): result = safe_error_message(Exception("err"), "getting news feed") assert "getting news feed" in result def test_context_with_while(self): result = safe_error_message(Exception("err"), "loading data") assert "while loading data" in result def test_empty_context(self): result = safe_error_message(Exception("err"), "") assert "An error occurred" in result assert "while" not in result def test_no_context(self): result = safe_error_message(Exception("err")) assert "An error occurred" in result def test_context_not_in_value_error(self): """ValueError messages should not include context.""" result = safe_error_message(ValueError("err"), "loading data") assert result == "Invalid input provided" def test_context_not_in_key_error(self): """KeyError messages should not include context.""" result = safe_error_message(KeyError("err"), "loading data") assert result == "Required data missing" def test_context_not_in_type_error(self): """TypeError messages should not include context.""" result = safe_error_message(TypeError("err"), "loading data") assert result == "Invalid data format" # --- Security: no internal details leaked --- class TestSafeErrorMessageSecurity: """Tests that internal error details are not exposed.""" def test_value_error_hides_message(self): result = safe_error_message(ValueError("secret_db_password")) assert "secret_db_password" not in result def test_key_error_hides_message(self): result = safe_error_message(KeyError("internal_column_name")) assert "internal_column_name" not in result def test_type_error_hides_message(self): result = safe_error_message(TypeError("NoneType has no attribute foo")) assert "NoneType" not in result def test_generic_error_hides_stack_trace(self): result = safe_error_message( RuntimeError("Traceback: at line 42 in secret.py") ) assert "Traceback" not in result assert "secret.py" not in result def test_generic_error_hides_exception_message(self): result = safe_error_message( RuntimeError("Connection refused at 192.168.1.1:5432") ) assert "192.168.1.1" not in result assert "5432" not in result # --- Flask API field mapping --- class TestFlaskApiFieldMapping: """Tests for the field mapping dictionary used in update_subscription.""" def test_field_mapping_keys(self): """Verify the expected request fields are mapped.""" # This tests the pattern used at flask_api.py:433-446 field_mapping = { "query": "query_or_topic", "name": "name", "refresh_minutes": "refresh_interval_minutes", "is_active": "is_active", "folder_id": "folder_id", "model_provider": "model_provider", "model": "model", "search_strategy": "search_strategy", "custom_endpoint": "custom_endpoint", "search_engine": "search_engine", "search_iterations": "search_iterations", "questions_per_iteration": "questions_per_iteration", } assert "query" in field_mapping assert field_mapping["query"] == "query_or_topic" def test_field_mapping_refresh_minutes(self): field_mapping = { "refresh_minutes": "refresh_interval_minutes", } data = {"refresh_minutes": 30} update_data = {} for req_field, storage_field in field_mapping.items(): if req_field in data: update_data[storage_field] = data[req_field] assert update_data["refresh_interval_minutes"] == 30 def test_field_mapping_only_present_keys(self): field_mapping = { "query": "query_or_topic", "name": "name", "refresh_minutes": "refresh_interval_minutes", } data = {"name": "My Sub"} update_data = {} for req_field, storage_field in field_mapping.items(): if req_field in data: update_data[storage_field] = data[req_field] assert "name" in update_data assert "query_or_topic" not in update_data assert "refresh_interval_minutes" not in update_data def test_field_mapping_all_fields(self): field_mapping = { "query": "query_or_topic", "name": "name", "refresh_minutes": "refresh_interval_minutes", "is_active": "is_active", } data = { "query": "AI news", "name": "My AI Feed", "refresh_minutes": 60, "is_active": True, } update_data = {} for req_field, storage_field in field_mapping.items(): if req_field in data: update_data[storage_field] = data[req_field] assert update_data["query_or_topic"] == "AI news" assert update_data["name"] == "My AI Feed" assert update_data["refresh_interval_minutes"] == 60 assert update_data["is_active"] is True # --- Subscription ID validation pattern --- class TestSubscriptionIdValidation: """Tests for the subscription ID validation pattern from flask_api.""" def test_null_string_is_invalid(self): subscription_id = "null" assert ( subscription_id == "null" or subscription_id == "undefined" or not subscription_id ) def test_undefined_string_is_invalid(self): subscription_id = "undefined" is_invalid = ( subscription_id == "null" or subscription_id == "undefined" or not subscription_id ) assert is_invalid def test_empty_string_is_invalid(self): subscription_id = "" is_invalid = ( subscription_id == "null" or subscription_id == "undefined" or not subscription_id ) assert is_invalid def test_valid_uuid_is_valid(self): subscription_id = "abc-123-def-456" is_invalid = ( subscription_id == "null" or subscription_id == "undefined" or not subscription_id ) assert not is_invalid def test_numeric_id_is_valid(self): subscription_id = "12345" is_invalid = ( subscription_id == "null" or subscription_id == "undefined" or not subscription_id ) assert not is_invalid # --- Vote validation pattern --- class TestVoteValidation: """Tests for the vote validation pattern.""" def test_up_is_valid(self): vote = "up" assert vote in ["up", "down"] def test_down_is_valid(self): vote = "down" assert vote in ["up", "down"] def test_sideways_is_invalid(self): vote = "sideways" assert vote not in ["up", "down"] def test_empty_is_invalid(self): vote = "" assert vote not in ["up", "down"] def test_none_is_invalid(self): vote = None assert vote not in ["up", "down"] def test_uppercase_up_is_invalid(self): vote = "Up" assert vote not in ["up", "down"] def test_numeric_is_invalid(self): vote = 1 assert vote not in ["up", "down"]