Files
learningcircuit--local-deep…/tests/test_api_key_frontend_settings.py
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

358 lines
13 KiB
Python

"""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}'"
)