fix(test_gateway): stop run_gateway() tests from rewriting the dev's installed systemd unit (#22900)

run_gateway() calls refresh_systemd_unit_if_needed() on every invocation
so restart settings stay current after exit-code-75 respawns. The
user-scope unit path resolves under Path.home() (NOT sandboxed by
conftest, only HERMES_HOME is), and generate_systemd_unit() bakes the
current HERMES_HOME into the unit's Environment= line.

Result: any test that exercises run_gateway() end-to-end on a real
Linux dev box silently rewrites the developer's installed
~/.config/systemd/user/hermes-gateway.service with a polluted
HERMES_HOME pointing at /tmp/pytest-of-<user>/.../hermes_test. On the
next reboot, systemd loads that unit, the gateway starts looking at an
empty tmp dir, and Telegram/Discord/etc. all show as 'No messaging
platforms enabled' even though the user's real config is fine. Three
tests in tests/hermes_cli/test_gateway.py hit this path:
test_run_gateway_exits_cleanly_on_keyboard_interrupt,
test_run_gateway_exits_nonzero_when_start_gateway_reports_failure, and
test_run_gateway_root_guard_has_escape_hatch.

Two-layer fix:

1. _install_fake_gateway_run helper (covers all four run_gateway() call
   sites in test_gateway.py and any future ones) now also stubs
   supports_systemd_services and refresh_systemd_unit_if_needed.

2. refresh_systemd_unit_if_needed() itself sniffs the generated unit
   body for /pytest-of- and /hermes_test markers and refuses to write
   when present. Defense in depth so a future test that bypasses the
   helper still can't corrupt the dev's gateway. Tests that legitimately
   exercise the refresh flow (test_run_gateway_refreshes_outdated_unit_on_boot)
   patch generate_systemd_unit to return synthetic content that doesn't
   carry those markers, so they keep working.

Adds test_refresh_refuses_to_bake_pytest_tmpdir_into_real_user_unit as a
regression test for the source-side guard.
This commit is contained in:
Teknium 2026-05-09 17:54:09 -07:00 committed by GitHub
parent 4f8d8ad912
commit 2ffef15675
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 93 additions and 1 deletions

View file

@ -13,6 +13,21 @@ def _install_fake_gateway_run(monkeypatch, start_gateway):
module = ModuleType("gateway.run")
module.start_gateway = start_gateway
monkeypatch.setitem(sys.modules, "gateway.run", module)
# ``run_gateway()`` calls ``refresh_systemd_unit_if_needed()`` on every
# invocation so that restart settings stay current after exit-code-75
# respawns. That helper writes to ``Path.home() / ".config/systemd/user
# /hermes-gateway.service"`` and runs ``systemctl --user daemon-reload``
# — both target the *real* user environment because the conftest only
# sandboxes ``HERMES_HOME``, not ``HOME``. Tests that drive
# ``run_gateway()`` end-to-end with a fake ``start_gateway`` MUST stub
# the refresh call too, or every run rewrites the developer's installed
# unit (baking in the test's pytest-tmp ``HERMES_HOME`` value, which
# systemd then uses on the next boot — silently breaking the gateway
# for the developer).
monkeypatch.setattr(gateway, "supports_systemd_services", lambda: False)
monkeypatch.setattr(
gateway, "refresh_systemd_unit_if_needed", lambda system=False: False
)
def test_run_gateway_exits_cleanly_on_keyboard_interrupt(monkeypatch, capsys):