"""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