""" Behavioral tests for web/models/settings module. Tests the custom key prefix validators — the only real logic in this module. Each setting subclass (LLMSetting, SearchSetting, ReportSetting, AppSetting) has a @field_validator that auto-prefixes keys missing their category prefix. """ import pytest class TestLLMSettingKeyValidator: """Tests for LLMSetting.validate_llm_key auto-prefix logic.""" def test_adds_prefix_when_missing(self): from local_deep_research.web.models.settings import LLMSetting setting = LLMSetting(key="model", value="gpt-4", name="Model") assert setting.key == "llm.model" def test_preserves_existing_prefix(self): from local_deep_research.web.models.settings import LLMSetting setting = LLMSetting(key="llm.temperature", value=0.7, name="Temp") assert setting.key == "llm.temperature" def test_no_double_prefix(self): """Applying the validator twice should not produce 'llm.llm.key'.""" from local_deep_research.web.models.settings import LLMSetting setting = LLMSetting(key="llm.model", value="gpt-4", name="Model") assert setting.key == "llm.model" assert not setting.key.startswith("llm.llm.") def test_prefix_substring_still_gets_prefixed(self): """Key starting with 'llm' but not 'llm.' should get prefixed. 'llm_model' starts with 'llm' but not 'llm.' — the validator checks startswith('llm.') specifically, so it should be prefixed. """ from local_deep_research.web.models.settings import LLMSetting setting = LLMSetting(key="llm_model", value="gpt-4", name="Model") assert setting.key == "llm.llm_model" def test_dotted_key_preserved(self): """Nested dotted keys after prefix are preserved.""" from local_deep_research.web.models.settings import LLMSetting setting = LLMSetting( key="llm.openai.api_key", value="sk-xxx", name="API Key" ) assert setting.key == "llm.openai.api_key" class TestSearchSettingKeyValidator: """Tests for SearchSetting.validate_search_key auto-prefix logic.""" def test_adds_prefix_when_missing(self): from local_deep_research.web.models.settings import SearchSetting setting = SearchSetting(key="engine", value="brave", name="Engine") assert setting.key == "search.engine" def test_preserves_existing_prefix(self): from local_deep_research.web.models.settings import SearchSetting setting = SearchSetting(key="search.max_results", value=10, name="Max") assert setting.key == "search.max_results" def test_prefix_substring_still_gets_prefixed(self): """'search_mode' starts with 'search' but not 'search.'.""" from local_deep_research.web.models.settings import SearchSetting setting = SearchSetting(key="search_mode", value="all", name="Mode") assert setting.key == "search.search_mode" class TestReportSettingKeyValidator: """Tests for ReportSetting.validate_report_key auto-prefix logic.""" def test_adds_prefix_when_missing(self): from local_deep_research.web.models.settings import ReportSetting setting = ReportSetting(key="format", value="markdown", name="Format") assert setting.key == "report.format" def test_preserves_existing_prefix(self): from local_deep_research.web.models.settings import ReportSetting setting = ReportSetting( key="report.format", value="markdown", name="Format" ) assert setting.key == "report.format" class TestAppSettingKeyValidator: """Tests for AppSetting.validate_app_key auto-prefix logic.""" def test_adds_prefix_when_missing(self): from local_deep_research.web.models.settings import AppSetting setting = AppSetting(key="theme", value="dark", name="Theme") assert setting.key == "app.theme" def test_preserves_existing_prefix(self): from local_deep_research.web.models.settings import AppSetting setting = AppSetting(key="app.theme", value="dark", name="Theme") assert setting.key == "app.theme" def test_prefix_substring_still_gets_prefixed(self): """'application_mode' starts with 'app' but not 'app.'.""" from local_deep_research.web.models.settings import AppSetting setting = AppSetting(key="application_mode", value="dev", name="Mode") assert setting.key == "app.application_mode" class TestAllValidatorsConsistentBehavior: """Cross-cutting tests verifying all 4 validators behave the same way.""" def test_all_setting_types_auto_prefix(self): """Each setting subclass auto-prefixes its category.""" from local_deep_research.web.models.settings import ( AppSetting, LLMSetting, ReportSetting, SearchSetting, ) cases = [ (LLMSetting, "model", "llm.model"), (SearchSetting, "engine", "search.engine"), (ReportSetting, "format", "report.format"), (AppSetting, "theme", "app.theme"), ] for cls, raw_key, expected_key in cases: setting = cls(key=raw_key, value="v", name="n") assert setting.key == expected_key, ( f"{cls.__name__} failed: {raw_key} -> {setting.key}" ) def test_all_setting_types_preserve_prefix(self): """Each setting subclass preserves already-prefixed keys.""" from local_deep_research.web.models.settings import ( AppSetting, LLMSetting, ReportSetting, SearchSetting, ) cases = [ (LLMSetting, "llm.model"), (SearchSetting, "search.engine"), (ReportSetting, "report.format"), (AppSetting, "app.theme"), ] for cls, prefixed_key in cases: setting = cls(key=prefixed_key, value="v", name="n") assert setting.key == prefixed_key def test_missing_required_fields_raises(self): """BaseSetting requires key, value, type, and name.""" from local_deep_research.web.models.settings import BaseSetting with pytest.raises(Exception): BaseSetting(key="test.key", value="v") # missing type and name