Files
hermes-agent/tests/hermes_cli/test_cmd_update.py
ethernet daa8dad6af test: drop updater tests a merge resurrected against retired code
890bbbda1f resolved modify/delete conflicts as main's side, bringing back
tests pm-clean had deleted or trimmed. 427d4936c2 cleaned up most of that
fallout; these were still broken:

- test_update_yes_flag.py, test_update_venv_ownership_preflight.py: import
  hermes_cli.update_cmd_deps, which PM removed (the first was also cut by the
  pm-test-audit merge f8bdc96033).
- test_ssl_ca_guard.py: imports SSLConfigurationError, removed with the TLS
  refactor 2f20ceb6f7.
- test_cmd_update.py: main's 37-test version needs main's
  isolated_update_runtime fixture and drives the old updater (lazy tool
  restore, Termux uv bootstrap, npm lockfile cache); back to pm-clean's
  audited 5 tests.
- test_tools_config.py: one import of the retired AGENT_BROWSER_NPX_SPEC
  failed the whole file, and main's twelve npx/Chromium post-setup tests
  reached a real `pm.ensure("agent-browser")` download. TestAgentBrowserPostSetup
  is pm-clean's own class again (pm.ensure stubbed); the Browser Use CLI
  contract test stays, with pm.ensure stubbed too.
2026-09-23 22:34:54 -04:00

130 lines
4.8 KiB
Python

"""Git trampoline recovery; branch updates use the real target-identity suite."""
import subprocess
from unittest.mock import patch
import pytest
from hermes_cli import update_cmd
@pytest.fixture(autouse=True)
def _isolate_venv_holders(monkeypatch):
"""The update flow's venv-holder guard sees the live gateway processes on
a dev machine and aborts with SystemExit 2 before reaching the branch
logic under test. Isolate it so the test exercises the intended path."""
monkeypatch.setattr("hermes_cli.update_cmd_windows._detect_venv_python_processes", lambda: [])
class TestGitTrampolineSelfHeal:
"""Proactive Git-for-Windows trampoline self-heal (#87876).
A broken bin\\git.exe / cmd\\git.exe shim (~46KB) refuses every git call
with a "BUG (fork bomb)" guard instead of re-execing the real git-core
binary. _ensure_non_trampoline_git detects this up front and swaps in a
real git binary when one can be located, so the normal git update path
survives instead of degrading to the ZIP fallback.
"""
@staticmethod
def _fake_run_healthy(command, **_kwargs):
return subprocess.CompletedProcess(
command, 0, stdout="git version 2.50.0.windows.1\n", stderr=""
)
@staticmethod
def _fake_run_trampoline(command, **_kwargs):
return subprocess.CompletedProcess(
command,
1,
stdout="",
stderr="BUG (fork bomb): tried to spawn itself, check your PATH\n",
)
@pytest.mark.platforms("windows")
def test_healthy_git_command_unchanged(self):
from hermes_cli import update_cmd
git_cmd = ["git", "-c", "windows.appendAtomically=false"]
with (
patch(
"hermes_cli.update_cmd.subprocess.run",
side_effect=self._fake_run_healthy,
),
patch("hermes_cli.update_cmd._locate_real_git") as locate,
):
result = update_cmd._ensure_non_trampoline_git(git_cmd)
assert result == git_cmd
locate.assert_not_called()
@pytest.mark.platforms("windows")
def test_trampoline_swaps_to_real_git(self, capsys):
from pathlib import Path
from hermes_cli import update_cmd
git_cmd = ["git", "-c", "windows.appendAtomically=false"]
real = Path(r"C:\Program Files\Git\mingw64\libexec\git-core\git.exe")
with (
patch(
"hermes_cli.update_cmd.subprocess.run",
side_effect=self._fake_run_trampoline,
),
patch(
"hermes_cli.update_cmd._locate_real_git", return_value=real
),
):
result = update_cmd._ensure_non_trampoline_git(git_cmd)
assert result == [str(real), "-c", "windows.appendAtomically=false"]
out = capsys.readouterr().out
assert "switching to real git" in out
@pytest.mark.platforms("windows")
def test_trampoline_no_real_git_keeps_command(self, capsys):
from hermes_cli import update_cmd
git_cmd = ["git", "-c", "windows.appendAtomically=false"]
with (
patch(
"hermes_cli.update_cmd.subprocess.run",
side_effect=self._fake_run_trampoline,
),
patch("hermes_cli.update_cmd._locate_real_git", return_value=None),
):
result = update_cmd._ensure_non_trampoline_git(git_cmd)
assert result == git_cmd
out = capsys.readouterr().out
assert "ZIP path" in out
@pytest.mark.platforms("not windows")
def test_off_windows_noop(self):
from hermes_cli import update_cmd
git_cmd = ["git"]
with patch("hermes_cli.update_cmd.subprocess.run") as run:
result = update_cmd._ensure_non_trampoline_git(git_cmd)
assert result == git_cmd
run.assert_not_called()
def test_portable_git_candidates_check_shared_root_first(self, tmp_path, monkeypatch):
# Profile-scoped layout: HERMES_HOME = <root>/profiles/foo, but the
# PortableGit tree lives under the SHARED root (monerostar review on
# #88136). The candidate list must check get_default_hermes_root()
# before the profile home.
import hermes_constants
from hermes_cli.update_cmd_git import _portable_git_candidates
root = tmp_path / "root"
profile_home = root / "profiles" / "foo"
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: root)
monkeypatch.setattr(hermes_constants, "get_hermes_home", lambda: profile_home)
candidates = _portable_git_candidates()
assert candidates[0] == (
root / "git" / "mingw64" / "libexec" / "git-core" / "git.exe"
)
assert candidates[1] == (
profile_home / "git" / "mingw64" / "libexec" / "git-core" / "git.exe"
)