Files
learningcircuit--local-deep…/tests/llm_providers/test_auto_discovery_coverage.py
T
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

223 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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)