chore: import upstream snapshot with attribution
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
"""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}'"
|
||||
)
|
||||
Reference in New Issue
Block a user