hermes-agent/tests/hermes_cli/test_apply_profile_override.py
Teknium 39975613b1
test: prune wave 2 + speed fixes — 28,106 → 19,757 test functions, suite wall 315s → 294s
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.
2026-07-29 13:39:40 -07:00

166 lines
6.3 KiB
Python

"""Regression tests for _apply_profile_override HERMES_HOME guard (issue #22502).
When HERMES_HOME is set to the hermes root (e.g. systemd hardcodes
HERMES_HOME=/root/.hermes), _apply_profile_override must still read
active_profile and update HERMES_HOME to the profile directory.
When HERMES_HOME is already a profile directory (.../profiles/<name>),
_apply_profile_override must trust it and return without re-reading
active_profile (child-process inheritance contract).
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
from types import SimpleNamespace
def _run_apply_profile_override(
tmp_path, monkeypatch, *, hermes_home: str | None, active_profile: str | None,
argv: list[str] | None = None,
):
"""Run _apply_profile_override in isolation.
Returns the value of os.environ["HERMES_HOME"] after the call,
or None if unset.
"""
hermes_root = tmp_path / ".hermes"
hermes_root.mkdir(parents=True, exist_ok=True)
if active_profile is not None:
(hermes_root / "active_profile").write_text(active_profile)
if active_profile and active_profile != "default":
(hermes_root / "profiles" / active_profile).mkdir(parents=True, exist_ok=True)
monkeypatch.setattr(Path, "home", lambda: tmp_path)
if hermes_home is not None:
monkeypatch.setenv("HERMES_HOME", hermes_home)
else:
monkeypatch.delenv("HERMES_HOME", raising=False)
monkeypatch.setattr(sys, "argv", argv or ["hermes", "gateway", "start"])
from hermes_cli.main import _apply_profile_override
_apply_profile_override()
return os.environ.get("HERMES_HOME")
class TestApplyProfileOverrideHermesHomeGuard:
"""Regression guard for issue #22502.
Verifies that HERMES_HOME pointing to the hermes root does NOT suppress
the active_profile check, while HERMES_HOME already pointing to a
profile directory IS trusted as-is.
"""
def test_hermes_home_at_root_with_active_profile_is_redirected(
self, tmp_path, monkeypatch
):
"""HERMES_HOME=/root/.hermes + active_profile=coder must redirect
HERMES_HOME to .../profiles/coder.
Bug scenario from #22502: systemd sets HERMES_HOME to the hermes root
and the user switches to a profile via `hermes profile use`.
Before the fix, the guard returned early and active_profile was ignored.
"""
hermes_root = tmp_path / ".hermes"
hermes_root.mkdir(parents=True, exist_ok=True)
result = _run_apply_profile_override(
tmp_path,
monkeypatch,
hermes_home=str(hermes_root),
active_profile="coder",
)
assert result is not None, "HERMES_HOME must be set after profile redirect"
assert "profiles" in result, (
f"Expected HERMES_HOME to point into profiles/ dir, got: {result!r}"
)
assert result.endswith("coder"), (
f"Expected HERMES_HOME to end with 'coder', got: {result!r}"
)
def test_sudo_explicit_profile_resolves_invoking_users_profile(self, tmp_path, monkeypatch):
"""sudo elias ... should resolve `-p elias` under SUDO_USER, not root."""
root_home = tmp_path / "root"
user_home = tmp_path / "home" / "hermes"
profile_dir = user_home / ".hermes" / "profiles" / "elias"
profile_dir.mkdir(parents=True, exist_ok=True)
(root_home / ".hermes").mkdir(parents=True, exist_ok=True)
monkeypatch.setattr(Path, "home", lambda: root_home)
monkeypatch.setenv("SUDO_USER", "hermes")
monkeypatch.delenv("HERMES_HOME", raising=False)
monkeypatch.setattr(os, "geteuid", lambda: 0, raising=False)
monkeypatch.setattr(sys, "argv", ["hermes", "-p", "elias", "gateway", "install", "--system"])
import pwd
monkeypatch.setattr(pwd, "getpwnam", lambda name: SimpleNamespace(pw_dir=str(user_home)))
from hermes_cli.main import _apply_profile_override
_apply_profile_override()
assert os.environ.get("HERMES_HOME") == str(profile_dir)
assert sys.argv == ["hermes", "gateway", "install", "--system"]
class TestSupervisedChildIgnoresStickyProfile:
"""The reserved default gateway s6 slot must not follow active_profile.
Inside the Docker s6 image the ``gateway-default`` service slot runs a
bare ``hermes gateway run`` (no ``-p``) to mean "the root HERMES_HOME
profile". The run-script exports ``HERMES_S6_SUPERVISED_CHILD=1``.
Without a guard, ``_apply_profile_override`` would read the sticky
``active_profile`` file (set by e.g. the dashboard profile switcher) and
redirect the reserved default gateway into that profile — producing a
duplicate gateway for the active profile and no real default gateway.
"""
def test_non_supervised_run_still_follows_active_profile(
self, tmp_path, monkeypatch
):
"""Without the sentinel, a normal `hermes gateway run` still honors
active_profile — the guard is scoped strictly to supervised children."""
result = _run_apply_profile_override(
tmp_path,
monkeypatch,
hermes_home=None,
active_profile="briefer",
argv=["hermes", "gateway", "run"],
)
assert result is not None
assert result.endswith("briefer")
def test_supervised_named_profile_flag_still_wins(self, tmp_path, monkeypatch):
"""A supervised named-profile slot passes ``-p <name>`` explicitly;
that must still resolve (the sentinel guard only skips the sticky
active_profile fallback, never an explicit flag)."""
hermes_root = tmp_path / ".hermes"
hermes_root.mkdir(parents=True, exist_ok=True)
(hermes_root / "active_profile").write_text("briefer")
(hermes_root / "profiles" / "briefer").mkdir(parents=True, exist_ok=True)
(hermes_root / "profiles" / "coder").mkdir(parents=True, exist_ok=True)
monkeypatch.setattr(Path, "home", lambda: tmp_path)
monkeypatch.delenv("HERMES_HOME", raising=False)
monkeypatch.setenv("HERMES_S6_SUPERVISED_CHILD", "1")
monkeypatch.setattr(sys, "argv", ["hermes", "-p", "coder", "gateway", "run"])
from hermes_cli.main import _apply_profile_override
_apply_profile_override()
result = os.environ.get("HERMES_HOME")
assert result is not None
assert result.endswith("coder")