spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / plugins / plugin-dev / skills / hook-development / scripts / test-hook.sh
252 lines · 5.2 KBRaw
1#!/bin/bash
2# Hook Testing Helper
3# Tests a hook with sample input and shows output
4
5set -euo pipefail
6
7# Usage
8show_usage() {
9 echo "Usage: $0 [options] <hook-script> <test-input.json>"
10 echo ""
11 echo "Options:"
12 echo " -h, --help Show this help message"
13 echo " -v, --verbose Show detailed execution information"
14 echo " -t, --timeout N Set timeout in seconds (default: 60)"
15 echo ""
16 echo "Examples:"
17 echo " $0 validate-bash.sh test-input.json"
18 echo " $0 -v -t 30 validate-write.sh write-input.json"
19 echo ""
20 echo "Creates sample test input with:"
21 echo " $0 --create-sample <event-type>"
22 exit 0
23}
24
25# Create sample input
26create_sample() {
27 event_type="$1"
28
29 case "$event_type" in
30 PreToolUse)
31 cat <<'EOF'
32{
33 "session_id": "test-session",
34 "transcript_path": "/tmp/transcript.txt",
35 "cwd": "/tmp/test-project",
36 "permission_mode": "ask",
37 "hook_event_name": "PreToolUse",
38 "tool_name": "Write",
39 "tool_input": {
40 "file_path": "/tmp/test.txt",
41 "content": "Test content"
42 }
43}
44EOF
45 ;;
46 PostToolUse)
47 cat <<'EOF'
48{
49 "session_id": "test-session",
50 "transcript_path": "/tmp/transcript.txt",
51 "cwd": "/tmp/test-project",
52 "permission_mode": "ask",
53 "hook_event_name": "PostToolUse",
54 "tool_name": "Bash",
55 "tool_result": "Command executed successfully"
56}
57EOF
58 ;;
59 Stop|SubagentStop)
60 cat <<'EOF'
61{
62 "session_id": "test-session",
63 "transcript_path": "/tmp/transcript.txt",
64 "cwd": "/tmp/test-project",
65 "permission_mode": "ask",
66 "hook_event_name": "Stop",
67 "reason": "Task appears complete"
68}
69EOF
70 ;;
71 UserPromptSubmit)
72 cat <<'EOF'
73{
74 "session_id": "test-session",
75 "transcript_path": "/tmp/transcript.txt",
76 "cwd": "/tmp/test-project",
77 "permission_mode": "ask",
78 "hook_event_name": "UserPromptSubmit",
79 "user_prompt": "Test user prompt"
80}
81EOF
82 ;;
83 SessionStart|SessionEnd)
84 cat <<'EOF'
85{
86 "session_id": "test-session",
87 "transcript_path": "/tmp/transcript.txt",
88 "cwd": "/tmp/test-project",
89 "permission_mode": "ask",
90 "hook_event_name": "SessionStart"
91}
92EOF
93 ;;
94 *)
95 echo "Unknown event type: $event_type"
96 echo "Valid types: PreToolUse, PostToolUse, Stop, SubagentStop, UserPromptSubmit, SessionStart, SessionEnd"
97 exit 1
98 ;;
99 esac
100}
101
102# Parse arguments
103VERBOSE=false
104TIMEOUT=60
105
106while [ $# -gt 0 ]; do
107 case "$1" in
108 -h|--help)
109 show_usage
110 ;;
111 -v|--verbose)
112 VERBOSE=true
113 shift
114 ;;
115 -t|--timeout)
116 TIMEOUT="$2"
117 shift 2
118 ;;
119 --create-sample)
120 create_sample "$2"
121 exit 0
122 ;;
123 *)
124 break
125 ;;
126 esac
127done
128
129if [ $# -ne 2 ]; then
130 echo "Error: Missing required arguments"
131 echo ""
132 show_usage
133fi
134
135HOOK_SCRIPT="$1"
136TEST_INPUT="$2"
137
138# Validate inputs
139if [ ! -f "$HOOK_SCRIPT" ]; then
140 echo "❌ Error: Hook script not found: $HOOK_SCRIPT"
141 exit 1
142fi
143
144if [ ! -x "$HOOK_SCRIPT" ]; then
145 echo "⚠️ Warning: Hook script is not executable. Attempting to run with bash..."
146 HOOK_SCRIPT="bash $HOOK_SCRIPT"
147fi
148
149if [ ! -f "$TEST_INPUT" ]; then
150 echo "❌ Error: Test input not found: $TEST_INPUT"
151 exit 1
152fi
153
154# Validate test input JSON
155if ! jq empty "$TEST_INPUT" 2>/dev/null; then
156 echo "❌ Error: Test input is not valid JSON"
157 exit 1
158fi
159
160echo "🧪 Testing hook: $HOOK_SCRIPT"
161echo "📥 Input: $TEST_INPUT"
162echo ""
163
164if [ "$VERBOSE" = true ]; then
165 echo "Input JSON:"
166 jq . "$TEST_INPUT"
167 echo ""
168fi
169
170# Set up environment
171export CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/tmp/test-project}"
172export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(pwd)}"
173export CLAUDE_ENV_FILE="${CLAUDE_ENV_FILE:-/tmp/test-env-$$}"
174
175if [ "$VERBOSE" = true ]; then
176 echo "Environment:"
177 echo " CLAUDE_PROJECT_DIR=$CLAUDE_PROJECT_DIR"
178 echo " CLAUDE_PLUGIN_ROOT=$CLAUDE_PLUGIN_ROOT"
179 echo " CLAUDE_ENV_FILE=$CLAUDE_ENV_FILE"
180 echo ""
181fi
182
183# Run the hook
184echo "▶️ Running hook (timeout: ${TIMEOUT}s)..."
185echo ""
186
187start_time=$(date +%s)
188
189set +e
190output=$(timeout "$TIMEOUT" bash -c "cat '$TEST_INPUT' | $HOOK_SCRIPT" 2>&1)
191exit_code=$?
192set -e
193
194end_time=$(date +%s)
195duration=$((end_time - start_time))
196
197# Analyze results
198echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
199echo "Results:"
200echo ""
201echo "Exit Code: $exit_code"
202echo "Duration: ${duration}s"
203echo ""
204
205case $exit_code in
206 0)
207 echo "✅ Hook approved/succeeded"
208 ;;
209 2)
210 echo "🚫 Hook blocked/denied"
211 ;;
212 124)
213 echo "⏱️ Hook timed out after ${TIMEOUT}s"
214 ;;
215 *)
216 echo "⚠️ Hook returned unexpected exit code: $exit_code"
217 ;;
218esac
219
220echo ""
221echo "Output:"
222if [ -n "$output" ]; then
223 echo "$output"
224 echo ""
225
226 # Try to parse as JSON
227 if echo "$output" | jq empty 2>/dev/null; then
228 echo "Parsed JSON output:"
229 echo "$output" | jq .
230 fi
231else
232 echo "(no output)"
233fi
234
235# Check for environment file
236if [ -f "$CLAUDE_ENV_FILE" ]; then
237 echo ""
238 echo "Environment file created:"
239 cat "$CLAUDE_ENV_FILE"
240 rm -f "$CLAUDE_ENV_FILE"
241fi
242
243echo ""
244echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
245
246if [ $exit_code -eq 0 ] || [ $exit_code -eq 2 ]; then
247 echo "✅ Test completed successfully"
248 exit 0
249else
250 echo "❌ Test failed"
251 exit 1
252fi