ba030bc0db
Tests did monkeypatch.setattr(<facade module>, name) where name is now defined in a sibling module and the production path reads the sibling's binding. Where production reads through BOTH bindings the setattr is duplicated onto the defining module (import added next to the existing alias import); where only the sibling reads it the target is repointed. Seams whose production readers go through the facade are left alone.
141 lines
6.0 KiB
Python
141 lines
6.0 KiB
Python
"""Regression tests for image_gen provider persistence (managed FAL clobber).
|
|
|
|
Historical bug: ``_select_plugin_image_gen_provider`` hardcoded the direct
|
|
(non-managed) routing. When a user picked FAL through the Nous-subscription
|
|
managed flow, the managed write landed first — then the image selector ran
|
|
and clobbered it back to direct, silently routing every generation through
|
|
the user's personal FAL_KEY instead of the Nous Tool Gateway (real incident:
|
|
personal key drained to zero while the subscription sat unused).
|
|
|
|
Current contract (strict provider-string selection): each picker row writes
|
|
exactly ONE provider string per category — ``image_gen.provider: nous`` for
|
|
the managed "Nous Subscription" row, ``image_gen.provider: fal`` for the
|
|
BYOK FAL row — and any legacy ``use_gateway`` key is popped so the
|
|
read-time shim (use_gateway: true ⇒ nous) cannot override the fresh pick.
|
|
The video twin (``_select_plugin_video_gen_provider``) shares the contract.
|
|
"""
|
|
|
|
from hermes_cli.tools_config import (
|
|
_select_plugin_image_gen_provider,
|
|
_select_plugin_video_gen_provider,
|
|
_write_provider_config,
|
|
)
|
|
|
|
|
|
def _quiet(monkeypatch):
|
|
import hermes_cli.tools_config_providers as tc
|
|
|
|
monkeypatch.setattr(tc, "_print_success", lambda *a, **k: None)
|
|
monkeypatch.setattr(tc, "_print_info", lambda *a, **k: None, raising=False)
|
|
monkeypatch.setattr(tc, "_configure_imagegen_model_for_plugin", lambda *a, **k: None)
|
|
monkeypatch.setattr(tc, "_configure_videogen_model_for_plugin", lambda *a, **k: None)
|
|
|
|
|
|
def test_image_gen_selector_preserves_managed_selection(monkeypatch):
|
|
"""Managed pick: the 'nous' provider string must survive the selector."""
|
|
_quiet(monkeypatch)
|
|
config = {}
|
|
|
|
# The managed flow first persists the managed selection...
|
|
_write_provider_config(
|
|
{"image_gen_plugin_name": "fal"}, config, managed_feature="image_gen"
|
|
)
|
|
assert config["image_gen"]["provider"] == "nous"
|
|
assert "use_gateway" not in config["image_gen"]
|
|
|
|
# ...then the selector runs; the managed kwarg must NOT clobber it
|
|
# back onto the vendor name (direct-key routing).
|
|
_select_plugin_image_gen_provider("fal", config, use_gateway=True)
|
|
assert config["image_gen"]["provider"] == "nous"
|
|
assert "use_gateway" not in config["image_gen"]
|
|
|
|
|
|
def test_image_gen_selector_direct_key_pick_writes_vendor(monkeypatch):
|
|
"""Non-managed pick writes the vendor name and pops the legacy flag."""
|
|
_quiet(monkeypatch)
|
|
config = {"image_gen": {"use_gateway": True}}
|
|
|
|
_select_plugin_image_gen_provider("fal", config)
|
|
assert config["image_gen"]["provider"] == "fal"
|
|
assert "use_gateway" not in config["image_gen"]
|
|
|
|
|
|
def test_image_and_video_selectors_share_the_selection_contract(monkeypatch):
|
|
"""The two selectors are twins: same kwarg, same persistence behavior."""
|
|
_quiet(monkeypatch)
|
|
|
|
for use_gateway, expected in ((True, "nous"), (False, "fal")):
|
|
config = {
|
|
"image_gen": {"use_gateway": not use_gateway},
|
|
"video_gen": {"use_gateway": not use_gateway},
|
|
}
|
|
_select_plugin_image_gen_provider("fal", config, use_gateway=use_gateway)
|
|
_select_plugin_video_gen_provider("fal", config, use_gateway=use_gateway)
|
|
assert config["image_gen"]["provider"] == expected
|
|
assert config["video_gen"]["provider"] == expected
|
|
assert "use_gateway" not in config["image_gen"]
|
|
assert "use_gateway" not in config["video_gen"]
|
|
|
|
|
|
def _quiet_reconfigure(monkeypatch):
|
|
"""Silence prints + model pickers for _reconfigure_provider paths."""
|
|
import hermes_cli.tools_config as tc
|
|
import hermes_cli.tools_config_post_setup as tools_config_post_setup
|
|
import hermes_cli.tools_config_providers as tcp
|
|
|
|
monkeypatch.setattr(tcp, "_print_success", lambda *a, **k: None)
|
|
monkeypatch.setattr(tcp, "_print_info", lambda *a, **k: None, raising=False)
|
|
monkeypatch.setattr(tcp, "_print_warning", lambda *a, **k: None, raising=False)
|
|
monkeypatch.setattr(tcp, "_configure_imagegen_model", lambda *a, **k: None)
|
|
# _configure_provider resolves the post-setup hook lazily from tools_config.
|
|
monkeypatch.setattr(tc, "_run_post_setup", lambda *a, **k: None, raising=False)
|
|
monkeypatch.setattr(tools_config_post_setup, "_run_post_setup", lambda *a, **k: None, raising=False)
|
|
# Managed rows gate on live Portal auth — stub it green.
|
|
import hermes_cli.nous_subscription as ns
|
|
|
|
monkeypatch.setattr(ns, "ensure_nous_portal_access", lambda **k: True)
|
|
|
|
|
|
def test_reconfigure_managed_fal_row_keeps_managed_selection(monkeypatch):
|
|
"""The sibling bug of fe63353cb: the legacy-backend model-pick step in
|
|
_reconfigure_provider hardcoded the direct selection AFTER the managed
|
|
branch wrote the managed one — a Nous Subscription user re-entering the
|
|
picker to change models was silently flipped onto their personal
|
|
FAL_KEY."""
|
|
_quiet_reconfigure(monkeypatch)
|
|
import hermes_cli.tools_config as tc
|
|
|
|
managed_row = {
|
|
"name": "Nous Subscription",
|
|
"env_vars": [],
|
|
"requires_nous_auth": True,
|
|
"managed_nous_feature": "image_gen",
|
|
"override_env_vars": ["FAL_KEY"],
|
|
"imagegen_backend": "fal",
|
|
}
|
|
config = {"image_gen": {"model": "fal-ai/gpt-image-2", "use_gateway": True}}
|
|
|
|
tc._reconfigure_provider(managed_row, config)
|
|
|
|
assert config["image_gen"]["provider"] == "nous"
|
|
assert "use_gateway" not in config["image_gen"]
|
|
|
|
|
|
def test_reconfigure_direct_fal_row_writes_vendor_selection(monkeypatch):
|
|
"""Direct-key FAL reconfig writes the vendor name and pops any stale
|
|
legacy use_gateway key so the read-time shim can't resurrect 'nous'."""
|
|
_quiet_reconfigure(monkeypatch)
|
|
import hermes_cli.tools_config as tc
|
|
|
|
direct_row = {
|
|
"name": "FAL.ai",
|
|
"env_vars": [],
|
|
"imagegen_backend": "fal",
|
|
}
|
|
config = {"image_gen": {"use_gateway": True}}
|
|
|
|
tc._reconfigure_provider(direct_row, config)
|
|
|
|
assert config["image_gen"]["provider"] == "fal"
|
|
assert "use_gateway" not in config["image_gen"]
|