diff --git a/tests/agent/test_auxiliary_client_ssl_verify.py b/tests/agent/test_auxiliary_client_ssl_verify.py index 1439f33c8a..a1f58c21aa 100644 --- a/tests/agent/test_auxiliary_client_ssl_verify.py +++ b/tests/agent/test_auxiliary_client_ssl_verify.py @@ -40,6 +40,7 @@ def test_build_keepalive_http_client_forwards_verify_context(clean_tls_env): def test_resolve_aux_verify_ssl_verify_false(clean_tls_env, monkeypatch): import hermes_cli.config as cfg + import hermes_cli.config_providers as config_providers from agent import auxiliary_client monkeypatch.setattr( @@ -47,6 +48,11 @@ def test_resolve_aux_verify_ssl_verify_false(clean_tls_env, monkeypatch): "get_custom_provider_tls_settings", lambda *a, **k: {"ssl_verify": False}, ) + monkeypatch.setattr( + config_providers, + "get_custom_provider_tls_settings", + lambda *a, **k: {"ssl_verify": False}, + ) assert auxiliary_client._resolve_aux_verify("https://ollama.example.com/v1") is False diff --git a/tests/agent/test_credential_pool_nous_refresh_stampede.py b/tests/agent/test_credential_pool_nous_refresh_stampede.py index 34a3128e69..3b14ba5265 100644 --- a/tests/agent/test_credential_pool_nous_refresh_stampede.py +++ b/tests/agent/test_credential_pool_nous_refresh_stampede.py @@ -20,6 +20,7 @@ import json import logging import hermes_cli.auth as auth_mod +import hermes_cli.auth_nous as auth_nous from agent.credential_pool import CredentialPool, PooledCredential from tests.hermes_cli.test_auth_nous_provider import _invoke_jwt, _setup_nous_auth @@ -54,6 +55,7 @@ def test_forced_refresh_adopts_peer_rotation_instead_of_reposting(tmp_path, monk } monkeypatch.setattr(auth_mod, "_refresh_access_token", _fake_refresh_access_token) + monkeypatch.setattr(auth_nous, "_refresh_access_token", _fake_refresh_access_token) creds = auth_mod.resolve_nous_runtime_credentials( force_refresh=True, stale_access_token=failed_token @@ -94,6 +96,7 @@ def test_lock_timeout_during_nous_refresh_does_not_bench_entry(monkeypatch, capl raise TimeoutError("Timed out waiting for auth store lock") monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", _busy) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", _busy) result = pool._refresh_entry_impl(entry, force=True) @@ -124,6 +127,7 @@ def test_agent_401_refresh_passes_failed_bearer_as_stale_hint(monkeypatch): return {"api_key": "fresh", "base_url": agent.base_url} monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", _fake_resolve) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", _fake_resolve) assert agent._try_refresh_nous_client_credentials(force=True) is True assert seen["force_refresh"] is True diff --git a/tests/agent/test_credential_pool_oauth_writethrough.py b/tests/agent/test_credential_pool_oauth_writethrough.py index 1ec5526403..e176a3f406 100644 --- a/tests/agent/test_credential_pool_oauth_writethrough.py +++ b/tests/agent/test_credential_pool_oauth_writethrough.py @@ -30,6 +30,7 @@ from agent.credential_pool import ( load_pool, ) from hermes_cli import auth as A +import hermes_cli.auth_codex as auth_codex def _write_store(path, store): @@ -218,6 +219,7 @@ def test_codex_pool_refresh_holds_auth_store_lock_across_post(monkeypatch, tmp_p } monkeypatch.setattr(A, "refresh_codex_oauth_pure", fake_refresh) + monkeypatch.setattr(auth_codex, "refresh_codex_oauth_pure", fake_refresh) entry = _entry( provider, diff --git a/tests/cli/test_cli_provider_resolution.py b/tests/cli/test_cli_provider_resolution.py index ab8c7a0a8a..fefd8c0b3b 100644 --- a/tests/cli/test_cli_provider_resolution.py +++ b/tests/cli/test_cli_provider_resolution.py @@ -8,6 +8,7 @@ import pytest from hermes_cli.auth import AuthError from hermes_cli import main as hermes_main +import hermes_cli.main_provider_setup as hermes_cli_main_provider_setup from hermes_cli import model_setup_flows @@ -561,6 +562,7 @@ def test_cmd_model_forwards_nous_login_tls_options(monkeypatch): monkeypatch.setattr("hermes_cli.auth.resolve_provider", lambda requested, **kwargs: "nous") monkeypatch.setattr("hermes_cli.auth.get_provider_auth_state", lambda provider_id: None) monkeypatch.setattr(hermes_main, "_prompt_provider_choice", lambda choices, **kwargs: 0) + monkeypatch.setattr(hermes_cli_main_provider_setup, "_prompt_provider_choice", lambda choices, **kwargs: 0) captured = {} diff --git a/tests/cron/test_restart_safe_worker.py b/tests/cron/test_restart_safe_worker.py index 1be7e30654..68143eb910 100644 --- a/tests/cron/test_restart_safe_worker.py +++ b/tests/cron/test_restart_safe_worker.py @@ -364,6 +364,7 @@ def test_worker_delivery_queue_is_keyed_by_the_delivering_jobs_own_execution( """A nested in-process dispatch inside a worker (e.g. a script running ``hermes cron run ``) must not queue under the OUTER execution id.""" import cron.scheduler as scheduler + import cron.scheduler_delivery as scheduler_delivery queued = [] monkeypatch.setattr( @@ -377,6 +378,11 @@ def test_worker_delivery_queue_is_keyed_by_the_delivering_jobs_own_execution( "_resolve_delivery_targets", lambda job, for_failure=False: [{"platform": "telegram", "chat_id": "123"}], ) + monkeypatch.setattr( + scheduler_delivery, + "_resolve_delivery_targets", + lambda job, for_failure=False: [{"platform": "telegram", "chat_id": "123"}], + ) def _standalone(*_args, **_kwargs): raise RuntimeError("standalone path reached") diff --git a/tests/gateway/test_api_server_active_work_drain.py b/tests/gateway/test_api_server_active_work_drain.py index 703977e40f..d29985e006 100644 --- a/tests/gateway/test_api_server_active_work_drain.py +++ b/tests/gateway/test_api_server_active_work_drain.py @@ -523,6 +523,7 @@ class TestShutdownSettleWindow: """ import tools.process_registry as _pr import tools.terminal_tool as _tt + import tools.terminal_tool_lifecycle as terminal_tool_lifecycle runner, adapter = make_restart_runner() runner._restart_drain_timeout = 0.01 # force the drain-timeout path @@ -538,6 +539,7 @@ class TestShutdownSettleWindow: monkeypatch.setattr(_pr.process_registry, "kill_all", _spy_kill_all) monkeypatch.setattr(_tt, "cleanup_all_environments", lambda: None) + monkeypatch.setattr(terminal_tool_lifecycle, "cleanup_all_environments", lambda: None) monkeypatch.setattr(bt_lifecycle, "cleanup_all_browsers", lambda: None) with patch("gateway.status.remove_pid_file"), \ @@ -566,6 +568,7 @@ class TestShutdownSettleWindow: """ import tools.process_registry as _pr import tools.terminal_tool as _tt + import tools.terminal_tool_lifecycle as terminal_tool_lifecycle runner, adapter = make_restart_runner() runner._restart_drain_timeout = 0.01 @@ -575,6 +578,7 @@ class TestShutdownSettleWindow: monkeypatch.setattr(_pr.process_registry, "kill_all", lambda task_id=None: 0) monkeypatch.setattr(_tt, "cleanup_all_environments", lambda: None) + monkeypatch.setattr(terminal_tool_lifecycle, "cleanup_all_environments", lambda: None) monkeypatch.setattr(bt_lifecycle, "cleanup_all_browsers", lambda: None) # Accelerate the loop clock: each time() call advances 1s of virtual diff --git a/tests/gateway/test_cron_active_work_drain.py b/tests/gateway/test_cron_active_work_drain.py index ac524e268a..5fc5db9013 100644 --- a/tests/gateway/test_cron_active_work_drain.py +++ b/tests/gateway/test_cron_active_work_drain.py @@ -84,6 +84,7 @@ class TestKillToolSubprocessesMarksCronInterrupted: import cron.scheduler as sched import tools.process_registry as _pr import tools.terminal_tool as _tt + import tools.terminal_tool_lifecycle as terminal_tool_lifecycle runner, adapter = make_restart_runner() runner._restart_drain_timeout = 0.01 # force the timeout path @@ -97,6 +98,7 @@ class TestKillToolSubprocessesMarksCronInterrupted: monkeypatch.setattr(_pr.process_registry, "kill_all", lambda task_id=None: 1) monkeypatch.setattr(_tt, "cleanup_all_environments", lambda: None) + monkeypatch.setattr(terminal_tool_lifecycle, "cleanup_all_environments", lambda: None) monkeypatch.setattr(bt_lifecycle, "cleanup_all_browsers", lambda: None) marked_calls = [] diff --git a/tests/gateway/test_gateway_shutdown.py b/tests/gateway/test_gateway_shutdown.py index 41cab9217c..0edb15ce1d 100644 --- a/tests/gateway/test_gateway_shutdown.py +++ b/tests/gateway/test_gateway_shutdown.py @@ -273,8 +273,10 @@ async def test_gateway_stop_kills_tool_subprocesses_before_adapter_disconnect_on # Patch the module-level names the stop() helper imports lazily. import tools.process_registry as _pr import tools.terminal_tool as _tt + import tools.terminal_tool_lifecycle as terminal_tool_lifecycle monkeypatch.setattr(_pr.process_registry, "kill_all", _fake_kill_all) monkeypatch.setattr(_tt, "cleanup_all_environments", _fake_cleanup_envs) + monkeypatch.setattr(terminal_tool_lifecycle, "cleanup_all_environments", _fake_cleanup_envs) monkeypatch.setattr(bt_lifecycle, "cleanup_all_browsers", _fake_cleanup_browsers) adapter.disconnect = _disconnect diff --git a/tests/hermes_cli/test_approval_transport.py b/tests/hermes_cli/test_approval_transport.py index a0fde80a12..e79d10e1f6 100644 --- a/tests/hermes_cli/test_approval_transport.py +++ b/tests/hermes_cli/test_approval_transport.py @@ -295,6 +295,7 @@ def test_cli_selected_transport_replaces_builtin_prompt(monkeypatch): def test_gateway_selected_transport_does_not_require_gateway_notifier(monkeypatch): from tools import approval + import tools.approval_context as tools_approval_context manager = PluginManager() seen = [] @@ -304,6 +305,7 @@ def test_gateway_selected_transport_does_not_require_gateway_notifier(monkeypatc _configure_manual_guard(monkeypatch, approval, manager) monkeypatch.setattr(approval, "_is_interactive_cli", lambda: False) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: True) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: True) monkeypatch.setattr(approval, "_gateway_notify_cbs", {}) result = approval.check_all_command_guards("rm -rf /tmp/example", "local") @@ -315,6 +317,7 @@ def test_gateway_selected_transport_does_not_require_gateway_notifier(monkeypatc def test_execute_code_gateway_uses_selected_transport(monkeypatch): from tools import approval + import tools.approval_context as tools_approval_context manager = PluginManager() seen = [] @@ -323,10 +326,15 @@ def test_execute_code_gateway_uses_selected_transport(monkeypatch): ) monkeypatch.setattr(approval_context, "_get_approval_mode", lambda: "manual") monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: True) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: True) monkeypatch.setattr(approval, "_is_cron_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_cron_approval_context", lambda: False) monkeypatch.setattr( approval, "get_current_session_key", lambda *args, **kwargs: "session-a" ) + monkeypatch.setattr( + tools_approval_context, "get_current_session_key", lambda *args, **kwargs: "session-a" + ) monkeypatch.setattr(approval, "is_approved", lambda *args: False) monkeypatch.setattr(approval_prompt, "get_plugin_manager", lambda: manager) monkeypatch.setattr( @@ -539,6 +547,7 @@ def register(ctx): def test_hardline_blocks_before_selected_transport(monkeypatch): from tools import approval + import tools.approval_detection as approval_detection manager = PluginManager() calls = [] @@ -551,6 +560,11 @@ def test_hardline_blocks_before_selected_transport(monkeypatch): "detect_hardline_command", lambda command: (True, "recursive delete of root filesystem"), ) + monkeypatch.setattr( + approval_detection, + "detect_hardline_command", + lambda command: (True, "recursive delete of root filesystem"), + ) result = approval.check_all_command_guards("rm -rf /", "local") diff --git a/tests/hermes_cli/test_approvals_test.py b/tests/hermes_cli/test_approvals_test.py index d8b78358ae..c67d967b12 100644 --- a/tests/hermes_cli/test_approvals_test.py +++ b/tests/hermes_cli/test_approvals_test.py @@ -16,6 +16,7 @@ import json import pytest import tools.approval as A +import tools.approval_prompt as approval_prompt from tools import approval_context from tools import approval_detection, approval_floors from hermes_cli import approvals_test as at @@ -42,6 +43,7 @@ def isolated_approvals(monkeypatch): def _boom(*_a, **_kw): # pragma: no cover - failure path raise AssertionError("read-only tester touched a prompt/persistence path") monkeypatch.setattr(A, "prompt_dangerous_approval", _boom) + monkeypatch.setattr(approval_prompt, "prompt_dangerous_approval", _boom) monkeypatch.setattr(A, "save_permanent_allowlist", _boom) monkeypatch.setattr(A, "submit_pending", _boom, raising=False) yield A diff --git a/tests/hermes_cli/test_auth_codex_quota_probe.py b/tests/hermes_cli/test_auth_codex_quota_probe.py index 44bbe41ad7..b4b5c6184b 100644 --- a/tests/hermes_cli/test_auth_codex_quota_probe.py +++ b/tests/hermes_cli/test_auth_codex_quota_probe.py @@ -15,6 +15,7 @@ from types import SimpleNamespace import pytest import hermes_cli.auth as auth_mod +import hermes_cli.auth_codex as auth_codex from hermes_cli.auth import ( AuthError, _codex_usage_probe_url, @@ -229,6 +230,9 @@ def test_resolver_recovers_when_probe_confirms_reset(tmp_path, monkeypatch): monkeypatch.setattr( auth_mod, "_probe_codex_quota_restored", lambda token, **kw: True ) + monkeypatch.setattr( + auth_codex, "_probe_codex_quota_restored", lambda token, **kw: True + ) resolved = resolve_codex_runtime_credentials() assert resolved["api_key"] == "tok-quota" @@ -270,6 +274,7 @@ def test_pool_probe_not_fired_for_non_quota_exhaustion(tmp_path, monkeypatch): return True monkeypatch.setattr(auth_mod, "_probe_codex_quota_restored", _spy) + monkeypatch.setattr(auth_codex, "_probe_codex_quota_restored", _spy) pool._available_entries(clear_expired=True, refresh=False) assert probes == [] diff --git a/tests/hermes_cli/test_auth_codex_self_heal.py b/tests/hermes_cli/test_auth_codex_self_heal.py index 2c1fd2f9b4..4c92cde2f5 100644 --- a/tests/hermes_cli/test_auth_codex_self_heal.py +++ b/tests/hermes_cli/test_auth_codex_self_heal.py @@ -15,6 +15,7 @@ import json import pytest import hermes_cli.auth as auth +import hermes_cli.auth_codex as auth_codex from hermes_cli.auth import AuthError, _refresh_codex_auth_tokens, resolve_codex_runtime_credentials STALE = {"access_token": "stale-access", "refresh_token": "stale-refresh"} @@ -38,8 +39,11 @@ def test_self_heals_on_stale_refresh_token(monkeypatch): ) monkeypatch.setattr(auth, "refresh_codex_oauth_pure", _rejected) + monkeypatch.setattr(auth_codex, "refresh_codex_oauth_pure", _rejected) monkeypatch.setattr(auth, "_import_codex_cli_tokens", lambda: dict(fresh)) + monkeypatch.setattr(auth_codex, "_import_codex_cli_tokens", lambda: dict(fresh)) monkeypatch.setattr(auth, "_save_codex_tokens", lambda t, *a, **k: saved.update(t)) + monkeypatch.setattr(auth_codex, "_save_codex_tokens", lambda t, *a, **k: saved.update(t)) out = _refresh_codex_auth_tokens(STALE, 20.0) diff --git a/tests/hermes_cli/test_auth_loopback_ssh_hint.py b/tests/hermes_cli/test_auth_loopback_ssh_hint.py index d54f10b91d..268d72bac4 100644 --- a/tests/hermes_cli/test_auth_loopback_ssh_hint.py +++ b/tests/hermes_cli/test_auth_loopback_ssh_hint.py @@ -16,6 +16,7 @@ import socket from hermes_cli import auth as auth_mod +import hermes_cli.auth_device_flow as auth_device_flow def _cap(fn): @@ -27,6 +28,7 @@ def _cap(fn): def test_loopback_ssh_hint_silent_when_not_remote(monkeypatch): monkeypatch.setattr(auth_mod, "_is_remote_session", lambda: False) + monkeypatch.setattr(auth_device_flow, "_is_remote_session", lambda: False) out = _cap(lambda: auth_mod._print_loopback_ssh_hint( "http://127.0.0.1:43827/spotify/callback", docs_url=auth_mod.SPOTIFY_DOCS_URL )) @@ -36,6 +38,7 @@ def test_loopback_ssh_hint_silent_when_not_remote(monkeypatch): def test_loopback_ssh_hint_has_visual_header(monkeypatch): """The hint should print a divider and header so it stands out in noisy output.""" monkeypatch.setattr(auth_mod, "_is_remote_session", lambda: True) + monkeypatch.setattr(auth_device_flow, "_is_remote_session", lambda: True) out = _cap(lambda: auth_mod._print_loopback_ssh_hint( "http://127.0.0.1:43827/callback" )) diff --git a/tests/hermes_cli/test_auth_nous_provider.py b/tests/hermes_cli/test_auth_nous_provider.py index 54b111a299..8add56de1d 100644 --- a/tests/hermes_cli/test_auth_nous_provider.py +++ b/tests/hermes_cli/test_auth_nous_provider.py @@ -184,6 +184,7 @@ def test_resolve_nous_runtime_credentials_invoke_jwt_is_idempotent( monkeypatch, ): import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous hermes_home = tmp_path / "hermes" hermes_home.mkdir(parents=True, exist_ok=True) @@ -232,11 +233,17 @@ def test_resolve_nous_runtime_credentials_invoke_jwt_is_idempotent( sync_calls = [] monkeypatch.setattr(auth_mod, "_write_shared_nous_state", _unexpected_shared_write) + monkeypatch.setattr(auth_nous, "_write_shared_nous_state", _unexpected_shared_write) monkeypatch.setattr( auth_mod, "_sync_nous_pool_from_auth_store", lambda: sync_calls.append(True), ) + monkeypatch.setattr( + auth_nous, + "_sync_nous_pool_from_auth_store", + lambda: sync_calls.append(True), + ) creds = auth_mod.resolve_nous_runtime_credentials() @@ -351,6 +358,7 @@ def test_nous_inference_auth_logs_do_not_include_secret_values( caplog, ): import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous hermes_home = tmp_path / "hermes" token = _invoke_jwt(seconds=3600) @@ -377,6 +385,7 @@ def test_nous_inference_auth_logs_do_not_include_secret_values( } monkeypatch.setattr(auth_mod, "_refresh_access_token", _fake_refresh_access_token) + monkeypatch.setattr(auth_nous, "_refresh_access_token", _fake_refresh_access_token) caplog.set_level(logging.DEBUG, logger="hermes_cli.auth") auth_mod.resolve_nous_runtime_credentials( @@ -499,6 +508,7 @@ class TestLoginNousSkipKeepsCurrent: def _patch_login_internals(self, monkeypatch, *, prompt_returns): """Patch OAuth + model-list + prompt so _login_nous doesn't hit network.""" import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous import hermes_cli.models as models_mod from hermes_cli import models_pricing import hermes_cli.nous_subscription as ns @@ -515,6 +525,10 @@ class TestLoginNousSkipKeepsCurrent: auth_mod, "_nous_device_code_login", lambda **kwargs: dict(fake_auth_state), ) + monkeypatch.setattr( + auth_nous, "_nous_device_code_login", + lambda **kwargs: dict(fake_auth_state), + ) monkeypatch.setattr( auth_mod, "_prompt_model_selection", lambda *a, **kw: prompt_returns, @@ -963,6 +977,7 @@ def test_try_import_shared_rehydrates_on_success(shared_store_env, monkeypatch): every field persist_nous_credentials() needs. """ from hermes_cli import auth as auth_mod + import hermes_cli.auth_nous as auth_nous auth_mod._write_shared_nous_state(_full_state_fixture()) fresh_jwt = _invoke_jwt(seconds=7200) @@ -979,6 +994,7 @@ def test_try_import_shared_rehydrates_on_success(shared_store_env, monkeypatch): } monkeypatch.setattr(auth_mod, "refresh_nous_oauth_from_state", _fake_refresh) + monkeypatch.setattr(auth_nous, "refresh_nous_oauth_from_state", _fake_refresh) result = auth_mod._try_import_shared_nous_state() @@ -1032,6 +1048,7 @@ class TestStalePortalBaseUrlMigration: ): """An allowlisted production host is still unsafe over plain HTTP.""" from hermes_cli import auth as auth_mod + import hermes_cli.auth_nous as auth_nous hermes_home = tmp_path / "hermes" monkeypatch.setenv("HERMES_HOME", str(hermes_home)) @@ -1066,6 +1083,9 @@ class TestStalePortalBaseUrlMigration: monkeypatch.setattr( auth_mod, "_refresh_access_token", _fake_refresh_access_token ) + monkeypatch.setattr( + auth_nous, "_refresh_access_token", _fake_refresh_access_token + ) auth_mod.resolve_nous_runtime_credentials() assert refresh_calls == [auth_mod.DEFAULT_NOUS_PORTAL_URL] diff --git a/tests/hermes_cli/test_auth_store_windows_encoding.py b/tests/hermes_cli/test_auth_store_windows_encoding.py index 0f45be6b45..12792f0449 100644 --- a/tests/hermes_cli/test_auth_store_windows_encoding.py +++ b/tests/hermes_cli/test_auth_store_windows_encoding.py @@ -22,6 +22,7 @@ from unittest import mock import pytest import hermes_cli.auth as auth +import hermes_cli.auth_codex as auth_codex # --- helpers --------------------------------------------------------------- @@ -170,6 +171,7 @@ class TestExplicitEncodingPassed: monkeypatch.setenv("CODEX_HOME", str(codex_home)) # Bypass the JWT-expiry check so a fake token doesn't short-circuit. monkeypatch.setattr(auth, "_codex_access_token_is_expiring", lambda *a, **k: False) + monkeypatch.setattr(auth_codex, "_codex_access_token_is_expiring", lambda *a, **k: False) with mock.patch.object(Path, "read_text", wraps=Path.read_text) as spy: auth._import_codex_cli_tokens() diff --git a/tests/hermes_cli/test_aux_config.py b/tests/hermes_cli/test_aux_config.py index fb2b0f7668..f26baa47fa 100644 --- a/tests/hermes_cli/test_aux_config.py +++ b/tests/hermes_cli/test_aux_config.py @@ -172,6 +172,7 @@ def test_leave_unchanged_replaces_cancel_label(tmp_path, monkeypatch): (tmp_path / ".hermes").mkdir(exist_ok=True) from hermes_cli import main as main_mod + import hermes_cli.main_provider_setup as hermes_cli_main_provider_setup captured: list[list[str]] = [] @@ -184,6 +185,7 @@ def test_leave_unchanged_replaces_cancel_label(tmp_path, monkeypatch): raise AssertionError("Leave unchanged not in provider list") monkeypatch.setattr(main_mod, "_prompt_provider_choice", fake_prompt) + monkeypatch.setattr(hermes_cli_main_provider_setup, "_prompt_provider_choice", fake_prompt) main_mod.select_provider_and_model() diff --git a/tests/hermes_cli/test_billing_scope_stepup.py b/tests/hermes_cli/test_billing_scope_stepup.py index 3841921393..c1520776f0 100644 --- a/tests/hermes_cli/test_billing_scope_stepup.py +++ b/tests/hermes_cli/test_billing_scope_stepup.py @@ -5,6 +5,7 @@ from __future__ import annotations import pytest import hermes_cli.auth as auth +import hermes_cli.auth_nous as auth_nous from hermes_cli.auth import ( NOUS_BILLING_MANAGE_SCOPE, nous_token_has_billing_scope, @@ -54,7 +55,9 @@ def _stub_persist(monkeypatch): monkeypatch.setattr(auth, "_save_provider_state", lambda *a, **kw: None) monkeypatch.setattr(auth, "_save_auth_store", lambda *a, **kw: "auth.json") monkeypatch.setattr(auth, "_write_shared_nous_state", lambda *a, **kw: None) + monkeypatch.setattr(auth_nous, "_write_shared_nous_state", lambda *a, **kw: None) monkeypatch.setattr(auth, "_sync_nous_pool_from_auth_store", lambda: None) + monkeypatch.setattr(auth_nous, "_sync_nous_pool_from_auth_store", lambda: None) class _NullCtx: @@ -84,6 +87,7 @@ def test_step_up_requests_billing_scope_and_reuses_prior_urls(monkeypatch, _stub return {"scope": "inference:invoke tool:invoke billing:manage", "access_token": "t"} monkeypatch.setattr(auth, "_nous_device_code_login", _fake_login) + monkeypatch.setattr(auth_nous, "_nous_device_code_login", _fake_login) granted = step_up_nous_billing_scope() assert granted is True diff --git a/tests/hermes_cli/test_checkout_mutation_guards.py b/tests/hermes_cli/test_checkout_mutation_guards.py index 86090ee73e..5c40d2513d 100644 --- a/tests/hermes_cli/test_checkout_mutation_guards.py +++ b/tests/hermes_cli/test_checkout_mutation_guards.py @@ -18,6 +18,7 @@ from __future__ import annotations from pathlib import Path import hermes_cli.main as main_mod +import hermes_cli.main_install_repair as hermes_cli_main_install_repair from hermes_cli import main_install_repair from hermes_cli import update_cmd from hermes_cli import _early_recovery as er @@ -107,4 +108,5 @@ class TestLaunchRecovery: raise AssertionError("launch recovery ran against the live checkout") monkeypatch.setattr(main_mod, "_update_marker_path", _boom) + monkeypatch.setattr(hermes_cli_main_install_repair, "_update_marker_path", _boom) main_mod._recover_from_interrupted_install() diff --git a/tests/hermes_cli/test_computer_use_cli.py b/tests/hermes_cli/test_computer_use_cli.py index 4766152077..a3e17aade1 100644 --- a/tests/hermes_cli/test_computer_use_cli.py +++ b/tests/hermes_cli/test_computer_use_cli.py @@ -53,6 +53,7 @@ def test_computer_use_status_returns_zero_for_compatible_driver( monkeypatch: pytest.MonkeyPatch, ) -> None: from hermes_cli import tools_config + import hermes_cli.tools_config_cua as tools_config_cua driver = r"C:\Users\tester\.local\bin\cua-driver.exe" monkeypatch.delenv("HERMES_CUA_DRIVER_CMD", raising=False) @@ -62,6 +63,11 @@ def test_computer_use_status_returns_zero_for_compatible_driver( "_cua_driver_contract_status", lambda _binary=None: {"ready": True}, ) + monkeypatch.setattr( + tools_config_cua, + "_cua_driver_contract_status", + lambda _binary=None: {"ready": True}, + ) monkeypatch.setattr( cua_backend_driver, "cua_driver_update_check", @@ -87,6 +93,7 @@ def test_computer_use_status_returns_nonzero_for_incompatible_standard_driver( capsys: pytest.CaptureFixture[str], ) -> None: from hermes_cli import tools_config + import hermes_cli.tools_config_cua as tools_config_cua driver = r"C:\Users\tester\.local\bin\cua-driver.exe" monkeypatch.delenv("HERMES_CUA_DRIVER_CMD", raising=False) @@ -99,6 +106,14 @@ def test_computer_use_status_returns_nonzero_for_incompatible_standard_driver( "reason": "required runtime features are missing", }, ) + monkeypatch.setattr( + tools_config_cua, + "_cua_driver_contract_status", + lambda _binary=None: { + "ready": False, + "reason": "required runtime features are missing", + }, + ) assert _invoke(monkeypatch, "status") == 1 output = capsys.readouterr().out @@ -111,6 +126,7 @@ def test_computer_use_status_returns_nonzero_for_incompatible_custom_driver( capsys: pytest.CaptureFixture[str], ) -> None: from hermes_cli import tools_config + import hermes_cli.tools_config_cua as tools_config_cua driver = r"C:\custom\cmd.exe" monkeypatch.setenv("HERMES_CUA_DRIVER_CMD", driver) @@ -120,6 +136,11 @@ def test_computer_use_status_returns_nonzero_for_incompatible_custom_driver( "_cua_driver_contract_status", lambda _binary=None: {"ready": False, "reason": "manifest is invalid"}, ) + monkeypatch.setattr( + tools_config_cua, + "_cua_driver_contract_status", + lambda _binary=None: {"ready": False, "reason": "manifest is invalid"}, + ) assert _invoke(monkeypatch, "status") == 1 output = capsys.readouterr().out @@ -134,6 +155,7 @@ def test_computer_use_install_checks_resulting_runtime_contract( expected: int, ) -> None: from hermes_cli import tools_config + import hermes_cli.tools_config_cua as tools_config_cua install = Mock(return_value=True) monkeypatch.setattr(tools_config, "install_cua_driver", install) @@ -142,6 +164,11 @@ def test_computer_use_install_checks_resulting_runtime_contract( "_cua_driver_contract_status", lambda: {"ready": ready}, ) + monkeypatch.setattr( + tools_config_cua, + "_cua_driver_contract_status", + lambda: {"ready": ready}, + ) assert _invoke(monkeypatch, "install") == expected install.assert_called_once_with(upgrade=False) @@ -151,6 +178,7 @@ def test_computer_use_install_returns_nonzero_for_unrepairable_custom_override( monkeypatch: pytest.MonkeyPatch, ) -> None: from hermes_cli import tools_config + import hermes_cli.tools_config_cua as tools_config_cua driver = r"C:\custom\cmd.exe" monkeypatch.setenv("HERMES_CUA_DRIVER_CMD", driver) @@ -158,6 +186,7 @@ def test_computer_use_install_returns_nonzero_for_unrepairable_custom_override( contract = Mock(side_effect=AssertionError("failed install must short-circuit")) monkeypatch.setattr(tools_config, "install_cua_driver", install) monkeypatch.setattr(tools_config, "_cua_driver_contract_status", contract) + monkeypatch.setattr(tools_config_cua, "_cua_driver_contract_status", contract) assert _invoke(monkeypatch, "install") == 1 install.assert_called_once_with(upgrade=False) diff --git a/tests/hermes_cli/test_imagegen_managed_gateway.py b/tests/hermes_cli/test_imagegen_managed_gateway.py index e9ddc67018..2ff4ee655b 100644 --- a/tests/hermes_cli/test_imagegen_managed_gateway.py +++ b/tests/hermes_cli/test_imagegen_managed_gateway.py @@ -80,6 +80,7 @@ def test_image_and_video_selectors_share_the_selection_contract(monkeypatch): 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) @@ -88,6 +89,7 @@ def _quiet_reconfigure(monkeypatch): 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 diff --git a/tests/hermes_cli/test_lazy_refresh_venv_repair.py b/tests/hermes_cli/test_lazy_refresh_venv_repair.py index e643691153..9885711516 100644 --- a/tests/hermes_cli/test_lazy_refresh_venv_repair.py +++ b/tests/hermes_cli/test_lazy_refresh_venv_repair.py @@ -8,6 +8,7 @@ from types import SimpleNamespace from unittest.mock import MagicMock, patch import hermes_cli.main as m +import hermes_cli.main_install_repair as hermes_cli_main_install_repair from hermes_cli import main_install_repair from hermes_cli import update_cmd import pytest @@ -23,6 +24,9 @@ def test_detect_returns_none_when_probe_subprocess_fails(tmp_path, monkeypatch): monkeypatch.setattr( m, "_resolve_install_target_python", lambda *a, **k: python ) + monkeypatch.setattr( + hermes_cli_main_install_repair, "_resolve_install_target_python", lambda *a, **k: python + ) monkeypatch.setattr( m.subprocess, "run", @@ -155,6 +159,11 @@ def test_restore_active_tool_dependencies_uses_static_allowlist(monkeypatch): "_run_package_only_install", lambda cmd, *, env=None: calls.append((cmd, env)), ) + monkeypatch.setattr( + hermes_cli_main_install_repair, + "_run_package_only_install", + lambda cmd, *, env=None: calls.append((cmd, env)), + ) env = {"VIRTUAL_ENV": "/tmp/venv"} m._restore_active_tool_dependencies( @@ -202,6 +211,7 @@ def test_cmd_update_captures_and_propagates_pre_rebuild_snapshot( m, "_capture_active_tool_dependencies", lambda: tool_snapshot.copy() ) monkeypatch.setattr(m, "_is_windows", lambda: False) + monkeypatch.setattr(hermes_cli_main_install_repair, "_is_windows", lambda: False) monkeypatch.setattr(m, "_run_pre_update_backup", lambda args: None) monkeypatch.setattr(m, "_pause_windows_gateways_for_update", lambda: None) monkeypatch.setattr(m, "_resume_windows_gateways_after_update", lambda state: None) diff --git a/tests/hermes_cli/test_list_picker_providers.py b/tests/hermes_cli/test_list_picker_providers.py index ab15213cd4..21581fd204 100644 --- a/tests/hermes_cli/test_list_picker_providers.py +++ b/tests/hermes_cli/test_list_picker_providers.py @@ -17,6 +17,7 @@ network or auth state is required. import pytest from hermes_cli import model_switch +import hermes_cli.model_switch_providers as hermes_cli_model_switch_providers from hermes_cli import model_switch_providers @@ -73,6 +74,7 @@ def test_passthrough_kwargs_to_base(monkeypatch): return [] monkeypatch.setattr(model_switch, "list_authenticated_providers", _capture) + monkeypatch.setattr(hermes_cli_model_switch_providers, "list_authenticated_providers", _capture) monkeypatch.setattr("hermes_cli.models.fetch_openrouter_models", lambda *a, **kw: []) @@ -159,6 +161,7 @@ def _stub_kimi_discovery(monkeypatch, *, canonical): """ import agent.models_dev as md import hermes_cli.models as hm + import hermes_cli.models_catalog_static as hermes_cli_models_catalog_static from hermes_cli import models_catalog_static kimi_map = { @@ -181,6 +184,7 @@ def _stub_kimi_discovery(monkeypatch, *, canonical): monkeypatch.setattr(md, "get_provider_info", lambda _pid: _PInfo()) monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {}) monkeypatch.setattr(hm, "CANONICAL_PROVIDERS", canonical) + monkeypatch.setattr(hermes_cli_models_catalog_static, "CANONICAL_PROVIDERS", canonical) monkeypatch.setattr(hm, "cached_provider_model_ids", lambda *a, **k: ["kimi-k2.6", "kimi-k2.5"]) monkeypatch.setattr(hm, "clear_provider_models_cache", lambda *a, **k: None) diff --git a/tests/hermes_cli/test_nous_inference_url_validation.py b/tests/hermes_cli/test_nous_inference_url_validation.py index a07ef9efbf..7a20149ab8 100644 --- a/tests/hermes_cli/test_nous_inference_url_validation.py +++ b/tests/hermes_cli/test_nous_inference_url_validation.py @@ -173,6 +173,7 @@ class TestHealsPoisonedStoredValue: def test_refresh_resets_rejected_url_to_default(self, monkeypatch): import hermes_cli.auth as auth + import hermes_cli.auth_nous as hermes_cli_auth_nous poisoned = "https://stg-inference-api.nousresearch.com/v1" state = { @@ -186,6 +187,7 @@ class TestHealsPoisonedStoredValue: # Force the refresh branch and return another rejected (staging) URL, # exercising the validator-returns-None heal path. monkeypatch.setattr(auth, "_nous_invoke_jwt_status", lambda *a, **k: "needs_refresh") + monkeypatch.setattr(hermes_cli_auth_nous, "_nous_invoke_jwt_status", lambda *a, **k: "needs_refresh") monkeypatch.setattr( auth, "_refresh_access_token", @@ -196,9 +198,21 @@ class TestHealsPoisonedStoredValue: "inference_base_url": poisoned, # Portal still hands back staging }, ) + monkeypatch.setattr( + hermes_cli_auth_nous, + "_refresh_access_token", + lambda **k: { + "access_token": "newtok", + "refresh_token": "newrtok", + "expires_in": 3600, + "inference_base_url": poisoned, # Portal still hands back staging + }, + ) # Skip the JWT usability assertions (orthogonal to URL healing). monkeypatch.setattr(auth, "_assert_nous_inference_jwt_usable", lambda *a, **k: None) + monkeypatch.setattr(hermes_cli_auth_nous, "_assert_nous_inference_jwt_usable", lambda *a, **k: None) monkeypatch.setattr(auth, "_select_nous_invoke_jwt", lambda *a, **k: None) + monkeypatch.setattr(hermes_cli_auth_nous, "_select_nous_invoke_jwt", lambda *a, **k: None) result = auth.refresh_nous_oauth_from_state(state, force_refresh=True) @@ -226,10 +240,12 @@ class TestEnvOverrideWins: STAGING = "https://stg-inference-api.nousresearch.com/v1" def _patch_no_refresh(self, monkeypatch, auth, state): + import hermes_cli.auth_nous as hermes_cli_auth_nous import contextlib # No refresh fires: the stored access token is a usable invoke JWT. monkeypatch.setattr(auth, "_nous_invoke_jwt_status", lambda *a, **k: None) + monkeypatch.setattr(hermes_cli_auth_nous, "_nous_invoke_jwt_status", lambda *a, **k: None) monkeypatch.setattr( auth, "_auth_store_lock", lambda *a, **k: contextlib.nullcontext() ) @@ -244,10 +260,14 @@ class TestEnvOverrideWins: monkeypatch.setattr(auth, "_save_provider_state_to_source", lambda *a, **k: None) monkeypatch.setattr(auth, "_save_auth_store", lambda *a, **k: None) monkeypatch.setattr(auth, "_write_shared_nous_state", lambda *a, **k: None) + monkeypatch.setattr(hermes_cli_auth_nous, "_write_shared_nous_state", lambda *a, **k: None) monkeypatch.setattr(auth, "_sync_nous_pool_from_auth_store", lambda *a, **k: None) + monkeypatch.setattr(hermes_cli_auth_nous, "_sync_nous_pool_from_auth_store", lambda *a, **k: None) monkeypatch.setattr(auth, "_resolve_verify", lambda *a, **k: True) monkeypatch.setattr(auth, "_assert_nous_inference_jwt_usable", lambda *a, **k: None) + monkeypatch.setattr(hermes_cli_auth_nous, "_assert_nous_inference_jwt_usable", lambda *a, **k: None) monkeypatch.setattr(auth, "_select_nous_invoke_jwt", lambda *a, **k: None) + monkeypatch.setattr(hermes_cli_auth_nous, "_select_nous_invoke_jwt", lambda *a, **k: None) def _base_state(self, auth, stored): return { diff --git a/tests/hermes_cli/test_nous_policy_surfaces.py b/tests/hermes_cli/test_nous_policy_surfaces.py index 689bce0532..d15f8e0333 100644 --- a/tests/hermes_cli/test_nous_policy_surfaces.py +++ b/tests/hermes_cli/test_nous_policy_surfaces.py @@ -34,6 +34,7 @@ class TestLoginNous: def _run(self, monkeypatch, tmp_path): import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous import hermes_cli.nous_subscription as ns seen: dict = {} @@ -50,6 +51,18 @@ class TestLoginNous: "token_expires_at": 9999999999, }, ) + monkeypatch.setattr( + auth_nous, + "_nous_device_code_login", + lambda **_k: { + "access_token": "tok", + "agent_key": "key", + "inference_base_url": "https://inference.example.com", + "portal_base_url": "https://portal.example.com", + "refresh_token": "r", + "token_expires_at": 9999999999, + }, + ) monkeypatch.setattr(models_mod, "get_curated_nous_model_ids", lambda: list(CURATED)) monkeypatch.setattr(models_pricing, "get_pricing_for_provider", lambda _p: {}) monkeypatch.setattr(models_mod, "check_nous_free_tier", lambda **_k: None) diff --git a/tests/hermes_cli/test_nous_portal_staging_allowlist.py b/tests/hermes_cli/test_nous_portal_staging_allowlist.py index 34e02049dd..d3fc7781b7 100644 --- a/tests/hermes_cli/test_nous_portal_staging_allowlist.py +++ b/tests/hermes_cli/test_nous_portal_staging_allowlist.py @@ -85,6 +85,7 @@ class TestResolveAccessTokenEnvOverrideWins: return auth_file def _run_and_capture(self, monkeypatch, auth): + import hermes_cli.auth_nous as auth_nous seen_portal_urls = [] # The resolve memo is module-level state; clear it so each test's @@ -101,6 +102,7 @@ class TestResolveAccessTokenEnvOverrideWins: } monkeypatch.setattr(auth, "_refresh_access_token", _fake_refresh) + monkeypatch.setattr(auth_nous, "_refresh_access_token", _fake_refresh) caplog_records = [] logger = logging.getLogger("hermes_cli.auth") diff --git a/tests/hermes_cli/test_nous_session_validity.py b/tests/hermes_cli/test_nous_session_validity.py index 579c2e7fb3..ff09cb77d8 100644 --- a/tests/hermes_cli/test_nous_session_validity.py +++ b/tests/hermes_cli/test_nous_session_validity.py @@ -5,6 +5,7 @@ import json import time import hermes_cli.auth as auth +import hermes_cli.auth_nous as auth_nous from hermes_cli.auth import ( NOUS_SESSION_TERMINAL, NOUS_SESSION_UNKNOWN, @@ -44,6 +45,11 @@ def _block_live_auth(monkeypatch): "resolve_nous_runtime_credentials", _fail_if_live_auth_is_used, ) + monkeypatch.setattr( + auth_nous, + "resolve_nous_runtime_credentials", + _fail_if_live_auth_is_used, + ) diff --git a/tests/hermes_cli/test_relay_shared_metrics_runtime.py b/tests/hermes_cli/test_relay_shared_metrics_runtime.py index 1e3f591743..1eecaaed73 100644 --- a/tests/hermes_cli/test_relay_shared_metrics_runtime.py +++ b/tests/hermes_cli/test_relay_shared_metrics_runtime.py @@ -745,6 +745,8 @@ def test_real_binding_correlates_plugin_approval_denial_to_tool_metric( ): from hermes_cli.observability.shared_metrics import SharedMetricsStore from tools import approval + import tools.approval_prompt as approval_prompt + import tools.approval_context as approval_context assert real_binding_runtime._native is not None base = { @@ -765,9 +767,12 @@ def test_real_binding_correlates_plugin_approval_denial_to_tool_metric( monkeypatch.setattr(approval, "is_current_session_yolo_enabled", lambda: False) monkeypatch.setattr(approval, "is_approved", lambda *args: False) monkeypatch.setattr(approval, "get_current_session_key", lambda: "session-key") + monkeypatch.setattr(approval_context, "get_current_session_key", lambda: "session-key") monkeypatch.setattr(approval, "_is_interactive_cli", lambda: True) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: False) + monkeypatch.setattr(approval_context, "_is_gateway_approval_context", lambda: False) monkeypatch.setattr(approval, "prompt_dangerous_approval", lambda *args, **kwargs: "deny") + monkeypatch.setattr(approval_prompt, "prompt_dangerous_approval", lambda *args, **kwargs: "deny") lifecycle.invoke_hook("on_session_start", **base) lifecycle.invoke_hook("pre_llm_call", **base, messages=["sensitive-prompt"]) diff --git a/tests/hermes_cli/test_serve_mcp_discovery_after_bind.py b/tests/hermes_cli/test_serve_mcp_discovery_after_bind.py index 0ae23f019e..d53d692579 100644 --- a/tests/hermes_cli/test_serve_mcp_discovery_after_bind.py +++ b/tests/hermes_cli/test_serve_mcp_discovery_after_bind.py @@ -12,6 +12,7 @@ import threading import hermes_cli.mcp_startup as mcp_startup import hermes_cli.web_server as web_server +import hermes_cli.web_server_lifecycle as web_server_lifecycle from tests.hermes_cli.test_dashboard_auth_gate import _stub_uvicorn_run @@ -30,6 +31,7 @@ def test_desktop_serve_arms_mcp_discovery_only_after_ready_sentinel(monkeypatch) lambda *, logger, thread_name: order.append("discovery:" + thread_name), ) monkeypatch.setattr(web_server, "_write_machine_sentinel_line", lambda line: order.append("sentinel")) + monkeypatch.setattr(web_server_lifecycle, "_write_machine_sentinel_line", lambda line: order.append("sentinel")) _stub_uvicorn_run(monkeypatch) web_server.start_server( diff --git a/tests/hermes_cli/test_serve_runtime_inventory.py b/tests/hermes_cli/test_serve_runtime_inventory.py index 40481462fc..553ba4d410 100644 --- a/tests/hermes_cli/test_serve_runtime_inventory.py +++ b/tests/hermes_cli/test_serve_runtime_inventory.py @@ -16,6 +16,8 @@ from unittest.mock import patch # noqa: F401 - kept for parity with siblings import hermes_cli.update_cmd as update_cmd import hermes_cli.update_inventory as update_inventory from hermes_cli import main as cli_main +import hermes_cli.main_install_repair as main_install_repair +import hermes_cli.main_dashboard as main_dashboard def _ledger_entry(**over): @@ -142,6 +144,7 @@ def test_ledger_manual_serve_holders_filters_correctly(monkeypatch): def test_serve_relaunch_commands_built_from_structured_identity(monkeypatch): monkeypatch.setattr(cli_main, "_venv_scripts_dir", lambda: None) + monkeypatch.setattr(main_install_repair, "_venv_scripts_dir", lambda: None) entries = [ _ledger_entry(), # default profile _ledger_entry(pid=5000, profile="work", port=9200, host=""), @@ -160,7 +163,11 @@ def test_relaunch_stopped_serves_is_idempotent(monkeypatch): monkeypatch.setattr( cli_main, "_respawn_dashboard_processes", lambda cmds: calls.append(cmds) or [] ) + monkeypatch.setattr( + main_dashboard, "_respawn_dashboard_processes", lambda cmds: calls.append(cmds) or [] + ) monkeypatch.setattr(cli_main, "_venv_scripts_dir", lambda: None) + monkeypatch.setattr(main_install_repair, "_venv_scripts_dir", lambda: None) token = {"pending": True, "entries": [_ledger_entry()]} update_cmd._relaunch_stopped_serves(token) @@ -175,6 +182,9 @@ def test_relaunch_stopped_serves_untriggered_token_noop(monkeypatch): monkeypatch.setattr( cli_main, "_respawn_dashboard_processes", lambda cmds: calls.append(cmds) or [] ) + monkeypatch.setattr( + main_dashboard, "_respawn_dashboard_processes", lambda cmds: calls.append(cmds) or [] + ) update_cmd._relaunch_stopped_serves({"pending": False, "entries": [_ledger_entry()]}) assert calls == [] diff --git a/tests/hermes_cli/test_shim_fail_closed_windows_live.py b/tests/hermes_cli/test_shim_fail_closed_windows_live.py index 9c109a8f9f..73d9446a99 100644 --- a/tests/hermes_cli/test_shim_fail_closed_windows_live.py +++ b/tests/hermes_cli/test_shim_fail_closed_windows_live.py @@ -75,6 +75,7 @@ def test_locked_shim_really_cannot_be_renamed(held_shim): def test_strict_quarantine_refuses_against_real_lock(held_shim, monkeypatch): import hermes_cli.main as cli_main + import hermes_cli.main_install_repair as hermes_cli_main_install_repair scripts, _shim = held_shim install_ran: list = [] @@ -83,6 +84,11 @@ def test_strict_quarantine_refuses_against_real_lock(held_shim, monkeypatch): "_run_install_with_heartbeat", lambda cmd, env=None: install_ran.append(cmd), ) + monkeypatch.setattr( + hermes_cli_main_install_repair, + "_run_install_with_heartbeat", + lambda cmd, env=None: install_ran.append(cmd), + ) with pytest.raises(main_install_repair.ShimQuarantineError) as exc_info: main_install_repair._run_quarantined_install( @@ -114,6 +120,7 @@ def test_recovery_installer_refuses_against_real_lock(held_shim, monkeypatch): def test_release_then_strict_quarantine_succeeds(tmp_path, monkeypatch): """After the holder exits, the same strict path proceeds normally.""" import hermes_cli.main as cli_main + import hermes_cli.main_install_repair as hermes_cli_main_install_repair scripts = tmp_path / "venv" / "Scripts" scripts.mkdir(parents=True) @@ -135,6 +142,11 @@ def test_release_then_strict_quarantine_succeeds(tmp_path, monkeypatch): "_run_install_with_heartbeat", lambda cmd, env=None: install_ran.append(cmd), ) + monkeypatch.setattr( + hermes_cli_main_install_repair, + "_run_install_with_heartbeat", + lambda cmd, env=None: install_ran.append(cmd), + ) main_install_repair._run_quarantined_install( ["fake"], scripts_dir=scripts, strict_quarantine=True ) diff --git a/tests/hermes_cli/test_sibling_config_migration.py b/tests/hermes_cli/test_sibling_config_migration.py index 3716dc6a0c..a9847ba2d7 100644 --- a/tests/hermes_cli/test_sibling_config_migration.py +++ b/tests/hermes_cli/test_sibling_config_migration.py @@ -14,6 +14,7 @@ import yaml from pathlib import Path import hermes_cli.update_cmd as update_cmd +import hermes_cli.update_cmd_config as update_cmd_config def _write_profile(root: Path, name: str, version: int) -> Path: @@ -44,6 +45,9 @@ def _setup(monkeypatch, tmp_path, active_home: Path): monkeypatch.setattr( update_cmd, "_reload_config_modules", lambda: None ) # module reload is orthogonal here; the real one re-imports from disk + monkeypatch.setattr( + update_cmd_config, "_reload_config_modules", lambda: None + ) # module reload is orthogonal here; the real one re-imports from disk def test_sibling_behind_is_migrated_on_disk(monkeypatch, tmp_path): diff --git a/tests/hermes_cli/test_spotify_auth.py b/tests/hermes_cli/test_spotify_auth.py index d9e66c66ce..07b86c6f4f 100644 --- a/tests/hermes_cli/test_spotify_auth.py +++ b/tests/hermes_cli/test_spotify_auth.py @@ -5,6 +5,7 @@ from types import SimpleNamespace import pytest from hermes_cli import auth as auth_mod +import hermes_cli.auth_spotify as auth_spotify from hermes_cli.auth import AuthError, resolve_spotify_runtime_credentials @@ -46,6 +47,15 @@ def test_resolve_spotify_runtime_credentials_refreshes_without_changing_active_p "expires_at": "2099-01-01T00:00:00+00:00", }, ) + monkeypatch.setattr( + auth_spotify, + "_refresh_spotify_oauth_state", + lambda state, timeout_seconds=20.0: { + **state, + "access_token": "fresh-token", + "expires_at": "2099-01-01T00:00:00+00:00", + }, + ) creds = auth_mod.resolve_spotify_runtime_credentials() @@ -131,6 +141,7 @@ def test_resolve_credentials_quarantines_dead_tokens_on_terminal_refresh_failure ) monkeypatch.setattr(auth_mod, "_refresh_spotify_oauth_state", _terminal_refresh) + monkeypatch.setattr(auth_spotify, "_refresh_spotify_oauth_state", _terminal_refresh) with pytest.raises(AuthError) as exc_info: resolve_spotify_runtime_credentials(force_refresh=True) diff --git a/tests/hermes_cli/test_subscription_cli.py b/tests/hermes_cli/test_subscription_cli.py index 5a16a8ce04..b5098f5c6f 100644 --- a/tests/hermes_cli/test_subscription_cli.py +++ b/tests/hermes_cli/test_subscription_cli.py @@ -132,8 +132,10 @@ def test_open_url_in_browser_refuses_remote_session(cli, monkeypatch): import webbrowser import hermes_cli.auth as auth + import hermes_cli.auth_device_flow as auth_device_flow monkeypatch.setattr(auth, "_is_remote_session", lambda: True, raising=False) + monkeypatch.setattr(auth_device_flow, "_is_remote_session", lambda: True, raising=False) called = {"n": 0} monkeypatch.setattr(webbrowser, "open", lambda url: called.update(n=called["n"] + 1) or True) diff --git a/tests/hermes_cli/test_update_autostash.py b/tests/hermes_cli/test_update_autostash.py index cb9ca5cb0e..5f4c7eb179 100644 --- a/tests/hermes_cli/test_update_autostash.py +++ b/tests/hermes_cli/test_update_autostash.py @@ -764,6 +764,7 @@ def test_restore_rejects_invalid_python_and_keeps_clean_updated_tree( """A cleanly-applied stash must not be allowed to brick every agent turn.""" import subprocess from hermes_cli import update_cmd + import hermes_cli.update_cmd_deps as update_cmd_deps def git(*args, check=True): return subprocess.run( @@ -787,6 +788,7 @@ def test_restore_rejects_invalid_python_and_keeps_clean_updated_tree( stash_ref = hermes_main._stash_local_changes_if_needed(["git"], tmp_path) assert stash_ref monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ()) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ()) with pytest.raises(SystemExit) as exc_info: hermes_main._restore_stashed_changes( @@ -809,6 +811,7 @@ def test_restore_rejects_new_import_time_failure_and_preserves_stash( """A valid-Python stash must not introduce a critical import failure.""" import subprocess from hermes_cli import update_cmd + import hermes_cli.update_cmd_deps as update_cmd_deps def git(*args, check=True): return subprocess.run( @@ -831,6 +834,7 @@ def test_restore_rejects_new_import_time_failure_and_preserves_stash( stash_ref = hermes_main._stash_local_changes_if_needed(["git"], tmp_path) assert stash_ref monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) with pytest.raises(SystemExit) as exc_info: hermes_main._restore_stashed_changes( @@ -851,6 +855,7 @@ def test_restore_allows_preexisting_import_time_failure(monkeypatch, tmp_path): """A restore may proceed when it does not worsen an environment failure.""" import subprocess from hermes_cli import update_cmd + import hermes_cli.update_cmd_deps as update_cmd_deps def git(*args, check=True): return subprocess.run( @@ -876,6 +881,7 @@ def test_restore_allows_preexisting_import_time_failure(monkeypatch, tmp_path): stash_ref = hermes_main._stash_local_changes_if_needed(["git"], tmp_path) assert stash_ref monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) assert hermes_main._restore_stashed_changes( ["git"], tmp_path, stash_ref, prompt_user=False @@ -890,6 +896,7 @@ def test_restore_rejects_later_failure_masked_by_preexisting_failure( """Every critical module must be compared, not only the first failure.""" import subprocess from hermes_cli import update_cmd + import hermes_cli.update_cmd_deps as update_cmd_deps def git(*args, check=True): return subprocess.run( @@ -915,6 +922,7 @@ def test_restore_rejects_later_failure_masked_by_preexisting_failure( stash_ref = hermes_main._stash_local_changes_if_needed(["git"], tmp_path) assert stash_ref monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("first", "second")) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("first", "second")) with pytest.raises(SystemExit) as exc_info: hermes_main._restore_stashed_changes( @@ -937,6 +945,7 @@ def test_restore_rejects_system_exit_masked_by_preexisting_failure( """A terminating import must be compared instead of hiding the marker.""" import subprocess from hermes_cli import update_cmd + import hermes_cli.update_cmd_deps as update_cmd_deps def git(*args, check=True): return subprocess.run( @@ -962,6 +971,7 @@ def test_restore_rejects_system_exit_masked_by_preexisting_failure( stash_ref = hermes_main._stash_local_changes_if_needed(["git"], tmp_path) assert stash_ref monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("first", "second")) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("first", "second")) with pytest.raises(SystemExit) as exc_info: hermes_main._restore_stashed_changes( @@ -982,6 +992,7 @@ def test_restore_rejects_probe_termination(monkeypatch, tmp_path, capsys): """A stash cannot bypass import validation by terminating the probe.""" import subprocess from hermes_cli import update_cmd + import hermes_cli.update_cmd_deps as update_cmd_deps def git(*args, check=True): return subprocess.run( @@ -1004,6 +1015,7 @@ def test_restore_rejects_probe_termination(monkeypatch, tmp_path, capsys): stash_ref = hermes_main._stash_local_changes_if_needed(["git"], tmp_path) assert stash_ref monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) with pytest.raises(SystemExit) as exc_info: hermes_main._restore_stashed_changes( @@ -1025,8 +1037,10 @@ def test_restore_stays_parked_when_untracked_baseline_is_unknown( ): """Unknown cleanup scope must not turn into a destructive empty baseline.""" from hermes_cli import update_cmd + import hermes_cli.update_cmd_stash as update_cmd_stash monkeypatch.setattr(update_cmd, "_git_untracked_paths", lambda *_args: None) + monkeypatch.setattr(update_cmd_stash, "_git_untracked_paths", lambda *_args: None) restored = hermes_main._restore_stashed_changes( ["git"], tmp_path, "stash@{0}", prompt_user=False @@ -1043,8 +1057,10 @@ def test_reject_does_not_claim_cleanup_when_git_state_is_unknown( ): """Cleanup failures must not be reported as a restored clean tree.""" from hermes_cli import update_cmd + import hermes_cli.update_cmd_stash as update_cmd_stash monkeypatch.setattr(update_cmd, "_git_untracked_paths", lambda *_args: None) + monkeypatch.setattr(update_cmd_stash, "_git_untracked_paths", lambda *_args: None) with pytest.raises(SystemExit): update_cmd._reject_unsafe_stash_restore( @@ -1062,6 +1078,8 @@ def test_restore_rejects_unknown_restored_python_paths( """A failed post-apply path query cannot skip restored syntax validation.""" import subprocess from hermes_cli import update_cmd + import hermes_cli.update_cmd_stash as update_cmd_stash + import hermes_cli.update_cmd_deps as update_cmd_deps def git(*args, check=True): return subprocess.run( @@ -1083,7 +1101,9 @@ def test_restore_rejects_unknown_restored_python_paths( stash_ref = hermes_main._stash_local_changes_if_needed(["git"], tmp_path) assert stash_ref monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ()) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ()) monkeypatch.setattr(update_cmd, "_restored_python_paths", lambda *_args: None) + monkeypatch.setattr(update_cmd_stash, "_restored_python_paths", lambda *_args: None) with pytest.raises(SystemExit) as exc_info: hermes_main._restore_stashed_changes( diff --git a/tests/hermes_cli/test_update_cold_start_gateway_liveness.py b/tests/hermes_cli/test_update_cold_start_gateway_liveness.py index 5b779291a9..cac4ae3a0b 100644 --- a/tests/hermes_cli/test_update_cold_start_gateway_liveness.py +++ b/tests/hermes_cli/test_update_cold_start_gateway_liveness.py @@ -15,11 +15,13 @@ import pytest from hermes_cli import gateway as hermes_gateway from hermes_cli import gateway_windows from hermes_cli import main as cli_main +import hermes_cli.main_install_repair as main_install_repair from hermes_cli import update_cmd def _run_cold_start(monkeypatch, capsys, *, surviving_pids): monkeypatch.setattr(cli_main, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) # The pre-spawn re-check (``all_profiles=True``) must find nothing # running so the cold-start path proceeds and actually spawns. diff --git a/tests/hermes_cli/test_update_concurrent_quarantine.py b/tests/hermes_cli/test_update_concurrent_quarantine.py index add237e2ba..3e93f27dbc 100644 --- a/tests/hermes_cli/test_update_concurrent_quarantine.py +++ b/tests/hermes_cli/test_update_concurrent_quarantine.py @@ -278,6 +278,7 @@ def test_pause_and_resume_windows_gateway_service( afterward instead of spawning a competing detached gateway.""" import hermes_cli.gateway as gateway_mod import hermes_cli.update_cmd as update_cmd + import hermes_cli.update_cmd_windows as update_cmd_windows profile_home = tmp_path / "profiles" / "default" profile_home.mkdir(parents=True) @@ -312,12 +313,24 @@ def test_pause_and_resume_windows_gateway_service( lambda name, **_kwargs: stopped.append(name), raising=False, ) + monkeypatch.setattr( + update_cmd_windows, + "_stop_windows_gateway_service", + lambda name, **_kwargs: stopped.append(name), + raising=False, + ) monkeypatch.setattr( update_cmd, "_start_windows_gateway_service", lambda name: started.append(name), raising=False, ) + monkeypatch.setattr( + update_cmd_windows, + "_start_windows_gateway_service", + lambda name: started.append(name), + raising=False, + ) monkeypatch.setattr(cli_main, "_refresh_windows_gateway_launchers", lambda: None) monkeypatch.setattr( cli_main, @@ -350,6 +363,7 @@ def test_pause_windows_gateway_service_failure_restores_every_attempted_service( """A service that times out after accepting stop is restarted too.""" import hermes_cli.gateway as gateway_mod import hermes_cli.update_cmd as update_cmd + import hermes_cli.update_cmd_windows as update_cmd_windows services = [ SimpleNamespace(name="HermesGateway", service_pid=11, service_create_time=11.0, gateway_pid=101, gateway_create_time=101.0, descendant_identities=()), @@ -366,12 +380,19 @@ def test_pause_windows_gateway_service_failure_restores_every_attempted_service( restarted = [] monkeypatch.setattr(update_cmd, "_stop_windows_gateway_service", fake_stop) + monkeypatch.setattr(update_cmd_windows, "_stop_windows_gateway_service", fake_stop) monkeypatch.setattr( update_cmd, "_restore_windows_gateway_service", lambda name: restarted.append(name), raising=False, ) + monkeypatch.setattr( + update_cmd_windows, + "_restore_windows_gateway_service", + lambda name: restarted.append(name), + raising=False, + ) with pytest.raises(RuntimeError, match="HermesGatewayPicasso"): cli_main._pause_windows_gateways_for_update() @@ -386,6 +407,7 @@ def test_pause_windows_gateway_service_surfaces_rollback_start_failure( ): import hermes_cli.gateway as gateway_mod import hermes_cli.update_cmd as update_cmd + import hermes_cli.update_cmd_windows as update_cmd_windows services = [ SimpleNamespace(name="HermesGateway", service_pid=11, service_create_time=11.0, gateway_pid=101, gateway_create_time=101.0, descendant_identities=()), @@ -405,12 +427,19 @@ def test_pause_windows_gateway_service_surfaces_rollback_start_failure( raise RuntimeError("simulated rollback start failure") monkeypatch.setattr(update_cmd, "_stop_windows_gateway_service", fake_stop) + monkeypatch.setattr(update_cmd_windows, "_stop_windows_gateway_service", fake_stop) monkeypatch.setattr( update_cmd, "_restore_windows_gateway_service", fake_start, raising=False, ) + monkeypatch.setattr( + update_cmd_windows, + "_restore_windows_gateway_service", + fake_start, + raising=False, + ) with pytest.raises(RuntimeError, match="rollback failures: HermesGateway"): cli_main._pause_windows_gateways_for_update() @@ -418,6 +447,7 @@ def test_pause_windows_gateway_service_surfaces_rollback_start_failure( def test_restore_windows_gateway_service_waits_out_stop_pending(monkeypatch): import hermes_cli.update_cmd as update_cmd + import hermes_cli.update_cmd_windows as update_cmd_windows statuses = iter(["stop_pending", "stopped"]) service = SimpleNamespace(status=lambda: next(statuses)) @@ -430,6 +460,11 @@ def test_restore_windows_gateway_service_waits_out_stop_pending(monkeypatch): "_start_windows_gateway_service", lambda name: restarted.append(name), ) + monkeypatch.setattr( + update_cmd_windows, + "_start_windows_gateway_service", + lambda name: restarted.append(name), + ) update_cmd._restore_windows_gateway_service("HermesGateway") @@ -509,6 +544,7 @@ def test_resume_windows_gateway_service_failure_stays_retryable( monkeypatch, ): import hermes_cli.update_cmd as update_cmd + import hermes_cli.update_cmd_windows as update_cmd_windows token = { "resume_needed": True, @@ -522,6 +558,11 @@ def test_resume_windows_gateway_service_failure_stays_retryable( "_start_windows_gateway_service", lambda _name: (_ for _ in ()).throw(RuntimeError("simulated start failure")), ) + monkeypatch.setattr( + update_cmd_windows, + "_start_windows_gateway_service", + lambda _name: (_ for _ in ()).throw(RuntimeError("simulated start failure")), + ) with pytest.raises(RuntimeError, match="HermesGateway"): cli_main._resume_windows_gateways_after_update(token) diff --git a/tests/hermes_cli/test_update_desktop_stale_warning.py b/tests/hermes_cli/test_update_desktop_stale_warning.py index 3596029bfe..012a40df6e 100644 --- a/tests/hermes_cli/test_update_desktop_stale_warning.py +++ b/tests/hermes_cli/test_update_desktop_stale_warning.py @@ -13,6 +13,7 @@ complete`` instead of the success line, and gateway mode writes ``1`` to import pytest from hermes_cli import update_cmd +import hermes_cli.update_cmd_maint as update_cmd_maint from hermes_cli.update_cmd import ( _print_update_summary, _rebuild_desktop_after_update, @@ -137,10 +138,16 @@ def test_summary_keeps_success_banner_when_desktop_ok(capsys, monkeypatch): monkeypatch.setattr( update_cmd, "_update_complete_message", lambda _v: "✓ Update complete! (v0.20.2)" ) + monkeypatch.setattr( + update_cmd_maint, "_update_complete_message", lambda _v: "✓ Update complete! (v0.20.2)" + ) monkeypatch.setattr(update_cmd, "_branch_head_suffix", lambda *a, **k: "") monkeypatch.setattr( update_cmd, "_post_update_sqlite_runtime_status", lambda: (True, None) ) + monkeypatch.setattr( + update_cmd_maint, "_post_update_sqlite_runtime_status", lambda: (True, None) + ) _print_update_summary( node_failures=[], desktop_build_ok=True, diff --git a/tests/hermes_cli/test_update_fleet_restart_pending.py b/tests/hermes_cli/test_update_fleet_restart_pending.py index 2a57f70788..156573a92e 100644 --- a/tests/hermes_cli/test_update_fleet_restart_pending.py +++ b/tests/hermes_cli/test_update_fleet_restart_pending.py @@ -22,7 +22,11 @@ from types import SimpleNamespace import pytest from hermes_cli import main as hermes_main +import hermes_cli.main_web_build as main_web_build +import hermes_cli.main_install_repair as main_install_repair from hermes_cli import update_cmd +import hermes_cli.update_cmd_fleet as update_cmd_fleet +import hermes_cli.update_cmd_deps as update_cmd_deps from hermes_constants import get_hermes_home @@ -77,6 +81,7 @@ def _patch_update_deps(monkeypatch, tmp_path, run_side_effect): (tmp_path / ".git").mkdir() monkeypatch.setattr(hermes_main, "_resolve_update_branch", lambda args: "main") monkeypatch.setattr(hermes_main, "_is_windows", lambda: False) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: False) monkeypatch.setattr( hermes_main, "_get_origin_url", @@ -90,6 +95,9 @@ def _patch_update_deps(monkeypatch, tmp_path, run_side_effect): monkeypatch.setattr( hermes_main, "_record_bytecode_fingerprint", lambda *a, **k: None ) + monkeypatch.setattr( + main_web_build, "_record_bytecode_fingerprint", lambda *a, **k: None + ) monkeypatch.setattr(hermes_main, "_run_pre_update_backup", lambda *a, **k: None) monkeypatch.setattr( hermes_main, "_pause_windows_gateways_for_update", lambda: None @@ -99,16 +107,19 @@ def _patch_update_deps(monkeypatch, tmp_path, run_side_effect): ) monkeypatch.setattr(hermes_main, "_write_update_incomplete_marker", lambda: None) monkeypatch.setattr(hermes_main, "_clear_update_incomplete_marker", lambda: None) + monkeypatch.setattr(main_install_repair, "_clear_update_incomplete_marker", lambda: None) monkeypatch.setattr(update_cmd, "_finish_dashboard_update_cleanup", lambda *a, **k: None ) monkeypatch.setattr( update_cmd, "_finish_dashboard_update_cleanup", lambda *a, **k: None ) monkeypatch.setattr(hermes_main, "_build_web_ui", lambda *a, **k: None) + monkeypatch.setattr(main_web_build, "_build_web_ui", lambda *a, **k: None) monkeypatch.setattr( update_cmd, "_venv_core_imports_healthy", lambda: (True, "") ) monkeypatch.setattr(update_cmd, "_update_node_dependencies", lambda: []) + monkeypatch.setattr(update_cmd_deps, "_update_node_dependencies", lambda: []) monkeypatch.setattr(update_cmd, "_purge_stale_hermes_modules", lambda: None) monkeypatch.setattr(hermes_main, "_purge_stale_hermes_modules", lambda: None) @@ -168,6 +179,7 @@ def test_pending_needed_when_unfinished_receipt_runtime_sha_skews(monkeypatch): disk_sha = "e" * 40 old_sha = "7" * 40 monkeypatch.setattr(update_cmd, "_current_checkout_sha", lambda: disk_sha) + monkeypatch.setattr(update_cmd_fleet, "_current_checkout_sha", lambda: disk_sha) receipt_dir = get_hermes_home() / "logs" / "update_receipts" receipt_dir.mkdir(parents=True) @@ -205,6 +217,7 @@ def test_successful_receipt_with_pre_update_plan_shas_does_not_retrigger( disk_sha = "n" * 40 old_sha = "o" * 40 monkeypatch.setattr(update_cmd, "_current_checkout_sha", lambda: disk_sha) + monkeypatch.setattr(update_cmd_fleet, "_current_checkout_sha", lambda: disk_sha) receipt_dir = get_hermes_home() / "logs" / "update_receipts" receipt_dir.mkdir(parents=True) @@ -244,6 +257,7 @@ def test_successful_receipt_with_pre_update_plan_shas_does_not_retrigger( def test_stale_fleet_matrix_on_latest_receipt_is_pending(monkeypatch): disk_sha = "n" * 40 monkeypatch.setattr(update_cmd, "_current_checkout_sha", lambda: disk_sha) + monkeypatch.setattr(update_cmd_fleet, "_current_checkout_sha", lambda: disk_sha) receipt_dir = get_hermes_home() / "logs" / "update_receipts" receipt_dir.mkdir(parents=True) @@ -430,6 +444,7 @@ def test_already_up_to_date_runs_pending_restart_when_marker_present( return True monkeypatch.setattr(update_cmd, "_run_pending_fleet_restart", _restart) + monkeypatch.setattr(update_cmd_fleet, "_run_pending_fleet_restart", _restart) hermes_main.cmd_update(args) @@ -447,6 +462,7 @@ def test_already_up_to_date_runs_pending_restart_when_receipt_skewed( disk_sha = "e" * 40 monkeypatch.setattr(update_cmd, "_current_checkout_sha", lambda: disk_sha) + monkeypatch.setattr(update_cmd_fleet, "_current_checkout_sha", lambda: disk_sha) receipt_dir = get_hermes_home() / "logs" / "update_receipts" receipt_dir.mkdir(parents=True) (receipt_dir / "latest.json").write_text( @@ -477,6 +493,11 @@ def test_already_up_to_date_runs_pending_restart_when_receipt_skewed( "_run_pending_fleet_restart", lambda: seen.__setitem__("ran", True) or True, ) + monkeypatch.setattr( + update_cmd_fleet, + "_run_pending_fleet_restart", + lambda: seen.__setitem__("ran", True) or True, + ) hermes_main.cmd_update(args) @@ -497,6 +518,11 @@ def test_already_up_to_date_skips_restart_when_nothing_pending( "_run_pending_fleet_restart", lambda: seen.__setitem__("ran", True) or True, ) + monkeypatch.setattr( + update_cmd_fleet, + "_run_pending_fleet_restart", + lambda: seen.__setitem__("ran", True) or True, + ) hermes_main.cmd_update(args) diff --git a/tests/hermes_cli/test_update_head_moved_gate.py b/tests/hermes_cli/test_update_head_moved_gate.py index d72a8476a0..0e3faafe7b 100644 --- a/tests/hermes_cli/test_update_head_moved_gate.py +++ b/tests/hermes_cli/test_update_head_moved_gate.py @@ -14,6 +14,8 @@ from types import SimpleNamespace import pytest from hermes_cli import main as hermes_main +import hermes_cli.main_web_build as main_web_build +import hermes_cli.main_install_repair as main_install_repair from hermes_cli import update_cmd @@ -80,6 +82,7 @@ def _patch_update_deps(monkeypatch, tmp_path, run_side_effect): hermes_main, "_resolve_update_branch", lambda args: "main" ) monkeypatch.setattr(hermes_main, "_is_windows", lambda: False) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: False) monkeypatch.setattr( hermes_main, "_get_origin_url", lambda *a, **k: "https://github.com/NousResearch/hermes-agent.git", @@ -92,6 +95,9 @@ def _patch_update_deps(monkeypatch, tmp_path, run_side_effect): monkeypatch.setattr( hermes_main, "_record_bytecode_fingerprint", lambda *a, **k: None ) + monkeypatch.setattr( + main_web_build, "_record_bytecode_fingerprint", lambda *a, **k: None + ) monkeypatch.setattr( hermes_main, "_run_pre_update_backup", lambda *a, **k: None ) @@ -104,6 +110,7 @@ def _patch_update_deps(monkeypatch, tmp_path, run_side_effect): # Short-circuit the long tail: dependency install + desktop build. monkeypatch.setattr(hermes_main, "_write_update_incomplete_marker", lambda: None) monkeypatch.setattr(hermes_main, "_clear_update_incomplete_marker", lambda: None) + monkeypatch.setattr(main_install_repair, "_clear_update_incomplete_marker", lambda: None) # Gateway restart path (called after a successful update). monkeypatch.setattr(update_cmd, "_finish_dashboard_update_cleanup", lambda *a, **k: None) # Keep the (now surfaced — #78574) gateway auto-restart phase away from diff --git a/tests/hermes_cli/test_update_import_guard.py b/tests/hermes_cli/test_update_import_guard.py index 097b5a2e0d..5318892be6 100644 --- a/tests/hermes_cli/test_update_import_guard.py +++ b/tests/hermes_cli/test_update_import_guard.py @@ -22,6 +22,7 @@ import pytest from hermes_cli import main as hermes_main from hermes_cli import update_cmd +import hermes_cli.update_cmd_deps as update_cmd_deps from hermes_constants import partial_update_hint @@ -57,6 +58,7 @@ def test_syntax_guard_passes_but_import_guard_catches_skew(monkeypatch, tmp_path # The import guard catches it. monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) ok, module, error = update_cmd._validate_critical_modules_import(tmp_path) assert ok is False assert module == "consumer" @@ -66,6 +68,7 @@ def test_syntax_guard_passes_but_import_guard_catches_skew(monkeypatch, tmp_path def test_import_guard_passes_on_consistent_tree(monkeypatch, tmp_path): _write_skewed_tree(tmp_path, skewed=False) monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) assert update_cmd._validate_critical_modules_import(tmp_path) == (True, None, None) @@ -77,6 +80,7 @@ def test_import_guard_ignores_non_import_errors(monkeypatch, tmp_path): "raise RuntimeError('no API key configured')\n" ) monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) ok, _, _ = update_cmd._validate_critical_modules_import(tmp_path) assert ok is True @@ -86,6 +90,7 @@ def test_import_guard_can_report_non_import_errors(monkeypatch, tmp_path): """Stash restore can compare runtime failures before and after apply.""" (tmp_path / "consumer.py").write_text("raise RuntimeError('broken config')\n") monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) ok, module, error = update_cmd._validate_critical_modules_import( tmp_path, report_runtime_errors=True @@ -102,6 +107,7 @@ def test_import_guard_can_report_missing_third_party_dependency( """Stash comparison must see newly introduced missing dependencies.""" (tmp_path / "consumer.py").write_text("import totally_not_installed_pkg\n") monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) ok, module, error = update_cmd._validate_critical_modules_import( tmp_path, report_runtime_errors=True @@ -115,6 +121,7 @@ def test_import_guard_can_report_missing_third_party_dependency( def test_import_failure_comparison_preserves_exception_type(monkeypatch, tmp_path): source = tmp_path / "consumer.py" monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) source.write_text("raise RuntimeError('stopped')\n") runtime_failure = update_cmd._critical_module_import_failures( tmp_path, report_runtime_errors=True @@ -134,6 +141,7 @@ def test_import_guard_reports_probe_termination_when_comparing_states( """A terminating import is unsafe when validating a restored stash.""" (tmp_path / "consumer.py").write_text("import os\nos._exit(7)\n") monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) ok, module, error = update_cmd._validate_critical_modules_import( tmp_path, report_runtime_errors=True @@ -148,6 +156,7 @@ def test_import_guard_reports_probe_termination_by_default(monkeypatch, tmp_path """A missing health marker must not classify a terminated probe as healthy.""" (tmp_path / "consumer.py").write_text("import os\nos._exit(9)\n") monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) ok, module, error = update_cmd._validate_critical_modules_import(tmp_path) @@ -160,6 +169,7 @@ def test_import_guard_reports_system_exit_by_default(monkeypatch, tmp_path): """Catchable terminating imports must not complete with a healthy marker.""" (tmp_path / "consumer.py").write_text("raise SystemExit('stopped')\n") monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) ok, module, error = update_cmd._validate_critical_modules_import(tmp_path) @@ -177,6 +187,7 @@ def test_import_guard_does_not_accept_forged_static_marker(monkeypatch, tmp_path "os._exit(7)\n" ) monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) ok, module, error = update_cmd._validate_critical_modules_import(tmp_path) @@ -311,6 +322,7 @@ def test_import_guard_ignores_missing_third_party_dependency(monkeypatch, tmp_pa """ (tmp_path / "consumer.py").write_text("import totally_not_installed_pkg\n") monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) assert update_cmd._validate_critical_modules_import(tmp_path) == (True, None, None) @@ -319,6 +331,7 @@ def test_import_guard_flags_missing_first_party_module(monkeypatch, tmp_path): """A missing *first-party* module IS skew — the update dropped a file.""" (tmp_path / "consumer.py").write_text("import tools.nonexistent_module\n") monkeypatch.setattr(update_cmd, "_UPDATE_CRITICAL_MODULES", ("consumer",)) + monkeypatch.setattr(update_cmd_deps, "_UPDATE_CRITICAL_MODULES", ("consumer",)) ok, module, error = update_cmd._validate_critical_modules_import(tmp_path) assert ok is False diff --git a/tests/hermes_cli/test_update_interrupted_recovery.py b/tests/hermes_cli/test_update_interrupted_recovery.py index 15c35f4a16..ea00951533 100644 --- a/tests/hermes_cli/test_update_interrupted_recovery.py +++ b/tests/hermes_cli/test_update_interrupted_recovery.py @@ -10,6 +10,7 @@ from __future__ import annotations from pathlib import Path import hermes_cli.main as m +import hermes_cli.main_install_repair as hermes_cli_main_install_repair from hermes_cli import main_install_repair from hermes_cli import update_cmd @@ -36,12 +37,14 @@ def test_marker_round_trip(tmp_path, monkeypatch): def _stub_install_env(monkeypatch, m, seen): """Common stubs so recovery's install path is inert and observable.""" + import hermes_cli.main_install_repair as hermes_cli_main_install_repair class R: returncode = 0 monkeypatch.setattr(m.subprocess, "run", lambda *a, **k: R()) monkeypatch.setattr(m, "_is_termux_env", lambda *a, **k: False) + monkeypatch.setattr(hermes_cli_main_install_repair, "_is_termux_env", lambda *a, **k: False) monkeypatch.setattr("hermes_cli.managed_uv.ensure_uv", lambda: None) # The install executor moved to hermes_cli._install_repair (shared between # the pre-import early pass and this late recovery path) — stub WHERE it @@ -70,7 +73,9 @@ def test_recovery_self_lock_does_not_clear_core_marker_via_import_probes( shim.write_text("") monkeypatch.setattr(m, "_is_windows", lambda: True) + monkeypatch.setattr(hermes_cli_main_install_repair, "_is_windows", lambda: True) monkeypatch.setattr(m, "_venv_scripts_dir", lambda: scripts_dir) + monkeypatch.setattr(hermes_cli_main_install_repair, "_venv_scripts_dir", lambda: scripts_dir) monkeypatch.setattr(main_install_repair, "_hermes_exe_shims", lambda d: [shim]) monkeypatch.setattr(main_install_repair, "_default_venv_install_target", lambda: (["uv", "pip"], {"VIRTUAL_ENV": str(tmp_path / "venv")}), @@ -78,6 +83,9 @@ def test_recovery_self_lock_does_not_clear_core_marker_via_import_probes( monkeypatch.setattr( m, "_repair_venv_via_import_probes", lambda *a, **k: "healthy" ) + monkeypatch.setattr( + hermes_cli_main_install_repair, "_repair_venv_via_import_probes", lambda *a, **k: "healthy" + ) class FakeProc: def __init__(self, exe_path): diff --git a/tests/hermes_cli/test_update_parked_branch_guard.py b/tests/hermes_cli/test_update_parked_branch_guard.py index 3d293c28fe..84b2f2e817 100644 --- a/tests/hermes_cli/test_update_parked_branch_guard.py +++ b/tests/hermes_cli/test_update_parked_branch_guard.py @@ -26,6 +26,8 @@ from types import SimpleNamespace import pytest from hermes_cli import main as hermes_main +import hermes_cli.main_web_build as main_web_build +import hermes_cli.main_install_repair as main_install_repair from hermes_cli import update_cmd @@ -251,6 +253,7 @@ def _patch_update_flow(monkeypatch, repo, run_real_git=True): monkeypatch.setattr(hermes_main, "PROJECT_ROOT", repo) monkeypatch.setattr(hermes_main, "_resolve_update_branch", lambda args: "main") monkeypatch.setattr(hermes_main, "_is_windows", lambda: False) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: False) monkeypatch.setattr( hermes_main, "_get_origin_url", lambda *a, **k: "https://github.com/NousResearch/hermes-agent.git", @@ -261,6 +264,7 @@ def _patch_update_flow(monkeypatch, repo, run_real_git=True): monkeypatch.setattr(update_cmd, "_normalize_managed_eol", lambda *a, **k: None) monkeypatch.setattr(hermes_main, "_clear_bytecode_cache", lambda *a, **k: 0) monkeypatch.setattr(hermes_main, "_record_bytecode_fingerprint", lambda *a, **k: None) + monkeypatch.setattr(main_web_build, "_record_bytecode_fingerprint", lambda *a, **k: None) monkeypatch.setattr(hermes_main, "_run_pre_update_backup", lambda *a, **k: None) monkeypatch.setattr(hermes_main, "_pause_windows_gateways_for_update", lambda: None) monkeypatch.setattr( diff --git a/tests/hermes_cli/test_update_sqlite_remediation.py b/tests/hermes_cli/test_update_sqlite_remediation.py index e2a280759a..e0dae08044 100644 --- a/tests/hermes_cli/test_update_sqlite_remediation.py +++ b/tests/hermes_cli/test_update_sqlite_remediation.py @@ -4,6 +4,8 @@ from pathlib import Path from types import SimpleNamespace from hermes_cli import update_cmd +import hermes_cli.update_cmd_maint as update_cmd_maint +import hermes_cli.update_cmd_deps as update_cmd_deps def test_runtime_status_probes_running_venv_outside_checkout(tmp_path, monkeypatch): @@ -31,11 +33,22 @@ def test_summary_withholds_success_when_sqlite_remediation_failed(capsys, monkey lambda: (False, SimpleNamespace(sqlite_version_string="3.46.1")), raising=False, ) + monkeypatch.setattr( + update_cmd_maint, + "_post_update_sqlite_runtime_status", + lambda: (False, SimpleNamespace(sqlite_version_string="3.46.1")), + raising=False, + ) monkeypatch.setattr( update_cmd, "_update_complete_message", lambda _version: "✓ Update complete! (v0.20.5)", ) + monkeypatch.setattr( + update_cmd_maint, + "_update_complete_message", + lambda _version: "✓ Update complete! (v0.20.5)", + ) complete = update_cmd._print_update_summary( node_failures=[], @@ -58,6 +71,11 @@ def test_current_checkout_completion_is_verified_before_success(capsys, monkeypa "_post_update_sqlite_runtime_status", lambda: (False, SimpleNamespace(sqlite_version_string="3.46.1")), ) + monkeypatch.setattr( + update_cmd_maint, + "_post_update_sqlite_runtime_status", + lambda: (False, SimpleNamespace(sqlite_version_string="3.46.1")), + ) complete = update_cmd._print_verified_update_completion("✓ Already up to date!") @@ -69,12 +87,18 @@ def test_current_checkout_completion_is_verified_before_success(capsys, monkeypa def test_current_checkout_repair_returns_verified_completion_result(monkeypatch): monkeypatch.setattr(update_cmd, "_update_node_dependencies", lambda: []) + monkeypatch.setattr(update_cmd_deps, "_update_node_dependencies", lambda: []) monkeypatch.setattr(update_cmd._m(), "_build_web_ui", lambda _path: None) monkeypatch.setattr( update_cmd, "_rebuild_desktop_after_update", lambda _dir, **_kwargs: True, ) + monkeypatch.setattr( + update_cmd_deps, + "_rebuild_desktop_after_update", + lambda _dir, **_kwargs: True, + ) complete = update_cmd._repair_node_deps_on_current_checkout( lambda _message: False diff --git a/tests/hermes_cli/test_update_venv_ownership_preflight.py b/tests/hermes_cli/test_update_venv_ownership_preflight.py index 56d9c67fcd..9a7820c2e4 100644 --- a/tests/hermes_cli/test_update_venv_ownership_preflight.py +++ b/tests/hermes_cli/test_update_venv_ownership_preflight.py @@ -16,6 +16,7 @@ import os import pytest from hermes_cli import update_cmd +import hermes_cli.update_cmd_deps as update_cmd_deps def _make_fake_venv(tmp_path): @@ -59,6 +60,7 @@ def test_foreign_owned_dist_info_child_detected(tmp_path, monkeypatch): return real_uid(path) monkeypatch.setattr(update_cmd, "_path_uid", fake_uid) + monkeypatch.setattr(update_cmd_deps, "_path_uid", fake_uid) foreign = update_cmd._venv_foreign_owned_paths(venv) assert foreign == [(installer, 0)] @@ -72,6 +74,11 @@ def test_foreign_owned_refuses_with_chown_hint(tmp_path, monkeypatch, capsys): "_path_uid", lambda p: 0 if str(p) == hermes_bin else real_uid(p), ) + monkeypatch.setattr( + update_cmd_deps, + "_path_uid", + lambda p: 0 if str(p) == hermes_bin else real_uid(p), + ) with pytest.raises(SystemExit) as exc: update_cmd._refuse_update_if_venv_foreign_owned(tmp_path) assert exc.value.code == 1 @@ -92,6 +99,11 @@ def test_limit_caps_reported_paths(tmp_path, monkeypatch): "_path_uid", lambda p: 0 if str(p).startswith(str(bin_dir) + os.sep) else 12345, ) + monkeypatch.setattr( + update_cmd_deps, + "_path_uid", + lambda p: 0 if str(p).startswith(str(bin_dir) + os.sep) else 12345, + ) monkeypatch.setattr(update_cmd.os, "geteuid", lambda: 12345, raising=False) foreign = update_cmd._venv_foreign_owned_paths(venv, limit=3) assert len(foreign) == 3 @@ -116,6 +128,7 @@ def test_running_as_root_returns_empty(tmp_path, monkeypatch): monkeypatch.setattr(update_cmd.os, "geteuid", lambda: 0, raising=False) # Even with foreign uids everywhere, root skips the gate. monkeypatch.setattr(update_cmd, "_path_uid", lambda p: 4242) + monkeypatch.setattr(update_cmd_deps, "_path_uid", lambda p: 4242) assert update_cmd._venv_foreign_owned_paths(venv) == [] diff --git a/tests/hermes_cli/test_update_zip_fallback_guards.py b/tests/hermes_cli/test_update_zip_fallback_guards.py index 61484de2cd..e68cf66a6f 100644 --- a/tests/hermes_cli/test_update_zip_fallback_guards.py +++ b/tests/hermes_cli/test_update_zip_fallback_guards.py @@ -17,6 +17,7 @@ from unittest.mock import patch import pytest from hermes_cli import main as hermes_main +import hermes_cli.main_install_repair as main_install_repair from hermes_cli import update_cmd @@ -74,18 +75,21 @@ def test_unknown_command_gets_generic_stage(): def test_windows_dep_failure_does_not_zip_fallback(monkeypatch): monkeypatch.setattr(hermes_main, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) exc = _cpe([r"C:\venv\Scripts\uv.exe", "pip", "install", "-e", "."]) assert update_cmd._should_zip_fallback_on_update_error(exc) is False def test_windows_git_failure_still_zips(monkeypatch): monkeypatch.setattr(hermes_main, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) exc = _cpe(["git", "pull"], returncode=1) assert update_cmd._should_zip_fallback_on_update_error(exc) is True def test_posix_git_failure_does_not_zip(monkeypatch): monkeypatch.setattr(hermes_main, "_is_windows", lambda: False) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: False) exc = _cpe(["git", "pull"], returncode=1) assert update_cmd._should_zip_fallback_on_update_error(exc) is False diff --git a/tests/hermes_cli/test_web_routers_tools_install_on_enable.py b/tests/hermes_cli/test_web_routers_tools_install_on_enable.py index 3c450994c8..60d415efd2 100644 --- a/tests/hermes_cli/test_web_routers_tools_install_on_enable.py +++ b/tests/hermes_cli/test_web_routers_tools_install_on_enable.py @@ -50,12 +50,13 @@ class TestToggleToolsetInstallOnEnable: self, monkeypatch ): import hermes_cli.tools_config as tools_config + import hermes_cli.tools_config_cua as tools_config_cua import hermes_cli.tools_config_post_setup as tools_config_post_setup calls = self._spawn_recorder(monkeypatch) # Binary missing → the cua_driver predicate reports unsatisfied. monkeypatch.setattr( - tools_config, "_resolved_cua_driver_cmd", lambda: None + tools_config_cua, "_resolved_cua_driver_cmd", lambda: None ) monkeypatch.setattr( tools_config_post_setup, "_cua_driver_install_ready", lambda: False @@ -77,11 +78,12 @@ class TestToggleToolsetInstallOnEnable: self, monkeypatch ): import hermes_cli.tools_config as tools_config + import hermes_cli.tools_config_cua as tools_config_cua import hermes_cli.tools_config_post_setup as tools_config_post_setup calls = self._spawn_recorder(monkeypatch) monkeypatch.setattr( - tools_config, "_resolved_cua_driver_cmd", lambda: "/usr/bin/cua-driver" + tools_config_cua, "_resolved_cua_driver_cmd", lambda: "/usr/bin/cua-driver" ) monkeypatch.setattr( tools_config_post_setup, "_cua_driver_install_ready", lambda: True @@ -96,11 +98,12 @@ class TestToggleToolsetInstallOnEnable: def test_disable_never_spawns_install(self, monkeypatch): import hermes_cli.tools_config as tools_config + import hermes_cli.tools_config_cua as tools_config_cua import hermes_cli.tools_config_post_setup as tools_config_post_setup calls = self._spawn_recorder(monkeypatch) monkeypatch.setattr( - tools_config, "_resolved_cua_driver_cmd", lambda: None + tools_config_cua, "_resolved_cua_driver_cmd", lambda: None ) monkeypatch.setattr( tools_config_post_setup, "_cua_driver_install_ready", lambda: False @@ -115,11 +118,12 @@ class TestToggleToolsetInstallOnEnable: def test_spawn_failure_does_not_fail_the_toggle(self, monkeypatch): import hermes_cli.tools_config as tools_config + import hermes_cli.tools_config_cua as tools_config_cua import hermes_cli.tools_config_post_setup as tools_config_post_setup import hermes_cli.web_server as web_server monkeypatch.setattr( - tools_config, "_resolved_cua_driver_cmd", lambda: None + tools_config_cua, "_resolved_cua_driver_cmd", lambda: None ) monkeypatch.setattr( tools_config_post_setup, "_cua_driver_install_ready", lambda: False diff --git a/tests/hermes_cli/test_windows_gateway_cold_start_desktop_lifecycle.py b/tests/hermes_cli/test_windows_gateway_cold_start_desktop_lifecycle.py index bc359d5c19..e1454f0fcc 100644 --- a/tests/hermes_cli/test_windows_gateway_cold_start_desktop_lifecycle.py +++ b/tests/hermes_cli/test_windows_gateway_cold_start_desktop_lifecycle.py @@ -14,8 +14,10 @@ from __future__ import annotations from hermes_cli import gateway as hermes_gateway from hermes_cli import gateway_windows from hermes_cli import main as cli_main +import hermes_cli.main_install_repair as main_install_repair from hermes_cli import process_identity from hermes_cli import update_cmd +import hermes_cli.update_cmd_windows as update_cmd_windows def _live_serve_ledger_entry() -> dict: @@ -86,24 +88,28 @@ def test_orphaned_control_plane_does_not_own_lifecycle(monkeypatch): def test_pause_skips_cold_start_plan_when_desktop_owns_lifecycle(monkeypatch): monkeypatch.setattr(cli_main, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) monkeypatch.setattr(hermes_gateway, "find_gateway_pids", lambda **_k: []) monkeypatch.setattr( hermes_gateway, "find_windows_gateway_services", lambda **_k: [] ) monkeypatch.setattr(gateway_windows, "is_installed", lambda: True) monkeypatch.setattr(update_cmd, "_desktop_owns_gateway_lifecycle", lambda: True) + monkeypatch.setattr(update_cmd_windows, "_desktop_owns_gateway_lifecycle", lambda: True) assert update_cmd._pause_windows_gateways_for_update() is None def test_pause_still_cold_starts_when_autostart_and_no_desktop_owner(monkeypatch): monkeypatch.setattr(cli_main, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) monkeypatch.setattr(hermes_gateway, "find_gateway_pids", lambda **_k: []) monkeypatch.setattr( hermes_gateway, "find_windows_gateway_services", lambda **_k: [] ) monkeypatch.setattr(gateway_windows, "is_installed", lambda: True) monkeypatch.setattr(update_cmd, "_desktop_owns_gateway_lifecycle", lambda: False) + monkeypatch.setattr(update_cmd_windows, "_desktop_owns_gateway_lifecycle", lambda: False) token = update_cmd._pause_windows_gateways_for_update() @@ -119,8 +125,10 @@ def test_pause_still_cold_starts_when_autostart_and_no_desktop_owner(monkeypatch def test_cold_start_aborts_when_desktop_owns_lifecycle(monkeypatch): spawned = [] monkeypatch.setattr(cli_main, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) monkeypatch.setattr(hermes_gateway, "find_gateway_pids", lambda **_k: []) monkeypatch.setattr(update_cmd, "_desktop_owns_gateway_lifecycle", lambda: True) + monkeypatch.setattr(update_cmd_windows, "_desktop_owns_gateway_lifecycle", lambda: True) monkeypatch.setattr( gateway_windows, "_spawn_detached", lambda: spawned.append(1) or 4242 ) diff --git a/tests/hermes_cli/test_windows_gateway_job_teardown_48820.py b/tests/hermes_cli/test_windows_gateway_job_teardown_48820.py index 285f0b1b6c..78d9dd5187 100644 --- a/tests/hermes_cli/test_windows_gateway_job_teardown_48820.py +++ b/tests/hermes_cli/test_windows_gateway_job_teardown_48820.py @@ -27,6 +27,7 @@ import pytest import hermes_cli.gateway as gateway import hermes_cli.gateway_windows as gateway_windows import hermes_cli.main as hm +import hermes_cli.main_install_repair as main_install_repair from hermes_cli._subprocess_compat import _WINDOWS_GATEWAY_BREAKAWAY_ENV from hermes_cli.update_cmd import _resume_windows_gateways_after_update @@ -119,6 +120,7 @@ class TestResumeLivenessGate: @pytest.fixture(autouse=True) def _windows(self, monkeypatch): monkeypatch.setattr(hm, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) monkeypatch.setattr(hm, "_refresh_windows_gateway_launchers", lambda: None) monkeypatch.setattr( gateway, "launch_detached_profile_gateway_restart", lambda *_a: True diff --git a/tests/hermes_cli/test_windows_update_restart_reconciliation.py b/tests/hermes_cli/test_windows_update_restart_reconciliation.py index 65bb3b0c82..33b8e6bc7e 100644 --- a/tests/hermes_cli/test_windows_update_restart_reconciliation.py +++ b/tests/hermes_cli/test_windows_update_restart_reconciliation.py @@ -26,6 +26,7 @@ import pytest import hermes_cli.gateway as gateway import hermes_cli.gateway_windows as gateway_windows import hermes_cli.main as hm +import hermes_cli.main_install_repair as main_install_repair from hermes_cli.update_cmd import _resume_windows_gateways_after_update from hermes_cli.update_inventory import ( RuntimeRecord, @@ -61,6 +62,7 @@ def _stub_post_relaunch_liveness(monkeypatch): def test_resume_records_successfully_relaunched_profiles_on_the_token(monkeypatch): monkeypatch.setattr(hm, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) monkeypatch.setattr(hm, "_refresh_windows_gateway_launchers", lambda: None) monkeypatch.setattr( gateway, "launch_detached_profile_gateway_restart", lambda *_a: True @@ -81,6 +83,7 @@ def test_resume_omits_profiles_whose_relaunch_failed(monkeypatch): 'relaunched' — it needs to keep surfacing as unaccounted so the user is told to restart it manually (Windows has no watcher to recover it).""" monkeypatch.setattr(hm, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) monkeypatch.setattr(hm, "_refresh_windows_gateway_launchers", lambda: None) def _relaunch(profile, _old_pid): @@ -110,6 +113,7 @@ def test_merged_windows_relaunch_resolves_as_restarted_not_unaccounted(monkeypat match_runtime_outcomes, must turn a Windows gateway's plan row from 'unaccounted' (loud warning + exit 1) into 'restarted' (clean).""" monkeypatch.setattr(hm, "_is_windows", lambda: True) + monkeypatch.setattr(main_install_repair, "_is_windows", lambda: True) monkeypatch.setattr(hm, "_refresh_windows_gateway_launchers", lambda: None) monkeypatch.setattr( gateway, "launch_detached_profile_gateway_restart", lambda *_a: True diff --git a/tests/test_resource_limits.py b/tests/test_resource_limits.py index 51a9e8c553..22018f30a7 100644 --- a/tests/test_resource_limits.py +++ b/tests/test_resource_limits.py @@ -205,6 +205,7 @@ async def test_gateway_startup_applies_limit_before_gateway_initialization(monke def test_serve_startup_applies_limit_before_web_server(monkeypatch): from hermes_cli import main as cli_main + import hermes_cli.main_web_build as main_web_build import hermes_cli.plugins import hermes_cli.web_server @@ -223,6 +224,7 @@ def test_serve_startup_applies_limit_before_web_server(monkeypatch): ) monkeypatch.setattr(cli_main, "_sync_bundled_skills_quietly", lambda: None) monkeypatch.setattr(cli_main, "_build_web_ui", lambda *args, **kwargs: True) + monkeypatch.setattr(main_web_build, "_build_web_ui", lambda *args, **kwargs: True) monkeypatch.setattr(cli_main, "_maybe_setup_dashboard_auth_interactively", lambda args: None) monkeypatch.setattr(hermes_cli.plugins, "discover_plugins", lambda: None) monkeypatch.setattr( @@ -320,6 +322,7 @@ def test_named_profile_reroute_defers_limit_to_final_process(monkeypatch, tmp_pa def test_dashboard_lifecycle_flags_skip_limit_adjustment(monkeypatch, lifecycle_flag): """Informational/stop-only commands must not mutate process limits.""" from hermes_cli import main as cli_main + import hermes_cli.main_dashboard as hermes_cli_main_dashboard calls: list[str] = [] monkeypatch.setattr( @@ -329,6 +332,7 @@ def test_dashboard_lifecycle_flags_skip_limit_adjustment(monkeypatch, lifecycle_ ) monkeypatch.setattr(dashboard_procs, "_scan_dashboard_processes", lambda: []) monkeypatch.setattr(cli_main, "_find_stale_dashboard_pids", lambda: []) + monkeypatch.setattr(hermes_cli_main_dashboard, "_find_stale_dashboard_pids", lambda: []) args = SimpleNamespace( status=lifecycle_flag == "status", diff --git a/tests/tools/test_approval_deny_rules.py b/tests/tools/test_approval_deny_rules.py index e809c7912c..90abde3d74 100644 --- a/tests/tools/test_approval_deny_rules.py +++ b/tests/tools/test_approval_deny_rules.py @@ -10,6 +10,7 @@ import os import pytest from tools import approval as mod +import tools.approval_floors as approval_floors from tools import approval_context @@ -109,6 +110,8 @@ class TestDenyOrdering: deny_config(["git push --force*"]) monkeypatch.setattr( mod, "_command_matches_permanent_allowlist", lambda c: True) + monkeypatch.setattr( + approval_floors, "_command_matches_permanent_allowlist", lambda c: True) result = mod.check_dangerous_command("git push --force origin main", "local") assert result["approved"] is False diff --git a/tests/tools/test_delegate_kanban_isolation.py b/tests/tools/test_delegate_kanban_isolation.py index ea08038645..27d45ddcc0 100644 --- a/tests/tools/test_delegate_kanban_isolation.py +++ b/tests/tools/test_delegate_kanban_isolation.py @@ -100,9 +100,11 @@ def test_build_child_agent_strips_kanban_toolset_even_when_parent_is_worker(monk import run_agent from tools import delegate_tool + import tools.delegate_tool_config as delegate_tool_config monkeypatch.setattr(run_agent, "AIAgent", FakeAgent) monkeypatch.setattr(delegate_tool, "_load_config", lambda: {}) + monkeypatch.setattr(delegate_tool_config, "_load_config", lambda: {}) class Parent: enabled_toolsets = ["terminal", "kanban"] diff --git a/tests/tools/test_denial_circuit_breaker.py b/tests/tools/test_denial_circuit_breaker.py index 63feb7b53e..e5b2627ef6 100644 --- a/tests/tools/test_denial_circuit_breaker.py +++ b/tests/tools/test_denial_circuit_breaker.py @@ -16,6 +16,8 @@ from __future__ import annotations import pytest from tools import approval as A +import tools.approval_prompt as approval_prompt +import tools.approval_detection as approval_detection from tools import approval_context from tools import approval_smart @@ -42,6 +44,10 @@ def breaker_session(monkeypatch): A, "detect_dangerous_command", lambda command: (True, "breaker-test-danger", f"risk:{command}"), ) + monkeypatch.setattr( + approval_detection, "detect_dangerous_command", + lambda command: (True, "breaker-test-danger", f"risk:{command}"), + ) monkeypatch.setattr( "tools.tirith_security.check_command_security", lambda _command: {"action": "allow", "findings": [], "summary": ""}, @@ -179,6 +185,10 @@ def test_headless_smart_deny_increments_and_trips(monkeypatch): A, "detect_dangerous_command", lambda command: (True, "headless-breaker-danger", f"risk:{command}"), ) + monkeypatch.setattr( + approval_detection, "detect_dangerous_command", + lambda command: (True, "headless-breaker-danger", f"risk:{command}"), + ) monkeypatch.setattr( "tools.tirith_security.check_command_security", lambda _command: {"action": "allow", "findings": [], "summary": ""}, @@ -187,6 +197,8 @@ def test_headless_smart_deny_increments_and_trips(monkeypatch): # CLI-interactive path: the owner denies via the prompt callback. monkeypatch.setattr(A, "prompt_dangerous_approval", lambda *args, **kwargs: "deny") + monkeypatch.setattr(approval_prompt, "prompt_dangerous_approval", + lambda *args, **kwargs: "deny") session_key = "headless-breaker-session" token = approval_context.set_current_session_key(session_key) diff --git a/tests/tools/test_execute_code_approval_cluster.py b/tests/tools/test_execute_code_approval_cluster.py index 0b2bd15aa9..a5664b1faf 100644 --- a/tests/tools/test_execute_code_approval_cluster.py +++ b/tests/tools/test_execute_code_approval_cluster.py @@ -23,6 +23,7 @@ import threading import pytest from tools import approval as A +import tools.approval_detection as approval_detection from tools import approval_context from tools import approval_context from tools import approval_smart @@ -294,6 +295,11 @@ def test_terminal_smart_deny_owner_override_is_one_operation(gw_session, monkeyp "detect_dangerous_command", lambda command: (True, "owner-override-test-danger", f"risk:{command}"), ) + monkeypatch.setattr( + approval_detection, + "detect_dangerous_command", + lambda command: (True, "owner-override-test-danger", f"risk:{command}"), + ) monkeypatch.setattr( "tools.tirith_security.check_command_security", lambda _command: {"action": "allow", "findings": [], "summary": ""}, @@ -349,6 +355,10 @@ def test_smart_escalate_still_persists_session_choice(gw_session, monkeypatch): A, "detect_dangerous_command", lambda command: (True, key, f"risk:{command}"), ) + monkeypatch.setattr( + approval_detection, "detect_dangerous_command", + lambda command: (True, key, f"risk:{command}"), + ) monkeypatch.setattr( "tools.tirith_security.check_command_security", lambda _command: {"action": "allow", "findings": [], "summary": ""}, @@ -371,6 +381,10 @@ def test_terminal_smart_deny_pending_payload_is_one_operation(gw_session, monkey A, "detect_dangerous_command", lambda command: (True, "pending-smart-deny", f"risk:{command}"), ) + monkeypatch.setattr( + approval_detection, "detect_dangerous_command", + lambda command: (True, "pending-smart-deny", f"risk:{command}"), + ) monkeypatch.setattr( "tools.tirith_security.check_command_security", lambda _command: {"action": "allow", "findings": [], "summary": ""}, diff --git a/tests/tools/test_request_tool_approval.py b/tests/tools/test_request_tool_approval.py index 94b1292b75..51b6e539e0 100644 --- a/tests/tools/test_request_tool_approval.py +++ b/tests/tools/test_request_tool_approval.py @@ -9,6 +9,8 @@ the gateway submit_pending path, cron_mode, and fail-closed timeouts. import pytest import tools.approval as approval +import tools.approval_prompt as approval_prompt +import tools.approval_context as tools_approval_context from tools import approval_context from tools.approval import request_tool_approval @@ -20,6 +22,10 @@ def _isolate_approval_state(monkeypatch): approval, "get_current_session_key", lambda default="default": "test-session", ) + monkeypatch.setattr( + tools_approval_context, "get_current_session_key", + lambda default="default": "test-session", + ) # Empty session + permanent approval stores so nothing pre-approves. monkeypatch.setattr(approval, "is_approved", lambda sk, pk: False) # Not a yolo session (the shared gate checks this first). @@ -40,13 +46,19 @@ class TestRequestToolApproval: approval, "prompt_dangerous_approval", lambda *a, **k: pytest.fail("should not prompt when already approved"), ) + monkeypatch.setattr( + approval_prompt, "prompt_dangerous_approval", + lambda *a, **k: pytest.fail("should not prompt when already approved"), + ) res = request_tool_approval("write_file", "sensitive path", rule_key="ssh") assert res == {"approved": True, "message": None} def test_cli_approve_once(self, monkeypatch): monkeypatch.setattr(approval, "_is_interactive_cli", lambda: True) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: False) monkeypatch.setattr(approval, "prompt_dangerous_approval", lambda *a, **k: "once") + monkeypatch.setattr(approval_prompt, "prompt_dangerous_approval", lambda *a, **k: "once") res = request_tool_approval("write_file", "writing ~/.ssh/authorized_keys") assert res["approved"] is True @@ -55,7 +67,9 @@ class TestRequestToolApproval: monkeypatch.setattr(approval, "_is_interactive_cli", lambda: True) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: False) monkeypatch.setattr(approval, "prompt_dangerous_approval", lambda *a, **k: "deny") + monkeypatch.setattr(approval_prompt, "prompt_dangerous_approval", lambda *a, **k: "deny") events = [] monkeypatch.setattr( lifecycle, @@ -84,7 +98,9 @@ class TestRequestToolApproval: def test_cli_session_persists_session_only(self, monkeypatch): monkeypatch.setattr(approval, "_is_interactive_cli", lambda: True) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: False) monkeypatch.setattr(approval, "prompt_dangerous_approval", lambda *a, **k: "session") + monkeypatch.setattr(approval_prompt, "prompt_dangerous_approval", lambda *a, **k: "session") calls = {"session": [], "permanent": []} monkeypatch.setattr(approval, "approve_session", lambda sk, pk: calls["session"].append(pk)) @@ -100,7 +116,9 @@ class TestRequestToolApproval: def test_cron_deny_mode_blocks(self, monkeypatch): monkeypatch.setattr(approval, "_is_interactive_cli", lambda: False) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: False) monkeypatch.setattr(approval, "_is_cron_approval_context", lambda: True) + monkeypatch.setattr(tools_approval_context, "_is_cron_approval_context", lambda: True) monkeypatch.setattr(approval_context, "_get_cron_approval_mode", lambda: "deny") res = request_tool_approval("terminal", "smtp send") assert res["approved"] is False @@ -109,7 +127,9 @@ class TestRequestToolApproval: def test_cron_approve_mode_allows(self, monkeypatch): monkeypatch.setattr(approval, "_is_interactive_cli", lambda: False) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: False) monkeypatch.setattr(approval, "_is_cron_approval_context", lambda: True) + monkeypatch.setattr(tools_approval_context, "_is_cron_approval_context", lambda: True) monkeypatch.setattr(approval_context, "_get_cron_approval_mode", lambda: "approve") res = request_tool_approval("terminal", "smtp send") assert res["approved"] is True @@ -120,7 +140,9 @@ class TestRequestToolApproval: allowlist entry (Finding 3: tool_name alone was too coarse).""" monkeypatch.setattr(approval, "_is_interactive_cli", lambda: True) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: False) monkeypatch.setattr(approval, "prompt_dangerous_approval", lambda *a, **k: "deny") + monkeypatch.setattr(approval_prompt, "prompt_dangerous_approval", lambda *a, **k: "deny") k1 = request_tool_approval("write_file", "write to ~/.ssh")["pattern_key"] k2 = request_tool_approval("write_file", "send an email")["pattern_key"] assert k1 != k2 @@ -128,7 +150,9 @@ class TestRequestToolApproval: def test_explicit_rule_key_overrides_derivation(self, monkeypatch): monkeypatch.setattr(approval, "_is_interactive_cli", lambda: True) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: False) monkeypatch.setattr(approval, "prompt_dangerous_approval", lambda *a, **k: "deny") + monkeypatch.setattr(approval_prompt, "prompt_dangerous_approval", lambda *a, **k: "deny") res = request_tool_approval("terminal", "any", rule_key="my-rule") assert res["pattern_key"] == "plugin_rule:my-rule" @@ -137,7 +161,9 @@ class TestRequestToolApproval: — a plugin-flagged action never runs ungated without a human.""" monkeypatch.setattr(approval, "_is_interactive_cli", lambda: False) monkeypatch.setattr(approval, "_is_gateway_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_gateway_approval_context", lambda: False) monkeypatch.setattr(approval, "_is_cron_approval_context", lambda: False) + monkeypatch.setattr(tools_approval_context, "_is_cron_approval_context", lambda: False) res = request_tool_approval("terminal", "smtp send") assert res["approved"] is False assert "no interactive user or gateway" in res["message"].lower() @@ -150,5 +176,9 @@ class TestRequestToolApproval: approval, "prompt_dangerous_approval", lambda *a, **k: pytest.fail("yolo must not prompt"), ) + monkeypatch.setattr( + approval_prompt, "prompt_dangerous_approval", + lambda *a, **k: pytest.fail("yolo must not prompt"), + ) res = request_tool_approval("terminal", "curl PUT", rule_key="ext") assert res == {"approved": True, "message": None} diff --git a/tests/tools/test_skills_sync_client.py b/tests/tools/test_skills_sync_client.py index dbe647069e..449194c9fd 100644 --- a/tests/tools/test_skills_sync_client.py +++ b/tests/tools/test_skills_sync_client.py @@ -310,8 +310,11 @@ class TestDevGate: ) # patch the lazily-imported symbol used inside resolve_identity import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", lambda **kw: {"api_key": token, "base_url": "https://x"}) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", + lambda **kw: {"api_key": token, "base_url": "https://x"}) ident = ssc.resolve_identity() assert ident["nous_admin"] is True assert ident["owner"] == "user1" @@ -319,34 +322,45 @@ class TestDevGate: def test_gate_closed_without_claim(self, monkeypatch): token = _jwt({"sub": "user1"}) # no tool_gateway_admin import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", lambda **kw: {"api_key": token, "base_url": "https://x"}) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", + lambda **kw: {"api_key": token, "base_url": "https://x"}) ident = ssc.resolve_identity() assert ident["nous_admin"] is False def test_gate_closed_when_claim_false(self, monkeypatch): token = _jwt({"sub": "u", "tool_gateway_admin": False}) import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", lambda **kw: {"api_key": token, "base_url": "https://x"}) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", + lambda **kw: {"api_key": token, "base_url": "https://x"}) assert ssc.resolve_identity()["nous_admin"] is False def test_maybe_push_inert_when_gate_closed(self, monkeypatch): token = _jwt({"sub": "u"}) import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", lambda **kw: {"api_key": token}) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", + lambda **kw: {"api_key": token}) monkeypatch.setattr(ssc, "resolve_sync_base_url", lambda: "http://x") # gate closed -> None (inert), never attempts a push assert ssc.maybe_push_skills() is None def test_maybe_pull_inert_when_not_logged_in(self, monkeypatch): import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous def _raise(**kw): raise RuntimeError("not logged in") monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", _raise) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", _raise) assert ssc.maybe_pull_skills() is None @@ -880,16 +894,22 @@ class TestOrgIdentityGate: # Personal org: NAS stamps NO org_role -> inert, not an error path. token = _jwt({"sub": "u", "org_id": "org-1"}) import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", lambda **kw: {"api_key": token, "base_url": "https://x"}) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", + lambda **kw: {"api_key": token, "base_url": "https://x"}) with pytest.raises(ssc.SyncInertError): ssc.resolve_org_identity() def test_org_identity_with_role(self, monkeypatch): token = _jwt({"sub": "u", "org_id": "org-9", "org_role": "MEMBER"}) import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", lambda **kw: {"api_key": token, "base_url": "https://x"}) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", + lambda **kw: {"api_key": token, "base_url": "https://x"}) ident = ssc.resolve_org_identity() assert ident["org_id"] == "org-9" assert ident["org_role"] == "MEMBER" @@ -1006,8 +1026,11 @@ class TestOrgEndToEnd: # Personal org: no org_role claim -> None, never raises. token = _jwt({"sub": "u", "org_id": "org-1"}) import hermes_cli.auth as auth_mod + import hermes_cli.auth_nous as auth_nous monkeypatch.setattr(auth_mod, "resolve_nous_runtime_credentials", lambda **kw: {"api_key": token}) + monkeypatch.setattr(auth_nous, "resolve_nous_runtime_credentials", + lambda **kw: {"api_key": token}) assert org.maybe_pull_org_skills() is None diff --git a/tests/tools/test_xai_http_credentials.py b/tests/tools/test_xai_http_credentials.py index 28f249b77e..88444d30ba 100644 --- a/tests/tools/test_xai_http_credentials.py +++ b/tests/tools/test_xai_http_credentials.py @@ -3,8 +3,10 @@ import pytest def _set_xai_oauth_unavailable(monkeypatch): from hermes_cli import auth + import hermes_cli.auth_xai as auth_xai monkeypatch.setattr(auth, "resolve_xai_oauth_runtime_credentials", lambda **_: {}) + monkeypatch.setattr(auth_xai, "resolve_xai_oauth_runtime_credentials", lambda **_: {}) def test_xai_credentials_fail_closed_without_profile_scope(tmp_path, monkeypatch):