docs: map existing codebase
This commit is contained in:
209
.planning/codebase/TESTING.md
Normal file
209
.planning/codebase/TESTING.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# 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*
|
||||
Reference in New Issue
Block a user