Zerf home

test_hooks.py

hooks/test_hooks.py

#!/usr/bin/env python3
"""Run every hook against known cases. Usage: python3 hooks/test_hooks.py  (exit 1 if any case fails)."""
import json
import os
import subprocess
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
CASES = [
    # (hook, tool, input, expected exit code)
    ("block_destructive.py", "Bash", {"command": "python3 scripts/normalize.py data/opera/hotel-001.json outputs/normalized"}, 0),
    ("block_destructive.py", "Bash", {"command": "python3 scripts/validate.py schemas/report.schema.json outputs/report.json"}, 0),
    ("block_destructive.py", "Bash", {"command": "ls data/opera"}, 0),
    ("block_destructive.py", "Bash", {"command": "rm data/opera/hotel-001-2027-09-27.json"}, 2),
    ("block_destructive.py", "Bash", {"command": "rm -f data/opera/*.json"}, 2),
    ("block_destructive.py", "Bash", {"command": "cd data/opera && rm old.json"}, 2),
    ("block_destructive.py", "Bash", {"command": "find data/opera -name '*-2027-09-*' -delete"}, 2),
    ("block_destructive.py", "Bash", {"command": "find data -name '*.json' -exec rm {} +"}, 2),
    ("block_destructive.py", "Bash", {"command": "unlink data/opera/old.json"}, 2),
    ("block_destructive.py", "Bash", {"command": "mv data/opera/old.json /tmp/"}, 2),
    ("block_destructive.py", "Bash", {"command": "python3 -c \"import os; os.remove('data/opera/old.json')\""}, 2),
    ("block_destructive.py", "Bash", {"command": "python3 -c \"import shutil; shutil.rmtree('data')\""}, 2),
    ("block_destructive.py", "Bash", {"command": "echo {} > data/opera/hotel-001.json"}, 2),
    ("block_destructive.py", "Bash", {"command": "git clean -fd"}, 2),
    ("block_destructive.py", "Write", {"file_path": "/p/demo-harness/data/opera/new.json", "content": "{}"}, 2),
    ("block_destructive.py", "Edit", {"file_path": "data/config.json", "old_string": "a", "new_string": "b"}, 2),
    ("block_destructive.py", "Write", {"file_path": "/p/demo-harness/outputs/report.json", "content": "{}"}, 0),
    ("block_guest_data.py", "Write", {"file_path": "outputs/x.txt", "content": "Group contact: maria.lopez@example.com"}, 2),
    ("block_guest_data.py", "Write", {"file_path": "outputs/x.txt", "content": "Call +1 305 555 0142"}, 2),
    ("block_guest_data.py", "Write", {"file_path": "outputs/report.json", "content": "{\"occupancy\": 0.627}"}, 0),
    ("protect_raw_export.py", "Bash", {"command": "python3 scripts/normalize.py data/opera/hotel-001.json outputs/normalized"}, 0),
    ("protect_raw_export.py", "Bash", {"command": "python3 scripts/calculate.py outputs/normalized config/thresholds.json outputs"}, 0),
    ("protect_raw_export.py", "Bash", {"command": "cat data/opera/hotel-001.json"}, 2),
    ("protect_raw_export.py", "Bash", {"command": "grep GUEST data/opera/hotel-001.json"}, 2),
    ("protect_raw_export.py", "Bash", {"command": "head -50 data/opera/hotel-001.json"}, 2),
    ("protect_raw_export.py", "Bash", {"command": "python3 -c \"print(open('data/opera/hotel-001.json').read())\""}, 2),
    ("protect_raw_export.py", "Bash", {"command": "python3 scripts/normalize.py data/opera/hotel-001.json out && cat data/opera/hotel-001.json"}, 2),
    ("protect_raw_export.py", "Bash", {"command": "python3 scripts/normalize.py data/opera/hotel-001.json out | head"}, 2),
    ("protect_raw_export.py", "Read", {"file_path": "/p/demo-harness/data/opera/hotel-001.json"}, 2),
    ("protect_raw_export.py", "Grep", {"pattern": "GUEST", "path": "data/opera"}, 2),
    ("protect_raw_export.py", "Read", {"file_path": "/p/demo-harness/outputs/normalized/days.csv"}, 0),
    ("protect_raw_export.py", "Read", {"file_path": "/p/demo-harness/config/thresholds.json"}, 0),
]

failed = 0
for hook, tool, args, want in CASES:
    r = subprocess.run([sys.executable, os.path.join(HERE, hook)], input=json.dumps({"tool_name": tool, "tool_input": args}),
                       capture_output=True, text=True, env={**os.environ, "CLAUDE_PROJECT_DIR": os.path.dirname(HERE)})
    ok = r.returncode == want
    failed += not ok
    print(f"{'ok  ' if ok else 'FAIL'} {hook:22} {tool:6} want {want} got {r.returncode}  {json.dumps(args)[:70]}")
print(f"\n{len(CASES) - failed}/{len(CASES)} cases pass")
sys.exit(1 if failed else 0)