Files
AgenticCode/.planning/codebase/TESTING.md
2026-03-27 00:21:00 +00:00

210 lines
8.1 KiB
Markdown

# 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*