"""Tests for pip entry-point provider discovery (hermes_agent.plugins group). Verifies that ``providers/__init__.py`` imports provider plugins exposed via a distribution's ``hermes_agent.plugins`` entry point, supporting both a ``module:func`` callable target and a bare self-registering ``module`` target. """ from __future__ import annotations import sys from pathlib import Path import pytest import providers REPO_ROOT = Path(__file__).resolve().parents[2] def _clear_provider_caches(): providers._REGISTRY.clear() providers._ALIASES.clear() providers._PROVIDER_LIST_CACHE = None providers._discovered = False for mod in list(sys.modules.keys()): if mod.startswith("plugins.model_providers") or mod.startswith( "_hermes_user_provider" ): del sys.modules[mod] @pytest.fixture(autouse=True) def _restore_real_discovery(): """Snapshot registry state; on teardown re-run REAL discovery. These tests monkeypatch ``importlib.metadata.entry_points`` and evict the ``plugins.model_providers`` submodules to force re-discovery. Without an explicit restore, the emptied registry / ``sys.modules`` would leak into later tests (e.g. ``from plugins.model_providers.custom import ...``). This fixture is autouse and declared before ``monkeypatch`` is requested, so it tears down LAST — after ``entry_points`` is restored to the real implementation — letting the final ``_discover_providers()`` repopulate both the registry and ``sys.modules`` from the real filesystem plugins. """ yield _clear_provider_caches() providers._discover_providers() class _FakeEP: def __init__(self, name, loader): self.name = name self.group = "hermes_agent.plugins" self._loader = loader def load(self): return self._loader() def _enable(monkeypatch, *names, disabled=()): """Gate helper: mark entry-point names enabled/disabled in config. ``_discover_entry_point_providers`` enforces the PluginManager's ``plugins.enabled`` opt-in allow-list, so tests must enable their fake entry points explicitly. """ import hermes_cli.plugins as hp monkeypatch.setattr(hp, "_get_enabled_plugins", lambda: set(names)) monkeypatch.setattr(hp, "_get_disabled_plugins", lambda: set(disabled)) class _FakeEntryPoints: def __init__(self, eps): self._eps = eps def select(self, group): return [e for e in self._eps if e.group == group] def _register_via_callable(): from providers.base import ProviderProfile def register(): providers.register_provider( ProviderProfile(name="ep-callable", aliases=("epc",), base_url="https://a.test/v1") ) return register # ep.load() returns the callable; discovery invokes it def _register_via_module(): # ep.load() returns a non-callable object; the import side effect already # registered the profile (mirrors a bare ``module`` target). from providers.base import ProviderProfile providers.register_provider( ProviderProfile(name="ep-module", base_url="https://b.test/v1") ) return object() # non-callable → discovery must NOT try to call it def test_entry_point_callable_and_module_targets(monkeypatch): fake_eps = _FakeEntryPoints( [ _FakeEP("ep-callable", _register_via_callable), _FakeEP("ep-module", _register_via_module), ] ) import importlib.metadata as md monkeypatch.setattr(md, "entry_points", lambda: fake_eps) _enable(monkeypatch, "ep-callable", "ep-module") _clear_provider_caches() try: assert providers.get_provider_profile("ep-callable") is not None assert providers.get_provider_profile("epc") is not None # alias assert providers.get_provider_profile("ep-module") is not None finally: _clear_provider_caches() def test_entry_point_not_enabled_is_skipped(monkeypatch): """Entry points honor the plugins.enabled opt-in gate — installed ≠ loaded.""" fake_eps = _FakeEntryPoints([_FakeEP("ep-callable", _register_via_callable)]) import importlib.metadata as md monkeypatch.setattr(md, "entry_points", lambda: fake_eps) _enable(monkeypatch, "some-other-plugin") # ep-callable NOT enabled _clear_provider_caches() try: assert providers.get_provider_profile("ep-callable") is None finally: _clear_provider_caches() def test_entry_point_disabled_wins_over_enabled(monkeypatch): """plugins.disabled is a deny-list that beats plugins.enabled.""" fake_eps = _FakeEntryPoints([_FakeEP("ep-callable", _register_via_callable)]) import importlib.metadata as md monkeypatch.setattr(md, "entry_points", lambda: fake_eps) _enable(monkeypatch, "ep-callable", disabled=("ep-callable",)) _clear_provider_caches() try: assert providers.get_provider_profile("ep-callable") is None finally: _clear_provider_caches() def test_general_plugin_register_ctx_not_invoked(monkeypatch): """A register(ctx)-style general plugin sharing the group is never called.""" calls = [] def _general_plugin_target(): def register(ctx): # requires an argument — PluginManager contract calls.append(ctx) return register fake_eps = _FakeEntryPoints([_FakeEP("general-plugin", _general_plugin_target)]) import importlib.metadata as md monkeypatch.setattr(md, "entry_points", lambda: fake_eps) _enable(monkeypatch, "general-plugin") _clear_provider_caches() try: providers._discover_providers() assert calls == [] # never invoked (would have been a TypeError anyway) finally: _clear_provider_caches() def test_entry_point_failure_is_isolated(monkeypatch): def _boom(): raise RuntimeError("broken plugin") fake_eps = _FakeEntryPoints( [ _FakeEP("broken", _boom), _FakeEP("ep-callable", _register_via_callable), ] ) import importlib.metadata as md monkeypatch.setattr(md, "entry_points", lambda: fake_eps) _enable(monkeypatch, "broken", "ep-callable") _clear_provider_caches() try: # A broken entry point must not prevent the good one from registering. assert providers.get_provider_profile("ep-callable") is not None finally: _clear_provider_caches() def test_filesystem_plugins_win_over_entry_points(monkeypatch): """Entry points are discovered FIRST (lowest precedence): last-writer-wins in register_provider() means a bundled/user profile of the same name overrides a pip impostor.""" from providers.base import ProviderProfile def _register_ep_openrouter(): def register(): providers.register_provider( ProviderProfile(name="openrouter", base_url="https://impostor.test/v1") ) return register fake_eps = _FakeEntryPoints([_FakeEP("openrouter", _register_ep_openrouter)]) import importlib.metadata as md monkeypatch.setattr(md, "entry_points", lambda: fake_eps) _enable(monkeypatch, "openrouter") # enabled, so precedence is what's tested _clear_provider_caches() try: p = providers.get_provider_profile("openrouter") assert p is not None # The bundled OpenRouter profile (real base_url) must win, not the impostor. assert "impostor.test" not in (p.base_url or "") finally: _clear_provider_caches()