Files
hermes-agent/tests/tui_gateway/test_make_agent_provider.py
teknium1 1693fca97a test: purge low-value tests, lane py20 (206 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

134 lines
6.0 KiB
Python

"""Regression test for #11884: _make_agent must resolve runtime provider.
Without resolve_runtime_provider(), bare-slug models in config
(e.g. ``claude-opus-4-6`` with ``model.provider: anthropic``) leave
provider/base_url/api_key empty in AIAgent, causing HTTP 404.
"""
import os
from unittest.mock import patch
def test_probe_config_health_flags_null_sections():
"""Bare YAML keys (`agent:` with no value) parse as None and silently
drop nested settings; probe must surface them so users can fix."""
from tui_gateway.server import _probe_config_health
assert _probe_config_health({"agent": {"x": 1}}) == ""
assert _probe_config_health({}) == ""
msg = _probe_config_health({"agent": None, "display": None, "model": {}})
assert "agent" in msg and "display" in msg
assert "model" not in msg
def test_apply_model_switch_does_not_leak_process_env():
"""Core fix for cross-session contamination: an in-session /model switch
must mutate only the target session (record a per-session override + switch
that session's agent in place) and must NOT write process-global env vars,
which the single-process desktop backend shares across every live session.
"""
from tui_gateway import server
class _FakeResult:
success = True
error_message = ""
warning_message = ""
new_model = "zai/glm-5.1"
target_provider = "zai"
base_url = "https://api.z.ai/v1"
api_key = "sk-glm"
api_mode = "chat_completions"
class _FakeAgent:
def __init__(self):
self.model = "minimax/m3"
self.provider = "minimax"
self.base_url = ""
self.api_key = ""
def switch_model(self, **kw):
self.model = kw["new_model"]
self.provider = kw["new_provider"]
env_keys = (
"HERMES_MODEL",
"HERMES_INFERENCE_MODEL",
"HERMES_TUI_PROVIDER",
"HERMES_INFERENCE_PROVIDER",
)
sess_b = {
"agent": _FakeAgent(), "session_key": "k-B", "model_override": None,
"follow_profile_config": True,
}
sess_a = {"agent": _FakeAgent(), "session_key": "k-A", "model_override": None}
persisted_composer_profiles = []
with (
patch("hermes_cli.model_switch.parse_model_flags",
return_value=("glm-5.1", None, False, False, True)),
patch("hermes_cli.model_switch.resolve_persist_behavior",
return_value=False),
patch("hermes_cli.model_switch.switch_model", return_value=_FakeResult()),
patch("tui_gateway.server._emit"),
patch("tui_gateway.server._restart_slash_worker"),
patch("tui_gateway.server._session_info", return_value={}),
patch("hermes_cli.model_switch.persist_model_selection") as mock_persist,
patch(
"tui_gateway.server._persist_live_session_runtime",
side_effect=lambda session: persisted_composer_profiles.append(
session.get("composer_override_profile")),
) as persist_runtime,
patch("tui_gateway.server._config_model_target", return_value=("minimax/m3", "minimax")),
):
before = {k: os.environ.get(k) for k in env_keys}
result = server._apply_model_switch("sidB", sess_b, "glm-5.1")
after = {k: os.environ.get(k) for k in env_keys}
assert result["value"] == "zai/glm-5.1"
# No process-global env mutation (the contamination vector).
assert before == after
# persist_global was False → config untouched.
mock_persist.assert_not_called()
# Target session recorded a per-session override.
assert sess_b["model_override"]["model"] == "zai/glm-5.1"
assert sess_b["model_override"]["provider"] == "zai"
assert sess_b["composer_override_profile"] == {"model": "minimax/m3", "provider": "minimax"}
# _commit_agent_switch owns the runtime transaction; provenance must be present
# on its first (and only) DB write rather than relying on a second best-effort write.
persist_runtime.assert_called_once_with(sess_b)
assert persisted_composer_profiles == [{"model": "minimax/m3", "provider": "minimax"}]
# The switched agent mutated in place.
assert sess_b["agent"].model == "zai/glm-5.1"
# Sibling session is completely untouched.
assert sess_a["model_override"] is None
assert sess_a["agent"].model == "minimax/m3"
def test_resumed_row_cannot_pin_stale_wire_onto_per_model_provider():
"""#96066: a persisted opencode-go row written while the session ran an anthropic_messages model must not
route deepseek-v4-flash-vision-exp through the Anthropic wire or the other family's relay URL on resume;
the route is re-derived from the target model. Fixed-wire providers keep honoring their row."""
from tui_gateway import server
def fake_resolve(**kwargs):
provider = kwargs["requested"]
fresh = {"opencode-go": ("chat_completions", "https://opencode.ai/zen/go/v1"),
"anthropic": ("anthropic_messages", "https://api.anthropic.com")}[provider]
return {"provider": provider, "requested_provider": provider, "api_mode": fresh[0], "base_url": fresh[1],
"api_key": "k", "source": "config"}
with patch("hermes_cli.runtime_provider.resolve_runtime_provider", side_effect=fake_resolve):
for stale_url in ("https://opencode.ai/zen/go", "https://opencode.ai/zen/v1"):
_, runtime = server._resolve_agent_model_runtime(
{"model": "deepseek-v4-flash-vision-exp", "provider": "opencode-go",
"base_url": stale_url, "api_mode": "anthropic_messages"}, None)
assert (runtime["api_mode"], runtime["base_url"]) == ("chat_completions", "https://opencode.ai/zen/go/v1")
_, runtime = server._resolve_agent_model_runtime(
{"model": "claude-opus-4-6", "provider": "anthropic",
"base_url": "https://my-proxy.example", "api_mode": "anthropic_messages"}, None)
assert (runtime["api_mode"], runtime["base_url"]) == ("anthropic_messages", "https://my-proxy.example")