/ tests / tools / test_terminal_tool.py
test_terminal_tool.py
  1  """Regression tests for sudo detection and sudo password handling."""
  2  
  3  import tools.terminal_tool as terminal_tool
  4  
  5  
  6  def setup_function():
  7      terminal_tool._reset_cached_sudo_passwords()
  8  
  9  
 10  def teardown_function():
 11      terminal_tool._reset_cached_sudo_passwords()
 12  
 13  
 14  def test_searching_for_sudo_does_not_trigger_rewrite(monkeypatch):
 15      monkeypatch.delenv("SUDO_PASSWORD", raising=False)
 16      monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
 17  
 18      command = "rg --line-number --no-heading --with-filename 'sudo' . | head -n 20"
 19      transformed, sudo_stdin = terminal_tool._transform_sudo_command(command)
 20  
 21      assert transformed == command
 22      assert sudo_stdin is None
 23  
 24  
 25  def test_printf_literal_sudo_does_not_trigger_rewrite(monkeypatch):
 26      monkeypatch.delenv("SUDO_PASSWORD", raising=False)
 27      monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
 28  
 29      command = "printf '%s\\n' sudo"
 30      transformed, sudo_stdin = terminal_tool._transform_sudo_command(command)
 31  
 32      assert transformed == command
 33      assert sudo_stdin is None
 34  
 35  
 36  def test_non_command_argument_named_sudo_does_not_trigger_rewrite(monkeypatch):
 37      monkeypatch.delenv("SUDO_PASSWORD", raising=False)
 38      monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
 39  
 40      command = "grep -n sudo README.md"
 41      transformed, sudo_stdin = terminal_tool._transform_sudo_command(command)
 42  
 43      assert transformed == command
 44      assert sudo_stdin is None
 45  
 46  
 47  def test_actual_sudo_command_uses_configured_password(monkeypatch):
 48      monkeypatch.setenv("SUDO_PASSWORD", "testpass")
 49      monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
 50  
 51      transformed, sudo_stdin = terminal_tool._transform_sudo_command("sudo apt install -y ripgrep")
 52  
 53      assert transformed == "sudo -S -p '' apt install -y ripgrep"
 54      assert sudo_stdin == "testpass\n"
 55  
 56  
 57  def test_actual_sudo_after_leading_env_assignment_is_rewritten(monkeypatch):
 58      monkeypatch.setenv("SUDO_PASSWORD", "testpass")
 59      monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
 60  
 61      transformed, sudo_stdin = terminal_tool._transform_sudo_command("DEBUG=1 sudo whoami")
 62  
 63      assert transformed == "DEBUG=1 sudo -S -p '' whoami"
 64      assert sudo_stdin == "testpass\n"
 65  
 66  
 67  def test_explicit_empty_sudo_password_tries_empty_without_prompt(monkeypatch):
 68      monkeypatch.setenv("SUDO_PASSWORD", "")
 69      monkeypatch.setenv("HERMES_INTERACTIVE", "1")
 70  
 71      def _fail_prompt(*_args, **_kwargs):
 72          raise AssertionError("interactive sudo prompt should not run for explicit empty password")
 73  
 74      monkeypatch.setattr(terminal_tool, "_prompt_for_sudo_password", _fail_prompt)
 75  
 76      transformed, sudo_stdin = terminal_tool._transform_sudo_command("sudo true")
 77  
 78      assert transformed == "sudo -S -p '' true"
 79      assert sudo_stdin == "\n"
 80  
 81  
 82  def test_cached_sudo_password_is_used_when_env_is_unset(monkeypatch):
 83      monkeypatch.delenv("SUDO_PASSWORD", raising=False)
 84      monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
 85      terminal_tool._set_cached_sudo_password("cached-pass")
 86  
 87      transformed, sudo_stdin = terminal_tool._transform_sudo_command("echo ok && sudo whoami")
 88  
 89      assert transformed == "echo ok && sudo -S -p '' whoami"
 90      assert sudo_stdin == "cached-pass\n"
 91  
 92  
 93  def test_cached_sudo_password_isolated_by_session_key(monkeypatch):
 94      monkeypatch.delenv("SUDO_PASSWORD", raising=False)
 95      monkeypatch.delenv("HERMES_INTERACTIVE", raising=False)
 96  
 97      monkeypatch.setenv("HERMES_SESSION_KEY", "session-a")
 98      terminal_tool._set_cached_sudo_password("alpha-pass")
 99  
100      monkeypatch.setenv("HERMES_SESSION_KEY", "session-b")
101      assert terminal_tool._get_cached_sudo_password() == ""
102  
103      monkeypatch.setenv("HERMES_SESSION_KEY", "session-a")
104      assert terminal_tool._get_cached_sudo_password() == "alpha-pass"
105  
106  
107  def test_passwordless_sudo_skips_interactive_prompt_and_rewrite(monkeypatch):
108      monkeypatch.delenv("SUDO_PASSWORD", raising=False)
109      monkeypatch.delenv("TERMINAL_ENV", raising=False)
110      monkeypatch.setenv("HERMES_INTERACTIVE", "1")
111  
112      def _fail_prompt(*_args, **_kwargs):
113          raise AssertionError(
114              "interactive sudo prompt should not run when sudo -n already works"
115          )
116  
117      monkeypatch.setattr(terminal_tool, "_prompt_for_sudo_password", _fail_prompt)
118      monkeypatch.setattr(terminal_tool, "_sudo_nopasswd_works", lambda: True, raising=False)
119  
120      transformed, sudo_stdin = terminal_tool._transform_sudo_command("sudo whoami")
121  
122      assert transformed == "sudo whoami"
123      assert sudo_stdin is None
124  
125  
126  def test_passwordless_sudo_probe_rechecks_local_terminal(monkeypatch):
127      monkeypatch.delenv("TERMINAL_ENV", raising=False)
128      calls = []
129  
130      class Result:
131          def __init__(self, returncode):
132              self.returncode = returncode
133  
134      def fake_run(args, **kwargs):
135          calls.append((args, kwargs))
136          return Result(0 if len(calls) == 1 else 1)
137  
138      monkeypatch.setattr(terminal_tool.subprocess, "run", fake_run)
139  
140      assert terminal_tool._sudo_nopasswd_works() is True
141      assert terminal_tool._sudo_nopasswd_works() is False
142      assert len(calls) == 2
143      assert calls[0][0] == ["sudo", "-n", "true"]
144      assert calls[1][0] == ["sudo", "-n", "true"]
145  
146  
147  def test_passwordless_sudo_probe_is_disabled_for_nonlocal_terminal_env(monkeypatch):
148      monkeypatch.setenv("TERMINAL_ENV", "docker")
149  
150      def _fail_run(*_args, **_kwargs):
151          raise AssertionError("host sudo probe must not run for non-local terminal envs")
152  
153      monkeypatch.setattr(terminal_tool.subprocess, "run", _fail_run)
154  
155      assert terminal_tool._sudo_nopasswd_works() is False
156  
157  
158  def test_validate_workdir_allows_windows_drive_paths():
159      assert terminal_tool._validate_workdir(r"C:\Users\Alice\project") is None
160      assert terminal_tool._validate_workdir("C:/Users/Alice/project") is None
161  
162  
163  def test_validate_workdir_allows_windows_unc_paths():
164      assert terminal_tool._validate_workdir(r"\\server\share\project") is None
165  
166  
167  def test_validate_workdir_blocks_shell_metacharacters_in_windows_paths():
168      assert terminal_tool._validate_workdir(r"C:\Users\Alice\project; rm -rf /")
169      assert terminal_tool._validate_workdir(r"C:\Users\Alice\project$(whoami)")
170      assert terminal_tool._validate_workdir("C:\\Users\\Alice\\project\nwhoami")