Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions scripts/test/test-T001-workflow-spec.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Test: spec and workflow YAML exist for 005-add-workflows-to-hookrunner
set -euo pipefail
cd "$(dirname "$0")/../.."

errors=0

# Spec exists
if [ -f "specs/005-add-workflows-to-hookrunner/spec.md" ]; then
echo "[OK] spec.md exists"
else
echo "[FAIL] specs/005-add-workflows-to-hookrunner/spec.md missing"
errors=$((errors + 1))
fi

# Spec has required sections
for section in "Problem" "Solution" "Architecture"; do
if grep -qi "$section" specs/005-add-workflows-to-hookrunner/spec.md 2>/dev/null; then
echo "[OK] spec has $section section"
else
echo "[FAIL] spec missing $section section"
errors=$((errors + 1))
fi
done

# Workflow YAML exists
if [ -f "workflows/005-add-workflows-to-hookrunner.yml" ]; then
echo "[OK] workflow YAML exists"
else
echo "[FAIL] workflows/005-add-workflows-to-hookrunner.yml missing"
errors=$((errors + 1))
fi

exit $errors
141 changes: 141 additions & 0 deletions scripts/test/test-T002-hookrunner-engine.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env bash
# Test: workflow.js exists in hook-runner and works correctly
set -euo pipefail

HR_DIR="$HOME/Documents/ProjectsCL1/hook-runner"

to_node() { cygpath -m "$1" 2>/dev/null || echo "$1"; }

errors=0

# workflow.js exists in hook-runner
if [ -f "$HR_DIR/workflow.js" ]; then
echo "[OK] workflow.js exists in hook-runner"
else
echo "[FAIL] workflow.js missing from hook-runner"
exit 1
fi

WF_JS=$(to_node "$HR_DIR/workflow.js")

# Can load without error
if node -e "require('$WF_JS')" 2>/dev/null; then
echo "[OK] workflow.js loads without error"
else
echo "[FAIL] workflow.js fails to load"
errors=$((errors + 1))
fi

# parseYaml works
RESULT=$(node -e "
const wf = require('$WF_JS');
const parsed = wf.parseYaml('name: test\nversion: 1\nsteps:\n - id: s1\n name: Step 1');
console.log(JSON.stringify(parsed));
")
if echo "$RESULT" | grep -q '"name":"test"'; then
echo "[OK] parseYaml works"
else
echo "[FAIL] parseYaml returned: $RESULT"
errors=$((errors + 1))
fi

# State management works
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT

# Create a test workflow YAML
cat > "$TMPDIR/test.yml" <<'YAML'
name: test-wf
steps:
- id: step1
name: First
gate:
require_files: []
completion:
require_files: []
- id: step2
name: Second
gate:
require_step: step1
completion:
require_files: []
YAML

TP=$(to_node "$TMPDIR")
TY=$(to_node "$TMPDIR/test.yml")

# initState + currentStep
CURRENT=$(node -e "
const wf = require('$WF_JS');
wf.initState('test-wf', '$TY', '$TP');
console.log(wf.currentStep('$TP'));
")
if [ "$CURRENT" = "step1" ]; then
echo "[OK] initState + currentStep works"
else
echo "[FAIL] expected step1, got: $CURRENT"
errors=$((errors + 1))
fi

# completeStep + advance
NEXT=$(node -e "
const wf = require('$WF_JS');
wf.completeStep('step1', '$TP');
console.log(wf.currentStep('$TP'));
")
if [ "$NEXT" = "step2" ]; then
echo "[OK] completeStep advances correctly"
else
echo "[FAIL] expected step2, got: $NEXT"
errors=$((errors + 1))
fi

# checkGate blocks when prerequisite not met
GATE=$(node -e "
const wf = require('$WF_JS');
const tmpDir2 = require('os').tmpdir() + '/wf-test-' + Date.now();
require('fs').mkdirSync(tmpDir2, {recursive:true});
require('fs').writeFileSync(tmpDir2 + '/test.yml', \`name: gate-test
steps:
- id: a
name: A
gate:
require_files: []
completion:
require_files: []
- id: b
name: B
gate:
require_step: a
completion:
require_files: []
\`);
const p = '$TP'.replace(/\\\\/g,'/');
wf.initState('gate-test', tmpDir2.replace(/\\\\/g,'/') + '/test.yml', tmpDir2.replace(/\\\\/g,'/'));
const check = wf.checkGate('b', tmpDir2.replace(/\\\\/g,'/'));
console.log(check.allowed ? 'allowed' : 'blocked');
")
if [ "$GATE" = "blocked" ]; then
echo "[OK] checkGate blocks when prerequisite not met"
else
echo "[FAIL] expected blocked, got: $GATE"
errors=$((errors + 1))
fi

# Exports all expected functions
EXPORTS=$(node -e "
const wf = require('$WF_JS');
const fns = ['parseYaml','loadWorkflow','findWorkflows','readState','writeState','initState','completeStep','currentStep','checkGate','checkEditAllowed'];
const missing = fns.filter(f => typeof wf[f] !== 'function');
console.log(missing.length === 0 ? 'all' : 'missing:' + missing.join(','));
")
if [ "$EXPORTS" = "all" ]; then
echo "[OK] all expected functions exported"
else
echo "[FAIL] $EXPORTS"
errors=$((errors + 1))
fi

echo ""
echo "=== $((7 - errors))/7 tests passed ==="
exit $errors
148 changes: 148 additions & 0 deletions scripts/test/test-T003-hookrunner-gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/usr/bin/env bash
# Test: workflow-gate PreToolUse module exists in hook-runner and works
set -euo pipefail

HR_DIR="$HOME/Documents/ProjectsCL1/hook-runner"

to_node() { cygpath -m "$1" 2>/dev/null || echo "$1"; }

errors=0

# Module exists
if [ -f "$HR_DIR/modules/PreToolUse/workflow-gate.js" ]; then
echo "[OK] workflow-gate.js exists"
else
echo "[FAIL] modules/PreToolUse/workflow-gate.js missing"
exit 1
fi

MOD_JS=$(to_node "$HR_DIR/modules/PreToolUse/workflow-gate.js")

# Module loads
if node -e "require('$MOD_JS')" 2>/dev/null; then
echo "[OK] module loads without error"
else
echo "[FAIL] module fails to load"
errors=$((errors + 1))
fi

# Module exports a function
TYPEOF=$(node -e "console.log(typeof require('$MOD_JS'))")
if [ "$TYPEOF" = "function" ]; then
echo "[OK] exports a function"
else
echo "[FAIL] expected function, got $TYPEOF"
errors=$((errors + 1))
fi

# Returns null when no active workflow
RESULT=$(node -e "
const gate = require('$MOD_JS');
process.env.CLAUDE_PROJECT_DIR = require('os').tmpdir();
const r = gate({tool_name:'Write', tool_input:{file_path:'/tmp/foo.js'}});
console.log(r === null ? 'null' : JSON.stringify(r));
")
if [ "$RESULT" = "null" ]; then
echo "[OK] returns null when no active workflow"
else
echo "[FAIL] expected null, got: $RESULT"
errors=$((errors + 1))
fi

# Returns null for allowed paths (TODO.md, specs/, etc.)
RESULT=$(node -e "
const gate = require('$MOD_JS');
process.env.CLAUDE_PROJECT_DIR = require('os').tmpdir();
const r = gate({tool_name:'Write', tool_input:{file_path:'/tmp/project/TODO.md'}});
console.log(r === null ? 'null' : JSON.stringify(r));
")
if [ "$RESULT" = "null" ]; then
echo "[OK] allows TODO.md edits"
else
echo "[FAIL] expected null for TODO.md, got: $RESULT"
errors=$((errors + 1))
fi

# Blocks when gate unsatisfied
WF_JS=$(to_node "$HR_DIR/workflow.js")
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
TP=$(to_node "$TMPDIR")

cat > "$TMPDIR/test.yml" <<'YAML'
name: gate-test
steps:
- id: step1
name: First
gate:
require_files: ["nonexistent.txt"]
completion:
require_files: []
YAML

TY=$(to_node "$TMPDIR/test.yml")

RESULT=$(node -e "
const wf = require('$WF_JS');
wf.initState('gate-test', '$TY', '$TP');
const gate = require('$MOD_JS');
process.env.CLAUDE_PROJECT_DIR = '$TP';
const r = gate({tool_name:'Write', tool_input:{file_path:'$TP/src/main.js'}});
console.log(r && r.decision === 'block' ? 'blocked' : 'not-blocked');
")
if [ "$RESULT" = "blocked" ]; then
echo "[OK] blocks when gate unsatisfied"
else
echo "[FAIL] expected blocked, got: $RESULT"
errors=$((errors + 1))
fi

# Passes when gate satisfied
RESULT=$(node -e "
const wf = require('$WF_JS');
const fs = require('fs');
// Reset and use a workflow with no gate requirements
fs.unlinkSync('$TP/.workflow-state.json');
fs.writeFileSync('$TP/test2.yml', \`name: pass-test
steps:
- id: step1
name: First
gate:
require_files: []
completion:
require_files: []
\`);
wf.initState('pass-test', '$TP/test2.yml', '$TP');
const gate = require('$MOD_JS');
process.env.CLAUDE_PROJECT_DIR = '$TP';
// Clear require cache to get fresh module
delete require.cache[require.resolve('$MOD_JS')];
const gate2 = require('$MOD_JS');
const r = gate2({tool_name:'Write', tool_input:{file_path:'$TP/src/main.js'}});
console.log(r === null ? 'passed' : JSON.stringify(r));
")
if [ "$RESULT" = "passed" ]; then
echo "[OK] passes when gate satisfied"
else
echo "[FAIL] expected passed, got: $RESULT"
errors=$((errors + 1))
fi

# Only triggers on Write/Edit
RESULT=$(node -e "
delete require.cache[require.resolve('$MOD_JS')];
const gate = require('$MOD_JS');
process.env.CLAUDE_PROJECT_DIR = '$TP';
const r = gate({tool_name:'Bash', tool_input:{command:'echo hi'}});
console.log(r === null ? 'skipped' : 'triggered');
")
if [ "$RESULT" = "skipped" ]; then
echo "[OK] skips non-Write/Edit tools"
else
echo "[FAIL] expected skipped, got: $RESULT"
errors=$((errors + 1))
fi

echo ""
echo "=== $((8 - errors))/8 tests passed ==="
exit $errors
Loading