diff --git a/src/panopticon/sessionservice/host.py b/src/panopticon/sessionservice/host.py index bde59aa8..4ca1c80d 100644 --- a/src/panopticon/sessionservice/host.py +++ b/src/panopticon/sessionservice/host.py @@ -194,9 +194,9 @@ def run_host( HostDaemon(client, spawner, provisioner, interval=interval, sleep=sleep).run(until=until) -def main( - argv: list[str] | None = None, *, client: TaskServiceClient | None = None -) -> None: # pragma: no cover - thin wiring + endless loop +def build_arg_parser() -> argparse.ArgumentParser: + """Build the ``host`` daemon's CLI parser — split out from :func:`main` so tests can inspect + argument defaults (e.g. ``--host``) without running the endless daemon loop.""" parser = argparse.ArgumentParser( prog="python -m panopticon.sessionservice.host", description="Per-host session service: spawn tasks + provision them (ADR 0008/0011).", @@ -226,7 +226,13 @@ def main( default=2.0, help="change-feed long-poll wait, seconds (the keepalive ceiling between blocking calls)", ) - args = parser.parse_args(argv) + return parser + + +def main( + argv: list[str] | None = None, *, client: TaskServiceClient | None = None +) -> None: # pragma: no cover - thin wiring + endless loop + args = build_arg_parser().parse_args(argv) logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s" ) diff --git a/tests/sessionservice/test_host.py b/tests/sessionservice/test_host.py index f493b9a1..226e0f35 100644 --- a/tests/sessionservice/test_host.py +++ b/tests/sessionservice/test_host.py @@ -9,13 +9,19 @@ from pathlib import Path import httpx +import pytest from fastapi.testclient import TestClient from panopticon.client import JsonObj, TaskServiceClient from panopticon.core.git import GitClones from panopticon.core.models import Repo from panopticon.sessionservice.clones import CloneCache -from panopticon.sessionservice.host import HostDaemon, hold_runner_liveness, run_host +from panopticon.sessionservice.host import ( + HostDaemon, + build_arg_parser, + hold_runner_liveness, + run_host, +) from panopticon.taskservice.api import create_app from panopticon.taskservice.artifacts_fs import FilesystemArtifactStore from panopticon.taskservice.service import TaskService @@ -356,6 +362,28 @@ def gen() -> Generator[None, None, None]: assert recorded == ["box.example.com"] +def test_build_arg_parser_host_defaults_to_empty_when_unset( + monkeypatch: pytest.MonkeyPatch, +) -> None: + # A local runner must not report a host at all — any truthy value makes the terminal + # supervisor wrap tmux attach in `ssh -t `, which breaks a purely local session. + monkeypatch.delenv("PANOPTICON_RUNNER_HOST", raising=False) + args = build_arg_parser().parse_args([]) + assert args.host == "" + + +def test_build_arg_parser_host_from_env(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("PANOPTICON_RUNNER_HOST", "box.example.com") + args = build_arg_parser().parse_args([]) + assert args.host == "box.example.com" + + +def test_build_arg_parser_host_flag_overrides_env(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("PANOPTICON_RUNNER_HOST", "box.example.com") + args = build_arg_parser().parse_args(["--host", "other.example.com"]) + assert args.host == "other.example.com" + + def test_run_host_spawns_then_provisions_end_to_end(tmp_path: Path) -> None: service = TaskService(SqlAlchemyStore(), {"spike": Spike()}, FilesystemArtifactStore(tmp_path)) asyncio.run(service.init())