# Testing Patterns **Analysis Date:** 2026-03-27 ## Test Framework **Status:** Not detected - No test files (*.test.js, *.spec.js) found in codebase - No test runner configured (no jest.config.js, vitest.config.js, or mocha configuration) - No test dependencies listed in package.json files **Code Context:** - The codebase consists of 5 Node.js hook scripts (579 total lines across `.claude/hooks/`) - Each hook is a standalone CLI tool that reads JSON from stdin and outputs JSON to stdout - Hooks are event-driven (SessionStart, PreToolUse, PostToolUse, AfterTool lifecycle events) - No application code beyond these hooks exists in the repository ## Script Type & Testing Approach **Current Architecture:** Each hook file (`gsd-*.js` in `/home/ys/family-repo/AgenticCode/.claude/hooks/`) follows the same structural pattern: - Shebang: `#!/usr/bin/env node` - Node.js built-in modules only (fs, path, os, child_process) - JSON stdin → processing → JSON stdout - Silent failure on errors (timeout guards, try-catch with exit(0)) **Hook Files:** - `gsd-prompt-guard.js` (96 lines) - Detects prompt injection patterns in written files - `gsd-statusline.js` (119 lines) - Renders context usage and task status - `gsd-context-monitor.js` (156 lines) - Warns when context window is low - `gsd-workflow-guard.js` (94 lines) - Advises on workflow compliance - `gsd-check-update.js` (114 lines) - Checks for GSD updates in background ## Manual Testing Indicators **Integration Points (evidence of real-world testing):** - Comments referencing specific issues: `See #775`, `See #1162`, `See #870`, `See #884` - Platform-specific workarounds: `windowsHide: true` for child_process to prevent console flash on Windows - Timeout guards: `const stdinTimeout = setTimeout(() => process.exit(0), 3000);` prevents hanging on pipe issues - Git Bash compatibility: explicit handling of stdin timeout on Windows Git Bash **Behavioral Validation Patterns:** - Config file validation: reads `.planning/config.json`, catches parse errors gracefully - File existence checks before operations: `if (fs.existsSync(filePath))` - Stale data detection: `if ((now - metrics.timestamp) > STALE_SECONDS)` - Severity escalation tracking: debounce counter resets on warning level change ## Input Validation **JSON Parsing with Error Handling:** All hooks follow this pattern (example from `gsd-prompt-guard.js`): ```javascript let input = ''; const stdinTimeout = setTimeout(() => process.exit(0), 3000); process.stdin.setEncoding('utf8'); process.stdin.on('data', chunk => input += chunk); process.stdin.on('end', () => { clearTimeout(stdinTimeout); try { const data = JSON.parse(input); // Process data } catch { // Silent fail — never block tool execution process.exit(0); } }); ``` **Defensive Checks:** - Field existence: `const toolName = data.tool_name;` then `if (toolName !== 'Write' && toolName !== 'Edit')` - Optional chaining: `data.tool_input?.file_path || ''` - Null checks: `if (!sessionId) { process.exit(0); }` - Default values: `data.cwd || process.cwd()`, `data.model?.display_name || 'Claude'` ## File I/O Testing **Patterns for Reliability:** - Synchronous I/O ensures order: `fs.readFileSync()` → process → `fs.writeFileSync()` - Directory existence checked before writing: `if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }); }` - Try-catch wraps all file operations that could fail: ```javascript try { const bridgeData = JSON.stringify({ ... }); fs.writeFileSync(bridgePath, bridgeData); } catch (e) { // Silent fail -- bridge is best-effort, don't break statusline } ``` ## State Machine / Behavior Testing **Context Monitor Debounce Logic** (`gsd-context-monitor.js`): - Tracks warning state in file: `/tmp/claude-ctx-{session_id}-warned.json` - Debounce counter incremented: `warnData.callsSinceWarn = (warnData.callsSinceWarn || 0) + 1` - Severity escalation bypasses debounce: `if (severityEscalated) { // emit immediately }` - State reset on warn: `warnData.callsSinceWarn = 0` This pattern validates behavior without formal tests: ```javascript let warnData = { callsSinceWarn: 0, lastLevel: null }; const isCritical = remaining <= CRITICAL_THRESHOLD; const currentLevel = isCritical ? 'critical' : 'warning'; const severityEscalated = currentLevel === 'critical' && warnData.lastLevel === 'warning'; if (!firstWarn && warnData.callsSinceWarn < DEBOUNCE_CALLS && !severityEscalated) { process.exit(0); // Suppress warning } ``` ## Regex Pattern Testing **Prompt Injection Detection** (`gsd-prompt-guard.js`): Patterns tested against content without formal unit tests: ```javascript const INJECTION_PATTERNS = [ /ignore\s+(all\s+)?previous\s+instructions/i, /override\s+(system|previous)\s+(prompt|instructions)/i, /you\s+are\s+now\s+(?:a|an|the)\s+/i, /(?:print|output|reveal|show|display|repeat)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions)/i, /\[SYSTEM\]/i, /<<\s*SYS\s*>>/i, ]; for (const pattern of INJECTION_PATTERNS) { if (pattern.test(content)) { findings.push(pattern.source); } } ``` Unicode detection without regex: `if (/[\u200B-\u200F\u2028-\u202F\uFEFF\u00AD]/.test(content))` ## Environment & Configuration Testing **Configuration Loading Safety** (all hooks): - Try-catch with silent fail: ```javascript const configPath = path.join(cwd, '.planning', 'config.json'); if (fs.existsSync(configPath)) { try { const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); if (config.hooks?.context_warnings === false) { process.exit(0); // Feature disabled } } catch (e) { // Ignore config parse errors } } ``` - Optional chaining for nested config: `config.hooks?.workflow_guard`, `config.hooks?.context_warnings` **Environment Variable Access:** ```javascript const envDir = process.env.CLAUDE_CONFIG_DIR; if (envDir && fs.existsSync(path.join(envDir, 'get-shit-done', 'VERSION'))) { return envDir; // Custom config dir detected } ``` ## Performance & Resource Management **Timeout Guards (prevent resource leaks):** - All hooks implement stdin timeout: `const stdinTimeout = setTimeout(() => process.exit(0), 3000);` - Longer timeout for high-volume operations: `const stdinTimeout = setTimeout(() => process.exit(0), 10000);` in context-monitor - Always cleared before processing: `clearTimeout(stdinTimeout);` **Background Process Management** (`gsd-check-update.js`): - Child process spawned with `stdio: 'ignore'`: doesn't inherit parent's stdio - Process detached on Windows: `detached: true` (required for proper cleanup) - Parent calls `child.unref()`: parent doesn't wait for child to exit ```javascript const child = spawn(process.execPath, ['-e', `...inline script...`], { stdio: 'ignore', windowsHide: true, detached: true }); child.unref(); ``` ## Test Coverage Gaps **Areas Without Formal Testing:** 1. **Regex Pattern Accuracy** - Injection patterns untested against false positives/negatives 2. **Debounce Counter Edge Cases** - Corruption recovery, counter reset logic 3. **Platform-Specific Behavior** - Windows vs Linux path handling, process detachment 4. **Concurrent Access** - Multiple hooks writing to same state files simultaneously 5. **Large Input Handling** - No tests for multi-megabyte JSON on stdin 6. **Stale File Cleanup** - No validation that temp files are properly removed 7. **Spawn Child Behavior** - Background update check success/failure not validated ## Recommendation for Testing **Given the architecture (CLI hooks, not library code):** - Integration testing more valuable than unit tests - Manual testing via real Claude Code sessions is current primary validation - Would recommend: 1. Snapshot tests for JSON output structure 2. Mock file system for configuration loading paths 3. Integration tests simulating tool use hook flow 4. Platform-specific testing (Windows, macOS, Linux) for path handling **Lack of tests is acceptable for:** - Simple stdin/stdout data transformation scripts - Hooks deployed once per installation (not on every tool call) - Silent-fail-safe design (errors don't break workflows) - Real-world testing via 50+ GitHub issues and fixes --- *Testing analysis: 2026-03-27*