""" Behavioral tests for security/security_settings module. Tests type conversion, bounds validation, and security default loading. """ import os from unittest.mock import patch import pytest class TestConvertValue: """Tests for _convert_value function.""" def test_converts_bool_true_values(self): """Converts 'true', '1', 'yes', 'on' to True.""" from local_deep_research.security.security_settings import ( _convert_value, ) for val in ("true", "1", "yes", "on"): assert _convert_value(val, bool, "test") is True def test_converts_bool_false_values(self): """Converts 'false', '0', 'no', 'off' to False.""" from local_deep_research.security.security_settings import ( _convert_value, ) for val in ("false", "0", "no", "off"): assert _convert_value(val, bool, "test") is False def test_bool_is_case_insensitive(self): """Bool conversion is case-insensitive.""" from local_deep_research.security.security_settings import ( _convert_value, ) assert _convert_value("TRUE", bool, "test") is True assert _convert_value("True", bool, "test") is True assert _convert_value("Yes", bool, "test") is True def test_converts_int(self): """Converts string to int.""" from local_deep_research.security.security_settings import ( _convert_value, ) assert _convert_value("42", int, "test") == 42 def test_converts_negative_int(self): """Converts negative string to int.""" from local_deep_research.security.security_settings import ( _convert_value, ) assert _convert_value("-5", int, "test") == -5 def test_converts_float(self): """Converts string to float.""" from local_deep_research.security.security_settings import ( _convert_value, ) assert _convert_value("3.14", float, "test") == pytest.approx(3.14) def test_converts_str(self): """Converts to string (identity).""" from local_deep_research.security.security_settings import ( _convert_value, ) assert _convert_value("hello", str, "test") == "hello" def test_returns_none_for_invalid_int(self): """Returns None for non-numeric int conversion.""" from local_deep_research.security.security_settings import ( _convert_value, ) assert _convert_value("abc", int, "test") is None def test_returns_none_for_invalid_float(self): """Returns None for non-numeric float conversion.""" from local_deep_research.security.security_settings import ( _convert_value, ) assert _convert_value("not_a_number", float, "test") is None class TestValidateBounds: """Tests for _validate_bounds function.""" def test_value_within_bounds_unchanged(self): """Value within bounds is unchanged.""" from local_deep_research.security.security_settings import ( _validate_bounds, ) assert _validate_bounds(50, 0, 100, "test") == 50 def test_clamps_below_minimum(self): """Value below minimum is clamped to minimum.""" from local_deep_research.security.security_settings import ( _validate_bounds, ) assert _validate_bounds(-5, 0, 100, "test") == 0 def test_clamps_above_maximum(self): """Value above maximum is clamped to maximum.""" from local_deep_research.security.security_settings import ( _validate_bounds, ) assert _validate_bounds(150, 0, 100, "test") == 100 def test_none_min_allows_any_low_value(self): """None min_value allows any low value.""" from local_deep_research.security.security_settings import ( _validate_bounds, ) assert _validate_bounds(-1000, None, 100, "test") == -1000 def test_none_max_allows_any_high_value(self): """None max_value allows any high value.""" from local_deep_research.security.security_settings import ( _validate_bounds, ) assert _validate_bounds(99999, 0, None, "test") == 99999 def test_both_none_returns_value_unchanged(self): """Both None bounds returns value unchanged.""" from local_deep_research.security.security_settings import ( _validate_bounds, ) assert _validate_bounds(42, None, None, "test") == 42 def test_exact_minimum_accepted(self): """Value equal to minimum is accepted.""" from local_deep_research.security.security_settings import ( _validate_bounds, ) assert _validate_bounds(0, 0, 100, "test") == 0 def test_exact_maximum_accepted(self): """Value equal to maximum is accepted.""" from local_deep_research.security.security_settings import ( _validate_bounds, ) assert _validate_bounds(100, 0, 100, "test") == 100 def test_works_with_floats(self): """Works with float values and bounds.""" from local_deep_research.security.security_settings import ( _validate_bounds, ) assert _validate_bounds(0.5, 0.0, 1.0, "test") == pytest.approx(0.5) assert _validate_bounds(-0.1, 0.0, 1.0, "test") == pytest.approx(0.0) assert _validate_bounds(1.5, 0.0, 1.0, "test") == pytest.approx(1.0) class TestGetSecurityDefault: """Tests for get_security_default function.""" def test_returns_default_when_no_env_or_json(self): """Returns default value when no env var or JSON setting.""" from local_deep_research.security.security_settings import ( get_security_default, _load_security_settings, ) # Clear the cache to ensure fresh load _load_security_settings.cache_clear() # Use a key that won't exist result = get_security_default("nonexistent.setting.key_xyz_12345", 42) assert result == 42 def test_env_var_overrides_default(self): """Environment variable overrides default value.""" from local_deep_research.security.security_settings import ( get_security_default, _load_security_settings, ) _load_security_settings.cache_clear() env_key = "LDR_TEST_ENV_OVERRIDE_ABC" with patch.dict(os.environ, {env_key: "99"}): result = get_security_default("test.env_override_abc", 42) assert result == 99 def test_env_var_key_format(self): """Environment variable uses LDR_ prefix with dots replaced by underscores.""" from local_deep_research.security.security_settings import ( get_security_default, _load_security_settings, ) _load_security_settings.cache_clear() # Key "security.session_days" -> env var "LDR_SECURITY_SESSION_DAYS" with patch.dict(os.environ, {"LDR_MY_CUSTOM_KEY": "hello"}): result = get_security_default("my.custom_key", "default") assert result == "hello" def test_bool_default_returns_bool(self): """Bool default causes bool return type.""" from local_deep_research.security.security_settings import ( get_security_default, _load_security_settings, ) _load_security_settings.cache_clear() result = get_security_default("nonexistent.bool_setting_xyz", False) assert result is False def test_env_var_bool_conversion(self): """Environment variable with bool default converts to bool.""" from local_deep_research.security.security_settings import ( get_security_default, _load_security_settings, ) _load_security_settings.cache_clear() with patch.dict(os.environ, {"LDR_TEST_BOOL_ENV_XYZ": "true"}): result = get_security_default("test.bool_env_xyz", False) assert result is True def test_invalid_env_var_falls_through(self): """Invalid env var value falls through to default.""" from local_deep_research.security.security_settings import ( get_security_default, _load_security_settings, ) _load_security_settings.cache_clear() with patch.dict(os.environ, {"LDR_TEST_BADINT_XYZ": "not_a_number"}): result = get_security_default("test.badint_xyz", 10) assert result == 10 class TestLoadSecuritySettings: """Tests for _load_security_settings function.""" def test_returns_dict(self): """Returns a dictionary.""" from local_deep_research.security.security_settings import ( _load_security_settings, ) _load_security_settings.cache_clear() result = _load_security_settings() assert isinstance(result, dict) def test_is_cached(self): """Subsequent calls return cached result.""" from local_deep_research.security.security_settings import ( _load_security_settings, ) _load_security_settings.cache_clear() result1 = _load_security_settings() result2 = _load_security_settings() assert result1 is result2 def test_returns_empty_dict_for_missing_file(self): """Returns empty dict if settings file doesn't exist.""" from local_deep_research.security.security_settings import ( _load_security_settings, _SETTINGS_PATH, ) _load_security_settings.cache_clear() with patch.object(type(_SETTINGS_PATH), "exists", return_value=False): _load_security_settings.cache_clear() result = _load_security_settings() assert result == {}