Files
wehub-resource-sync 7a0da7932b
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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

284 lines
11 KiB
Python

"""Tests for Anthropic LLM provider."""
import pytest
from unittest.mock import Mock, patch
from local_deep_research.llm.providers.implementations.anthropic import (
AnthropicProvider,
)
class TestAnthropicProviderMetadata:
"""Tests for AnthropicProvider class metadata."""
def test_provider_name(self):
"""Provider name is correct."""
assert AnthropicProvider.provider_name == "Anthropic"
def test_provider_key(self):
"""Provider key is correct."""
assert AnthropicProvider.provider_key == "ANTHROPIC"
def test_is_cloud(self):
"""Anthropic is a cloud provider."""
assert AnthropicProvider.is_cloud is True
def test_company_name(self):
"""Company name is Anthropic."""
assert AnthropicProvider.company_name == "Anthropic"
def test_api_key_setting(self):
"""API key setting is correct."""
assert AnthropicProvider.api_key_setting == "llm.anthropic.api_key"
def test_default_model(self):
"""Default model is empty by design — users must explicitly pick one."""
assert AnthropicProvider.default_model == ""
def test_default_base_url(self):
"""Default base URL is correct."""
assert "anthropic.com" in AnthropicProvider.default_base_url
class TestAnthropicCreateLLM:
"""Tests for create_llm method."""
def test_create_llm_raises_without_api_key(self):
"""Raises ValueError when API key not configured."""
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.return_value = None
with pytest.raises(ValueError) as exc_info:
AnthropicProvider.create_llm()
assert "api key" in str(exc_info.value).lower()
def test_create_llm_with_valid_api_key(self):
"""Successfully creates ChatAnthropic instance with valid API key."""
def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
settings_map = {
"llm.anthropic.api_key": "test-anthropic-key",
"llm.max_tokens": None,
}
return settings_map.get(key, default)
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.side_effect = mock_get_setting_side_effect
with patch(
"local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
) as mock_chat:
mock_llm = Mock()
mock_chat.return_value = mock_llm
result = AnthropicProvider.create_llm(model_name="test-model")
assert result is mock_llm
mock_chat.assert_called_once()
def test_create_llm_uses_default_model_when_none(self):
"""Raises ValueError when no model name is provided (no silent default)."""
def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
settings_map = {
"llm.anthropic.api_key": "test-key",
"llm.max_tokens": None,
}
return settings_map.get(key, default)
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.side_effect = mock_get_setting_side_effect
with pytest.raises(ValueError, match="model not configured"):
AnthropicProvider.create_llm()
def test_create_llm_with_custom_model(self):
"""Uses custom model when specified."""
def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
settings_map = {
"llm.anthropic.api_key": "test-key",
"llm.max_tokens": None,
}
return settings_map.get(key, default)
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.side_effect = mock_get_setting_side_effect
with patch(
"local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
) as mock_chat:
AnthropicProvider.create_llm(
model_name="claude-3-opus-20240229"
)
call_kwargs = mock_chat.call_args[1]
assert call_kwargs["model"] == "claude-3-opus-20240229"
def test_create_llm_passes_temperature(self):
"""Passes temperature parameter."""
def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
settings_map = {
"llm.anthropic.api_key": "test-key",
"llm.max_tokens": None,
}
return settings_map.get(key, default)
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.side_effect = mock_get_setting_side_effect
with patch(
"local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
) as mock_chat:
AnthropicProvider.create_llm(
model_name="test-model", temperature=0.5
)
call_kwargs = mock_chat.call_args[1]
assert call_kwargs["temperature"] == 0.5
def test_create_llm_passes_max_tokens_when_set(self):
"""Passes max_tokens when configured in settings."""
def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
settings_map = {
"llm.anthropic.api_key": "test-key",
"llm.max_tokens": 4096,
}
return settings_map.get(key, default)
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.side_effect = mock_get_setting_side_effect
with patch(
"local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
) as mock_chat:
AnthropicProvider.create_llm(model_name="test-model")
call_kwargs = mock_chat.call_args[1]
assert call_kwargs["max_tokens"] == 4096
def test_create_llm_uses_anthropic_api_key_param(self):
"""Uses anthropic_api_key parameter name."""
def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
settings_map = {
"llm.anthropic.api_key": "my-anthropic-key",
"llm.max_tokens": None,
}
return settings_map.get(key, default)
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.side_effect = mock_get_setting_side_effect
with patch(
"local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
) as mock_chat:
AnthropicProvider.create_llm(model_name="test-model")
call_kwargs = mock_chat.call_args[1]
assert call_kwargs["anthropic_api_key"] == "my-anthropic-key"
class TestAnthropicIsAvailable:
"""Tests for is_available method."""
def test_is_available_true_when_key_exists(self):
"""Returns True when API key is configured."""
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.return_value = "test-key"
result = AnthropicProvider.is_available()
assert result is True
def test_is_available_false_when_no_key(self):
"""Returns False when API key is not configured."""
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.return_value = None
result = AnthropicProvider.is_available()
assert result is False
def test_is_available_false_when_empty_key(self):
"""Returns False when API key is empty string."""
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.return_value = ""
result = AnthropicProvider.is_available()
assert result is False
def test_is_available_false_on_exception(self):
"""Returns False when exception occurs."""
with patch(
"local_deep_research.config.thread_settings.get_setting_from_snapshot"
) as mock_get_setting:
mock_get_setting.side_effect = Exception("Settings error")
result = AnthropicProvider.is_available()
assert result is False
class TestAnthropicListModels:
"""The cloud provider lists models via the anthropic SDK, not the OpenAI
SDK it would otherwise inherit from OpenAICompatibleProvider (which sends
Bearer auth and 401s against the Anthropic API). This is the fix for the
discovered-provider loop clobbering the cloud Anthropic model list."""
def test_uses_anthropic_sdk_and_maps_models(self):
fake_model = Mock(
id="claude-3-5-sonnet", display_name="Claude 3.5 Sonnet"
)
fake_client = Mock()
fake_client.models.list.return_value = Mock(data=[fake_model])
with patch("anthropic.Anthropic", return_value=fake_client) as mk:
models = AnthropicProvider.list_models_for_api(api_key="sk-test")
assert models == [
{"value": "claude-3-5-sonnet", "label": "Claude 3.5 Sonnet"}
]
fake_client.models.list.assert_called_once()
# Cloud provider (url_setting is None) -> SDK uses its cloud default.
_, kwargs = mk.call_args
assert kwargs.get("base_url") is None
def test_cloud_no_base_url_does_not_short_circuit(self):
"""Unlike the custom-endpoint subclass, the cloud provider has
url_setting=None, so a missing base_url must NOT return [] — it must
query the cloud default."""
fake_client = Mock()
fake_client.models.list.return_value = Mock(data=[])
with patch("anthropic.Anthropic", return_value=fake_client) as mk:
AnthropicProvider.list_models_for_api(
api_key="sk-test", base_url=None
)
mk.assert_called_once()
def test_sdk_error_returns_empty(self):
fake_client = Mock()
fake_client.models.list.side_effect = RuntimeError("401 Unauthorized")
with patch("anthropic.Anthropic", return_value=fake_client):
assert (
AnthropicProvider.list_models_for_api(api_key="sk-test") == []
)