mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
Add focused regression coverage for install, runtime, provider, state, health, supervisor, and persistent wrap flows so the new persistent deployment surfaces are exercised more thoroughly in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
343 lines
12 KiB
Python
343 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
import click
|
|
from click.testing import CliRunner
|
|
|
|
from headroom.cli.main import main
|
|
|
|
|
|
def test_install_apply_starts_service_supervisor(monkeypatch) -> None:
|
|
runner = CliRunner()
|
|
calls: list[str] = []
|
|
|
|
class Manifest:
|
|
profile = "default"
|
|
preset = "persistent-service"
|
|
runtime_kind = "python"
|
|
supervisor_kind = "service"
|
|
scope = "user"
|
|
health_url = "http://127.0.0.1:8787/readyz"
|
|
targets = ["claude", "codex"]
|
|
mutations = []
|
|
artifacts = []
|
|
|
|
manifest = Manifest()
|
|
|
|
monkeypatch.setattr("headroom.cli.install.build_manifest", lambda **_: manifest)
|
|
monkeypatch.setattr("headroom.cli.install.load_manifest", lambda profile: None)
|
|
monkeypatch.setattr("headroom.cli.install.apply_mutations", lambda deployment: [])
|
|
monkeypatch.setattr("headroom.cli.install.install_supervisor", lambda deployment: [])
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.save_manifest", lambda deployment: calls.append("save")
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.start_supervisor", lambda deployment: calls.append("start_service")
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.start_detached_agent", lambda profile: calls.append("start_agent")
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.start_persistent_docker",
|
|
lambda deployment: calls.append("start_docker"),
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.wait_ready", lambda deployment, timeout_seconds=45: True
|
|
)
|
|
|
|
result = runner.invoke(main, ["install", "apply"])
|
|
|
|
assert result.exit_code == 0, result.output
|
|
assert "Installed persistent deployment 'default'" in result.output
|
|
assert "Targets: claude, codex" in result.output
|
|
assert calls == ["save", "start_service"]
|
|
|
|
|
|
def test_install_status_includes_backend_from_health_probe(monkeypatch) -> None:
|
|
runner = CliRunner()
|
|
|
|
class Manifest:
|
|
profile = "default"
|
|
preset = "persistent-service"
|
|
runtime_kind = "python"
|
|
supervisor_kind = "service"
|
|
scope = "user"
|
|
port = 8787
|
|
backend = "anthropic"
|
|
health_url = "http://127.0.0.1:8787/readyz"
|
|
|
|
monkeypatch.setattr("headroom.cli.install.load_manifest", lambda profile: Manifest())
|
|
monkeypatch.setattr("headroom.cli.install.runtime_status", lambda manifest: "running")
|
|
monkeypatch.setattr("headroom.cli.install.probe_ready", lambda url: True)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.probe_json",
|
|
lambda url: {"config": {"backend": "anthropic"}},
|
|
)
|
|
|
|
result = runner.invoke(main, ["install", "status"])
|
|
|
|
assert result.exit_code == 0, result.output
|
|
assert "Status: running" in result.output
|
|
assert "Healthy: yes" in result.output
|
|
assert "Backend: anthropic" in result.output
|
|
|
|
|
|
def test_install_restart_uses_internal_helpers(monkeypatch) -> None:
|
|
runner = CliRunner()
|
|
calls: list[str] = []
|
|
|
|
class Manifest:
|
|
profile = "default"
|
|
preset = "persistent-service"
|
|
runtime_kind = "python"
|
|
supervisor_kind = "service"
|
|
scope = "user"
|
|
health_url = "http://127.0.0.1:8787/readyz"
|
|
|
|
monkeypatch.setattr("headroom.cli.install.load_manifest", lambda profile: Manifest())
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.stop_supervisor", lambda manifest: calls.append("stop_supervisor")
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.stop_runtime", lambda manifest: calls.append("stop_runtime")
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.start_supervisor", lambda manifest: calls.append("start_supervisor")
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.wait_ready", lambda manifest, timeout_seconds=45: True
|
|
)
|
|
|
|
result = runner.invoke(main, ["install", "restart"])
|
|
|
|
assert result.exit_code == 0, result.output
|
|
assert "Restarted deployment 'default'." in result.output
|
|
assert calls == ["stop_supervisor", "stop_runtime", "start_supervisor"]
|
|
|
|
|
|
def test_install_apply_rejects_invalid_profile() -> None:
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(main, ["install", "apply", "--profile", "../bad"])
|
|
|
|
assert result.exit_code != 0
|
|
assert "Invalid profile name '../bad'" in result.output
|
|
|
|
|
|
def test_install_apply_rejects_provider_scope_targets_without_support() -> None:
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(
|
|
main,
|
|
["install", "apply", "--scope", "provider", "--providers", "manual", "--target", "copilot"],
|
|
)
|
|
|
|
assert result.exit_code != 0
|
|
assert "Provider scope supports only claude, codex, and openclaw" in result.output
|
|
|
|
|
|
def test_install_apply_restores_previous_deployment_after_failed_update(monkeypatch) -> None:
|
|
runner = CliRunner()
|
|
calls: list[str] = []
|
|
|
|
class Manifest:
|
|
def __init__(self, profile: str, targets: list[str]) -> None:
|
|
self.profile = profile
|
|
self.preset = "persistent-service"
|
|
self.runtime_kind = "python"
|
|
self.supervisor_kind = "service"
|
|
self.scope = "user"
|
|
self.health_url = "http://127.0.0.1:8787/readyz"
|
|
self.targets = targets
|
|
self.mutations = []
|
|
self.artifacts = []
|
|
|
|
new_manifest = Manifest("default", ["claude"])
|
|
existing_manifest = Manifest("default", ["codex"])
|
|
|
|
monkeypatch.setattr("headroom.cli.install.build_manifest", lambda **_: new_manifest)
|
|
monkeypatch.setattr("headroom.cli.install.load_manifest", lambda profile: existing_manifest)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.apply_mutations",
|
|
lambda deployment: calls.append(f"apply:{','.join(deployment.targets)}") or [],
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.install_supervisor",
|
|
lambda deployment: calls.append(f"supervisor:{','.join(deployment.targets)}") or [],
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.save_manifest",
|
|
lambda deployment: calls.append(f"save:{','.join(deployment.targets)}"),
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.stop_supervisor",
|
|
lambda deployment: calls.append(f"stop-supervisor:{','.join(deployment.targets)}"),
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.stop_runtime",
|
|
lambda deployment: calls.append(f"stop-runtime:{','.join(deployment.targets)}"),
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.remove_supervisor",
|
|
lambda deployment: calls.append(f"remove-supervisor:{','.join(deployment.targets)}"),
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.revert_mutations",
|
|
lambda deployment: calls.append(f"revert:{','.join(deployment.targets)}"),
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.delete_manifest",
|
|
lambda profile: calls.append(f"delete:{profile}"),
|
|
)
|
|
|
|
def _start(deployment) -> None:
|
|
calls.append(f"start:{','.join(deployment.targets)}")
|
|
if deployment is new_manifest:
|
|
raise click.ClickException("boom")
|
|
|
|
monkeypatch.setattr("headroom.cli.install._start_deployment", _start)
|
|
|
|
result = runner.invoke(main, ["install", "apply"])
|
|
|
|
assert result.exit_code != 0
|
|
assert "Restoring previous deployment 'default'" in result.output
|
|
assert calls == [
|
|
"stop-supervisor:codex",
|
|
"stop-runtime:codex",
|
|
"remove-supervisor:codex",
|
|
"revert:codex",
|
|
"delete:default",
|
|
"apply:claude",
|
|
"supervisor:claude",
|
|
"save:claude",
|
|
"start:claude",
|
|
"stop-supervisor:claude",
|
|
"stop-runtime:claude",
|
|
"remove-supervisor:claude",
|
|
"revert:claude",
|
|
"delete:default",
|
|
"apply:codex",
|
|
"supervisor:codex",
|
|
"save:codex",
|
|
"start:codex",
|
|
]
|
|
|
|
|
|
def test_install_start_rejects_task_lifecycle(monkeypatch) -> None:
|
|
runner = CliRunner()
|
|
|
|
class Manifest:
|
|
profile = "default"
|
|
preset = "persistent-task"
|
|
runtime_kind = "python"
|
|
supervisor_kind = "task"
|
|
scope = "user"
|
|
health_url = "http://127.0.0.1:8787/readyz"
|
|
|
|
monkeypatch.setattr("headroom.cli.install.load_manifest", lambda profile: Manifest())
|
|
|
|
result = runner.invoke(main, ["install", "start"])
|
|
|
|
assert result.exit_code != 0
|
|
assert "headroom install start" in result.output
|
|
|
|
|
|
def test_install_apply_uses_docker_runtime_for_persistent_docker(monkeypatch) -> None:
|
|
runner = CliRunner()
|
|
calls: list[str] = []
|
|
|
|
class Manifest:
|
|
profile = "default"
|
|
preset = "persistent-docker"
|
|
runtime_kind = "docker"
|
|
supervisor_kind = "none"
|
|
scope = "user"
|
|
health_url = "http://127.0.0.1:8787/readyz"
|
|
targets: list[str] = []
|
|
mutations = []
|
|
artifacts = []
|
|
|
|
monkeypatch.setattr("headroom.cli.install.build_manifest", lambda **_: Manifest())
|
|
monkeypatch.setattr("headroom.cli.install.load_manifest", lambda profile: None)
|
|
monkeypatch.setattr("headroom.cli.install.apply_mutations", lambda deployment: [])
|
|
monkeypatch.setattr("headroom.cli.install.install_supervisor", lambda deployment: [])
|
|
monkeypatch.setattr("headroom.cli.install.save_manifest", lambda deployment: None)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.start_persistent_docker",
|
|
lambda deployment: calls.append("start_docker"),
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.wait_ready", lambda deployment, timeout_seconds=45: True
|
|
)
|
|
|
|
result = runner.invoke(main, ["install", "apply", "--preset", "persistent-docker"])
|
|
|
|
assert result.exit_code == 0, result.output
|
|
assert calls == ["start_docker"]
|
|
|
|
|
|
def test_install_remove_continues_when_runtime_teardown_errors(monkeypatch) -> None:
|
|
runner = CliRunner()
|
|
calls: list[str] = []
|
|
|
|
class Manifest:
|
|
profile = "default"
|
|
preset = "persistent-service"
|
|
runtime_kind = "python"
|
|
supervisor_kind = "service"
|
|
scope = "user"
|
|
health_url = "http://127.0.0.1:8787/readyz"
|
|
|
|
monkeypatch.setattr("headroom.cli.install.load_manifest", lambda profile: Manifest())
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.stop_supervisor",
|
|
lambda manifest: (_ for _ in ()).throw(RuntimeError("boom")),
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.stop_runtime",
|
|
lambda manifest: (_ for _ in ()).throw(RuntimeError("boom")),
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.remove_supervisor", lambda manifest: calls.append("remove_supervisor")
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.revert_mutations", lambda manifest: calls.append("revert")
|
|
)
|
|
monkeypatch.setattr(
|
|
"headroom.cli.install.delete_manifest", lambda profile: calls.append("delete")
|
|
)
|
|
|
|
result = runner.invoke(main, ["install", "remove"])
|
|
|
|
assert result.exit_code == 0, result.output
|
|
assert calls == ["remove_supervisor", "revert", "delete"]
|
|
|
|
|
|
def test_install_agent_ensure_reports_already_healthy(monkeypatch) -> None:
|
|
runner = CliRunner()
|
|
|
|
class Manifest:
|
|
profile = "default"
|
|
health_url = "http://127.0.0.1:8787/readyz"
|
|
|
|
monkeypatch.setattr("headroom.cli.install.load_manifest", lambda profile: Manifest())
|
|
monkeypatch.setattr("headroom.cli.install.probe_ready", lambda url: True)
|
|
|
|
result = runner.invoke(main, ["install", "agent", "ensure"])
|
|
|
|
assert result.exit_code == 0, result.output
|
|
assert "already healthy" in result.output
|
|
|
|
|
|
def test_install_agent_run_exits_with_foreground_status(monkeypatch) -> None:
|
|
runner = CliRunner()
|
|
|
|
class Manifest:
|
|
profile = "default"
|
|
health_url = "http://127.0.0.1:8787/readyz"
|
|
|
|
monkeypatch.setattr("headroom.cli.install.load_manifest", lambda profile: Manifest())
|
|
monkeypatch.setattr("headroom.cli.install.run_foreground", lambda manifest: 7)
|
|
|
|
result = runner.invoke(main, ["install", "agent", "run"])
|
|
|
|
assert result.exit_code == 7
|