hermes-agent/tests/tools/test_terminal_tool.py
alt-glitch 2bd9c9b881 opentui(phase3): launcher integration — HERMES_TUI_ENGINE dual-engine
hermes --tui launches the native OpenTUI engine (Bun) when
HERMES_TUI_ENGINE=opentui (env) or display.tui_engine=opentui (config);
Ink stays the default and the shipping path is untouched.

- _resolve_tui_engine() (env > config > ink); refuses opentui on
  Windows/Termux (no Bun) -> falls back to ink with a notice.
- _make_opentui_argv() -> [bun, src/entry.real.tsx] (no build step).
- _bun_bin() with HERMES_BUN override.
- Branch at top of _make_tui_argv BEFORE _ensure_tui_node (Bun-only host
  must not bootstrap Node).
- Gate _launch_tui NODE_OPTIONS/--max-old-space-size on engine==ink (Bun
  is JSC; the V8 flag errors/ignores).

Verified end-to-end via tmux: real hermes --tui -> Bun -> OpenTUI ->
real Python gateway streamed a real reply. No-flag default still ink.
2026-06-08 11:11:54 +00:00

170 lines
6 KiB
Python

"""Regression tests for sudo detection and sudo password handling."""
import tools.terminal_tool as terminal_tool
def setup_function():
terminal_tool._reset_cached_sudo_passwords()
def teardown_function():
terminal_tool._reset_cached_sudo_passwords()
def test_searching_for_sudo_does_not_trigger_rewrite(monkeypatch):
monkeypatch.delenv("SUDO_PASSWORD", raising=False)
monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
command = "rg --line-number --no-heading --with-filename 'sudo' . | head -n 20"
transformed, sudo_stdin = terminal_tool._transform_sudo_command(command)
assert transformed == command
assert sudo_stdin is None
def test_printf_literal_sudo_does_not_trigger_rewrite(monkeypatch):
monkeypatch.delenv("SUDO_PASSWORD", raising=False)
monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
command = "printf '%s\\n' sudo"
transformed, sudo_stdin = terminal_tool._transform_sudo_command(command)
assert transformed == command
assert sudo_stdin is None
def test_non_command_argument_named_sudo_does_not_trigger_rewrite(monkeypatch):
monkeypatch.delenv("SUDO_PASSWORD", raising=False)
monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
command = "grep -n sudo README.md"
transformed, sudo_stdin = terminal_tool._transform_sudo_command(command)
assert transformed == command
assert sudo_stdin is None
def test_actual_sudo_command_uses_configured_password(monkeypatch):
monkeypatch.setenv("SUDO_PASSWORD", "testpass")
monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
transformed, sudo_stdin = terminal_tool._transform_sudo_command("sudo apt install -y ripgrep")
assert transformed == "sudo -S -p '' apt install -y ripgrep"
assert sudo_stdin == "testpass\n"
def test_actual_sudo_after_leading_env_assignment_is_rewritten(monkeypatch):
monkeypatch.setenv("SUDO_PASSWORD", "testpass")
monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
transformed, sudo_stdin = terminal_tool._transform_sudo_command("DEBUG=1 sudo whoami")
assert transformed == "DEBUG=1 sudo -S -p '' whoami"
assert sudo_stdin == "testpass\n"
def test_explicit_empty_sudo_password_tries_empty_without_prompt(monkeypatch):
monkeypatch.setenv("SUDO_PASSWORD", "")
monkeypatch.setenv("HERMES_INTERACTIVE", "1")
def _fail_prompt(*_args, **_kwargs):
raise AssertionError("interactive sudo prompt should not run for explicit empty password")
monkeypatch.setattr(terminal_tool, "_prompt_for_sudo_password", _fail_prompt)
transformed, sudo_stdin = terminal_tool._transform_sudo_command("sudo true")
assert transformed == "sudo -S -p '' true"
assert sudo_stdin == "\n"
def test_cached_sudo_password_is_used_when_env_is_unset(monkeypatch):
monkeypatch.delenv("SUDO_PASSWORD", raising=False)
monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
terminal_tool._set_cached_sudo_password("cached-pass")
transformed, sudo_stdin = terminal_tool._transform_sudo_command("echo ok && sudo whoami")
assert transformed == "echo ok && sudo -S -p '' whoami"
assert sudo_stdin == "cached-pass\n"
def test_cached_sudo_password_isolated_by_session_key(monkeypatch):
monkeypatch.delenv("SUDO_PASSWORD", raising=False)
monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
monkeypatch.setenv("HERMES_SESSION_KEY", "session-a")
terminal_tool._set_cached_sudo_password("alpha-pass")
monkeypatch.setenv("HERMES_SESSION_KEY", "session-b")
assert terminal_tool._get_cached_sudo_password() == ""
monkeypatch.setenv("HERMES_SESSION_KEY", "session-a")
assert terminal_tool._get_cached_sudo_password() == "alpha-pass"
def test_passwordless_sudo_skips_interactive_prompt_and_rewrite(monkeypatch):
monkeypatch.delenv("SUDO_PASSWORD", raising=False)
monkeypatch.delenv("TERMINAL_ENV", raising=False)
monkeypatch.setenv("HERMES_INTERACTIVE", "1")
def _fail_prompt(*_args, **_kwargs):
raise AssertionError(
"interactive sudo prompt should not run when sudo -n already works"
)
monkeypatch.setattr(terminal_tool, "_prompt_for_sudo_password", _fail_prompt)
monkeypatch.setattr(terminal_tool, "_sudo_nopasswd_works", lambda: True, raising=False)
transformed, sudo_stdin = terminal_tool._transform_sudo_command("sudo whoami")
assert transformed == "sudo whoami"
assert sudo_stdin is None
def test_passwordless_sudo_probe_rechecks_local_terminal(monkeypatch):
monkeypatch.delenv("TERMINAL_ENV", raising=False)
calls = []
class Result:
def __init__(self, returncode):
self.returncode = returncode
def fake_run(args, **kwargs):
calls.append((args, kwargs))
return Result(0 if len(calls) == 1 else 1)
monkeypatch.setattr(terminal_tool.subprocess, "run", fake_run)
assert terminal_tool._sudo_nopasswd_works() is True
assert terminal_tool._sudo_nopasswd_works() is False
assert len(calls) == 2
assert calls[0][0] == ["sudo", "-n", "true"]
assert calls[1][0] == ["sudo", "-n", "true"]
def test_passwordless_sudo_probe_is_disabled_for_nonlocal_terminal_env(monkeypatch):
monkeypatch.setenv("TERMINAL_ENV", "docker")
def _fail_run(*_args, **_kwargs):
raise AssertionError("host sudo probe must not run for non-local terminal envs")
monkeypatch.setattr(terminal_tool.subprocess, "run", _fail_run)
assert terminal_tool._sudo_nopasswd_works() is False
def test_validate_workdir_allows_windows_drive_paths():
assert terminal_tool._validate_workdir(r"C:\Users\Alice\project") is None
assert terminal_tool._validate_workdir("C:/Users/Alice/project") is None
def test_validate_workdir_allows_windows_unc_paths():
assert terminal_tool._validate_workdir(r"\\server\share\project") is None
def test_validate_workdir_blocks_shell_metacharacters_in_windows_paths():
assert terminal_tool._validate_workdir(r"C:\Users\Alice\project; rm -rf /")
assert terminal_tool._validate_workdir(r"C:\Users\Alice\project$(whoami)")
assert terminal_tool._validate_workdir("C:\\Users\\Alice\\project\nwhoami")