"""Tests for hermes_cli.doctor.""" import os import sys import types import io import contextlib from argparse import Namespace from types import SimpleNamespace import pytest import hermes_cli.doctor as doctor import hermes_cli.gateway as gateway_cli from hermes_cli import doctor as doctor_mod from hermes_cli.doctor import _has_provider_env_config from hermes_cli.gateway import GatewayRuntimeHealth, GatewayRuntimeSnapshot _DEFAULT_RUNTIME_UPDATED_AT = "2026-04-23T00:00:00+00:00" class TestDoctorPlatformHints: def test_termux_package_hint(self, monkeypatch): monkeypatch.setenv("TERMUX_VERSION", "0.118.3") monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr") assert doctor._is_termux() is True assert doctor._python_install_cmd() == "python -m pip install" assert doctor._system_package_install_cmd("ripgrep") == "pkg install ripgrep" def test_non_termux_package_hint_defaults_to_apt(self, monkeypatch): monkeypatch.delenv("TERMUX_VERSION", raising=False) monkeypatch.setenv("PREFIX", "/usr") monkeypatch.setattr(sys, "platform", "linux") assert doctor._is_termux() is False assert doctor._python_install_cmd() == "uv pip install" assert doctor._system_package_install_cmd("ripgrep") == "sudo apt install ripgrep" class TestProviderEnvDetection: def test_detects_openai_api_key(self): content = "OPENAI_BASE_URL=http://localhost:1234/v1\nOPENAI_API_KEY=***" assert _has_provider_env_config(content) def test_detects_custom_endpoint_without_openrouter_key(self): content = "OPENAI_BASE_URL=http://localhost:8080/v1\n" assert _has_provider_env_config(content) def test_detects_kimi_cn_api_key(self): content = "KIMI_CN_API_KEY=sk-test\n" assert _has_provider_env_config(content) def test_returns_false_when_no_provider_settings(self): content = "TERMINAL_ENV=local\n" assert not _has_provider_env_config(content) class TestDoctorToolAvailabilityOverrides: def test_marks_honcho_available_when_configured(self, monkeypatch): monkeypatch.setattr(doctor, "_honcho_is_configured_for_doctor", lambda: True) available, unavailable = doctor._apply_doctor_tool_availability_overrides( [], [{"name": "honcho", "env_vars": [], "tools": ["query_user_context"]}], ) assert available == ["honcho"] assert unavailable == [] def test_leaves_honcho_unavailable_when_not_configured(self, monkeypatch): monkeypatch.setattr(doctor, "_honcho_is_configured_for_doctor", lambda: False) honcho_entry = {"name": "honcho", "env_vars": [], "tools": ["query_user_context"]} available, unavailable = doctor._apply_doctor_tool_availability_overrides( [], [honcho_entry], ) assert available == [] assert unavailable == [honcho_entry] class TestHonchoDoctorConfigDetection: def test_reports_configured_when_enabled_with_api_key(self, monkeypatch): fake_config = SimpleNamespace(enabled=True, api_key="***") monkeypatch.setattr( "plugins.memory.honcho.client.HonchoClientConfig.from_global_config", lambda: fake_config, ) assert doctor._honcho_is_configured_for_doctor() def test_reports_not_configured_without_api_key(self, monkeypatch): fake_config = SimpleNamespace(enabled=True, api_key="") monkeypatch.setattr( "plugins.memory.honcho.client.HonchoClientConfig.from_global_config", lambda: fake_config, ) assert not doctor._honcho_is_configured_for_doctor() def test_run_doctor_sets_interactive_env_for_tool_checks(monkeypatch, tmp_path): """Doctor should present CLI-gated tools as available in CLI context.""" project_root = tmp_path / "project" hermes_home = tmp_path / ".hermes" project_root.mkdir() hermes_home.mkdir() monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project_root) monkeypatch.setattr(doctor_mod, "HERMES_HOME", hermes_home) monkeypatch.delenv("HERMES_INTERACTIVE", raising=False) seen = {} def fake_check_tool_availability(*args, **kwargs): seen["interactive"] = os.getenv("HERMES_INTERACTIVE") raise SystemExit(0) fake_model_tools = types.SimpleNamespace( check_tool_availability=fake_check_tool_availability, TOOLSET_REQUIREMENTS={}, ) monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) with pytest.raises(SystemExit): doctor_mod.run_doctor(Namespace(fix=False)) assert seen["interactive"] == "1" def test_check_gateway_service_linger_warns_when_disabled(monkeypatch, tmp_path, capsys): unit_path = tmp_path / "hermes-gateway.service" unit_path.write_text("[Unit]\n") monkeypatch.setattr(gateway_cli, "is_linux", lambda: True) monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda: unit_path) monkeypatch.setattr(gateway_cli, "get_systemd_linger_status", lambda: (False, "")) issues = [] doctor._check_gateway_service_linger(issues) out = capsys.readouterr().out assert "Gateway Service" in out assert "Systemd linger disabled" in out assert "loginctl enable-linger" in out assert issues == [ "Enable linger for the gateway user service: sudo loginctl enable-linger $USER" ] def test_check_gateway_service_linger_skips_when_service_not_installed(monkeypatch, tmp_path, capsys): unit_path = tmp_path / "missing.service" monkeypatch.setattr(gateway_cli, "is_linux", lambda: True) monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda: unit_path) issues = [] doctor._check_gateway_service_linger(issues) out = capsys.readouterr().out assert out == "" assert issues == [] def _gateway_health( *, snapshot=None, configured=(), runtime_status_available=True, gateway_state="running", exit_reason=None, platforms=None, systemd_unit=None, updated_at=_DEFAULT_RUNTIME_UPDATED_AT, ): if not runtime_status_available and updated_at == _DEFAULT_RUNTIME_UPDATED_AT: updated_at = None return GatewayRuntimeHealth( snapshot=snapshot or GatewayRuntimeSnapshot( manager="manual process", gateway_pids=(1234,), ), configured_platforms=tuple(configured), runtime_status_available=runtime_status_available, gateway_state=gateway_state, exit_reason=exit_reason, platforms=platforms or {}, updated_at=updated_at, systemd_unit=systemd_unit or {}, ) def _run_runtime_check(monkeypatch, capsys, health, *, active_cron_jobs=0): monkeypatch.setattr(gateway_cli, "get_gateway_runtime_health", lambda: health) monkeypatch.setattr(doctor, "_count_active_cron_jobs", lambda: active_cron_jobs) issues = [] doctor._check_runtime_health(issues) return capsys.readouterr().out, issues def test_runtime_health_no_gateway_configured_is_info_only(monkeypatch, capsys): health = _gateway_health( snapshot=GatewayRuntimeSnapshot(manager="manual process"), configured=(), runtime_status_available=False, gateway_state=None, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "No long-lived gateway-managed runtime configured" in out assert issues == [] def test_runtime_health_gateway_not_running_adds_one_liveness_issue(monkeypatch, capsys): health = _gateway_health( snapshot=GatewayRuntimeSnapshot(manager="manual process"), configured=("telegram",), gateway_state="stopped", ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway is not running" in out assert len(issues) == 1 assert issues[0] == "Start the gateway so configured platforms can receive messages" def test_runtime_health_gateway_not_running_includes_startup_failure(monkeypatch, capsys): health = _gateway_health( snapshot=GatewayRuntimeSnapshot(manager="manual process"), configured=("telegram",), gateway_state="startup_failed", exit_reason="telegram conflict", ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "last startup issue: telegram conflict" in out assert issues == [ "Start the gateway so configured platforms can receive messages; last startup issue: telegram conflict" ] def test_runtime_health_missing_status_file_does_not_emit_platform_issues(monkeypatch, capsys): health = _gateway_health( configured=("telegram", "discord", "slack"), runtime_status_available=False, gateway_state=None, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway runtime status unavailable" in out assert "runtime health unknown" not in out assert issues == [] def test_runtime_health_startup_failed_adds_issue(monkeypatch, capsys): health = _gateway_health( configured=("telegram",), gateway_state="startup_failed", exit_reason="telegram conflict", platforms={"telegram": {"state": "connected"}}, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway startup failed" in out assert "telegram conflict" in out assert issues == ["Gateway startup failed: telegram conflict"] def test_runtime_health_platform_retrying_adds_issue(monkeypatch, capsys): health = _gateway_health( configured=("telegram",), platforms={ "telegram": { "state": "retrying", "error_message": "another poller is active", } }, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "telegram retrying" in out assert "another poller is active" in out assert issues == ["telegram runtime state is retrying"] def test_runtime_health_unknown_non_alert_platform_state_is_info_only(monkeypatch, capsys): health = _gateway_health( configured=("telegram",), platforms={"telegram": {"state": "idle"}}, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "telegram idle" in out assert issues == [] def test_runtime_health_missing_configured_platform_entry_adds_issue(monkeypatch, capsys): health = _gateway_health(configured=("telegram",), platforms={}) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "telegram runtime health unknown" in out assert issues == ["telegram is configured but missing from gateway runtime status"] def test_runtime_health_transient_states_are_info_only(monkeypatch, capsys): health = _gateway_health( configured=("telegram",), gateway_state="draining", platforms={"telegram": {"state": "connecting"}}, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway runtime state draining" in out assert "telegram connecting" in out assert issues == [] def test_runtime_health_cron_jobs_without_gateway_adds_issue(monkeypatch, capsys): health = _gateway_health( snapshot=GatewayRuntimeSnapshot(manager="manual process"), configured=(), runtime_status_available=False, gateway_state=None, ) out, issues = _run_runtime_check(monkeypatch, capsys, health, active_cron_jobs=2) assert "scheduled jobs will not fire automatically" in out assert issues == ["Start the gateway so scheduled jobs can fire automatically"] def test_runtime_health_cron_jobs_with_gateway_are_ok(monkeypatch, capsys): health = _gateway_health(configured=(), platforms={}) out, issues = _run_runtime_check(monkeypatch, capsys, health, active_cron_jobs=2) assert "Scheduled jobs can fire automatically" in out assert "scheduled jobs will not fire automatically" not in out assert issues == [] def test_runtime_health_renders_updated_at_for_running_state(monkeypatch, capsys): health = _gateway_health( configured=("telegram",), platforms={"telegram": {"state": "connected"}}, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "updated 2026-04-23T00:00:00+00:00" in out assert issues == [] def test_runtime_health_running_state_without_updated_at_has_no_empty_detail(monkeypatch, capsys): health = _gateway_health( configured=("telegram",), platforms={"telegram": {"state": "connected"}}, updated_at=None, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway runtime state running" in out assert "updated " not in out assert issues == [] def test_runtime_health_running_gateway_with_no_surfaces_is_info_only(monkeypatch, capsys): health = _gateway_health(configured=(), platforms={}) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway process running" in out assert "No configured delivery surfaces or scheduled jobs to check" in out assert issues == [] def test_runtime_health_unknown_runtime_state_is_warn_only(monkeypatch, capsys): health = _gateway_health( configured=(), runtime_status_available=True, gateway_state=None, platforms={}, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway runtime state unknown" in out assert issues == [] def test_runtime_health_stopped_service_without_consumers_is_info_only(monkeypatch, capsys): health = _gateway_health( snapshot=GatewayRuntimeSnapshot( manager="systemd (user)", service_installed=True, service_running=False, ), configured=(), runtime_status_available=False, gateway_state=None, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway service installed but stopped" in out assert issues == [] def test_runtime_health_stopped_service_with_configured_platform_adds_issue(monkeypatch, capsys): health = _gateway_health( snapshot=GatewayRuntimeSnapshot( manager="systemd (user)", service_installed=True, service_running=False, ), configured=("telegram",), gateway_state="stopped", ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway service installed but stopped" in out assert "Start the installed gateway service with 'hermes gateway start'" in issues def test_runtime_health_service_process_mismatch_adds_issue(monkeypatch, capsys): health = _gateway_health( snapshot=GatewayRuntimeSnapshot( manager="systemd (user)", service_installed=True, service_running=False, gateway_pids=(1234,), ), configured=(), platforms={}, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "installed service is not active" in out assert issues == [ "Gateway process is not service-managed — stop the manual process or start the service" ] def test_runtime_health_service_process_mismatch_suppresses_stopped_service_issue(monkeypatch, capsys): health = _gateway_health( snapshot=GatewayRuntimeSnapshot( manager="systemd (user)", service_installed=True, service_running=False, gateway_pids=(1234,), ), configured=("telegram",), platforms={"telegram": {"state": "connected"}}, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert "Gateway process is running but the installed service is not active" in out assert "Gateway service installed but stopped" not in out assert issues == [ "Gateway process is not service-managed — stop the manual process or start the service" ] @pytest.mark.parametrize( ("systemd_unit", "expected"), [ ( {"ActiveState": "activating", "SubState": "auto-restart"}, "Gateway service is auto-restarting", ), ( {"ActiveState": "failed", "Result": "exit-code", "ExecMainStatus": "1"}, "Gateway service failed", ), ], ) def test_runtime_health_systemd_failure_states_add_issue( monkeypatch, capsys, systemd_unit, expected, ): health = _gateway_health( snapshot=GatewayRuntimeSnapshot( manager="systemd (user)", service_installed=True, service_running=False, ), configured=(), gateway_state="stopped", systemd_unit=systemd_unit, ) out, issues = _run_runtime_check(monkeypatch, capsys, health) assert expected in out assert len(issues) == 1 assert expected in issues[0] # ── Memory provider section (doctor should only check the *active* provider) ── class TestDoctorMemoryProviderSection: """The ◆ Memory Provider section should respect memory.provider config.""" def _make_hermes_home(self, tmp_path, provider=""): """Create a minimal HERMES_HOME with config.yaml.""" home = tmp_path / ".hermes" home.mkdir(parents=True, exist_ok=True) import yaml config = {"memory": {"provider": provider}} if provider else {"memory": {}} (home / "config.yaml").write_text(yaml.dump(config)) return home def _run_doctor_and_capture(self, monkeypatch, tmp_path, provider=""): """Run doctor and capture stdout.""" home = self._make_hermes_home(tmp_path, provider) monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", tmp_path / "project") monkeypatch.setattr(doctor_mod, "_DHH", str(home)) (tmp_path / "project").mkdir(exist_ok=True) # Stub tool availability (returns empty) so doctor runs past it fake_model_tools = types.SimpleNamespace( check_tool_availability=lambda *a, **kw: ([], []), TOOLSET_REQUIREMENTS={}, ) monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) # Stub auth checks to avoid real API calls try: from hermes_cli import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: pass import io, contextlib buf = io.StringIO() with contextlib.redirect_stdout(buf): doctor_mod.run_doctor(Namespace(fix=False)) return buf.getvalue() def test_no_provider_shows_builtin_ok(self, monkeypatch, tmp_path): out = self._run_doctor_and_capture(monkeypatch, tmp_path, provider="") assert "Memory Provider" in out assert "Built-in memory active" in out # Should NOT mention Honcho or Mem0 errors assert "Honcho API key" not in out assert "Mem0" not in out def test_honcho_provider_not_installed_shows_fail(self, monkeypatch, tmp_path): # Make honcho import fail monkeypatch.setitem( sys.modules, "plugins.memory.honcho.client", None ) out = self._run_doctor_and_capture(monkeypatch, tmp_path, provider="honcho") assert "Memory Provider" in out # Should show failure since honcho is set but not importable assert "Built-in memory active" not in out def test_mem0_provider_not_installed_shows_fail(self, monkeypatch, tmp_path): # Make mem0 import fail monkeypatch.setitem(sys.modules, "plugins.memory.mem0", None) out = self._run_doctor_and_capture(monkeypatch, tmp_path, provider="mem0") assert "Memory Provider" in out assert "Built-in memory active" not in out def test_run_doctor_termux_treats_docker_and_browser_warnings_as_expected(monkeypatch, tmp_path): helper = TestDoctorMemoryProviderSection() monkeypatch.setenv("TERMUX_VERSION", "0.118.3") monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr") real_which = doctor_mod.shutil.which def fake_which(cmd): if cmd in {"docker", "node", "npm"}: return None return real_which(cmd) monkeypatch.setattr(doctor_mod.shutil, "which", fake_which) out = helper._run_doctor_and_capture(monkeypatch, tmp_path, provider="") assert "Docker backend is not available inside Termux" in out assert "Node.js not found (browser tools are optional in the tested Termux path)" in out assert "Install Node.js on Termux with: pkg install nodejs" in out assert "Termux browser setup:" in out assert "1) pkg install nodejs" in out assert "2) npm install -g agent-browser" in out assert "3) agent-browser install" in out assert "docker not found (optional)" not in out def test_run_doctor_accepts_named_provider_from_providers_section(monkeypatch, tmp_path): home = tmp_path / ".hermes" home.mkdir(parents=True, exist_ok=True) import yaml (home / "config.yaml").write_text( yaml.dump( { "model": { "provider": "volcengine-plan", "default": "doubao-seed-2.0-code", }, "providers": { "volcengine-plan": { "name": "volcengine-plan", "base_url": "https://ark.cn-beijing.volces.com/api/coding/v3", "default_model": "doubao-seed-2.0-code", "models": {"doubao-seed-2.0-code": {}}, } }, } ) ) monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", tmp_path / "project") monkeypatch.setattr(doctor_mod, "_DHH", str(home)) (tmp_path / "project").mkdir(exist_ok=True) fake_model_tools = types.SimpleNamespace( check_tool_availability=lambda *a, **kw: ([], []), TOOLSET_REQUIREMENTS={}, ) monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) try: from hermes_cli import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: pass buf = io.StringIO() with contextlib.redirect_stdout(buf): doctor_mod.run_doctor(Namespace(fix=False)) out = buf.getvalue() assert "model.provider 'volcengine-plan' is not a recognised provider" not in out def test_run_doctor_termux_does_not_mark_browser_available_without_agent_browser(monkeypatch, tmp_path): home = tmp_path / ".hermes" home.mkdir(parents=True, exist_ok=True) (home / "config.yaml").write_text("memory: {}\n", encoding="utf-8") project = tmp_path / "project" project.mkdir(exist_ok=True) monkeypatch.setenv("TERMUX_VERSION", "0.118.3") monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr") monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project) monkeypatch.setattr(doctor_mod, "_DHH", str(home)) monkeypatch.setattr(doctor_mod.shutil, "which", lambda cmd: "/data/data/com.termux/files/usr/bin/node" if cmd in {"node", "npm"} else None) fake_model_tools = types.SimpleNamespace( check_tool_availability=lambda *a, **kw: (["terminal"], [{"name": "browser", "env_vars": [], "tools": ["browser_navigate"]}]), TOOLSET_REQUIREMENTS={ "terminal": {"name": "terminal"}, "browser": {"name": "browser"}, }, ) monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) try: from hermes_cli import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: pass import io, contextlib buf = io.StringIO() with contextlib.redirect_stdout(buf): doctor_mod.run_doctor(Namespace(fix=False)) out = buf.getvalue() assert "✓ browser" not in out assert "browser" in out assert "system dependency not met" in out assert "agent-browser is not installed (expected in the tested Termux path)" in out assert "npm install -g agent-browser && agent-browser install" in out def test_run_doctor_kimi_cn_env_is_detected_and_probe_is_null_safe(monkeypatch, tmp_path): home = tmp_path / ".hermes" home.mkdir(parents=True, exist_ok=True) (home / "config.yaml").write_text("memory: {}\n", encoding="utf-8") (home / ".env").write_text("KIMI_CN_API_KEY=sk-test\n", encoding="utf-8") project = tmp_path / "project" project.mkdir(exist_ok=True) monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project) monkeypatch.setattr(doctor_mod, "_DHH", str(home)) monkeypatch.setenv("KIMI_CN_API_KEY", "sk-test") fake_model_tools = types.SimpleNamespace( check_tool_availability=lambda *a, **kw: ([], []), TOOLSET_REQUIREMENTS={}, ) monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) try: from hermes_cli import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: pass calls = [] def fake_get(url, headers=None, timeout=None): calls.append((url, headers, timeout)) return types.SimpleNamespace(status_code=200) import httpx monkeypatch.setattr(httpx, "get", fake_get) import io, contextlib buf = io.StringIO() with contextlib.redirect_stdout(buf): doctor_mod.run_doctor(Namespace(fix=False)) out = buf.getvalue() assert "API key or custom endpoint configured" in out assert "Kimi / Moonshot (China)" in out assert "str expected, not NoneType" not in out assert any(url == "https://api.moonshot.cn/v1/models" for url, _, _ in calls) @pytest.mark.parametrize("base_url", [None, "https://opencode.ai/zen/go/v1"]) def test_run_doctor_opencode_go_skips_invalid_models_probe(monkeypatch, tmp_path, base_url): home = tmp_path / ".hermes" home.mkdir(parents=True, exist_ok=True) (home / "config.yaml").write_text("memory: {}\n", encoding="utf-8") (home / ".env").write_text("OPENCODE_GO_API_KEY=***\n", encoding="utf-8") project = tmp_path / "project" project.mkdir(exist_ok=True) monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project) monkeypatch.setattr(doctor_mod, "_DHH", str(home)) monkeypatch.setenv("OPENCODE_GO_API_KEY", "sk-test") if base_url: monkeypatch.setenv("OPENCODE_GO_BASE_URL", base_url) else: monkeypatch.delenv("OPENCODE_GO_BASE_URL", raising=False) fake_model_tools = types.SimpleNamespace( check_tool_availability=lambda *a, **kw: ([], []), TOOLSET_REQUIREMENTS={}, ) monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) try: from hermes_cli import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except ImportError: pass calls = [] def fake_get(url, headers=None, timeout=None): calls.append((url, headers, timeout)) return types.SimpleNamespace(status_code=200) import httpx monkeypatch.setattr(httpx, "get", fake_get) import io, contextlib buf = io.StringIO() with contextlib.redirect_stdout(buf): doctor_mod.run_doctor(Namespace(fix=False)) out = buf.getvalue() assert any( "OpenCode Go" in line and "(key configured)" in line for line in out.splitlines() ) assert not any(url == "https://opencode.ai/zen/go/v1/models" for url, _, _ in calls) assert not any("opencode" in url.lower() and "models" in url.lower() for url, _, _ in calls)