"""Tests for the Command Installation check in hermes doctor.""" import sys import types from argparse import Namespace from pathlib import Path import pytest import hermes_cli.doctor as doctor_mod def _setup_doctor_env(monkeypatch, tmp_path, venv_name="venv"): """Create a minimal HERMES_HOME + PROJECT_ROOT for doctor tests.""" home = tmp_path / ".hermes" home.mkdir(parents=True, exist_ok=True) (home / "config.yaml").write_text("memory: {}\n", encoding="utf-8") project = tmp_path / "project" project.mkdir(exist_ok=True) # Create a fake venv entry point venv_bin_dir = project / venv_name / "bin" venv_bin_dir.mkdir(parents=True, exist_ok=True) hermes_bin = venv_bin_dir / "hermes" hermes_bin.write_text("#!/usr/bin/env python\n# entry point\n") hermes_bin.chmod(0o755) monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project) monkeypatch.setattr(doctor_mod, "_DHH", str(home)) # Stub model_tools so doctor doesn't fail on import fake_model_tools = types.SimpleNamespace( check_tool_availability=lambda *a, **kw: ([], []), TOOLSET_REQUIREMENTS={}, ) monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) # Stub auth checks try: from hermes_cli import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: pass # Stub httpx.get to avoid network calls try: import httpx monkeypatch.setattr(httpx, "get", lambda *a, **kw: types.SimpleNamespace(status_code=200)) except Exception: pass return home, project, hermes_bin def _run_doctor(fix=False): """Run doctor and capture stdout.""" import io import contextlib buf = io.StringIO() with contextlib.redirect_stdout(buf): doctor_mod.run_doctor(Namespace(fix=fix)) return buf.getvalue() class TestDoctorCommandInstallation: """Tests for the ◆ Command Installation section.""" @pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only") def test_fix_repairs_wrong_symlink(self, monkeypatch, tmp_path): home, project, hermes_bin = _setup_doctor_env(monkeypatch, tmp_path) # Create a symlink pointing to wrong target cmd_link_dir = tmp_path / ".local" / "bin" cmd_link_dir.mkdir(parents=True) cmd_link = cmd_link_dir / "hermes" wrong_target = tmp_path / "wrong_hermes" wrong_target.write_text("#!/usr/bin/env python\n") cmd_link.symlink_to(wrong_target) monkeypatch.setattr(Path, "home", lambda: tmp_path) out = _run_doctor(fix=True) assert "Fixed symlink" in out # Verify the symlink now points to the correct target assert cmd_link.is_symlink() assert cmd_link.resolve() == hermes_bin.resolve() @pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only") def test_missing_venv_entry_point_shows_warn(self, monkeypatch, tmp_path): home = tmp_path / ".hermes" home.mkdir(parents=True, exist_ok=True) (home / "config.yaml").write_text("memory: {}\n", encoding="utf-8") project = tmp_path / "project" project.mkdir(exist_ok=True) # Do NOT create any venv entry point monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project) monkeypatch.setattr(doctor_mod, "_DHH", str(home)) monkeypatch.setattr(Path, "home", lambda: tmp_path) fake_model_tools = types.SimpleNamespace( check_tool_availability=lambda *a, **kw: ([], []), TOOLSET_REQUIREMENTS={}, ) monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) try: from hermes_cli import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: pass try: import httpx monkeypatch.setattr(httpx, "get", lambda *a, **kw: types.SimpleNamespace(status_code=200)) except Exception: pass out = _run_doctor(fix=False) assert "Command Installation" in out assert "Venv entry point not found" in out @pytest.mark.skipif(sys.platform == "win32", reason="Symlink check is Unix-only") def test_termux_uses_prefix_bin(self, monkeypatch, tmp_path): """On Termux, the command link dir is $PREFIX/bin.""" prefix_dir = tmp_path / "termux_prefix" prefix_bin = prefix_dir / "bin" prefix_bin.mkdir(parents=True) home, project, hermes_bin = _setup_doctor_env(monkeypatch, tmp_path) monkeypatch.setenv("TERMUX_VERSION", "0.118.3") monkeypatch.setenv("PREFIX", str(prefix_dir)) monkeypatch.setattr(Path, "home", lambda: tmp_path) out = _run_doctor(fix=False) assert "Command Installation" in out assert "$PREFIX/bin" in out