Files
hermes-agent/tests/hermes_cli/test_image_gen_picker.py
teknium1 4f1dcc2dad test: purge low-value tests, lane py11 (303 removed)
Change-detectors, tautologies, source-reading tests, redundant duplicates,
mock-echo tests and dead/unrunnable tests. Per-test rationale in the lane
ledger (category + reason for every removal).
2026-09-23 03:15:26 -07:00

204 lines
7.8 KiB
Python

"""Tests for plugin image_gen providers injecting themselves into the picker.
Covers `_plugin_image_gen_providers`, `_visible_providers`, and
`_toolset_needs_configuration_prompt` handling of plugin providers.
"""
from __future__ import annotations
from types import SimpleNamespace
import pytest
from agent import image_gen_registry
from agent.image_gen_provider import ImageGenProvider
class _FakeProvider(ImageGenProvider):
def __init__(self, name: str, available: bool = True, schema=None, models=None):
self._name = name
self._available = available
self._schema = schema or {
"name": name.title(),
"badge": "test",
"tag": f"{name} test tag",
"env_vars": [{"key": f"{name.upper()}_API_KEY", "prompt": f"{name} key"}],
}
self._models = models or [
{"id": f"{name}-model-v1", "display": f"{name} v1",
"speed": "~5s", "strengths": "test", "price": "$"},
]
@property
def name(self) -> str:
return self._name
def is_available(self) -> bool:
return self._available
def list_models(self):
return list(self._models)
def default_model(self):
return self._models[0]["id"] if self._models else None
def get_setup_schema(self):
return dict(self._schema)
def generate(self, prompt, aspect_ratio="landscape", **kw):
return {"success": True, "image": f"{self._name}://{prompt}"}
@pytest.fixture(autouse=True)
def _reset_registry():
image_gen_registry._reset_for_tests()
yield
image_gen_registry._reset_for_tests()
class TestPluginPickerInjection:
def test_visible_providers_includes_plugins_for_image_gen(self, monkeypatch):
from hermes_cli import tools_config
image_gen_registry.register_provider(_FakeProvider("someimg"))
cat = tools_config.TOOL_CATEGORIES["image_gen"]
visible = tools_config._visible_providers(cat, {})
plugin_names = [p.get("image_gen_plugin_name") for p in visible if p.get("image_gen_plugin_name")]
assert "someimg" in plugin_names
class TestPluginCatalog:
def test_plugin_catalog_returns_models(self):
from hermes_cli import tools_config
image_gen_registry.register_provider(_FakeProvider("catimg"))
catalog, default = tools_config._plugin_image_gen_catalog("catimg")
assert "catimg-model-v1" in catalog
assert default == "catimg-model-v1"
class TestConfigPrompt:
def test_image_gen_satisfied_by_plugin_provider(self, monkeypatch, tmp_path):
"""When a plugin provider reports is_available(), the picker should
not force a setup prompt on the user."""
from hermes_cli import tools_config
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.delenv("FAL_KEY", raising=False)
image_gen_registry.register_provider(_FakeProvider("avail-img", available=True))
assert tools_config._toolset_needs_configuration_prompt("image_gen", {}) is False
class TestConfigWriting:
def test_picking_plugin_provider_writes_provider_and_model(self, monkeypatch, tmp_path):
"""When a user picks a plugin-backed image_gen provider with no
env vars needed, ``_configure_provider`` should write both
``image_gen.provider`` and ``image_gen.model``."""
from hermes_cli import tools_config
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
image_gen_registry.register_provider(_FakeProvider("noenv", schema={
"name": "NoEnv",
"badge": "free",
"tag": "",
"env_vars": [],
}))
# Stub out the interactive model picker — no TTY in tests.
monkeypatch.setattr(tools_config, "_prompt_choice", lambda *a, **kw: 0)
config: dict = {}
provider_row = {
"name": "NoEnv",
"env_vars": [],
"image_gen_plugin_name": "noenv",
}
tools_config._configure_provider(provider_row, config)
assert config["image_gen"]["provider"] == "noenv"
assert config["image_gen"]["model"] == "noenv-model-v1"
def test_plugin_provider_active_overrides_managed_nous_active_label(self, monkeypatch):
from hermes_cli import tools_config
monkeypatch.setattr(
tools_config,
"get_nous_subscription_features",
lambda config, **kwargs: SimpleNamespace(
features={"image_gen": SimpleNamespace(managed_by_nous=True)}
),
)
config = {"image_gen": {"provider": "openai", "use_gateway": False}}
nous_row = {
"name": "Nous Subscription",
"managed_nous_feature": "image_gen",
}
openai_row = {
"name": "OpenAI",
"image_gen_plugin_name": "openai",
}
assert tools_config._is_provider_active(openai_row, config) is True
assert tools_config._is_provider_active(nous_row, config) is False
class TestCodexOAuthBootstrapHook:
"""#102144: the Image Generation 'OpenAI (Codex auth)' row is keyless, so its ``post_setup``
hook is the only thing that can sign the user in. Selecting it with no Codex credentials must
start the device-code flow and save tokens without hijacking ``model.provider``; with existing
credentials it must not re-prompt."""
@pytest.mark.parametrize("logged_in", [False, True])
def test_hook_starts_codex_oauth_only_when_credentials_missing(self, monkeypatch, logged_in):
from hermes_cli import auth, tools_config_post_setup
monkeypatch.setattr(auth, "get_codex_auth_status", lambda: {"logged_in": logged_in})
monkeypatch.setattr("hermes_cli.setup.prompt_choice", lambda *a, **kw: 0)
started, saved = [], []
monkeypatch.setattr(auth, "_codex_device_code_login",
lambda: started.append(1) or {"tokens": {"access_token": "t"}, "last_refresh": "x"})
monkeypatch.setattr(auth, "_save_codex_tokens", lambda tokens, last_refresh=None, **kw: saved.append(kw))
tools_config_post_setup._POST_SETUP_HOOKS["openai_codex"]()
assert len(started) == (0 if logged_in else 1)
# Side-tool sign-in must not make Codex the active inference provider.
assert saved == ([] if logged_in else [{"set_active": False}])
def test_hook_prints_auth_command_instead_of_device_login_when_noninteractive(self, monkeypatch, capsys):
"""Desktop's PostSetupRunner spawns `hermes tools post-setup openai_codex` with stdin=DEVNULL and
HERMES_NONINTERACTIVE=1: nobody can complete a device-code login there, so the hook must name
the real command and return instead of starting one."""
from hermes_cli import auth, tools_config_post_setup
monkeypatch.setenv("HERMES_NONINTERACTIVE", "1")
monkeypatch.setattr(auth, "get_codex_auth_status", lambda: {"logged_in": False})
monkeypatch.setattr("hermes_cli.setup.prompt_choice", lambda *a, **kw: 0)
monkeypatch.setattr(auth, "_codex_device_code_login",
lambda: pytest.fail("device-code login must not start without a human"))
tools_config_post_setup._POST_SETUP_HOOKS["openai_codex"]()
assert "hermes auth add openai-codex" in capsys.readouterr().out
def test_readiness_reports_codex_row_from_auth_store(self, monkeypatch):
from hermes_cli import auth, tools_config
row = {"name": "OpenAI (Codex auth)", "env_vars": [], "image_gen_plugin_name": "openai-codex",
"post_setup": "openai_codex"}
monkeypatch.setattr(auth, "get_codex_auth_status", lambda: {"logged_in": False})
assert tools_config.provider_readiness_status(row, {}) == "needs_auth"
monkeypatch.setattr(auth, "get_codex_auth_status", lambda: {"logged_in": True})
assert tools_config.provider_readiness_status(row, {}) == "ready"