mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Second, deeper pass over tools/gateway/hermes_cli plus first pass over the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker, dashboard, conformance, monitoring, secret_sources, hermes_state, providers). Same rubric as wave 1 (AGENTS.md test policy); security, alternation/caching invariants, issue-number regressions, and E2E kept. Real test-quality fixes found and rooted out along the way: - tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls (DEFAULT_CONFIG smart-approval leaked in) — pinned approval mode=manual via autouse fixture: 17.4s → 0.4s. - test_model_switch_custom_providers.py / test_user_providers_model_switch.py silently probed live provider catalogs (~2s/test) — stubbed cached_provider_model_ids/provider_model_ids/fetch_api_models. - test_telegram_noise_filter.py: 15-platform copy-paste matrix over shared gateway.run logic → 3 representative platforms (55s → 3.9s). - test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on MagicMock agents — interrupt.side_effect now clears _running_agents (22s → 1.0s). - test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait 5s → 0.5s. - test_telegram_init_deadline.py: loop-block margin restored to 1.0s with rationale comment — the watchdog-dump assertion needs the loop blocked well past deadline+grace under parallel load (flaked once in the 40-worker verification run at a 0.2s margin). Verification: full hermetic suite via scripts/run_tests.sh — 2,438 files, 21,718 tests passed, 0 failed, 293.9s wall. Suite totals vs original baseline: 46,820 → 19,757 test functions (−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s.
151 lines
5.1 KiB
Python
151 lines
5.1 KiB
Python
"""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
|
|
|