jettypod 4.1.2 → 4.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/docs/COMPLETE-TESTING-STRATEGY.md +970 -0
- package/docs/DECISIONS.md +10 -12
- package/docs/NODE_VERSION.md +83 -0
- package/docs/TDD-INFRASTRUCTURE-STRATEGY.md +1374 -0
- package/docs/TESTING-FOR-NON-ENGINEERS.md +1588 -0
- package/docs/TESTING-STRATEGY-AUDIT.md +698 -0
- package/hooks/post-checkout +17 -0
- package/hooks/post-merge +17 -0
- package/hooks/pre-commit +30 -0
- package/jettypod.js +259 -120
- package/lib/coverage-tracker.js +218 -0
- package/lib/database.js +2 -0
- package/lib/db-export.js +192 -0
- package/lib/db-import.js +193 -0
- package/lib/external-transition-handler.js +32 -0
- package/lib/git-hook-helpers.js +174 -0
- package/lib/git-root.js +90 -0
- package/lib/infrastructure-chore-generator.js +45 -0
- package/lib/install-hooks.js +52 -0
- package/lib/jettypod-backup.js +238 -0
- package/lib/merge-lock.js +193 -0
- package/lib/migrations/012-add-worktree-path.js +38 -0
- package/lib/migrations/013-worktrees-table.js +86 -0
- package/lib/migrations/014-migrate-worktree-data.js +161 -0
- package/lib/migrations/015-merge-locks-table.js +67 -0
- package/lib/pattern-finder.js +152 -0
- package/lib/process-manager.js +140 -0
- package/lib/production-standards-reader.js +13 -2
- package/lib/production-standards-writer.js +85 -0
- package/lib/skills/feature-planning/dry-run-validator.js +135 -0
- package/lib/skills/feature-planning/validation-formatter.js +160 -0
- package/lib/smart-conflict-detection.js +168 -0
- package/lib/smart-fetch-rebase.js +614 -0
- package/lib/step-definition-parser.js +76 -0
- package/lib/unit-test-generator.js +232 -0
- package/lib/verification-command-generator.js +66 -0
- package/lib/worktree-diagnostics.js +413 -0
- package/lib/worktree-facade.js +174 -0
- package/lib/worktree-manager.js +636 -0
- package/lib/worktree-reconciler.js +429 -0
- package/package.json +30 -3
- package/skills-templates/external-transition/SKILL.md +34 -3
- package/skills-templates/feature-planning/SKILL.md +190 -24
- package/skills-templates/production-mode/SKILL.md +127 -9
- package/skills-templates/speed-mode/SKILL.md +454 -51
- package/skills-templates/stable-mode/SKILL.md +285 -76
- package/.claude/PROTECT_SKILLS.md +0 -28
- package/.claude/settings.json +0 -24
- package/.claude/settings.local.json +0 -16
- package/.claude/skills/epic-planning/SKILL.md +0 -297
- package/.claude/skills/external-transition/SKILL.md +0 -384
- package/.claude/skills/feature-planning/SKILL.md +0 -464
- package/.claude/skills/production-mode/SKILL.md +0 -369
- package/.claude/skills/speed-mode/SKILL.md +0 -481
- package/.claude/skills/stable-mode/SKILL.md +0 -713
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/stable-mode/SKILL.md +0 -673
- package/.claude/skills.backup-2025-11-11T16-15-10-070Z/epic-discover/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/stable-mode/SKILL.md +0 -673
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/stable-mode/SKILL.md +0 -673
- package/.devpod/current-work.json +0 -10
- package/.devpod/work.db +0 -0
- package/.github/workflows/test-safety.yml +0 -85
- package/.jettypod/config.json +0 -5
- package/.jettypod/current-work.json +0 -10
- package/.jettypod/hooks/README.md +0 -77
- package/.jettypod/hooks/protect-claude-md.js +0 -338
- package/.jettypod/test-work.db +0 -0
- package/.jettypod/work.db +0 -0
- package/CLAUDE.md +0 -49
- package/SPEED-STABLE-AUDIT.md +0 -853
- package/SYSTEM-BEHAVIOR.md +0 -2199
- package/TEST_SAFETY_AUDIT.md +0 -314
- package/TEST_SAFETY_IMPLEMENTATION.md +0 -97
- package/cucumber-report.html +0 -45
- package/dist/devpod-linux +0 -0
- package/dist/devpod-macos +0 -0
- package/dist/devpod-win.exe +0 -0
- package/docs/features/jettypod-standards-explained.md +0 -543
- package/docs/features/standards-inventory.md +0 -257
- package/features/auto-generate-production-chores.feature +0 -13
- package/features/backlog-command.feature +0 -26
- package/features/backlog-filtering-production.feature +0 -10
- package/features/claude-md-protection/steps.js +0 -498
- package/features/decisions/index.js +0 -490
- package/features/decisions/index.test.js +0 -208
- package/features/fix-text-wrapping.feature +0 -42
- package/features/git-hooks/git-hooks.feature +0 -30
- package/features/git-hooks/index.js +0 -93
- package/features/git-hooks/index.test.js +0 -137
- package/features/git-hooks/post-commit +0 -56
- package/features/git-hooks/post-merge +0 -47
- package/features/git-hooks/pre-commit +0 -28
- package/features/git-hooks/simple-steps.js +0 -53
- package/features/git-hooks/simple-test.feature +0 -10
- package/features/git-hooks/steps.js +0 -196
- package/features/jettypod-update-command.feature +0 -46
- package/features/mode-prompts/index.js +0 -95
- package/features/mode-prompts/simple-steps.js +0 -44
- package/features/mode-prompts/simple-test.feature +0 -9
- package/features/mode-prompts/validation.test.js +0 -120
- package/features/multiple-claude-instances.feature +0 -121
- package/features/production-mode-skill.feature +0 -121
- package/features/refactor-mode/steps.js +0 -217
- package/features/refactor-mode.feature +0 -49
- package/features/simplify-external-transition.feature +0 -166
- package/features/skills-update/index.test.js +0 -216
- package/features/step_definitions/backlog-command.steps.js +0 -37
- package/features/step_definitions/fix-text-wrapping.steps.js +0 -271
- package/features/step_definitions/multiple-claude-instances.steps.js +0 -621
- package/features/step_definitions/production-mode-skill.steps.js +0 -862
- package/features/step_definitions/simplify-external-transition.steps.js +0 -370
- package/features/step_definitions/terminal-logo.steps.js +0 -145
- package/features/step_definitions/update-command.steps.js +0 -183
- package/features/support/hooks.js +0 -9
- package/features/terminal-logo/index.js +0 -39
- package/features/terminal-logo/terminal-logo.feature +0 -30
- package/features/update-command/index.js +0 -181
- package/features/update-command/index.test.js +0 -225
- package/features/work-commands/bug-workflow-display.feature +0 -22
- package/features/work-commands/index.js +0 -498
- package/features/work-commands/simple-steps.js +0 -69
- package/features/work-commands/stable-tests.feature +0 -57
- package/features/work-commands/steps.js +0 -1174
- package/features/work-commands/validation.test.js +0 -88
- package/features/work-commands/work-commands.feature +0 -13
- package/features/work-tracking/discovery-validation.test.js +0 -228
- package/features/work-tracking/index.js +0 -1921
- package/features/work-tracking/mode-required.feature +0 -112
- package/features/work-tracking/phase-tracking.test.js +0 -482
- package/features/work-tracking/prototype-tracking.test.js +0 -485
- package/features/work-tracking/tree-view.test.js +0 -310
- package/features/work-tracking/work-set-mode.feature +0 -71
- package/features/work-tracking/work-start-mode.feature +0 -88
- package/full-test.txt +0 -0
- package/lib/bug-workflow.test.js +0 -177
- package/lib/claudemd.test.js +0 -195
- package/lib/config.test.js +0 -511
- package/lib/constants.test.js +0 -164
- package/lib/current-work.test.js +0 -146
- package/lib/database-project-config.test.js +0 -111
- package/lib/database.test.js +0 -106
- package/lib/decisions-generator.test.js +0 -457
- package/lib/decisions-helpers.test.js +0 -310
- package/lib/git-coordinator.js +0 -167
- package/lib/git.test.js +0 -145
- package/lib/migrations/002-default-work-item-modes.test.js +0 -351
- package/lib/production-chore-generator.test.js +0 -432
- package/lib/production-context-detector.test.js +0 -277
- package/lib/production-scenario-appender.test.js +0 -235
- package/lib/production-scenario-validator.test.js +0 -246
- package/lib/production-standards-reader.test.js +0 -270
- package/lib/project-state.test.js +0 -92
- package/lib/push-queue.js +0 -417
- package/lib/queue-processor.js +0 -74
- package/lib/test-helpers.js +0 -202
- package/lib/test-helpers.test.js +0 -255
- package/prototypes/2025-01-11-production-mode-autonomous.js +0 -119
- package/prototypes/2025-01-11-production-mode-collaborative.js +0 -166
- package/prototypes/2025-01-11-production-mode-guided.js +0 -217
- package/prototypes/2025-01-11-production-mode-smart-context.js +0 -347
- package/prototypes/2025-01-11-production-standards-example.md +0 -204
- package/prototypes/2025-11-10-backlog-filtering-tree-aware.js +0 -242
- package/prototypes/test/index.html +0 -1
- package/setup-dist-repo.sh +0 -68
- package/test-production-standards-engine.js +0 -130
- package/test-results.json +0 -2195
- package/test-safety-check.sh +0 -80
- package/work-item-tracking-plan.md +0 -199
- /package/{.jettypod/devpod.db → jettypod.db} +0 -0
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
# Complete Testing Strategy for JettyPod
|
|
2
|
+
|
|
3
|
+
**Date**: 2025-11-14
|
|
4
|
+
**Purpose**: Honest, comprehensive testing strategy incorporating ALL test types
|
|
5
|
+
**Scope**: Unit, Integration, E2E, Performance, Security, Contract, Visual, Mutation
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Honesty First: What We Actually Need
|
|
10
|
+
|
|
11
|
+
### The Truth About Testing
|
|
12
|
+
|
|
13
|
+
**You don't need every test type.** You need the right tests for your project.
|
|
14
|
+
|
|
15
|
+
**JettyPod is a CLI tool with:**
|
|
16
|
+
- Local database (SQLite)
|
|
17
|
+
- Git integration
|
|
18
|
+
- File system operations
|
|
19
|
+
- No UI (no visual regression tests needed)
|
|
20
|
+
- No external APIs (no contract tests needed yet)
|
|
21
|
+
- Single-user (no load tests needed yet)
|
|
22
|
+
|
|
23
|
+
**Therefore, prioritize:**
|
|
24
|
+
1. ✅ **Unit tests** - Most important (fast, reliable)
|
|
25
|
+
2. ✅ **Integration tests** - Important (BDD scenarios)
|
|
26
|
+
3. ✅ **E2E tests** - Some (full CLI workflows)
|
|
27
|
+
4. ⚠️ **Performance tests** - Basic (timing checks)
|
|
28
|
+
5. ⚠️ **Security tests** - Basic (input validation)
|
|
29
|
+
6. ❌ **Visual tests** - Not needed (no UI)
|
|
30
|
+
7. ❌ **Contract tests** - Not needed yet (no external APIs)
|
|
31
|
+
8. ❌ **Mutation tests** - Nice to have (not MVP)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## The Test Pyramid (Applied to JettyPod)
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
/\
|
|
39
|
+
/ \
|
|
40
|
+
/ E2E \ 10% - Full CLI workflows
|
|
41
|
+
/-------\ (~10 tests)
|
|
42
|
+
/ BDD \ 30% - Feature scenarios
|
|
43
|
+
/-----------\ (~30 scenarios)
|
|
44
|
+
/ Unit \ 60% - Function/module tests
|
|
45
|
+
/_______________\ (~100+ tests)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Why This Ratio?
|
|
49
|
+
|
|
50
|
+
**Unit tests (60%):**
|
|
51
|
+
- Test pure functions in lib/
|
|
52
|
+
- Fast (milliseconds)
|
|
53
|
+
- Reliable (no dependencies)
|
|
54
|
+
- Easy to debug
|
|
55
|
+
- Cheap to maintain
|
|
56
|
+
|
|
57
|
+
**Integration/BDD tests (30%):**
|
|
58
|
+
- Test modules working together
|
|
59
|
+
- Medium speed (seconds)
|
|
60
|
+
- Some setup required
|
|
61
|
+
- Test user workflows
|
|
62
|
+
- Living documentation
|
|
63
|
+
|
|
64
|
+
**E2E tests (10%):**
|
|
65
|
+
- Test complete system
|
|
66
|
+
- Slow (minutes)
|
|
67
|
+
- Brittle (environment-dependent)
|
|
68
|
+
- High maintenance
|
|
69
|
+
- Catch integration gaps
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Test Type 1: Unit Tests (Jest)
|
|
74
|
+
|
|
75
|
+
### What to Unit Test
|
|
76
|
+
|
|
77
|
+
**Everything in lib/ directory:**
|
|
78
|
+
```
|
|
79
|
+
lib/
|
|
80
|
+
├── merge-lock.js → merge-lock.test.js
|
|
81
|
+
├── validators.js → validators.test.js
|
|
82
|
+
├── database.js → database.test.js
|
|
83
|
+
├── git-helpers.js → git-helpers.test.js
|
|
84
|
+
├── decisions-helpers.js → decisions-helpers.test.js
|
|
85
|
+
└── current-work.js → current-work.test.js
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**What makes a good unit test:**
|
|
89
|
+
- Tests ONE function/method
|
|
90
|
+
- No external dependencies (or mocked)
|
|
91
|
+
- Fast (< 100ms per test)
|
|
92
|
+
- Deterministic (same input = same output)
|
|
93
|
+
- Independent (can run in any order)
|
|
94
|
+
|
|
95
|
+
### Example: Comprehensive Unit Test
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
// lib/validators.test.js
|
|
99
|
+
const { validateWorkItemTitle, validateMode, validateStatus } = require('./validators');
|
|
100
|
+
|
|
101
|
+
describe('validateWorkItemTitle', () => {
|
|
102
|
+
describe('valid titles', () => {
|
|
103
|
+
it('accepts standard title', () => {
|
|
104
|
+
expect(validateWorkItemTitle('Add login feature')).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('accepts title with numbers', () => {
|
|
108
|
+
expect(validateWorkItemTitle('Fix bug #123')).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('accepts title with special characters', () => {
|
|
112
|
+
expect(validateWorkItemTitle('Add @mentions support')).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('accepts minimum length title', () => {
|
|
116
|
+
expect(validateWorkItemTitle('Fix')).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('invalid titles', () => {
|
|
121
|
+
it('rejects null', () => {
|
|
122
|
+
expect(validateWorkItemTitle(null)).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('rejects undefined', () => {
|
|
126
|
+
expect(validateWorkItemTitle(undefined)).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('rejects empty string', () => {
|
|
130
|
+
expect(validateWorkItemTitle('')).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('rejects whitespace only', () => {
|
|
134
|
+
expect(validateWorkItemTitle(' ')).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('rejects too long title', () => {
|
|
138
|
+
const longTitle = 'a'.repeat(501);
|
|
139
|
+
expect(validateWorkItemTitle(longTitle)).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('edge cases', () => {
|
|
144
|
+
it('handles title at max length', () => {
|
|
145
|
+
const maxTitle = 'a'.repeat(500);
|
|
146
|
+
expect(validateWorkItemTitle(maxTitle)).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('trims whitespace before validation', () => {
|
|
150
|
+
expect(validateWorkItemTitle(' Valid title ')).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('handles unicode characters', () => {
|
|
154
|
+
expect(validateWorkItemTitle('Add 日本語 support')).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('validateMode', () => {
|
|
160
|
+
it('accepts speed mode', () => {
|
|
161
|
+
expect(validateMode('speed')).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('accepts stable mode', () => {
|
|
165
|
+
expect(validateMode('stable')).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('accepts production mode', () => {
|
|
169
|
+
expect(validateMode('production')).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('rejects invalid mode', () => {
|
|
173
|
+
expect(validateMode('fast')).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('rejects null', () => {
|
|
177
|
+
expect(validateMode(null)).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('is case sensitive', () => {
|
|
181
|
+
expect(validateMode('Speed')).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Unit Test Best Practices
|
|
187
|
+
|
|
188
|
+
**1. Use descriptive test names:**
|
|
189
|
+
```javascript
|
|
190
|
+
// ❌ Bad
|
|
191
|
+
it('works', () => { ... });
|
|
192
|
+
|
|
193
|
+
// ✅ Good
|
|
194
|
+
it('returns false when title exceeds 500 characters', () => { ... });
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**2. Arrange-Act-Assert pattern:**
|
|
198
|
+
```javascript
|
|
199
|
+
it('calculates queue position correctly', () => {
|
|
200
|
+
// Arrange
|
|
201
|
+
const queue = [
|
|
202
|
+
{ id: 1, timestamp: 100 },
|
|
203
|
+
{ id: 2, timestamp: 200 },
|
|
204
|
+
{ id: 3, timestamp: 300 }
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
// Act
|
|
208
|
+
const position = getQueuePosition(queue, 2);
|
|
209
|
+
|
|
210
|
+
// Assert
|
|
211
|
+
expect(position).toBe(1); // 0-indexed
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**3. One assertion per test (when possible):**
|
|
216
|
+
```javascript
|
|
217
|
+
// ❌ Too many concerns
|
|
218
|
+
it('validates and sanitizes input', () => {
|
|
219
|
+
expect(validate(input)).toBe(true);
|
|
220
|
+
expect(sanitize(input)).toBe(cleaned);
|
|
221
|
+
expect(process(cleaned)).toBe(result);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// ✅ Separate concerns
|
|
225
|
+
it('validates input', () => {
|
|
226
|
+
expect(validate(input)).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('sanitizes input', () => {
|
|
230
|
+
expect(sanitize(input)).toBe(cleaned);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('processes sanitized input', () => {
|
|
234
|
+
expect(process(cleaned)).toBe(result);
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**4. Test error conditions:**
|
|
239
|
+
```javascript
|
|
240
|
+
describe('error handling', () => {
|
|
241
|
+
it('throws when database is locked', () => {
|
|
242
|
+
mockDb.isLocked = true;
|
|
243
|
+
expect(() => acquireLock()).toThrow('Database is locked');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('throws with helpful message when file not found', () => {
|
|
247
|
+
expect(() => readConfig('/nonexistent')).toThrow(/Config file not found/);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**5. Mock external dependencies:**
|
|
253
|
+
```javascript
|
|
254
|
+
// ❌ Bad - depends on real file system
|
|
255
|
+
it('reads config file', () => {
|
|
256
|
+
const config = readConfig('.jettypod/config.json');
|
|
257
|
+
expect(config).toBeDefined();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ✅ Good - mocked file system
|
|
261
|
+
jest.mock('fs');
|
|
262
|
+
it('reads config file', () => {
|
|
263
|
+
fs.readFileSync.mockReturnValue('{"key": "value"}');
|
|
264
|
+
const config = readConfig('.jettypod/config.json');
|
|
265
|
+
expect(config.key).toBe('value');
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### When to Write Unit Tests
|
|
270
|
+
|
|
271
|
+
**In workflow:**
|
|
272
|
+
- **Stable mode**: When adding error handling logic
|
|
273
|
+
- **Standalone chores**: When implementing utility functions
|
|
274
|
+
- **Refactoring**: Before changing implementation
|
|
275
|
+
|
|
276
|
+
**What to unit test:**
|
|
277
|
+
- ✅ Pure functions (no side effects)
|
|
278
|
+
- ✅ Business logic
|
|
279
|
+
- ✅ Validation functions
|
|
280
|
+
- ✅ Utility functions
|
|
281
|
+
- ✅ Error handling paths
|
|
282
|
+
- ✅ Edge cases and boundary conditions
|
|
283
|
+
|
|
284
|
+
**What NOT to unit test:**
|
|
285
|
+
- ❌ External libraries (trust they're tested)
|
|
286
|
+
- ❌ Configuration files (use schema validation)
|
|
287
|
+
- ❌ Generated code
|
|
288
|
+
- ❌ Trivial getters/setters
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Test Type 2: Integration Tests (BDD/Cucumber)
|
|
293
|
+
|
|
294
|
+
### What to Integration Test
|
|
295
|
+
|
|
296
|
+
**User workflows that span multiple modules:**
|
|
297
|
+
- Work item lifecycle (create → start → complete)
|
|
298
|
+
- Git integration (hooks, branches, worktrees)
|
|
299
|
+
- Feature workflow (planning → speed → stable → production)
|
|
300
|
+
- Database operations with business logic
|
|
301
|
+
|
|
302
|
+
### Integration vs Unit Tests
|
|
303
|
+
|
|
304
|
+
| Unit Test | Integration Test |
|
|
305
|
+
|-----------|------------------|
|
|
306
|
+
| Single function | Multiple modules |
|
|
307
|
+
| Mocked dependencies | Real dependencies |
|
|
308
|
+
| Milliseconds | Seconds |
|
|
309
|
+
| Testing implementation | Testing behavior |
|
|
310
|
+
| Easy to debug | Harder to debug |
|
|
311
|
+
|
|
312
|
+
### Example: Integration Test (BDD)
|
|
313
|
+
|
|
314
|
+
```gherkin
|
|
315
|
+
# features/work-lifecycle.feature
|
|
316
|
+
|
|
317
|
+
Feature: Work Item Lifecycle
|
|
318
|
+
Test complete workflow from creation to completion
|
|
319
|
+
|
|
320
|
+
Background:
|
|
321
|
+
Given I have initialized jettypod with git
|
|
322
|
+
And I am on the main branch
|
|
323
|
+
|
|
324
|
+
Scenario: Create and complete a chore
|
|
325
|
+
When I create a chore "Fix merge lock bug"
|
|
326
|
+
Then a work item should exist with title "Fix merge lock bug"
|
|
327
|
+
And the work item status should be "todo"
|
|
328
|
+
|
|
329
|
+
When I start work on the chore
|
|
330
|
+
Then a git worktree should be created
|
|
331
|
+
And the work item status should be "in_progress"
|
|
332
|
+
And current work should be set to the chore
|
|
333
|
+
|
|
334
|
+
When I make a commit with message "Fix bug"
|
|
335
|
+
Then the work item status should be "in_progress"
|
|
336
|
+
|
|
337
|
+
When I merge the branch to main
|
|
338
|
+
Then the work item status should be "done"
|
|
339
|
+
And the worktree should be cleaned up
|
|
340
|
+
And current work should be cleared
|
|
341
|
+
|
|
342
|
+
Scenario: Multiple chores in sequence
|
|
343
|
+
Given I have a feature "User Authentication"
|
|
344
|
+
When I create a chore "Implement login" under the feature
|
|
345
|
+
And I create a chore "Add password reset" under the feature
|
|
346
|
+
Then both chores should have mode "speed"
|
|
347
|
+
|
|
348
|
+
When I start work on "Implement login"
|
|
349
|
+
And I complete the chore
|
|
350
|
+
Then I can start work on "Add password reset"
|
|
351
|
+
And the first chore should remain done
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Integration Test Step Definitions
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
// features/step_definitions/work-lifecycle.steps.js
|
|
358
|
+
|
|
359
|
+
const { Given, When, Then } = require('@cucumber/cucumber');
|
|
360
|
+
const { execSync } = require('child_process');
|
|
361
|
+
const fs = require('fs');
|
|
362
|
+
const path = require('path');
|
|
363
|
+
const { getDb } = require('../../lib/database');
|
|
364
|
+
|
|
365
|
+
// Setup and cleanup using test safety patterns
|
|
366
|
+
const { Before, After } = require('@cucumber/cucumber');
|
|
367
|
+
|
|
368
|
+
Before(function() {
|
|
369
|
+
// Track created items for cleanup
|
|
370
|
+
this.createdFiles = [];
|
|
371
|
+
this.createdDirs = [];
|
|
372
|
+
this.testWorkItems = [];
|
|
373
|
+
|
|
374
|
+
// Create isolated test environment
|
|
375
|
+
this.testDir = path.join(process.cwd(), '.test-temp', `test-${Date.now()}`);
|
|
376
|
+
fs.mkdirSync(this.testDir, { recursive: true });
|
|
377
|
+
this.createdDirs.push(this.testDir);
|
|
378
|
+
|
|
379
|
+
// Initialize test database
|
|
380
|
+
this.testDb = path.join(this.testDir, 'work.db');
|
|
381
|
+
process.env.JETTYPOD_DB = this.testDb;
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
After(function() {
|
|
385
|
+
// Cleanup test environment
|
|
386
|
+
if (this.testDir && fs.existsSync(this.testDir)) {
|
|
387
|
+
try {
|
|
388
|
+
fs.rmSync(this.testDir, { recursive: true, force: true });
|
|
389
|
+
} catch (e) {
|
|
390
|
+
console.error('Cleanup failed:', e);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Close database connections
|
|
395
|
+
const db = getDb();
|
|
396
|
+
if (db) {
|
|
397
|
+
db.close();
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Step implementations
|
|
402
|
+
When('I create a chore {string}', function(title) {
|
|
403
|
+
const { create } = require('../../features/work-tracking');
|
|
404
|
+
const result = create('chore', title);
|
|
405
|
+
|
|
406
|
+
this.lastWorkItemId = result.id;
|
|
407
|
+
this.testWorkItems.push(result.id);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
Then('a work item should exist with title {string}', function(expectedTitle) {
|
|
411
|
+
const db = getDb();
|
|
412
|
+
const item = db.prepare('SELECT * FROM work_items WHERE id = ?').get(this.lastWorkItemId);
|
|
413
|
+
|
|
414
|
+
expect(item).toBeDefined();
|
|
415
|
+
expect(item.title).toBe(expectedTitle);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
When('I start work on the chore', function() {
|
|
419
|
+
const { startWork } = require('../../features/work-tracking');
|
|
420
|
+
startWork(this.lastWorkItemId);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
Then('a git worktree should be created', function() {
|
|
424
|
+
const worktrees = execSync('git worktree list', { encoding: 'utf-8' });
|
|
425
|
+
const workItemBranch = `feature/work-${this.lastWorkItemId}`;
|
|
426
|
+
|
|
427
|
+
expect(worktrees).toContain(workItemBranch);
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Integration Test Best Practices
|
|
432
|
+
|
|
433
|
+
**1. Test realistic workflows:**
|
|
434
|
+
```gherkin
|
|
435
|
+
# ✅ Good - realistic user workflow
|
|
436
|
+
Scenario: Developer completes a feature
|
|
437
|
+
Given I have a feature "Login" with 3 chores
|
|
438
|
+
When I complete all chores in speed mode
|
|
439
|
+
Then the feature should auto-elevate to stable mode
|
|
440
|
+
And stable mode chores should be auto-generated
|
|
441
|
+
|
|
442
|
+
# ❌ Bad - testing internals
|
|
443
|
+
Scenario: Database query returns correct result
|
|
444
|
+
When I execute SQL "SELECT * FROM work_items"
|
|
445
|
+
Then the result should have correct columns
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**2. Use Background for common setup:**
|
|
449
|
+
```gherkin
|
|
450
|
+
Background:
|
|
451
|
+
Given I have initialized jettypod with git
|
|
452
|
+
And I am on the main branch
|
|
453
|
+
And no work is currently in progress
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**3. Tag scenarios for selective running:**
|
|
457
|
+
```gherkin
|
|
458
|
+
@wip
|
|
459
|
+
Scenario: Feature under development
|
|
460
|
+
...
|
|
461
|
+
|
|
462
|
+
@slow
|
|
463
|
+
Scenario: Complex workflow with many steps
|
|
464
|
+
...
|
|
465
|
+
|
|
466
|
+
@production
|
|
467
|
+
Scenario: Security hardening validation
|
|
468
|
+
...
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**4. Keep scenarios independent:**
|
|
472
|
+
```gherkin
|
|
473
|
+
# ❌ Bad - depends on previous scenario
|
|
474
|
+
Scenario: Create feature
|
|
475
|
+
When I create feature "Login"
|
|
476
|
+
|
|
477
|
+
Scenario: Add chore to feature
|
|
478
|
+
When I add chore to "Login" # Assumes Login exists
|
|
479
|
+
|
|
480
|
+
# ✅ Good - independent
|
|
481
|
+
Scenario: Add chore to feature
|
|
482
|
+
Given I have a feature "Login"
|
|
483
|
+
When I add chore to the feature
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Test Type 3: End-to-End Tests
|
|
489
|
+
|
|
490
|
+
### What Makes E2E Different
|
|
491
|
+
|
|
492
|
+
**Integration tests:**
|
|
493
|
+
- Test modules working together
|
|
494
|
+
- May use test doubles/mocks
|
|
495
|
+
- Run in test environment
|
|
496
|
+
- Focus on specific workflows
|
|
497
|
+
|
|
498
|
+
**E2E tests:**
|
|
499
|
+
- Test ENTIRE system as user sees it
|
|
500
|
+
- NO mocks (real everything)
|
|
501
|
+
- Production-like environment
|
|
502
|
+
- Test critical paths only
|
|
503
|
+
|
|
504
|
+
### E2E Test Strategy for JettyPod
|
|
505
|
+
|
|
506
|
+
**Critical paths to E2E test:**
|
|
507
|
+
1. First-time user initialization
|
|
508
|
+
2. Feature planning → implementation → completion
|
|
509
|
+
3. Git hook integration (real git commits)
|
|
510
|
+
4. Worktree creation and cleanup
|
|
511
|
+
5. Database migration/initialization
|
|
512
|
+
|
|
513
|
+
### Example: E2E Test
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
// test/e2e/complete-workflow.e2e.test.js
|
|
517
|
+
|
|
518
|
+
const { execSync } = require('child_process');
|
|
519
|
+
const fs = require('fs');
|
|
520
|
+
const path = require('path');
|
|
521
|
+
const os = require('os');
|
|
522
|
+
|
|
523
|
+
describe('E2E: Complete Feature Workflow', () => {
|
|
524
|
+
let testDir;
|
|
525
|
+
let originalCwd;
|
|
526
|
+
|
|
527
|
+
beforeEach(() => {
|
|
528
|
+
// Create completely isolated environment
|
|
529
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jettypod-e2e-'));
|
|
530
|
+
originalCwd = process.cwd();
|
|
531
|
+
process.chdir(testDir);
|
|
532
|
+
|
|
533
|
+
// Initialize real git repo
|
|
534
|
+
execSync('git init');
|
|
535
|
+
execSync('git config user.email "test@example.com"');
|
|
536
|
+
execSync('git config user.name "Test User"');
|
|
537
|
+
|
|
538
|
+
// Create initial commit (git requires one)
|
|
539
|
+
fs.writeFileSync('README.md', '# Test Project');
|
|
540
|
+
execSync('git add .');
|
|
541
|
+
execSync('git commit -m "Initial commit"');
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
afterEach(() => {
|
|
545
|
+
process.chdir(originalCwd);
|
|
546
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test('Complete feature workflow from init to done', () => {
|
|
550
|
+
// 1. Initialize jettypod
|
|
551
|
+
const initOutput = execSync('node /path/to/jettypod.js init', {
|
|
552
|
+
encoding: 'utf-8'
|
|
553
|
+
});
|
|
554
|
+
expect(initOutput).toContain('JettyPod initialized');
|
|
555
|
+
expect(fs.existsSync('.jettypod/work.db')).toBe(true);
|
|
556
|
+
|
|
557
|
+
// 2. Create feature
|
|
558
|
+
const createOutput = execSync(
|
|
559
|
+
'node /path/to/jettypod.js work create feature "User Login"',
|
|
560
|
+
{ encoding: 'utf-8' }
|
|
561
|
+
);
|
|
562
|
+
expect(createOutput).toContain('Created feature');
|
|
563
|
+
const featureId = parseInt(createOutput.match(/ID: (\d+)/)[1]);
|
|
564
|
+
|
|
565
|
+
// 3. Start feature planning (manual for E2E)
|
|
566
|
+
execSync(`node /path/to/jettypod.js work start ${featureId}`);
|
|
567
|
+
|
|
568
|
+
// Create BDD scenario manually (simulating skill output)
|
|
569
|
+
fs.mkdirSync('features', { recursive: true });
|
|
570
|
+
fs.writeFileSync('features/user-login.feature', `
|
|
571
|
+
Feature: User Login
|
|
572
|
+
Scenario: User logs in successfully
|
|
573
|
+
Given I am on the login page
|
|
574
|
+
When I enter valid credentials
|
|
575
|
+
Then I am redirected to dashboard
|
|
576
|
+
`);
|
|
577
|
+
|
|
578
|
+
// 4. Transition to implementation
|
|
579
|
+
execSync(
|
|
580
|
+
`node /path/to/jettypod.js work implement ${featureId} --winner="Simple form"`,
|
|
581
|
+
{ encoding: 'utf-8' }
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// 5. Create and complete speed mode chore
|
|
585
|
+
const choreOutput = execSync(
|
|
586
|
+
`node /path/to/jettypod.js work create chore "Implement login form" --parent=${featureId}`,
|
|
587
|
+
{ encoding: 'utf-8' }
|
|
588
|
+
);
|
|
589
|
+
const choreId = parseInt(choreOutput.match(/ID: (\d+)/)[1]);
|
|
590
|
+
|
|
591
|
+
execSync(`node /path/to/jettypod.js work start ${choreId}`);
|
|
592
|
+
|
|
593
|
+
// Check worktree was created
|
|
594
|
+
const worktrees = execSync('git worktree list', { encoding: 'utf-8' });
|
|
595
|
+
expect(worktrees).toContain(`work-${choreId}`);
|
|
596
|
+
|
|
597
|
+
// Make commit (should trigger post-commit hook)
|
|
598
|
+
fs.writeFileSync('login.js', 'module.exports = function login() {}');
|
|
599
|
+
execSync('git add .');
|
|
600
|
+
execSync('git commit -m "Implement login"');
|
|
601
|
+
|
|
602
|
+
// 6. Merge to main (should trigger post-merge hook)
|
|
603
|
+
execSync('git checkout main');
|
|
604
|
+
execSync(`git merge feature/work-${choreId}-implement-login-form`);
|
|
605
|
+
|
|
606
|
+
// Check work item marked as done
|
|
607
|
+
const statusOutput = execSync(
|
|
608
|
+
`node /path/to/jettypod.js work show ${choreId}`,
|
|
609
|
+
{ encoding: 'utf-8' }
|
|
610
|
+
);
|
|
611
|
+
expect(statusOutput).toContain('Status: done');
|
|
612
|
+
|
|
613
|
+
// Check worktree cleaned up
|
|
614
|
+
const finalWorktrees = execSync('git worktree list', { encoding: 'utf-8' });
|
|
615
|
+
expect(finalWorktrees).not.toContain(`work-${choreId}`);
|
|
616
|
+
|
|
617
|
+
}, 30000); // 30 second timeout for E2E
|
|
618
|
+
|
|
619
|
+
test('Git hooks work correctly', () => {
|
|
620
|
+
execSync('node /path/to/jettypod.js init');
|
|
621
|
+
|
|
622
|
+
const choreId = parseInt(
|
|
623
|
+
execSync('node /path/to/jettypod.js work create chore "Test hooks"', {
|
|
624
|
+
encoding: 'utf-8'
|
|
625
|
+
}).match(/ID: (\d+)/)[1]
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
execSync(`node /path/to/jettypod.js work start ${choreId}`);
|
|
629
|
+
|
|
630
|
+
// Check initial status
|
|
631
|
+
let status = execSync(`node /path/to/jettypod.js work show ${choreId}`, {
|
|
632
|
+
encoding: 'utf-8'
|
|
633
|
+
});
|
|
634
|
+
expect(status).toContain('Status: todo');
|
|
635
|
+
|
|
636
|
+
// Make first commit - should trigger post-commit hook
|
|
637
|
+
fs.writeFileSync('test.js', 'console.log("test")');
|
|
638
|
+
execSync('git add .');
|
|
639
|
+
execSync('git commit -m "Test commit"');
|
|
640
|
+
|
|
641
|
+
// Check status updated to in_progress
|
|
642
|
+
status = execSync(`node /path/to/jettypod.js work show ${choreId}`, {
|
|
643
|
+
encoding: 'utf-8'
|
|
644
|
+
});
|
|
645
|
+
expect(status).toContain('Status: in_progress');
|
|
646
|
+
|
|
647
|
+
// Merge to main - should trigger post-merge hook
|
|
648
|
+
execSync('git checkout main');
|
|
649
|
+
execSync(`git merge feature/work-${choreId}-test-hooks`);
|
|
650
|
+
|
|
651
|
+
// Check status updated to done
|
|
652
|
+
status = execSync(`node /path/to/jettypod.js work show ${choreId}`, {
|
|
653
|
+
encoding: 'utf-8'
|
|
654
|
+
});
|
|
655
|
+
expect(status).toContain('Status: done');
|
|
656
|
+
}, 30000);
|
|
657
|
+
});
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### E2E Test Best Practices
|
|
661
|
+
|
|
662
|
+
**1. Only test critical paths:**
|
|
663
|
+
- Don't duplicate integration test coverage
|
|
664
|
+
- Focus on "user can accomplish their goal"
|
|
665
|
+
- Test with real data, real environment
|
|
666
|
+
|
|
667
|
+
**2. Make tests resilient:**
|
|
668
|
+
```javascript
|
|
669
|
+
// ❌ Brittle - depends on exact output format
|
|
670
|
+
expect(output).toBe('Created feature #1: User Login');
|
|
671
|
+
|
|
672
|
+
// ✅ Resilient - checks essential information
|
|
673
|
+
expect(output).toContain('Created feature');
|
|
674
|
+
expect(output).toMatch(/ID: \d+/);
|
|
675
|
+
expect(output).toContain('User Login');
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**3. Clean up properly:**
|
|
679
|
+
```javascript
|
|
680
|
+
afterEach(() => {
|
|
681
|
+
// Always clean up, even if test failed
|
|
682
|
+
try {
|
|
683
|
+
process.chdir(originalCwd);
|
|
684
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
685
|
+
} catch (e) {
|
|
686
|
+
console.error('E2E cleanup failed:', e);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
**4. Use longer timeouts:**
|
|
692
|
+
```javascript
|
|
693
|
+
test('complete workflow', () => {
|
|
694
|
+
// E2E tests are slower
|
|
695
|
+
}, 30000); // 30 seconds
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
## Test Type 4: Performance Tests
|
|
701
|
+
|
|
702
|
+
### What to Performance Test
|
|
703
|
+
|
|
704
|
+
**For JettyPod (CLI tool):**
|
|
705
|
+
- Command execution time
|
|
706
|
+
- Database query performance
|
|
707
|
+
- File operations (especially with many worktrees)
|
|
708
|
+
- Git operations
|
|
709
|
+
|
|
710
|
+
### Performance Testing Strategy
|
|
711
|
+
|
|
712
|
+
```javascript
|
|
713
|
+
// test/performance/commands.perf.test.js
|
|
714
|
+
|
|
715
|
+
describe('Performance: Command Execution', () => {
|
|
716
|
+
test('work create should complete in under 100ms', () => {
|
|
717
|
+
const start = Date.now();
|
|
718
|
+
|
|
719
|
+
execSync('node jettypod.js work create chore "Test"');
|
|
720
|
+
|
|
721
|
+
const duration = Date.now() - start;
|
|
722
|
+
expect(duration).toBeLessThan(100);
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
test('backlog command scales with many work items', () => {
|
|
726
|
+
// Create 1000 work items
|
|
727
|
+
for (let i = 0; i < 1000; i++) {
|
|
728
|
+
execSync(`node jettypod.js work create chore "Item ${i}"`);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const start = Date.now();
|
|
732
|
+
execSync('node jettypod.js backlog');
|
|
733
|
+
const duration = Date.now() - start;
|
|
734
|
+
|
|
735
|
+
// Should still be under 500ms with 1000 items
|
|
736
|
+
expect(duration).toBeLessThan(500);
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### Performance Test with BDD
|
|
742
|
+
|
|
743
|
+
```gherkin
|
|
744
|
+
# features/performance.feature
|
|
745
|
+
|
|
746
|
+
@performance
|
|
747
|
+
Feature: Performance Requirements
|
|
748
|
+
Ensure commands execute within acceptable time limits
|
|
749
|
+
|
|
750
|
+
Scenario: Work item creation is fast
|
|
751
|
+
Given I have 100 existing work items
|
|
752
|
+
When I create a new work item
|
|
753
|
+
Then it should complete in under 100ms
|
|
754
|
+
|
|
755
|
+
Scenario: Backlog scales with large datasets
|
|
756
|
+
Given I have 1000 work items
|
|
757
|
+
When I run the backlog command
|
|
758
|
+
Then it should complete in under 500ms
|
|
759
|
+
And it should return all items
|
|
760
|
+
|
|
761
|
+
Scenario: Git operations don't block
|
|
762
|
+
Given I have 50 worktrees
|
|
763
|
+
When I create a new worktree
|
|
764
|
+
Then it should complete in under 2 seconds
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
```javascript
|
|
768
|
+
// features/step_definitions/performance.steps.js
|
|
769
|
+
|
|
770
|
+
When('I create a new work item', function() {
|
|
771
|
+
this.startTime = Date.now();
|
|
772
|
+
execSync('node jettypod.js work create chore "Test"');
|
|
773
|
+
this.duration = Date.now() - this.startTime;
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
Then('it should complete in under {int}ms', function(maxMs) {
|
|
777
|
+
expect(this.duration).toBeLessThan(maxMs);
|
|
778
|
+
});
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
## Test Type 5: Security Tests
|
|
784
|
+
|
|
785
|
+
### What to Security Test
|
|
786
|
+
|
|
787
|
+
**For JettyPod:**
|
|
788
|
+
- SQL injection in database queries
|
|
789
|
+
- Command injection in git operations
|
|
790
|
+
- Path traversal in file operations
|
|
791
|
+
- Input validation
|
|
792
|
+
|
|
793
|
+
### Security Testing Examples
|
|
794
|
+
|
|
795
|
+
```javascript
|
|
796
|
+
// test/security/injection.test.js
|
|
797
|
+
|
|
798
|
+
describe('Security: SQL Injection Prevention', () => {
|
|
799
|
+
test('prevents SQL injection in work item title', () => {
|
|
800
|
+
const maliciousTitle = "'; DROP TABLE work_items; --";
|
|
801
|
+
|
|
802
|
+
const { create } = require('../../features/work-tracking');
|
|
803
|
+
const result = create('chore', maliciousTitle);
|
|
804
|
+
|
|
805
|
+
// Should escape SQL properly
|
|
806
|
+
expect(result.id).toBeDefined();
|
|
807
|
+
|
|
808
|
+
// Table should still exist
|
|
809
|
+
const db = getDb();
|
|
810
|
+
const items = db.prepare('SELECT * FROM work_items').all();
|
|
811
|
+
expect(items).toBeDefined();
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test('prevents SQL injection in search', () => {
|
|
815
|
+
const maliciousSearch = "' OR '1'='1";
|
|
816
|
+
|
|
817
|
+
const { search } = require('../../features/work-tracking');
|
|
818
|
+
const results = search(maliciousSearch);
|
|
819
|
+
|
|
820
|
+
// Should not return all items
|
|
821
|
+
expect(results.length).toBe(0);
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
describe('Security: Command Injection Prevention', () => {
|
|
826
|
+
test('prevents command injection in git operations', () => {
|
|
827
|
+
const maliciousMessage = 'commit"; rm -rf /; echo "';
|
|
828
|
+
|
|
829
|
+
expect(() => {
|
|
830
|
+
execSync(`git commit -m "${maliciousMessage}"`);
|
|
831
|
+
}).not.toThrow();
|
|
832
|
+
|
|
833
|
+
// System should still be intact
|
|
834
|
+
expect(fs.existsSync('.')).toBe(true);
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
describe('Security: Path Traversal Prevention', () => {
|
|
839
|
+
test('prevents path traversal in file operations', () => {
|
|
840
|
+
const maliciousPath = '../../../../etc/passwd';
|
|
841
|
+
|
|
842
|
+
const { readConfig } = require('../../lib/config');
|
|
843
|
+
|
|
844
|
+
expect(() => {
|
|
845
|
+
readConfig(maliciousPath);
|
|
846
|
+
}).toThrow(/Invalid path/);
|
|
847
|
+
});
|
|
848
|
+
});
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
---
|
|
852
|
+
|
|
853
|
+
## Test Coverage Strategy
|
|
854
|
+
|
|
855
|
+
### Coverage Goals
|
|
856
|
+
|
|
857
|
+
```javascript
|
|
858
|
+
// jest.config.js
|
|
859
|
+
module.exports = {
|
|
860
|
+
collectCoverageFrom: [
|
|
861
|
+
'lib/**/*.js',
|
|
862
|
+
'features/**/*.js',
|
|
863
|
+
'!**/*.test.js',
|
|
864
|
+
'!**/node_modules/**',
|
|
865
|
+
'!**/__tests__/**'
|
|
866
|
+
],
|
|
867
|
+
coverageThresholds: {
|
|
868
|
+
global: {
|
|
869
|
+
branches: 80,
|
|
870
|
+
functions: 80,
|
|
871
|
+
lines: 80,
|
|
872
|
+
statements: 80
|
|
873
|
+
},
|
|
874
|
+
// Stricter for core library code
|
|
875
|
+
'lib/**/*.js': {
|
|
876
|
+
branches: 90,
|
|
877
|
+
functions: 90,
|
|
878
|
+
lines: 90,
|
|
879
|
+
statements: 90
|
|
880
|
+
}
|
|
881
|
+
},
|
|
882
|
+
coverageReporters: ['text', 'text-summary', 'html', 'lcov']
|
|
883
|
+
};
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
### What Coverage Numbers Mean
|
|
887
|
+
|
|
888
|
+
**Lines covered: 80%** = 80% of code lines executed during tests
|
|
889
|
+
**Branches covered: 80%** = 80% of if/else paths tested
|
|
890
|
+
**Functions covered: 80%** = 80% of functions called
|
|
891
|
+
**Statements covered: 80%** = 80% of statements executed
|
|
892
|
+
|
|
893
|
+
**100% coverage ≠ bug-free code**
|
|
894
|
+
- Coverage measures execution, not correctness
|
|
895
|
+
- Can have 100% coverage with bad assertions
|
|
896
|
+
- Focus on meaningful tests, not coverage %
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
## Complete Testing Checklist
|
|
901
|
+
|
|
902
|
+
### Feature Workflow Testing
|
|
903
|
+
|
|
904
|
+
**Feature-Planning:**
|
|
905
|
+
- [ ] Dry-run BDD scenarios (syntax validation)
|
|
906
|
+
- [ ] Step definitions match scenario steps
|
|
907
|
+
- [ ] Test safety hooks in place
|
|
908
|
+
|
|
909
|
+
**Speed-Mode:**
|
|
910
|
+
- [ ] Happy path BDD scenario passes
|
|
911
|
+
- [ ] Basic unit tests for new functions
|
|
912
|
+
- [ ] No regressions in existing tests
|
|
913
|
+
|
|
914
|
+
**Stable-Mode:**
|
|
915
|
+
- [ ] All BDD scenarios pass (happy + error + edge)
|
|
916
|
+
- [ ] Unit tests for error handling logic
|
|
917
|
+
- [ ] Integration tests for module interactions
|
|
918
|
+
- [ ] Test coverage >= 80% for new code
|
|
919
|
+
|
|
920
|
+
**Production-Mode:**
|
|
921
|
+
- [ ] Production BDD scenarios pass
|
|
922
|
+
- [ ] Security tests for vulnerabilities
|
|
923
|
+
- [ ] Performance tests meet targets
|
|
924
|
+
- [ ] No regressions in full test suite
|
|
925
|
+
|
|
926
|
+
### Standalone Chore Testing
|
|
927
|
+
|
|
928
|
+
- [ ] Unit tests for new functionality
|
|
929
|
+
- [ ] Integration tests if touching multiple modules
|
|
930
|
+
- [ ] No regressions in existing tests
|
|
931
|
+
- [ ] Test coverage >= 80% for changes
|
|
932
|
+
|
|
933
|
+
---
|
|
934
|
+
|
|
935
|
+
## Summary: Realistic Testing Strategy
|
|
936
|
+
|
|
937
|
+
### What You Actually Need
|
|
938
|
+
|
|
939
|
+
**Priority 1 (Must Have):**
|
|
940
|
+
1. ✅ Unit tests for lib/ code (60% of tests)
|
|
941
|
+
2. ✅ BDD integration tests for workflows (30% of tests)
|
|
942
|
+
3. ✅ Test safety infrastructure (cleanup hooks)
|
|
943
|
+
4. ✅ Pre-commit/pre-push git hooks
|
|
944
|
+
|
|
945
|
+
**Priority 2 (Should Have):**
|
|
946
|
+
5. ✅ E2E tests for critical paths (10% of tests)
|
|
947
|
+
6. ✅ Basic performance tests (timing checks)
|
|
948
|
+
7. ✅ Test coverage tracking (80% threshold)
|
|
949
|
+
|
|
950
|
+
**Priority 3 (Nice to Have):**
|
|
951
|
+
8. ⚠️ Security tests (injection prevention)
|
|
952
|
+
9. ⚠️ Mutation testing (test quality)
|
|
953
|
+
|
|
954
|
+
**Not Needed (For Your Project):**
|
|
955
|
+
- ❌ Visual regression tests (no UI)
|
|
956
|
+
- ❌ Contract tests (no external APIs yet)
|
|
957
|
+
- ❌ Load tests (single-user CLI)
|
|
958
|
+
- ❌ Accessibility tests (no UI)
|
|
959
|
+
|
|
960
|
+
### The Honest Truth
|
|
961
|
+
|
|
962
|
+
You don't need ALL testing best practices. You need:
|
|
963
|
+
- **Fast unit tests** that give immediate feedback
|
|
964
|
+
- **Reliable integration tests** that catch workflow bugs
|
|
965
|
+
- **Selective E2E tests** for critical paths
|
|
966
|
+
- **Smart git hooks** that don't kill velocity
|
|
967
|
+
|
|
968
|
+
The previous document (TDD-INFRASTRUCTURE-STRATEGY.md) had the git hooks and timing strategy right. This document fills in the gaps on WHAT to test and HOW to test it properly.
|
|
969
|
+
|
|
970
|
+
**Together they form a complete strategy.**
|