Files
CASAN/packages/casan-devkit/tests/casan-init-tests.py
T

197 lines
6.9 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import contextlib
import importlib.util
import io
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[3]
def module(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
value = importlib.util.module_from_spec(spec)
spec.loader.exec_module(value)
return value
INIT = module("casan_init_test", ROOT / "packages/casan-devkit/casan-init.py")
def codex_hooks():
hooks = {}
for event in ("UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"):
hooks[event] = [{
"hooks": [{
"type": "command",
"command": (
'python3 ".casan/casan-hook.py" '
f"--client codex --event {event}"
),
"timeout": 15,
}],
}]
return {"hooks": hooks}
class FakeProcess:
def __init__(self, response):
self.stdin = io.StringIO()
self.stdout = io.StringIO(json.dumps(response) + "\n")
self.returncode = None
def poll(self):
return self.returncode
def terminate(self):
self.returncode = 0
def wait(self, timeout=None):
self.returncode = 0
return 0
def kill(self):
self.returncode = -9
class CasanInitTests(unittest.TestCase):
def trust_response(self, target: str, statuses):
source = str(Path(target, ".codex", "hooks.json").resolve())
hooks = []
for index, (event, status) in enumerate(zip(
("user_prompt_submit", "pre_tool_use", "post_tool_use", "stop"),
statuses,
)):
hooks.append({
"key": f"{source}:{event}:0:0",
"sourcePath": source,
"command": (
'python3 ".casan/casan-hook.py" '
f"--client codex --event {event}"
),
"enabled": True,
"trustStatus": status,
"displayOrder": index,
})
return {
"id": 2,
"result": {
"data": [{
"cwd": target,
"hooks": hooks,
"warnings": [],
"errors": [],
}],
},
}
def test_codex_trust_uses_effective_app_server_state(self):
with tempfile.TemporaryDirectory() as target:
hooks_path = Path(target, ".codex", "hooks.json")
hooks_path.parent.mkdir()
hooks_path.write_text(json.dumps(codex_hooks()), encoding="utf-8")
response = self.trust_response(target, ["trusted"] * 4)
process = FakeProcess(response)
with patch.object(INIT.shutil, "which", return_value="/bin/codex"), \
patch.object(INIT.subprocess, "Popen", return_value=process):
result = INIT._codex_project_hook_trust(target)
self.assertTrue(result["verified"])
self.assertEqual(result["status"], "trusted")
self.assertEqual(result["trusted_hooks"], 4)
self.assertFalse(result["review_required"])
self.assertTrue(result["operational"])
requests = [
json.loads(line) for line in process.stdin.getvalue().splitlines()
]
self.assertEqual(requests[-1]["method"], "hooks/list")
self.assertEqual(requests[-1]["params"]["cwds"], [target])
def test_codex_trust_detects_modified_hook(self):
with tempfile.TemporaryDirectory() as target:
hooks_path = Path(target, ".codex", "hooks.json")
hooks_path.parent.mkdir()
hooks_path.write_text(json.dumps(codex_hooks()), encoding="utf-8")
response = self.trust_response(
target, ["trusted", "modified", "trusted", "trusted"])
with patch.object(INIT.shutil, "which", return_value="/bin/codex"), \
patch.object(
INIT.subprocess, "Popen",
return_value=FakeProcess(response),
):
result = INIT._codex_project_hook_trust(target)
self.assertTrue(result["verified"])
self.assertEqual(result["status"], "modified")
self.assertEqual(result["modified_hooks"], 1)
self.assertTrue(result["review_required"])
self.assertFalse(result["operational"])
def test_doctor_does_not_warn_when_codex_hooks_are_trusted(self):
with tempfile.TemporaryDirectory() as target:
root = Path(target)
(root / ".casan").mkdir()
(root / ".codex").mkdir()
(root / ".casan/config.json").write_text(json.dumps({
"project_id": "trusted-project",
"enforcement_mode": "enforce",
"integration_mode": "project_hook",
"clients": ["codex"],
}), encoding="utf-8")
(root / ".casan/version.lock").write_text(
json.dumps({"harness_hash": "sha256:harness"}),
encoding="utf-8",
)
(root / ".casan/casan-hook.py").write_text(
"# test bootstrap\n", encoding="utf-8")
(root / ".codex/hooks.json").write_text(
json.dumps(codex_hooks()), encoding="utf-8")
trusted = {
"verified": True,
"review_required": False,
"operational": True,
"status": "trusted",
"disabled_hooks": 0,
}
args = argparse.Namespace(
target=target, harness=None, client=None, json=True)
with patch.object(
INIT, "resolve_project_harness", return_value="/harness"
), patch.object(
INIT, "compute_live",
return_value=("sha256:harness", "live"),
), patch.object(
INIT, "_legacy_instruction_conflicts", return_value=[]
), patch.object(
INIT, "_smoke_bootstrap",
return_value={"ok": True, "decision": "allow"},
), patch.object(
INIT, "_codex_project_hook_trust", return_value=trusted
), patch.object(
INIT.shutil, "which", return_value=None
), contextlib.redirect_stdout(io.StringIO()) as output:
returncode = INIT.cmd_doctor(args)
result = json.loads(output.getvalue())
self.assertEqual(returncode, 0)
self.assertEqual(result["status"], "ready")
self.assertEqual(result["operational_status"], "ready")
self.assertEqual(result["warnings"], [])
self.assertEqual(result["required_actions"], [])
self.assertFalse(
result["client_checks"]["codex"]["trust_review_required"])
if __name__ == "__main__":
unittest.main()