217 lines
8.7 KiB
Python
217 lines
8.7 KiB
Python
"""Tests for Packet S4: CmdForge CLI integration with agent-harness-setup."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from cmdforge.cli import main
|
|
from cmdforge.cli.provider_commands import _find_setup_helper, _get_provider_defaults_for_harness
|
|
from cmdforge.providers import Provider, get_provider, load_providers, save_providers
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_providers_file(tmp_path):
|
|
providers_file = tmp_path / ".cmdforge" / "providers.yaml"
|
|
with patch("cmdforge.providers.PROVIDERS_FILE", providers_file):
|
|
save_providers([])
|
|
yield providers_file
|
|
|
|
|
|
def _mock_helper_run(status: str, harness_id: str = "claude-code", exit_code: int = 0, diagnostics: str | None = None, executable_path: str = "/usr/local/bin/claude"):
|
|
"""Create a mock subprocess.run that writes a valid completion result to --result-file."""
|
|
def _run(cmd, *args, **kwargs):
|
|
# Extract --result-file and --request-id from cmd
|
|
res_idx = cmd.index("--result-file")
|
|
result_file_path = Path(cmd[res_idx + 1])
|
|
req_idx = cmd.index("--request-id")
|
|
request_id = cmd[req_idx + 1]
|
|
|
|
data = {
|
|
"schema_version": 1,
|
|
"helper_version": "0.1.0",
|
|
"request_id": request_id,
|
|
"timestamp": "2026-09-11T22:00:00Z",
|
|
"status": status,
|
|
"harness_id": harness_id,
|
|
"recipe_id": harness_id,
|
|
"recipe_version": "2026-09-10",
|
|
"install_method": "argv",
|
|
"installer_exit_code": 0 if status == "success" else None,
|
|
"verification": {
|
|
"verified": True,
|
|
"executable_path": executable_path,
|
|
"version_output": f"{harness_id} 1.0.0",
|
|
} if status == "success" else None,
|
|
"path_updates": ["/usr/local/bin"],
|
|
"auth_status": "setup_required",
|
|
"manual_steps_remaining": ["Run 'claude' to complete browser sign-in."],
|
|
"partial_changes": {
|
|
"observed_created": [executable_path] if status == "success" else [],
|
|
"observed_modified": [],
|
|
"unknown_changes": True,
|
|
},
|
|
"error_diagnostics": diagnostics,
|
|
}
|
|
result_file_path.write_text(json.dumps(data), encoding="utf-8")
|
|
return SimpleNamespace(returncode=exit_code)
|
|
|
|
return _run
|
|
|
|
|
|
def test_helper_discovery_finds_executable():
|
|
"""Verify _find_setup_helper locates agent-harness-setup."""
|
|
with patch("shutil.which", return_value="/usr/local/bin/agent-harness-setup"):
|
|
helper = _find_setup_helper()
|
|
assert helper == ["/usr/local/bin/agent-harness-setup"]
|
|
|
|
|
|
def test_get_provider_defaults_for_claude():
|
|
"""Verify provider defaults derived for claude-code."""
|
|
defaults = _get_provider_defaults_for_harness("claude-code", "/custom/bin/claude")
|
|
assert defaults["name"] == "claude"
|
|
assert defaults["command"] == "/custom/bin/claude -p"
|
|
assert "Claude" in defaults["description"]
|
|
|
|
|
|
def test_providers_install_verified_success_registers_provider(temp_providers_file, capsys):
|
|
"""Test verified installation prompts and registers provider in providers.yaml."""
|
|
with (
|
|
patch("cmdforge.cli.provider_commands._find_setup_helper", return_value=["agent-harness-setup"]),
|
|
patch("subprocess.run", side_effect=_mock_helper_run("success", "claude-code", 0)) as mock_run,
|
|
patch("builtins.input", return_value="y"),
|
|
patch("sys.argv", ["cmdforge", "providers", "install", "--harness", "claude-code"]),
|
|
):
|
|
exit_code = main()
|
|
|
|
assert exit_code == 0
|
|
captured = capsys.readouterr()
|
|
assert "Harness Setup Verified: claude-code" in captured.out
|
|
assert "Provider 'claude' successfully added" in captured.out
|
|
|
|
# Verify provider stored in providers.yaml
|
|
provider = get_provider("claude")
|
|
assert provider is not None
|
|
assert provider.command == "/usr/local/bin/claude -p"
|
|
assert "code" in provider.tags
|
|
|
|
# Verify subprocess invocation arguments
|
|
call_args = mock_run.call_args[0][0]
|
|
assert call_args[0] == "agent-harness-setup"
|
|
assert "--result-file" in call_args
|
|
assert "--request-id" in call_args
|
|
assert "--harness" in call_args
|
|
assert "claude-code" in call_args
|
|
|
|
|
|
def test_providers_install_declined_registration(temp_providers_file, capsys):
|
|
"""Test declining the provider registration prompt leaves providers.yaml unchanged."""
|
|
with (
|
|
patch("cmdforge.cli.provider_commands._find_setup_helper", return_value=["agent-harness-setup"]),
|
|
patch("subprocess.run", side_effect=_mock_helper_run("success", "claude-code", 0)),
|
|
patch("builtins.input", return_value="n"),
|
|
patch("sys.argv", ["cmdforge", "providers", "install"]),
|
|
):
|
|
exit_code = main()
|
|
|
|
assert exit_code == 0
|
|
captured = capsys.readouterr()
|
|
assert "Harness Setup Verified: claude-code" in captured.out
|
|
assert "Skipped provider registration" in captured.out
|
|
|
|
assert get_provider("claude") is None
|
|
|
|
|
|
def test_providers_install_already_configured(temp_providers_file, capsys):
|
|
"""Test that existing provider is recognized and not re-registered."""
|
|
save_providers([Provider("claude", "existing-claude -p")])
|
|
|
|
with (
|
|
patch("cmdforge.cli.provider_commands._find_setup_helper", return_value=["agent-harness-setup"]),
|
|
patch("subprocess.run", side_effect=_mock_helper_run("success", "claude-code", 0)),
|
|
patch("sys.argv", ["cmdforge", "providers", "install"]),
|
|
):
|
|
exit_code = main()
|
|
|
|
assert exit_code == 0
|
|
captured = capsys.readouterr()
|
|
assert "already configured in ~/.cmdforge/providers.yaml" in captured.out
|
|
assert get_provider("claude").command == "existing-claude -p"
|
|
|
|
|
|
def test_providers_install_cancelled(temp_providers_file, capsys):
|
|
"""Test cancellation exits cleanly without changing providers."""
|
|
with (
|
|
patch("cmdforge.cli.provider_commands._find_setup_helper", return_value=["agent-harness-setup"]),
|
|
patch("subprocess.run", side_effect=_mock_helper_run("cancelled", "claude-code", 10)),
|
|
patch("sys.argv", ["cmdforge", "providers", "install"]),
|
|
):
|
|
exit_code = main()
|
|
|
|
assert exit_code == 0
|
|
captured = capsys.readouterr()
|
|
assert "Setup cancelled. No provider configuration was changed." in captured.out
|
|
assert get_provider("claude") is None
|
|
|
|
|
|
def test_providers_install_execution_failure(temp_providers_file, capsys):
|
|
"""Test helper failure reports error diagnostics and does not register provider."""
|
|
with (
|
|
patch("cmdforge.cli.provider_commands._find_setup_helper", return_value=["agent-harness-setup"]),
|
|
patch("subprocess.run", side_effect=_mock_helper_run("failed", "claude-code", 1, "Missing node.js")),
|
|
patch("sys.argv", ["cmdforge", "providers", "install"]),
|
|
):
|
|
exit_code = main()
|
|
|
|
assert exit_code == 1
|
|
captured = capsys.readouterr()
|
|
assert "Setup failed: Missing node.js" in captured.out
|
|
assert get_provider("claude") is None
|
|
|
|
|
|
def test_providers_install_incoherent_result_rejected(temp_providers_file, capsys):
|
|
"""Test exit code 1 with status 'success' is rejected as incoherent."""
|
|
with (
|
|
patch("cmdforge.cli.provider_commands._find_setup_helper", return_value=["agent-harness-setup"]),
|
|
patch("subprocess.run", side_effect=_mock_helper_run("success", "claude-code", 1)),
|
|
patch("sys.argv", ["cmdforge", "providers", "install"]),
|
|
):
|
|
exit_code = main()
|
|
|
|
assert exit_code == 1
|
|
captured = capsys.readouterr()
|
|
assert "Treating as failure" in captured.out
|
|
assert get_provider("claude") is None
|
|
|
|
|
|
def test_providers_install_helper_not_found(temp_providers_file, capsys):
|
|
"""Test that missing helper prints actionable instructions without crashing."""
|
|
with (
|
|
patch("cmdforge.cli.provider_commands._find_setup_helper", return_value=None),
|
|
patch("sys.argv", ["cmdforge", "providers", "install"]),
|
|
):
|
|
exit_code = main()
|
|
|
|
assert exit_code == 0
|
|
captured = capsys.readouterr()
|
|
assert "agent-harness-setup" in captured.out
|
|
assert "pip install" in captured.out
|
|
assert "PYTHONPATH=" in captured.out
|
|
|
|
|
|
def test_providers_install_dry_run(temp_providers_file, capsys):
|
|
"""Test --dry-run passes flag to helper and makes no provider changes."""
|
|
with (
|
|
patch("cmdforge.cli.provider_commands._find_setup_helper", return_value=["agent-harness-setup"]),
|
|
patch("subprocess.run", side_effect=_mock_helper_run("dry_run", "claude-code", 0)) as mock_run,
|
|
patch("sys.argv", ["cmdforge", "providers", "install", "--dry-run"]),
|
|
):
|
|
exit_code = main()
|
|
|
|
assert exit_code == 0
|
|
call_args = mock_run.call_args[0][0]
|
|
assert "--dry-run" in call_args
|
|
assert get_provider("claude") is None
|