fix(gateway): surface missing linger in status and doctor (#1296)
* fix(gateway): surface missing linger in status and doctor Warn when a systemd user gateway service has linger disabled so users can spot the common 'gateway sleeps after logout' deployment issue from both hermes doctor and hermes gateway status. * fix(gateway): check linger status after install After installing the systemd user service, report whether linger is already enabled instead of always printing the generic hint. This makes post-install guidance match the user's actual deployment state.
This commit is contained in:
@@ -9,6 +9,7 @@ from types import SimpleNamespace
|
||||
import pytest
|
||||
|
||||
import hermes_cli.doctor as doctor
|
||||
import hermes_cli.gateway as gateway_cli
|
||||
from hermes_cli import doctor as doctor_mod
|
||||
from hermes_cli.doctor import _has_provider_env_config
|
||||
|
||||
@@ -101,3 +102,37 @@ def test_run_doctor_sets_interactive_env_for_tool_checks(monkeypatch, tmp_path):
|
||||
doctor_mod.run_doctor(Namespace(fix=False))
|
||||
|
||||
assert seen["interactive"] == "1"
|
||||
|
||||
|
||||
def test_check_gateway_service_linger_warns_when_disabled(monkeypatch, tmp_path, capsys):
|
||||
unit_path = tmp_path / "hermes-gateway.service"
|
||||
unit_path.write_text("[Unit]\n")
|
||||
|
||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
||||
monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda: unit_path)
|
||||
monkeypatch.setattr(gateway_cli, "get_systemd_linger_status", lambda: (False, ""))
|
||||
|
||||
issues = []
|
||||
doctor._check_gateway_service_linger(issues)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "Gateway Service" in out
|
||||
assert "Systemd linger disabled" in out
|
||||
assert "loginctl enable-linger" in out
|
||||
assert issues == [
|
||||
"Enable linger for the gateway user service: sudo loginctl enable-linger $USER"
|
||||
]
|
||||
|
||||
|
||||
def test_check_gateway_service_linger_skips_when_service_not_installed(monkeypatch, tmp_path, capsys):
|
||||
unit_path = tmp_path / "missing.service"
|
||||
|
||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
||||
monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda: unit_path)
|
||||
|
||||
issues = []
|
||||
doctor._check_gateway_service_linger(issues)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert out == ""
|
||||
assert issues == []
|
||||
|
||||
82
tests/hermes_cli/test_gateway.py
Normal file
82
tests/hermes_cli/test_gateway.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Tests for hermes_cli.gateway."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
import hermes_cli.gateway as gateway
|
||||
|
||||
|
||||
class TestSystemdLingerStatus:
|
||||
def test_reports_enabled(self, monkeypatch):
|
||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||
monkeypatch.setenv("USER", "alice")
|
||||
monkeypatch.setattr(
|
||||
gateway.subprocess,
|
||||
"run",
|
||||
lambda *args, **kwargs: SimpleNamespace(returncode=0, stdout="yes\n", stderr=""),
|
||||
)
|
||||
monkeypatch.setattr("shutil.which", lambda name: "/usr/bin/loginctl")
|
||||
|
||||
assert gateway.get_systemd_linger_status() == (True, "")
|
||||
|
||||
def test_reports_disabled(self, monkeypatch):
|
||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||
monkeypatch.setenv("USER", "alice")
|
||||
monkeypatch.setattr(
|
||||
gateway.subprocess,
|
||||
"run",
|
||||
lambda *args, **kwargs: SimpleNamespace(returncode=0, stdout="no\n", stderr=""),
|
||||
)
|
||||
monkeypatch.setattr("shutil.which", lambda name: "/usr/bin/loginctl")
|
||||
|
||||
assert gateway.get_systemd_linger_status() == (False, "")
|
||||
|
||||
|
||||
def test_systemd_status_warns_when_linger_disabled(monkeypatch, tmp_path, capsys):
|
||||
unit_path = tmp_path / "hermes-gateway.service"
|
||||
unit_path.write_text("[Unit]\n")
|
||||
|
||||
monkeypatch.setattr(gateway, "get_systemd_unit_path", lambda: unit_path)
|
||||
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (False, ""))
|
||||
|
||||
def fake_run(cmd, capture_output=False, text=False, check=False):
|
||||
if cmd[:4] == ["systemctl", "--user", "status", gateway.SERVICE_NAME]:
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
if cmd[:3] == ["systemctl", "--user", "is-active"]:
|
||||
return SimpleNamespace(returncode=0, stdout="active\n", stderr="")
|
||||
raise AssertionError(f"Unexpected command: {cmd}")
|
||||
|
||||
monkeypatch.setattr(gateway.subprocess, "run", fake_run)
|
||||
|
||||
gateway.systemd_status(deep=False)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "Gateway service is running" in out
|
||||
assert "Systemd linger is disabled" in out
|
||||
assert "loginctl enable-linger" in out
|
||||
|
||||
|
||||
def test_systemd_install_checks_linger_status(monkeypatch, tmp_path, capsys):
|
||||
unit_path = tmp_path / "systemd" / "user" / "hermes-gateway.service"
|
||||
|
||||
monkeypatch.setattr(gateway, "get_systemd_unit_path", lambda: unit_path)
|
||||
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (False, ""))
|
||||
|
||||
calls = []
|
||||
|
||||
def fake_run(cmd, check=False, **kwargs):
|
||||
calls.append((cmd, check))
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(gateway.subprocess, "run", fake_run)
|
||||
|
||||
gateway.systemd_install(force=False)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert unit_path.exists()
|
||||
assert [cmd for cmd, _ in calls] == [
|
||||
["systemctl", "--user", "daemon-reload"],
|
||||
["systemctl", "--user", "enable", gateway.SERVICE_NAME],
|
||||
]
|
||||
assert "Service installed and enabled" in out
|
||||
assert "Systemd linger is disabled" in out
|
||||
assert "loginctl enable-linger" in out
|
||||
Reference in New Issue
Block a user