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
223 lines
7.8 KiB
Python
223 lines
7.8 KiB
Python
"""
|
||
Coverage tests for local_deep_research/llm/providers/auto_discovery.py
|
||
|
||
Focuses on paths not covered by tests/llm_providers/test_auto_discovery.py:
|
||
- ProviderInfo.__init__ with and without optional class attributes
|
||
- ProviderInfo._generate_display_name() for cloud=True, cloud=False
|
||
- ProviderInfo.to_dict() shape
|
||
- ProviderDiscovery singleton behaviour
|
||
- ProviderDiscovery.get_provider_info() triggers discover_providers if not done
|
||
- ProviderDiscovery.get_provider_class() returns None for unknown key
|
||
- ProviderDiscovery.get_provider_options() sorts by label
|
||
- get_available_discovered_provider_options() filters unavailable providers
|
||
- Module-level convenience functions delegate correctly
|
||
"""
|
||
|
||
from unittest.mock import MagicMock
|
||
|
||
|
||
from local_deep_research.llm.providers.auto_discovery import (
|
||
ProviderInfo,
|
||
ProviderDiscovery,
|
||
discover_providers,
|
||
get_discovered_provider_options,
|
||
get_available_discovered_provider_options,
|
||
get_provider_class,
|
||
)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Minimal provider class stubs
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def _make_provider_class(
|
||
provider_name="TestProv",
|
||
provider_key="TESTPROV",
|
||
company_name=None,
|
||
is_cloud=True,
|
||
requires_auth=True,
|
||
):
|
||
cls = MagicMock()
|
||
cls.__name__ = f"{provider_name}Provider"
|
||
cls.provider_name = provider_name
|
||
cls.provider_key = provider_key
|
||
if company_name is not None:
|
||
cls.company_name = company_name
|
||
else:
|
||
del cls.company_name # force AttributeError → getattr default
|
||
cls.is_cloud = is_cloud
|
||
cls.requires_auth_for_models = MagicMock(return_value=requires_auth)
|
||
cls.is_available = MagicMock(return_value=True)
|
||
return cls
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# ProviderInfo
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestProviderInfo:
|
||
def test_basic_attributes_set(self):
|
||
cls = _make_provider_class(
|
||
provider_name="Alpha", provider_key="ALPHA", is_cloud=True
|
||
)
|
||
info = ProviderInfo(cls)
|
||
assert info.provider_name == "Alpha"
|
||
assert info.provider_key == "ALPHA"
|
||
assert info.is_cloud is True
|
||
|
||
def test_display_name_cloud(self):
|
||
cls = _make_provider_class(is_cloud=True)
|
||
info = ProviderInfo(cls)
|
||
assert "Cloud" in info.display_name or "☁️" in info.display_name
|
||
|
||
def test_display_name_local(self):
|
||
cls = _make_provider_class(is_cloud=False)
|
||
info = ProviderInfo(cls)
|
||
assert "Local" in info.display_name or "💻" in info.display_name
|
||
|
||
def test_to_dict_shape(self):
|
||
cls = _make_provider_class(
|
||
provider_name="Beta", provider_key="BETA", is_cloud=True
|
||
)
|
||
info = ProviderInfo(cls)
|
||
d = info.to_dict()
|
||
assert "value" in d
|
||
assert "label" in d
|
||
assert "is_cloud" in d
|
||
assert d["value"] == "BETA"
|
||
|
||
def test_provider_key_falls_back_to_class_name(self):
|
||
"""When provider_key is not an attribute, falls back to class name."""
|
||
cls = MagicMock(spec=[])
|
||
cls.provider_name = "Gamma"
|
||
cls.__name__ = "GammaProvider"
|
||
cls.is_cloud = True
|
||
cls.requires_auth_for_models = MagicMock(return_value=True)
|
||
info = ProviderInfo(cls)
|
||
assert info.provider_key # must be some non-empty string
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# ProviderDiscovery singleton
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestProviderDiscoverySingleton:
|
||
def test_same_instance_returned(self):
|
||
d1 = ProviderDiscovery()
|
||
d2 = ProviderDiscovery()
|
||
assert d1 is d2
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# get_provider_info
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestGetProviderInfo:
|
||
def test_unknown_key_returns_none(self):
|
||
discovery = ProviderDiscovery()
|
||
# Force providers to be discovered first
|
||
discovery.discover_providers()
|
||
result = discovery.get_provider_info("NONEXISTENT_PROVIDER_XYZ")
|
||
assert result is None
|
||
|
||
def test_triggers_discover_if_not_done(self):
|
||
"""get_provider_info() discovers providers if not already done."""
|
||
discovery = ProviderDiscovery()
|
||
discovery._discovered = False
|
||
# Just verifying it runs without error
|
||
result = discovery.get_provider_info("NONEXISTENT_XYZ")
|
||
assert result is None # not found, but no crash
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# get_provider_class
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestGetProviderClass:
|
||
def test_unknown_key_returns_none(self):
|
||
result = get_provider_class("TOTALLY_NONEXISTENT_KEY_ZZZ")
|
||
assert result is None
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# get_provider_options sorting
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestGetProviderOptions:
|
||
def test_options_sorted_by_label(self):
|
||
discovery = ProviderDiscovery()
|
||
# Inject two fake providers with known labels
|
||
cls_z = _make_provider_class(provider_name="Zebra", provider_key="Z")
|
||
cls_a = _make_provider_class(provider_name="Apple", provider_key="A")
|
||
info_z = ProviderInfo(cls_z)
|
||
info_a = ProviderInfo(cls_a)
|
||
|
||
original = dict(discovery._providers)
|
||
discovery._providers = {"Z": info_z, "A": info_a}
|
||
discovery._discovered = True
|
||
try:
|
||
options = discovery.get_provider_options()
|
||
labels = [o["label"] for o in options]
|
||
assert labels == sorted(labels)
|
||
finally:
|
||
discovery._providers = original
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# get_available_provider_options – filters unavailable
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestGetAvailableProviderOptions:
|
||
def test_unavailable_providers_filtered_out(self):
|
||
discovery = ProviderDiscovery()
|
||
|
||
cls_ok = _make_provider_class(provider_name="OK", provider_key="OK")
|
||
cls_ok.is_available = MagicMock(return_value=True)
|
||
|
||
cls_bad = _make_provider_class(provider_name="BAD", provider_key="BAD")
|
||
cls_bad.is_available = MagicMock(return_value=False)
|
||
|
||
info_ok = ProviderInfo(cls_ok)
|
||
info_ok.provider_class = cls_ok
|
||
info_bad = ProviderInfo(cls_bad)
|
||
info_bad.provider_class = cls_bad
|
||
|
||
original = dict(discovery._providers)
|
||
original_disc = discovery._discovered
|
||
discovery._providers = {"OK": info_ok, "BAD": info_bad}
|
||
discovery._discovered = True
|
||
try:
|
||
options = discovery.get_available_provider_options()
|
||
values = [o["value"] for o in options]
|
||
assert "OK" in values
|
||
assert "BAD" not in values
|
||
finally:
|
||
discovery._providers = original
|
||
discovery._discovered = original_disc
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Module-level convenience functions
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestModuleLevelFunctions:
|
||
def test_discover_providers_returns_dict(self):
|
||
result = discover_providers()
|
||
assert isinstance(result, dict)
|
||
|
||
def test_get_discovered_provider_options_returns_list(self):
|
||
result = get_discovered_provider_options()
|
||
assert isinstance(result, list)
|
||
|
||
def test_get_available_discovered_provider_options_returns_list(self):
|
||
result = get_available_discovered_provider_options()
|
||
assert isinstance(result, list)
|