fix(gateway): harden Windows gateway install lifecycle

Preserve Windows profile install decisions across UAC handoff, avoid visible console windows by launching via pythonw, make repeated install/start idempotent, recreate stale Scheduled Tasks, and separate start-now from login auto-start behavior. Add Windows gateway regression coverage and systemd setup tests for the shared install flow.
This commit is contained in:
nekwo 2026-05-16 13:22:52 -04:00 committed by Teknium
parent 95683c0283
commit d948de39e9
5 changed files with 914 additions and 54 deletions

View file

@ -237,11 +237,13 @@ def test_gateway_install_in_container_with_operational_systemd_uses_systemd(monk
monkeypatch.setattr(gateway, "is_managed", lambda: False)
calls = []
monkeypatch.setattr(gateway, "prompt_yes_no", lambda question, default=True: calls.append(("prompt", question, default)) or True)
monkeypatch.setattr(
gateway,
"systemd_install",
lambda force=False, system=False, run_as_user=None: calls.append((force, system, run_as_user)),
lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", force, system, run_as_user, enable_on_startup)),
)
monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start", system)))
args = SimpleNamespace(
gateway_command="install",
@ -251,7 +253,12 @@ def test_gateway_install_in_container_with_operational_systemd_uses_systemd(monk
)
gateway.gateway_command(args)
assert calls == [(False, False, None)]
assert calls == [
("prompt", "Start the gateway now after installing the service?", True),
("prompt", "Start the gateway automatically on login/boot with systemd?", True),
("install", False, False, None, True),
("start", False),
]
def test_gateway_start_in_container_with_operational_systemd_uses_systemd(monkeypatch):
@ -386,6 +393,34 @@ def test_systemd_install_checks_linger_status(monkeypatch, tmp_path, capsys):
assert "User service installed and enabled" in out
def test_systemd_install_can_skip_enable_on_startup(monkeypatch, tmp_path, capsys):
unit_path = tmp_path / "systemd" / "user" / "hermes-gateway.service"
monkeypatch.setattr(gateway, "get_systemd_unit_path", lambda system=False: unit_path)
calls = []
helper_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)
monkeypatch.setattr(gateway, "_ensure_user_systemd_env", lambda: None)
monkeypatch.setattr(gateway, "_ensure_linger_enabled", lambda: helper_calls.append(True))
gateway.systemd_install(force=False, enable_on_startup=False)
out = capsys.readouterr().out
assert unit_path.exists()
assert [cmd for cmd, _ in calls] == [
["systemctl", "--user", "daemon-reload"],
]
assert helper_calls == [True]
assert "User service installed!" in out
assert "installed and enabled" not in out
def test_systemd_install_system_scope_skips_linger_and_uses_systemctl(monkeypatch, tmp_path, capsys):
unit_path = tmp_path / "etc" / "systemd" / "system" / "hermes-gateway.service"
@ -466,13 +501,55 @@ def test_install_linux_gateway_from_setup_system_choice_as_root_installs(monkeyp
monkeypatch.setattr(
gateway,
"systemd_install",
lambda force=False, system=False, run_as_user=None: calls.append((force, system, run_as_user)),
lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append((force, system, run_as_user, enable_on_startup)),
)
scope, did_install = gateway.install_linux_gateway_from_setup(force=True)
assert (scope, did_install) == ("system", True)
assert calls == [(True, True, "alice")]
assert calls == [(True, True, "alice", True)]
def test_install_linux_gateway_from_setup_passes_startup_choice(monkeypatch):
monkeypatch.setattr(gateway, "prompt_linux_gateway_install_scope", lambda: "user")
calls = []
monkeypatch.setattr(
gateway,
"systemd_install",
lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append((force, system, run_as_user, enable_on_startup)),
)
scope, did_install = gateway.install_linux_gateway_from_setup(force=False, enable_on_startup=False)
assert (scope, did_install) == ("user", True)
assert calls == [(False, False, None, False)]
def test_gateway_install_can_decline_start_now_and_startup(monkeypatch):
monkeypatch.setattr(gateway, "supports_systemd_services", lambda: True)
monkeypatch.setattr(gateway, "is_wsl", lambda: False)
monkeypatch.setattr(gateway, "is_macos", lambda: False)
monkeypatch.setattr(gateway, "is_managed", lambda: False)
answers = iter([False, False])
calls = []
monkeypatch.setattr(gateway, "prompt_yes_no", lambda question, default=True: calls.append(("prompt", question, default)) or next(answers))
monkeypatch.setattr(
gateway,
"systemd_install",
lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", force, system, run_as_user, enable_on_startup)),
)
monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start", system)))
args = SimpleNamespace(gateway_command="install", force=True, system=False, run_as_user=None)
gateway.gateway_command(args)
assert calls == [
("prompt", "Start the gateway now after installing the service?", True),
("prompt", "Start the gateway automatically on login/boot with systemd?", True),
("install", True, False, None, False),
]
def test_find_gateway_pids_falls_back_to_pid_file_when_process_scan_fails(monkeypatch):