Enforce canonical setup result contract in CLI and GUI

This commit is contained in:
rob
2026-09-14 22:37:12 -03:00
parent 4bd0a7cc02
commit e042cd752b
9 changed files with 1350 additions and 53 deletions
+56 -7
View File
@@ -29,6 +29,20 @@ from cmdforge.gui.dialogs.provider_install_dialog import (
from cmdforge.providers import Provider, get_provider, load_providers, save_providers
def _write_completion(path, data):
payload = {
"schema_version": 1, "helper_version": "0.1.0",
"timestamp": "2026-09-14T00:00:00Z", "harness_id": "claude-code",
"auth_status": "unknown", "path_updates": [], "manual_steps_remaining": [],
"partial_changes": {"observed_created": [], "observed_modified": [], "unknown_changes": True},
"installer_exit_code": 0,
"recipe_id": "test-recipe", "recipe_version": "2026-09-10", "install_method": "argv",
}
payload.update(data)
path.write_text(json.dumps(payload), encoding="utf-8")
path.chmod(0o600)
@pytest.fixture
def temp_providers_file(tmp_path):
providers_file = tmp_path / ".cmdforge" / "providers.yaml"
@@ -141,6 +155,13 @@ def test_setup_terminal_worker_success(tmp_path):
result_file = tmp_path / "result.json"
req_id = "test-uuid-1234"
data = {
"schema_version": 1,
"helper_version": "0.1.0",
"timestamp": "2026-09-14T00:00:00Z",
"auth_status": "unknown",
"manual_steps_remaining": [],
"partial_changes": {"observed_created": [], "observed_modified": [], "unknown_changes": True},
"verification": {"verified": True, "executable_path": "/custom/bin/claude", "version_output": "test"},
"status": "success",
"request_id": req_id,
"harness_id": "claude-code",
@@ -150,7 +171,7 @@ def test_setup_terminal_worker_success(tmp_path):
class MockProcess:
def __init__(self, *args, **kwargs):
self.stdout = None
result_file.write_text(json.dumps(data), encoding="utf-8")
_write_completion(result_file, data)
def wait(self):
return 0
@@ -179,6 +200,26 @@ def test_setup_terminal_worker_success(tmp_path):
assert res_dict["path_updates"] == ["/custom/bin"]
@pytest.mark.parametrize("overrides", [
{"schema_version": True}, {"unexpected": 1}, {"status": []},
{"partial_changes": None}, {"cleanup_complete": "true"},
])
def test_worker_rejects_malformed_contract(tmp_path, overrides):
path = tmp_path / "result.json"
data = {"status": "failed", "request_id": "req-1"}
data.update(overrides)
_write_completion(path, data)
process = MagicMock(stdout=None)
process.wait.return_value = 1
worker = SetupTerminalWorker(["fake-helper"], "claude-code", path, "req-1")
outcomes = []
worker.finished.connect(lambda *args: outcomes.append(args))
with patch("subprocess.Popen", return_value=process):
worker.run()
assert len(outcomes) == 1 and outcomes[0][0] is False
assert "unknown" in outcomes[0][1]
def test_setup_terminal_worker_security_mismatch(tmp_path):
"""Test SetupTerminalWorker rejects result with request_id mismatch."""
result_file = tmp_path / "result.json"
@@ -191,7 +232,7 @@ def test_setup_terminal_worker_security_mismatch(tmp_path):
class MockProcess:
def __init__(self, *args, **kwargs):
self.stdout = None
result_file.write_text(json.dumps(data), encoding="utf-8")
_write_completion(result_file, data)
def wait(self):
return 0
@@ -212,7 +253,7 @@ def test_setup_terminal_worker_security_mismatch(tmp_path):
assert len(results) == 1
success, msg, _ = results[0]
assert success is False
assert "Security error" in msg
assert "does not match the expected" in msg
def test_setup_terminal_worker_incoherent_exit(tmp_path):
@@ -227,7 +268,7 @@ def test_setup_terminal_worker_incoherent_exit(tmp_path):
class MockProcess:
def __init__(self, *args, **kwargs):
self.stdout = None
result_file.write_text(json.dumps(data), encoding="utf-8")
_write_completion(result_file, data)
def wait(self):
return 1
@@ -248,7 +289,7 @@ def test_setup_terminal_worker_incoherent_exit(tmp_path):
assert len(results) == 1
success, msg, _ = results[0]
assert success is False
assert "error code 1" in msg
assert "exit code 1" in msg
def test_setup_terminal_worker_cancelled(tmp_path):
@@ -256,6 +297,14 @@ def test_setup_terminal_worker_cancelled(tmp_path):
result_file = tmp_path / "result.json"
req_id = "cancel-uuid"
data = {
"schema_version": 1,
"helper_version": "0.1.0",
"timestamp": "2026-09-14T00:00:00Z",
"harness_id": "claude-code",
"auth_status": "unknown",
"path_updates": [],
"manual_steps_remaining": [],
"partial_changes": {"observed_created": [], "observed_modified": [], "unknown_changes": True},
"status": "cancelled",
"request_id": req_id,
}
@@ -263,7 +312,7 @@ def test_setup_terminal_worker_cancelled(tmp_path):
class MockProcess:
def __init__(self, *args, **kwargs):
self.stdout = None
result_file.write_text(json.dumps(data), encoding="utf-8")
_write_completion(result_file, data)
def wait(self):
return 10
@@ -300,7 +349,7 @@ def test_setup_terminal_worker_failure_with_diagnostics(tmp_path):
class MockProcess:
def __init__(self, *args, **kwargs):
self.stdout = None
result_file.write_text(json.dumps(data), encoding="utf-8")
_write_completion(result_file, data)
def wait(self):
return 1