mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
Phase G's wrap-CLI breadth (PRs #492-#494) inherited a pre-existing
duplication pattern across the wrap subcommands and faithfully
extended it for cline/continue/goose/openhands. Each Pattern-B
subcommand (proxy-only watcher) inlined the same ~50 LOC of
proxy_holder + _make_cleanup + signal handlers + box-drawing banner
+ `while True: time.sleep(1)` watcher + try/except postlude. Each
Pattern-A subcommand (binary-launching) inlined the same ~15 LOC of
rtk-vs-lean-ctx fork + KeyboardInterrupt handler.
Replace with three focused helpers in wrap.py:
_print_wrap_banner(agent)
Centered 47-char unicode box. Adding a 9th agent no longer
requires hand-padding the title to match the box width.
_setup_context_tool_for_agent(...)
rtk-or-lean-ctx fork + on_rtk_ready callback + rtk_required
gate + KeyboardInterrupt -> SystemExit(130) with marker-path
reporting. Used by cursor/cline/continue/goose/openhands.
_run_proxy_only_watcher(...)
Pattern-B scaffolding: signal handlers + banner + _ensure_proxy
+ setup callback + watcher loop + cleanup-on-finally. Used by
cursor/cline/continue.
Production-code delta is small in raw LOC (+33 net on wrap.py)
because each subcommand still has a ~25-line `_print_X_setup`
callback closure. The win is architectural: adding wrap subcommand
#9 is now a ~25-line affair instead of ~150 lines, and behavior
(banner shape, Ctrl-C handling, cleanup ordering) is centralized
so a future fix lands in every subcommand at once.
Tests:
- New test_wrap_helpers.py (17 tests) directly pins each helper's
contract — 5 branches of _setup_context_tool, 4 of
_run_proxy_only_watcher, centering math of _print_wrap_banner.
- Merged the cline+goose hint-file tests into a single parametrized
test_wrap_hintfile_agents.py (10 tests across [cline, goose]
agents). test_wrap_cline.py is deleted; test_wrap_goose.py keeps
only the goose-specific env-fan-out + binary-missing tests.
- Goose gained the "preserves existing hint-file content" test
case that cline already had — net +1 coverage point.
Side benefit: cursor (pre-existing, not touched by G1) now gets
the SystemExit(130) on Ctrl-C-during-setup behavior the G1
subcommands had. Previously it would have surfaced a KeyboardInterrupt
traceback to the shell.
181 CLI tests pass; ci-precheck green.
72 lines
2.5 KiB
Python
72 lines
2.5 KiB
Python
"""Tests for `headroom wrap goose` command (PR-G1, Phase G).
|
|
|
|
Hint-file injection tests (.goosehints idempotency, no-context-tool,
|
|
existing-content preservation, Ctrl-C handling) live in
|
|
`test_wrap_hintfile_agents.py` — the shared parameterized file that
|
|
covers `wrap cline` too. This file keeps only goose-specific behavior:
|
|
the OPENAI/ANTHROPIC env-var fan-out for the child binary launch, and
|
|
the goose-binary-not-found error path.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from headroom.cli import wrap as wrap_mod
|
|
from headroom.cli.main import main
|
|
|
|
|
|
@pytest.fixture
|
|
def runner() -> CliRunner:
|
|
return CliRunner()
|
|
|
|
|
|
def test_wrap_goose_sets_provider_envs(
|
|
runner: CliRunner,
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""OPENAI_BASE_URL, OPENAI_API_BASE, ANTHROPIC_BASE_URL are set on launch."""
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.delenv("HEADROOM_CONTEXT_TOOL", raising=False)
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
def fake_launch_tool(**kwargs): # noqa: ANN003
|
|
captured.update(kwargs)
|
|
|
|
with patch.object(wrap_mod.shutil, "which", return_value="goose"):
|
|
with patch.object(wrap_mod, "_launch_tool", side_effect=fake_launch_tool):
|
|
with patch.object(wrap_mod, "_ensure_rtk_binary", return_value=Path("/tmp/rtk")):
|
|
result = runner.invoke(main, ["wrap", "goose", "--port", "9000", "--", "session"])
|
|
|
|
assert result.exit_code == 0, result.output
|
|
env = captured["env"]
|
|
assert isinstance(env, dict)
|
|
assert env["OPENAI_BASE_URL"] == "http://127.0.0.1:9000/v1"
|
|
assert env["OPENAI_API_BASE"] == "http://127.0.0.1:9000/v1"
|
|
assert env["ANTHROPIC_BASE_URL"] == "http://127.0.0.1:9000"
|
|
assert captured["tool_label"] == "GOOSE"
|
|
assert captured["agent_type"] == "goose"
|
|
assert captured["args"] == ("session",)
|
|
|
|
|
|
def test_wrap_goose_missing_binary_errors_clearly(
|
|
runner: CliRunner,
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""If the goose binary is missing the command must fail with a clear error."""
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.delenv("HEADROOM_CONTEXT_TOOL", raising=False)
|
|
|
|
with patch.object(wrap_mod.shutil, "which", return_value=None):
|
|
with patch.object(wrap_mod, "_ensure_rtk_binary", return_value=Path("/tmp/rtk")):
|
|
result = runner.invoke(main, ["wrap", "goose"])
|
|
|
|
assert result.exit_code == 1
|
|
assert "'goose' not found in PATH" in result.output
|