Merge pull request 'fix(adapter,tests): isolate _load_providers tests from multi-path lookup' (#8) from fix/load-providers-tests-isolate-multipath into main
All checks were successful
Secret scan / Scan diff for credential-shaped strings (push) Successful in 3s
CI / Adapter unit tests (push) Successful in 54s
CI / validate (push) Successful in 3m6s

This commit is contained in:
claude-ceo-assistant 2026-05-08 20:28:14 +00:00
commit 56a045f38e
2 changed files with 48 additions and 10 deletions

View File

@ -154,6 +154,12 @@ def _normalize_provider(entry: dict):
# against the site-packages copy that bit us 2026-05-04 11:08Z.
_CANONICAL_ADAPTER_DIR = "/opt/adapter"
# Adjacent-to-adapter.py path. Module-level so tests can monkeypatch it
# to redirect the path-2 lookup at a controlled tmp dir. Production code
# resolves this once at import time and never touches it again — same
# semantics as before.
_TEMPLATE_DIR = os.path.dirname(os.path.abspath(__file__))
def _load_providers(config_path: str) -> tuple:
"""Load the provider registry from the template's bundled config.yaml.
@ -207,9 +213,7 @@ def _load_providers(config_path: str) -> tuple:
a warning; the rest of the registry survives.
"""
canonical_yaml = os.path.join(_CANONICAL_ADAPTER_DIR, "config.yaml")
template_yaml = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "config.yaml"
)
template_yaml = os.path.join(_TEMPLATE_DIR, "config.yaml")
workspace_yaml = os.path.join(config_path, "config.yaml")
# Deduplicate while preserving order — _CANONICAL_ADAPTER_DIR and
# the __file__ dir collide in dev/test (when imported from

View File

@ -514,8 +514,15 @@ async def test_setup_auth_token_alone_satisfies_third_party_check(
# ---- _load_providers / _resolve_provider unit tests ----
def test_load_providers_returns_builtin_when_yaml_missing(tmp_path):
"""FileNotFoundError path returns the in-code defaults verbatim."""
def test_load_providers_returns_builtin_when_yaml_missing(tmp_path, monkeypatch):
"""FileNotFoundError path returns the in-code defaults verbatim.
Monkeypatches the canonical + template paths to a non-existent dir
so only the workspace config_path is in scope. Without this, the
multi-path lookup picks up the repo-root config.yaml that ships
with the template (path 2 finds the bundled providers list and
returns it instead of falling through to builtins).
"""
_install_stubs()
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if parent_dir not in sys.path:
@ -523,6 +530,10 @@ def test_load_providers_returns_builtin_when_yaml_missing(tmp_path):
sys.modules.pop("adapter", None)
import adapter as adapter_module
nonexistent = str(tmp_path / "_isolate_canonical")
monkeypatch.setattr(adapter_module, "_CANONICAL_ADAPTER_DIR", nonexistent)
monkeypatch.setattr(adapter_module, "_TEMPLATE_DIR", nonexistent)
result = adapter_module._load_providers(str(tmp_path))
assert result == adapter_module._BUILTIN_PROVIDERS
@ -576,8 +587,12 @@ async def test_setup_routes_extra_providers(
assert os.environ.get("ANTHROPIC_BASE_URL") == expected_url
def test_load_providers_falls_back_on_malformed_yaml(tmp_path, caplog):
"""Malformed YAML → log warning + fallback (don't kill boot)."""
def test_load_providers_falls_back_on_malformed_yaml(tmp_path, caplog, monkeypatch):
"""Malformed YAML → log warning + fallback (don't kill boot).
Isolated from the multi-path lookup by pinning canonical + template
dirs at a non-existent path; only the workspace config_path is read.
"""
_install_stubs()
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if parent_dir not in sys.path:
@ -585,6 +600,10 @@ def test_load_providers_falls_back_on_malformed_yaml(tmp_path, caplog):
sys.modules.pop("adapter", None)
import adapter as adapter_module
nonexistent = str(tmp_path / "_isolate_canonical")
monkeypatch.setattr(adapter_module, "_CANONICAL_ADAPTER_DIR", nonexistent)
monkeypatch.setattr(adapter_module, "_TEMPLATE_DIR", nonexistent)
(tmp_path / "config.yaml").write_text("providers: [not valid yaml: {{{")
import logging
@ -622,7 +641,7 @@ def test_resolve_provider_minimax_prefix_matches_minimax_provider():
assert result2["name"] == "minimax"
def test_load_providers_drops_bad_entry_keeps_rest(tmp_path, caplog):
def test_load_providers_drops_bad_entry_keeps_rest(tmp_path, caplog, monkeypatch):
"""Per-entry isolation: one malformed entry shouldn't nuke the registry.
Pre-fix: ``_load_providers`` built the registry via a generator inside
@ -634,6 +653,9 @@ def test_load_providers_drops_bad_entry_keeps_rest(tmp_path, caplog):
Post-fix: per-entry try/except drops the bad entry with a warning,
rest of the registry survives.
Isolated from the multi-path lookup so only the test's tmp config.yaml
is read.
"""
_install_stubs()
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -642,6 +664,10 @@ def test_load_providers_drops_bad_entry_keeps_rest(tmp_path, caplog):
sys.modules.pop("adapter", None)
import adapter as adapter_module
nonexistent = str(tmp_path / "_isolate_canonical")
monkeypatch.setattr(adapter_module, "_CANONICAL_ADAPTER_DIR", nonexistent)
monkeypatch.setattr(adapter_module, "_TEMPLATE_DIR", nonexistent)
yaml_with_typo = textwrap.dedent("""
providers:
- name: good-zai
@ -690,7 +716,7 @@ def test_load_providers_drops_bad_entry_keeps_rest(tmp_path, caplog):
)
def test_load_providers_string_as_prefix_does_not_split_into_chars(tmp_path, caplog):
def test_load_providers_string_as_prefix_does_not_split_into_chars(tmp_path, caplog, monkeypatch):
"""A YAML field declared as list-of-strings but written as a bare
string (operator forgot brackets) used to silently iterate over
characters ``('m','i','m','o','-')``. Post-fix: non-list value
@ -705,6 +731,10 @@ def test_load_providers_string_as_prefix_does_not_split_into_chars(tmp_path, cap
sys.modules.pop("adapter", None)
import adapter as adapter_module
nonexistent = str(tmp_path / "_isolate_canonical")
monkeypatch.setattr(adapter_module, "_CANONICAL_ADAPTER_DIR", nonexistent)
monkeypatch.setattr(adapter_module, "_TEMPLATE_DIR", nonexistent)
yaml_str_prefix = textwrap.dedent("""
providers:
- name: typo-prefix
@ -723,7 +753,7 @@ def test_load_providers_string_as_prefix_does_not_split_into_chars(tmp_path, cap
)
def test_load_providers_drops_entry_without_name(tmp_path, caplog):
def test_load_providers_drops_entry_without_name(tmp_path, caplog, monkeypatch):
"""An entry without ``name`` is operator error — no silent fallback
to ``<unnamed>``. Drop the entry with a warning so the boot log
surfaces the typo.
@ -735,6 +765,10 @@ def test_load_providers_drops_entry_without_name(tmp_path, caplog):
sys.modules.pop("adapter", None)
import adapter as adapter_module
nonexistent = str(tmp_path / "_isolate_canonical")
monkeypatch.setattr(adapter_module, "_CANONICAL_ADAPTER_DIR", nonexistent)
monkeypatch.setattr(adapter_module, "_TEMPLATE_DIR", nonexistent)
yaml_no_name = textwrap.dedent("""
providers:
- name: good