"""Tests for API key frontend settings functionality. These tests verify that the API key settings used by the research form frontend are correctly configured and can be saved/retrieved via the settings API. """ # Provider to API key setting mapping (must match research.js) API_KEY_SETTINGS = { "OPENAI": "llm.openai.api_key", "ANTHROPIC": "llm.anthropic.api_key", "GOOGLE": "llm.google.api_key", "OPENROUTER": "llm.openrouter.api_key", "XAI": "llm.xai.api_key", "IONOS": "llm.ionos.api_key", "OPENAI_ENDPOINT": "llm.openai_endpoint.api_key", "OLLAMA": "llm.ollama.api_key", } # Providers that require API keys (cloud providers) CLOUD_PROVIDERS = [ "OPENAI", "ANTHROPIC", "GOOGLE", "OPENROUTER", "XAI", "IONOS", ] # Providers with optional API keys (key is read from settings but missing # is OK; falls back to a placeholder ChatOpenAI accepts). OPTIONAL_API_KEY_PROVIDERS = [ "OLLAMA", "OPENAI_ENDPOINT", "LMSTUDIO", "LLAMACPP", ] # Providers that have no API key concept at all (api_key_setting=None). # Currently empty after the LLM construction collapse — every built-in # provider declares a real api_key_setting now. LOCAL_PROVIDERS_NO_KEY: list[str] = [] class TestAPIKeyProviderMapping: """Test that provider implementations define correct API key settings.""" def test_openai_provider_api_key_setting(self): """Test OpenAI provider uses correct API key setting.""" from local_deep_research.llm.providers.implementations.openai import ( OpenAIProvider, ) assert OpenAIProvider.api_key_setting == "llm.openai.api_key" def test_anthropic_provider_api_key_setting(self): """Test Anthropic provider uses correct API key setting.""" from local_deep_research.llm.providers.implementations.anthropic import ( AnthropicProvider, ) assert AnthropicProvider.api_key_setting == "llm.anthropic.api_key" def test_google_provider_api_key_setting(self): """Test Google provider uses correct API key setting.""" from local_deep_research.llm.providers.implementations.google import ( GoogleProvider, ) assert GoogleProvider.api_key_setting == "llm.google.api_key" def test_openrouter_provider_api_key_setting(self): """Test OpenRouter provider uses correct API key setting.""" from local_deep_research.llm.providers.implementations.openrouter import ( OpenRouterProvider, ) assert OpenRouterProvider.api_key_setting == "llm.openrouter.api_key" def test_xai_provider_api_key_setting(self): """Test xAI provider uses correct API key setting.""" from local_deep_research.llm.providers.implementations.xai import ( XAIProvider, ) assert XAIProvider.api_key_setting == "llm.xai.api_key" def test_ionos_provider_api_key_setting(self): """Test IONOS provider uses correct API key setting.""" from local_deep_research.llm.providers.implementations.ionos import ( IONOSProvider, ) assert IONOSProvider.api_key_setting == "llm.ionos.api_key" def test_openai_endpoint_provider_api_key_setting(self): """Test OpenAI Endpoint provider uses correct API key setting.""" from local_deep_research.llm.providers.implementations.custom_openai_endpoint import ( CustomOpenAIEndpointProvider, ) assert ( CustomOpenAIEndpointProvider.api_key_setting == "llm.openai_endpoint.api_key" ) def test_ollama_provider_api_key_setting(self): """Test Ollama provider uses correct API key setting (optional).""" from local_deep_research.llm.providers.implementations.ollama import ( OllamaProvider, ) assert OllamaProvider.api_key_setting == "llm.ollama.api_key" def test_lmstudio_provider_optional_api_key(self): """LM Studio declares its API key setting as optional. Older versions of LDR set ``api_key_setting = None`` on LMStudio to signal "no auth needed". Now we model it as a real setting + the ``api_key_optional`` flag so ``has_api_key``/``is_available`` semantics are uniform across providers. """ from local_deep_research.llm.providers.implementations.lmstudio import ( LMStudioProvider, ) assert LMStudioProvider.api_key_setting == "llm.lmstudio.api_key" assert LMStudioProvider.api_key_optional is True def test_llamacpp_provider_optional_api_key(self): """llama.cpp declares its API key setting as optional (proxy auth).""" from local_deep_research.llm.providers.implementations.llamacpp import ( LlamaCppProvider, ) assert LlamaCppProvider.api_key_setting == "llm.llamacpp.api_key" assert LlamaCppProvider.api_key_optional is True class TestAPIKeySettingsInMemory: """Test API key settings with InMemorySettingsManager.""" def test_set_and_get_api_key(self): """Test setting and retrieving API key values.""" from local_deep_research.api.settings_utils import ( InMemorySettingsManager, ) manager = InMemorySettingsManager() # Set an API key test_key = "sk-test-12345" result = manager.set_setting("llm.openai.api_key", test_key) assert result is True # Retrieve the API key retrieved = manager.get_setting("llm.openai.api_key") assert retrieved == test_key def test_api_key_defaults_to_empty(self): """Test that API keys default to empty string.""" from local_deep_research.api.settings_utils import ( InMemorySettingsManager, ) manager = InMemorySettingsManager() for provider, setting_key in API_KEY_SETTINGS.items(): value = manager.get_setting(setting_key) # API keys should default to empty string or None assert value in ("", None), ( f"API key '{setting_key}' should default to empty, got '{value}'" ) def test_multiple_api_keys_independent(self): """Test that setting one API key doesn't affect others.""" from local_deep_research.api.settings_utils import ( InMemorySettingsManager, ) manager = InMemorySettingsManager() # Set OpenAI key manager.set_setting("llm.openai.api_key", "openai-key-123") # Set Anthropic key manager.set_setting("llm.anthropic.api_key", "anthropic-key-456") # Verify both are independent assert manager.get_setting("llm.openai.api_key") == "openai-key-123" assert ( manager.get_setting("llm.anthropic.api_key") == "anthropic-key-456" ) class TestAPIKeySettingsSnapshot: """Test API key settings in settings snapshots.""" def test_core_api_keys_in_snapshot(self): """Test that core API keys are included in settings snapshot.""" from local_deep_research.api.settings_utils import ( get_default_settings_snapshot, ) snapshot = get_default_settings_snapshot() # Core API keys that should always be in snapshot core_api_keys = [ "llm.openai.api_key", "llm.anthropic.api_key", "llm.openai_endpoint.api_key", ] for setting_key in core_api_keys: assert setting_key in snapshot, ( f"API key '{setting_key}' not found in settings snapshot" ) def test_create_snapshot_with_api_key(self): """Test creating snapshot with API key value.""" from local_deep_research.api.settings_utils import ( create_settings_snapshot, ) snapshot = create_settings_snapshot( provider="openai", api_key="sk-test-snapshot-key" ) # Check that the API key was set assert snapshot["llm.openai.api_key"]["value"] == "sk-test-snapshot-key" def test_snapshot_api_key_metadata(self): """Test that API key settings have proper metadata in snapshot.""" from local_deep_research.api.settings_utils import ( get_default_settings_snapshot, ) snapshot = get_default_settings_snapshot() # Check metadata for core API keys that are in the snapshot core_api_keys = [ "llm.openai.api_key", "llm.anthropic.api_key", "llm.openai_endpoint.api_key", ] for setting_key in core_api_keys: if setting_key in snapshot: setting = snapshot[setting_key] # Check required metadata fields assert "value" in setting, f"{setting_key} missing 'value'" assert "editable" in setting, ( f"{setting_key} missing 'editable'" ) # API keys should be editable assert setting["editable"] is True, ( f"{setting_key} should be editable" ) class TestAPIKeyEnvironmentOverride: """Test that API keys can be overridden by environment variables.""" def test_openai_api_key_env_override(self, monkeypatch): """Test OpenAI API key can be set via environment variable.""" from local_deep_research.api.settings_utils import ( InMemorySettingsManager, ) test_key = "sk-env-override-key" monkeypatch.setenv("LDR_LLM_OPENAI_API_KEY", test_key) manager = InMemorySettingsManager() retrieved = manager.get_setting("llm.openai.api_key") assert retrieved == test_key def test_anthropic_api_key_env_override(self, monkeypatch): """Test Anthropic API key can be set via environment variable.""" from local_deep_research.api.settings_utils import ( InMemorySettingsManager, ) test_key = "sk-ant-env-override" monkeypatch.setenv("LDR_LLM_ANTHROPIC_API_KEY", test_key) manager = InMemorySettingsManager() retrieved = manager.get_setting("llm.anthropic.api_key") assert retrieved == test_key def test_openai_endpoint_api_key_env_override(self, monkeypatch): """Test OpenAI Endpoint API key can be set via environment variable.""" from local_deep_research.api.settings_utils import ( InMemorySettingsManager, ) test_key = "sk-endpoint-env-override" monkeypatch.setenv("LDR_LLM_OPENAI_ENDPOINT_API_KEY", test_key) manager = InMemorySettingsManager() retrieved = manager.get_setting("llm.openai_endpoint.api_key") assert retrieved == test_key class TestFrontendAPIKeyMapping: """Test that frontend API key mapping matches backend.""" def test_frontend_mapping_matches_providers(self): """Verify the frontend mapping in this test matches actual providers.""" from local_deep_research.llm.providers.implementations.openai import ( OpenAIProvider, ) from local_deep_research.llm.providers.implementations.anthropic import ( AnthropicProvider, ) from local_deep_research.llm.providers.implementations.google import ( GoogleProvider, ) from local_deep_research.llm.providers.implementations.openrouter import ( OpenRouterProvider, ) from local_deep_research.llm.providers.implementations.xai import ( XAIProvider, ) from local_deep_research.llm.providers.implementations.ionos import ( IONOSProvider, ) from local_deep_research.llm.providers.implementations.custom_openai_endpoint import ( CustomOpenAIEndpointProvider, ) from local_deep_research.llm.providers.implementations.ollama import ( OllamaProvider, ) # Map provider classes to their expected settings provider_classes = { "OPENAI": OpenAIProvider, "ANTHROPIC": AnthropicProvider, "GOOGLE": GoogleProvider, "OPENROUTER": OpenRouterProvider, "XAI": XAIProvider, "IONOS": IONOSProvider, "OPENAI_ENDPOINT": CustomOpenAIEndpointProvider, "OLLAMA": OllamaProvider, } for provider_name, expected_setting in API_KEY_SETTINGS.items(): provider_class = provider_classes.get(provider_name) if provider_class: actual_setting = provider_class.api_key_setting assert actual_setting == expected_setting, ( f"Frontend mapping for {provider_name} is '{expected_setting}' " f"but provider class uses '{actual_setting}'" )