"""Tests for validate_setting() edge cases. Covers: - validate_setting() min/max constraints, select edge cases, unknown ui_element (12 tests) """ from unittest.mock import patch from local_deep_research.web.routes.settings_routes import ( validate_setting, DYNAMIC_SETTINGS, ) from local_deep_research.web.models.settings import BaseSetting, SettingType # ── validate_setting() edge case tests ────────────────────────────── def _make_setting(ui_element, **kwargs): """Helper to build a BaseSetting with minimal boilerplate.""" defaults = dict( key="test.key", value=0, type=SettingType.APP, name="Test", ) defaults.update(kwargs) defaults["ui_element"] = ui_element return BaseSetting(**defaults) class TestValidateSettingMinMax: """Tests for number/slider/range min/max constraint validation.""" def test_number_below_min(self): """Number below min_value → (False, error).""" setting = _make_setting("number", min_value=0) valid, msg = validate_setting(setting, -1) assert valid is False assert "at least" in msg def test_number_above_max(self): """Number above max_value → (False, error).""" setting = _make_setting("number", max_value=100) valid, msg = validate_setting(setting, 150) assert valid is False assert "at most" in msg def test_number_no_constraints(self): """Number with no min/max → (True, None).""" setting = _make_setting("number") valid, msg = validate_setting(setting, 999) assert valid is True assert msg is None def test_number_only_min_passes(self): """Number with only min_value, value above → (True, None).""" setting = _make_setting("number", min_value=0) valid, msg = validate_setting(setting, 5) assert valid is True def test_number_only_max_passes(self): """Number with only max_value, value below → (True, None).""" setting = _make_setting("number", max_value=100) valid, msg = validate_setting(setting, 50) assert valid is True def test_slider_below_min(self): """Slider below min_value → (False, error). 'slider' is not in UI_ELEMENT_TO_SETTING_TYPE, so get_typed_setting_value returns default=None. We patch it to pass through the numeric value so we can test the min/max constraint logic in validate_setting. """ setting = _make_setting("slider", min_value=5) with patch( "local_deep_research.web.routes.settings_routes.get_typed_setting_value", return_value=2, ): valid, msg = validate_setting(setting, 2) assert valid is False assert "at least" in msg def test_range_above_max(self): """Range above max_value → (False, error).""" setting = _make_setting("range", max_value=10) valid, msg = validate_setting(setting, 15) assert valid is False assert "at most" in msg class TestValidateSettingSelect: """Tests for select ui_element edge cases.""" def test_select_invalid_option(self): """Select with value not in options → (False, error).""" setting = _make_setting( "select", options=[{"value": "a"}, {"value": "b"}], ) valid, msg = validate_setting(setting, "x") assert valid is False assert "must be one of" in msg def test_select_plain_string_options(self): """Select with plain string options list → valid option passes. The Pydantic model enforces List[Dict], but at runtime the validate_setting code handles both dict and non-dict options (line 199). We use a mock to bypass Pydantic validation. """ from unittest.mock import MagicMock setting = MagicMock() setting.key = "test.select" setting.ui_element = "select" setting.options = ["a", "b", "c"] # Patch get_typed_setting_value to pass through the value unchanged with patch( "local_deep_research.web.routes.settings_routes.get_typed_setting_value", return_value="b", ): valid, msg = validate_setting(setting, "b") assert valid is True def test_select_empty_options_skips_validation(self): """Select with empty options list → validation skipped → (True, None).""" setting = _make_setting("select", options=[]) valid, msg = validate_setting(setting, "anything") assert valid is True def test_select_dynamic_setting_skips_validation(self): """Select with key in DYNAMIC_SETTINGS → validation skipped.""" # Use a real DYNAMIC_SETTINGS key dynamic_key = DYNAMIC_SETTINGS[0] # "llm.provider" setting = _make_setting( "select", key=dynamic_key, options=[{"value": "a"}, {"value": "b"}], ) # Value not in options, but should pass because it's dynamic valid, msg = validate_setting(setting, "nonexistent_provider") assert valid is True class TestValidateSettingUnknownElement: """Test that unknown ui_element types pass through.""" def test_unknown_ui_element_passes(self): """Unknown ui_element like 'custom_widget' → (True, None).""" setting = _make_setting("custom_widget") valid, msg = validate_setting(setting, "any value") assert valid is True assert msg is None