left4me/l4d2host/tests/test_cli.py
mwiegand 434ee20339
refactor(deploy): venv + steam now under /var/lib/left4me
Sync deployment references for the runtime state relocation
shipped via ckn-bw (commit 6fae2fd). /opt/left4me/ is now a
root-owned deploy-artifact root (just src/); .venv and steamcmd
live at /var/lib/left4me/{.venv,steam}.

Touches:
- deploy/files/.../left4me-web.service: PATH + ExecStart
- deploy/files/.../left4me-workshop-refresh.service: WorkingDirectory
  (was /opt/left4me, now /opt/left4me/src to match the web unit),
  PATH, ExecStart
- scripts/sbin/left4me wrapper: flask path
- deploy/tests/test_example_units.py: PATH + ExecStart assertions
  for the web unit; also fix a pre-existing broken assertion that
  read "Environment=PATH=..." (the unit has Environment=HOME=...
  PATH=... on one line, so "Environment=PATH=" was never present)
  - now reads just "PATH=..."
- deploy/README.md: paths
- l4d2host/tests/test_cli.py: LEFT4ME_STEAMCMD fixture path

Design + as-shipped record:
docs/superpowers/specs/2026-05-15-runtime-state-relocation-design.md.
The original (narrower) prereq spec at
docs/superpowers/specs/2026-05-15-handoff-noneditable-install.md
is marked superseded with a pointer to what shipped + why the
scope grew (setuptools writes egg-info to source during PEP 517
build prep).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:56:32 +02:00

110 lines
3.3 KiB
Python

import subprocess
from types import SimpleNamespace
import json
from typer.testing import CliRunner
from l4d2host.cli import app
def test_help_lists_v1_commands() -> None:
result = CliRunner().invoke(app, ["--help"])
assert result.exit_code == 0
for command in ["install", "initialize", "start", "stop", "delete"]:
assert command in result.output
def test_install_uses_left4me_steamcmd_env_var(monkeypatch) -> None:
captured: dict[str, str] = {}
class FakeInstaller:
def __init__(self, *, steamcmd):
captured["steamcmd"] = steamcmd
def install_or_update(self, **kwargs):
del kwargs
monkeypatch.setattr("l4d2host.cli.SteamInstaller", FakeInstaller)
monkeypatch.setenv("LEFT4ME_STEAMCMD", "/var/lib/left4me/steam/steamcmd.sh")
result = CliRunner().invoke(app, ["install"])
assert result.exit_code == 0
assert captured["steamcmd"] == "/var/lib/left4me/steam/steamcmd.sh"
def test_install_defaults_to_bare_steamcmd_when_env_unset(monkeypatch) -> None:
captured: dict[str, str] = {}
class FakeInstaller:
def __init__(self, *, steamcmd):
captured["steamcmd"] = steamcmd
def install_or_update(self, **kwargs):
del kwargs
monkeypatch.setattr("l4d2host.cli.SteamInstaller", FakeInstaller)
monkeypatch.delenv("LEFT4ME_STEAMCMD", raising=False)
result = CliRunner().invoke(app, ["install"])
assert result.exit_code == 0
assert captured["steamcmd"] == "steamcmd"
def test_cli_propagates_subprocess_return_code(monkeypatch) -> None:
def fail(*args, **kwargs):
del args
del kwargs
raise subprocess.CalledProcessError(returncode=9, cmd=["x"], stderr="boom")
monkeypatch.setattr("l4d2host.cli.start_instance", fail)
result = CliRunner().invoke(app, ["start", "alpha"])
assert result.exit_code == 9
assert "boom" in result.stderr
def test_status_command_outputs_json(monkeypatch) -> None:
monkeypatch.setattr(
"l4d2host.cli.get_instance_status",
lambda name: SimpleNamespace(state="running", raw_active_state="active", raw_sub_state="running"),
raising=False,
)
result = CliRunner().invoke(app, ["status", "alpha", "--json"])
assert result.exit_code == 0
assert json.loads(result.output) == {
"state": "running",
"raw_active_state": "active",
"raw_sub_state": "running",
}
def test_logs_command_streams_lines(monkeypatch) -> None:
monkeypatch.setattr(
"l4d2host.cli.stream_instance_logs",
lambda name, *, lines, follow: iter([f"{name}:{lines}:{follow}", "ready"]),
raising=False,
)
result = CliRunner().invoke(app, ["logs", "alpha", "--lines", "25", "--no-follow"])
assert result.exit_code == 0
assert result.output.splitlines() == ["alpha:25:False", "ready"]
def test_logs_command_propagates_subprocess_return_code(monkeypatch) -> None:
def fail_logs(*args, **kwargs):
del args
del kwargs
raise subprocess.CalledProcessError(returncode=7, cmd=["logs"], stderr="sudo denied")
monkeypatch.setattr("l4d2host.cli.stream_instance_logs", fail_logs, raising=False)
result = CliRunner().invoke(app, ["logs", "alpha", "--no-follow"])
assert result.exit_code == 7
assert "sudo denied" in result.stderr