Files
nousresearch--hermes-agent/tests/tools/test_terminal_requirements.py
T
wehub-resource-sync b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:56:03 +08:00

188 lines
7.2 KiB
Python

import importlib
import logging
terminal_tool_module = importlib.import_module("tools.terminal_tool")
def _clear_terminal_env(monkeypatch):
"""Remove terminal env vars that could affect requirements checks."""
keys = [
"TERMINAL_ENV",
"TERMINAL_CONTAINER_CPU",
"TERMINAL_CONTAINER_DISK",
"TERMINAL_CONTAINER_MEMORY",
"TERMINAL_DOCKER_FORWARD_ENV",
"TERMINAL_DOCKER_VOLUMES",
"TERMINAL_LIFETIME_SECONDS",
"TERMINAL_MODAL_MODE",
"TERMINAL_SSH_HOST",
"TERMINAL_SSH_PORT",
"TERMINAL_SSH_USER",
"TERMINAL_TIMEOUT",
"MODAL_TOKEN_ID",
"MODAL_TOKEN_SECRET",
"HOME",
"USERPROFILE",
]
for key in keys:
monkeypatch.delenv(key, raising=False)
# Default: no Nous subscription — patch both the terminal_tool local
# binding and tool_backend_helpers (used by resolve_modal_backend_state).
monkeypatch.setattr(terminal_tool_module, "managed_nous_tools_enabled", lambda: False)
import tools.tool_backend_helpers as _tbh
monkeypatch.setattr(_tbh, "managed_nous_tools_enabled", lambda: False)
def test_local_terminal_requirements(monkeypatch, caplog):
"""Local backend uses Hermes' own LocalEnvironment wrapper."""
_clear_terminal_env(monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "local")
with caplog.at_level(logging.ERROR):
ok = terminal_tool_module.check_terminal_requirements()
assert ok is True
assert "Terminal requirements check failed" not in caplog.text
def test_unknown_terminal_env_logs_error_and_returns_false(monkeypatch, caplog):
_clear_terminal_env(monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "unknown-backend")
with caplog.at_level(logging.ERROR):
ok = terminal_tool_module.check_terminal_requirements()
assert ok is False
assert any(
"Unknown TERMINAL_ENV 'unknown-backend'" in record.getMessage()
for record in caplog.records
)
def test_ssh_backend_without_host_or_user_logs_and_returns_false(monkeypatch, caplog):
_clear_terminal_env(monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "ssh")
with caplog.at_level(logging.ERROR):
ok = terminal_tool_module.check_terminal_requirements()
assert ok is False
assert any(
"SSH backend selected but TERMINAL_SSH_HOST and TERMINAL_SSH_USER" in record.getMessage()
for record in caplog.records
)
def test_modal_backend_without_token_or_config_logs_specific_error(monkeypatch, caplog, tmp_path):
_clear_terminal_env(monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "modal")
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("USERPROFILE", str(tmp_path))
monkeypatch.setattr(terminal_tool_module, "is_managed_tool_gateway_ready", lambda _vendor: False)
monkeypatch.setattr(terminal_tool_module.importlib.util, "find_spec", lambda _name: object())
with caplog.at_level(logging.ERROR):
ok = terminal_tool_module.check_terminal_requirements()
assert ok is False
assert any(
"Modal backend selected but no direct Modal credentials/config was found" in record.getMessage()
for record in caplog.records
)
def test_modal_backend_with_managed_gateway_does_not_require_direct_creds_or_minisweagent(monkeypatch, tmp_path):
_clear_terminal_env(monkeypatch)
monkeypatch.setattr(terminal_tool_module, "managed_nous_tools_enabled", lambda: True)
import tools.tool_backend_helpers as _tbh
monkeypatch.setattr(_tbh, "managed_nous_tools_enabled", lambda: True)
monkeypatch.setenv("TERMINAL_ENV", "modal")
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("USERPROFILE", str(tmp_path))
monkeypatch.setenv("TERMINAL_MODAL_MODE", "managed")
monkeypatch.setattr(terminal_tool_module, "is_managed_tool_gateway_ready", lambda _vendor: True)
monkeypatch.setattr(
terminal_tool_module.importlib.util,
"find_spec",
lambda _name: (_ for _ in ()).throw(AssertionError("should not be called")),
)
assert terminal_tool_module.check_terminal_requirements() is True
def test_modal_backend_auto_mode_prefers_managed_gateway_over_direct_creds(monkeypatch, tmp_path):
_clear_terminal_env(monkeypatch)
monkeypatch.setattr(terminal_tool_module, "managed_nous_tools_enabled", lambda: True)
import tools.tool_backend_helpers as _tbh
monkeypatch.setattr(_tbh, "managed_nous_tools_enabled", lambda: True)
monkeypatch.setenv("TERMINAL_ENV", "modal")
monkeypatch.setenv("MODAL_TOKEN_ID", "tok-id")
monkeypatch.setenv("MODAL_TOKEN_SECRET", "tok-secret")
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("USERPROFILE", str(tmp_path))
monkeypatch.setattr(terminal_tool_module, "is_managed_tool_gateway_ready", lambda _vendor: True)
monkeypatch.setattr(
terminal_tool_module.importlib.util,
"find_spec",
lambda _name: (_ for _ in ()).throw(AssertionError("should not be called")),
)
assert terminal_tool_module.check_terminal_requirements() is True
def test_modal_backend_direct_mode_does_not_fall_back_to_managed(monkeypatch, caplog, tmp_path):
_clear_terminal_env(monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "modal")
monkeypatch.setenv("TERMINAL_MODAL_MODE", "direct")
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("USERPROFILE", str(tmp_path))
monkeypatch.setattr(terminal_tool_module, "is_managed_tool_gateway_ready", lambda _vendor: True)
with caplog.at_level(logging.ERROR):
ok = terminal_tool_module.check_terminal_requirements()
assert ok is False
assert any(
"TERMINAL_MODAL_MODE=direct" in record.getMessage()
for record in caplog.records
)
def test_modal_backend_managed_mode_does_not_fall_back_to_direct(monkeypatch, caplog, tmp_path):
_clear_terminal_env(monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "modal")
monkeypatch.setenv("TERMINAL_MODAL_MODE", "managed")
monkeypatch.setenv("MODAL_TOKEN_ID", "tok-id")
monkeypatch.setenv("MODAL_TOKEN_SECRET", "tok-secret")
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("USERPROFILE", str(tmp_path))
monkeypatch.setattr(terminal_tool_module, "is_managed_tool_gateway_ready", lambda _vendor: False)
with caplog.at_level(logging.ERROR):
ok = terminal_tool_module.check_terminal_requirements()
assert ok is False
assert any(
"Nous Tool Gateway access is not currently available" in record.getMessage()
for record in caplog.records
)
def test_modal_backend_managed_mode_without_feature_flag_logs_clear_error(monkeypatch, caplog, tmp_path):
_clear_terminal_env(monkeypatch)
monkeypatch.setenv("TERMINAL_ENV", "modal")
monkeypatch.setenv("TERMINAL_MODAL_MODE", "managed")
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("USERPROFILE", str(tmp_path))
monkeypatch.setattr(terminal_tool_module, "is_managed_tool_gateway_ready", lambda _vendor: False)
with caplog.at_level(logging.ERROR):
ok = terminal_tool_module.check_terminal_requirements()
assert ok is False
assert any(
"Nous Tool Gateway access is not currently available" in record.getMessage()
for record in caplog.records
)