hermes-agent/tests/hermes_cli/test_gateway_wsl.py
alt-glitch a1e667b9f2 fix(restructure): fix test regressions from import rewrite
Fix variable name breakage (run_agent, hermes_constants, etc.) where
import rewriter changed 'import X' to 'import hermes_agent.Y' but
test code still referenced 'X' as a variable name.

Fix package-vs-module confusion (cli.auth, cli.models, cli.ui) where
single files became directories.

Fix hardcoded file paths in tests pointing to old locations.
Fix tool registry to discover tools in subpackage directories.
Fix stale import in hermes_agent/tools/__init__.py.

Part of #14182, #14183
2026-04-23 12:05:10 +05:30

279 lines
11 KiB
Python

"""Tests for WSL detection and WSL-aware gateway behavior."""
import io
import subprocess
import sys
from types import SimpleNamespace
from unittest.mock import patch, MagicMock, mock_open
import pytest
import hermes_agent.cli.gateway as gateway
from hermes_agent import constants as hermes_constants
# =============================================================================
# is_wsl() in hermes_constants
# =============================================================================
class TestIsWsl:
"""Test the shared is_wsl() utility."""
def setup_method(self):
# Reset cached value between tests
hermes_constants._wsl_detected = None
def test_detects_wsl2(self):
fake_content = (
"Linux version 5.15.146.1-microsoft-standard-WSL2 "
"(gcc (GCC) 11.2.0) #1 SMP Thu Jan 11 04:09:03 UTC 2024\n"
)
with patch("builtins.open", mock_open(read_data=fake_content)):
assert hermes_constants.is_wsl() is True
def test_detects_wsl1(self):
fake_content = (
"Linux version 4.4.0-19041-Microsoft "
"(Microsoft@Microsoft.com) (gcc version 5.4.0) #1\n"
)
with patch("builtins.open", mock_open(read_data=fake_content)):
assert hermes_constants.is_wsl() is True
def test_native_linux(self):
fake_content = (
"Linux version 6.5.0-44-generic (buildd@lcy02-amd64-015) "
"(x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0) #44\n"
)
with patch("builtins.open", mock_open(read_data=fake_content)):
assert hermes_constants.is_wsl() is False
def test_no_proc_version(self):
with patch("builtins.open", side_effect=FileNotFoundError):
assert hermes_constants.is_wsl() is False
def test_result_is_cached(self):
"""After first detection, subsequent calls return the cached value."""
hermes_constants._wsl_detected = True
# Even with open raising, cached value is returned
with patch("builtins.open", side_effect=FileNotFoundError):
assert hermes_constants.is_wsl() is True
# =============================================================================
# _wsl_systemd_operational() in gateway
# =============================================================================
class TestWslSystemdOperational:
"""Test the WSL systemd check."""
def test_running(self, monkeypatch):
monkeypatch.setattr(
gateway.subprocess, "run",
lambda *a, **kw: SimpleNamespace(
returncode=0, stdout="running\n", stderr=""
),
)
assert gateway._wsl_systemd_operational() is True
def test_degraded(self, monkeypatch):
monkeypatch.setattr(
gateway.subprocess, "run",
lambda *a, **kw: SimpleNamespace(
returncode=1, stdout="degraded\n", stderr=""
),
)
assert gateway._wsl_systemd_operational() is True
def test_starting(self, monkeypatch):
monkeypatch.setattr(
gateway.subprocess, "run",
lambda *a, **kw: SimpleNamespace(
returncode=1, stdout="starting\n", stderr=""
),
)
assert gateway._wsl_systemd_operational() is True
def test_offline_no_systemd(self, monkeypatch):
monkeypatch.setattr(
gateway.subprocess, "run",
lambda *a, **kw: SimpleNamespace(
returncode=1, stdout="offline\n", stderr=""
),
)
assert gateway._wsl_systemd_operational() is False
def test_systemctl_not_found(self, monkeypatch):
monkeypatch.setattr(
gateway.subprocess, "run",
MagicMock(side_effect=FileNotFoundError),
)
assert gateway._wsl_systemd_operational() is False
def test_timeout(self, monkeypatch):
monkeypatch.setattr(
gateway.subprocess, "run",
MagicMock(side_effect=subprocess.TimeoutExpired("systemctl", 5)),
)
assert gateway._wsl_systemd_operational() is False
# =============================================================================
# supports_systemd_services() WSL integration
# =============================================================================
class TestSupportsSystemdServicesWSL:
"""Test that supports_systemd_services() handles WSL correctly."""
def test_wsl_with_systemd(self, monkeypatch):
"""WSL + working systemd → True."""
monkeypatch.setattr(gateway, "is_linux", lambda: True)
monkeypatch.setattr(gateway, "is_termux", lambda: False)
monkeypatch.setattr(gateway, "is_wsl", lambda: True)
monkeypatch.setattr(gateway, "_wsl_systemd_operational", lambda: True)
assert gateway.supports_systemd_services() is True
def test_wsl_without_systemd(self, monkeypatch):
"""WSL + no systemd → False."""
monkeypatch.setattr(gateway, "is_linux", lambda: True)
monkeypatch.setattr(gateway, "is_termux", lambda: False)
monkeypatch.setattr(gateway, "is_wsl", lambda: True)
monkeypatch.setattr(gateway, "_wsl_systemd_operational", lambda: False)
assert gateway.supports_systemd_services() is False
def test_native_linux(self, monkeypatch):
"""Native Linux (not WSL) → True without checking systemd."""
monkeypatch.setattr(gateway, "is_linux", lambda: True)
monkeypatch.setattr(gateway, "is_termux", lambda: False)
monkeypatch.setattr(gateway, "is_wsl", lambda: False)
assert gateway.supports_systemd_services() is True
def test_termux_still_excluded(self, monkeypatch):
"""Termux → False regardless of WSL status."""
monkeypatch.setattr(gateway, "is_linux", lambda: True)
monkeypatch.setattr(gateway, "is_termux", lambda: True)
assert gateway.supports_systemd_services() is False
# =============================================================================
# WSL messaging in gateway commands
# =============================================================================
class TestGatewayCommandWSLMessages:
"""Test that WSL users see appropriate guidance."""
def test_install_wsl_no_systemd(self, monkeypatch, capsys):
"""hermes gateway install on WSL without systemd shows guidance."""
monkeypatch.setattr(gateway, "is_linux", lambda: True)
monkeypatch.setattr(gateway, "is_termux", lambda: False)
monkeypatch.setattr(gateway, "is_wsl", lambda: True)
monkeypatch.setattr(gateway, "supports_systemd_services", lambda: False)
monkeypatch.setattr(gateway, "is_macos", lambda: False)
monkeypatch.setattr(gateway, "is_managed", lambda: False)
args = SimpleNamespace(
gateway_command="install", force=False, system=False,
run_as_user=None,
)
with pytest.raises(SystemExit) as exc_info:
gateway.gateway_command(args)
assert exc_info.value.code == 1
out = capsys.readouterr().out
assert "WSL detected" in out
assert "systemd is not running" in out
assert "hermes gateway run" in out
assert "tmux" in out
def test_start_wsl_no_systemd(self, monkeypatch, capsys):
"""hermes gateway start on WSL without systemd shows guidance."""
monkeypatch.setattr(gateway, "is_linux", lambda: True)
monkeypatch.setattr(gateway, "is_termux", lambda: False)
monkeypatch.setattr(gateway, "is_wsl", lambda: True)
monkeypatch.setattr(gateway, "supports_systemd_services", lambda: False)
monkeypatch.setattr(gateway, "is_macos", lambda: False)
args = SimpleNamespace(gateway_command="start", system=False)
with pytest.raises(SystemExit) as exc_info:
gateway.gateway_command(args)
assert exc_info.value.code == 1
out = capsys.readouterr().out
assert "WSL detected" in out
assert "hermes gateway run" in out
assert "wsl.conf" in out
def test_install_wsl_with_systemd_warns(self, monkeypatch, capsys):
"""hermes gateway install on WSL with systemd shows warning but proceeds."""
monkeypatch.setattr(gateway, "is_linux", lambda: True)
monkeypatch.setattr(gateway, "is_termux", lambda: False)
monkeypatch.setattr(gateway, "is_wsl", lambda: True)
monkeypatch.setattr(gateway, "supports_systemd_services", lambda: True)
monkeypatch.setattr(gateway, "is_macos", lambda: False)
monkeypatch.setattr(gateway, "is_managed", lambda: False)
# Mock systemd_install to capture call
install_called = []
monkeypatch.setattr(
gateway, "systemd_install",
lambda **kwargs: install_called.append(kwargs),
)
args = SimpleNamespace(
gateway_command="install", force=False, system=False,
run_as_user=None,
)
gateway.gateway_command(args)
out = capsys.readouterr().out
assert "WSL detected" in out
assert "may not survive WSL restarts" in out
assert len(install_called) == 1 # install still proceeded
def test_status_wsl_running_manual(self, monkeypatch, capsys):
"""hermes gateway status on WSL with manual process shows WSL note."""
monkeypatch.setattr(gateway, "supports_systemd_services", lambda: False)
monkeypatch.setattr(gateway, "is_macos", lambda: False)
monkeypatch.setattr(gateway, "is_termux", lambda: False)
monkeypatch.setattr(gateway, "is_wsl", lambda: True)
monkeypatch.setattr(gateway, "find_gateway_pids", lambda: [12345])
monkeypatch.setattr(gateway, "_runtime_health_lines", lambda: [])
# Stub out the systemd unit path check
monkeypatch.setattr(
gateway, "get_systemd_unit_path",
lambda system=False: SimpleNamespace(exists=lambda: False),
)
monkeypatch.setattr(
gateway, "get_launchd_plist_path",
lambda: SimpleNamespace(exists=lambda: False),
)
args = SimpleNamespace(gateway_command="status", deep=False, system=False)
gateway.gateway_command(args)
out = capsys.readouterr().out
assert "WSL note" in out
assert "tmux or screen" in out
def test_status_wsl_not_running(self, monkeypatch, capsys):
"""hermes gateway status on WSL with no process shows WSL start advice."""
monkeypatch.setattr(gateway, "supports_systemd_services", lambda: False)
monkeypatch.setattr(gateway, "is_macos", lambda: False)
monkeypatch.setattr(gateway, "is_termux", lambda: False)
monkeypatch.setattr(gateway, "is_wsl", lambda: True)
monkeypatch.setattr(gateway, "find_gateway_pids", lambda: [])
monkeypatch.setattr(gateway, "_runtime_health_lines", lambda: [])
monkeypatch.setattr(
gateway, "get_systemd_unit_path",
lambda system=False: SimpleNamespace(exists=lambda: False),
)
monkeypatch.setattr(
gateway, "get_launchd_plist_path",
lambda: SimpleNamespace(exists=lambda: False),
)
args = SimpleNamespace(gateway_command="status", deep=False, system=False)
gateway.gateway_command(args)
out = capsys.readouterr().out
assert "hermes gateway run" in out
assert "tmux" in out