omgkit 2.21.7 → 2.22.0
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/package.json +13 -2
- package/plugin/commands/omgdeploy/edge.md +1 -1
- package/plugin/commands/quality/test-chaos.md +99 -0
- package/plugin/commands/quality/test-mutate.md +65 -0
- package/plugin/commands/quality/test-omega.md +61 -0
- package/plugin/commands/quality/test-performance.md +82 -0
- package/plugin/commands/quality/test-property.md +61 -0
- package/plugin/commands/quality/test-security.md +76 -0
- package/plugin/skills/testing/chaos-testing/SKILL.md +389 -0
- package/plugin/skills/testing/comprehensive-testing/SKILL.md +248 -0
- package/plugin/skills/testing/mutation-testing/SKILL.md +335 -0
- package/plugin/skills/testing/performance-testing/SKILL.md +361 -0
- package/plugin/skills/testing/property-testing/SKILL.md +341 -0
- package/plugin/skills/testing/security-testing/SKILL.md +347 -0
- package/plugin/workflows/testing/comprehensive-testing.md +130 -0
- package/plugin/workflows/testing/security-hardening.md +196 -0
- package/plugin/workflows/testing/test-driven-development.md +181 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing/chaos-testing
|
|
3
|
+
description: Chaos engineering and fault injection patterns for testing system resilience, failure recovery, and graceful degradation
|
|
4
|
+
category: testing
|
|
5
|
+
tags:
|
|
6
|
+
- testing
|
|
7
|
+
- chaos
|
|
8
|
+
- resilience
|
|
9
|
+
- fault-injection
|
|
10
|
+
- reliability
|
|
11
|
+
related_skills:
|
|
12
|
+
- testing/comprehensive-testing
|
|
13
|
+
- devops/observability
|
|
14
|
+
- backend/event-driven-architecture
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Chaos Testing
|
|
18
|
+
|
|
19
|
+
Build resilient systems by intentionally introducing failures and verifying recovery.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Run chaos tests
|
|
25
|
+
npm test -- tests/chaos/
|
|
26
|
+
|
|
27
|
+
# Run with chaos monkey enabled
|
|
28
|
+
CHAOS_ENABLED=true npm start
|
|
29
|
+
|
|
30
|
+
# Simulate network failures
|
|
31
|
+
npm run chaos:network
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Core Principles
|
|
35
|
+
|
|
36
|
+
1. **Hypothesis-Driven**: Define expected behavior before experiments
|
|
37
|
+
2. **Minimize Blast Radius**: Start small, expand gradually
|
|
38
|
+
3. **Automate Rollback**: Always have a kill switch
|
|
39
|
+
4. **Monitor Everything**: Observe system behavior during chaos
|
|
40
|
+
|
|
41
|
+
## Fault Injection Patterns
|
|
42
|
+
|
|
43
|
+
### 1. Network Failures
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
describe('Network Resilience', () => {
|
|
47
|
+
it('handles API timeout gracefully', async () => {
|
|
48
|
+
// Simulate slow API
|
|
49
|
+
server.delay('/api/users', 10000);
|
|
50
|
+
|
|
51
|
+
const start = Date.now();
|
|
52
|
+
const result = await fetchWithTimeout('/api/users', 3000);
|
|
53
|
+
const duration = Date.now() - start;
|
|
54
|
+
|
|
55
|
+
expect(duration).toBeLessThan(4000);
|
|
56
|
+
expect(result.fallback).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('retries on network failure', async () => {
|
|
60
|
+
let attempts = 0;
|
|
61
|
+
|
|
62
|
+
server.intercept('/api/data', () => {
|
|
63
|
+
attempts++;
|
|
64
|
+
if (attempts < 3) throw new Error('Network error');
|
|
65
|
+
return { data: 'success' };
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const result = await fetchWithRetry('/api/data');
|
|
69
|
+
|
|
70
|
+
expect(attempts).toBe(3);
|
|
71
|
+
expect(result.data).toBe('success');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('handles partial responses', async () => {
|
|
75
|
+
server.intercept('/api/large', (req, res) => {
|
|
76
|
+
res.write('{\"data\":');
|
|
77
|
+
res.destroy(); // Simulate connection drop
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const result = await fetchWithRecovery('/api/large');
|
|
81
|
+
expect(result.error).toBe('incomplete_response');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 2. Service Failures
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
describe('Service Resilience', () => {
|
|
90
|
+
it('falls back when primary service fails', async () => {
|
|
91
|
+
// Kill primary service
|
|
92
|
+
await killService('payment-primary');
|
|
93
|
+
|
|
94
|
+
const result = await processPayment({ amount: 100 });
|
|
95
|
+
|
|
96
|
+
expect(result.provider).toBe('fallback');
|
|
97
|
+
expect(result.success).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('circuit breaker opens after failures', async () => {
|
|
101
|
+
const breaker = new CircuitBreaker(unreliableService, {
|
|
102
|
+
failureThreshold: 3,
|
|
103
|
+
resetTimeout: 1000,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Trigger failures
|
|
107
|
+
for (let i = 0; i < 5; i++) {
|
|
108
|
+
await breaker.call().catch(() => {});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
expect(breaker.state).toBe('open');
|
|
112
|
+
|
|
113
|
+
// Fast fail while open
|
|
114
|
+
const start = Date.now();
|
|
115
|
+
await breaker.call().catch(() => {});
|
|
116
|
+
expect(Date.now() - start).toBeLessThan(10);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('recovers after circuit breaker resets', async () => {
|
|
120
|
+
const breaker = new CircuitBreaker(service, { resetTimeout: 100 });
|
|
121
|
+
|
|
122
|
+
// Open the breaker
|
|
123
|
+
await triggerCircuitOpen(breaker);
|
|
124
|
+
expect(breaker.state).toBe('open');
|
|
125
|
+
|
|
126
|
+
// Wait for reset
|
|
127
|
+
await sleep(150);
|
|
128
|
+
|
|
129
|
+
// Should be half-open, allowing a test request
|
|
130
|
+
service.mockImplementation(() => ({ success: true }));
|
|
131
|
+
const result = await breaker.call();
|
|
132
|
+
|
|
133
|
+
expect(breaker.state).toBe('closed');
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 3. Database Failures
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
describe('Database Resilience', () => {
|
|
143
|
+
it('handles connection pool exhaustion', async () => {
|
|
144
|
+
// Exhaust connection pool
|
|
145
|
+
const connections = [];
|
|
146
|
+
for (let i = 0; i < db.maxConnections; i++) {
|
|
147
|
+
connections.push(await db.getConnection());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// New request should wait or fail gracefully
|
|
151
|
+
const start = Date.now();
|
|
152
|
+
const result = await db.query('SELECT 1').catch(e => e);
|
|
153
|
+
|
|
154
|
+
expect(result.message).toMatch(/timeout|pool exhausted/);
|
|
155
|
+
expect(Date.now() - start).toBeLessThan(6000);
|
|
156
|
+
|
|
157
|
+
// Cleanup
|
|
158
|
+
await Promise.all(connections.map(c => c.release()));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('handles replica lag', async () => {
|
|
162
|
+
// Write to primary
|
|
163
|
+
await db.primary.insert('users', { name: 'test' });
|
|
164
|
+
|
|
165
|
+
// Read from replica immediately (may not have replicated)
|
|
166
|
+
const result = await db.replica.query('SELECT * FROM users WHERE name = ?', ['test']);
|
|
167
|
+
|
|
168
|
+
// System should handle missing data
|
|
169
|
+
if (result.length === 0) {
|
|
170
|
+
// Fallback to primary read
|
|
171
|
+
const fallback = await db.primary.query('SELECT * FROM users WHERE name = ?', ['test']);
|
|
172
|
+
expect(fallback.length).toBe(1);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('handles deadlocks', async () => {
|
|
177
|
+
const ops = [
|
|
178
|
+
async () => {
|
|
179
|
+
await db.transaction(async (tx) => {
|
|
180
|
+
await tx.update('accounts', { id: 1 }, { balance: 100 });
|
|
181
|
+
await sleep(50);
|
|
182
|
+
await tx.update('accounts', { id: 2 }, { balance: 200 });
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
async () => {
|
|
186
|
+
await db.transaction(async (tx) => {
|
|
187
|
+
await tx.update('accounts', { id: 2 }, { balance: 300 });
|
|
188
|
+
await sleep(50);
|
|
189
|
+
await tx.update('accounts', { id: 1 }, { balance: 400 });
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const results = await Promise.allSettled(ops.map(op => op()));
|
|
195
|
+
const retried = results.filter(r =>
|
|
196
|
+
r.status === 'fulfilled' || r.reason.retried
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(retried.length).toBeGreaterThan(0);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 4. Resource Exhaustion
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
describe('Resource Exhaustion', () => {
|
|
208
|
+
it('handles disk full', async () => {
|
|
209
|
+
// Simulate disk full
|
|
210
|
+
fs.mockImplementation('writeFile', () => {
|
|
211
|
+
throw new Error('ENOSPC: no space left on device');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const result = await saveData({ large: 'data' });
|
|
215
|
+
|
|
216
|
+
expect(result.error).toBe('storage_full');
|
|
217
|
+
expect(result.cached).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('handles memory pressure', async () => {
|
|
221
|
+
const originalHeap = process.memoryUsage().heapUsed;
|
|
222
|
+
|
|
223
|
+
// Allocate significant memory
|
|
224
|
+
const allocations = [];
|
|
225
|
+
for (let i = 0; i < 10; i++) {
|
|
226
|
+
allocations.push(new Array(10000000).fill('x'));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// System should still respond
|
|
230
|
+
const result = await healthCheck();
|
|
231
|
+
expect(result.status).toBe('degraded');
|
|
232
|
+
|
|
233
|
+
// Cleanup
|
|
234
|
+
allocations.length = 0;
|
|
235
|
+
if (global.gc) global.gc();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('handles CPU saturation', async () => {
|
|
239
|
+
// Saturate CPU
|
|
240
|
+
const workers = [];
|
|
241
|
+
for (let i = 0; i < 4; i++) {
|
|
242
|
+
workers.push(cpuIntensiveTask());
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Critical endpoint should still respond
|
|
246
|
+
const start = Date.now();
|
|
247
|
+
const result = await api.get('/health');
|
|
248
|
+
const duration = Date.now() - start;
|
|
249
|
+
|
|
250
|
+
expect(result.status).toBe(200);
|
|
251
|
+
expect(duration).toBeLessThan(5000);
|
|
252
|
+
|
|
253
|
+
await Promise.all(workers);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 5. Clock Skew
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
describe('Clock Resilience', () => {
|
|
262
|
+
it('handles clock skew between services', () => {
|
|
263
|
+
// Simulate 5-minute clock difference
|
|
264
|
+
const serverTime = Date.now() - 5 * 60 * 1000;
|
|
265
|
+
|
|
266
|
+
const token = jwt.sign({ exp: serverTime + 3600000 }, secret);
|
|
267
|
+
const result = validateToken(token, { clockTolerance: 300 });
|
|
268
|
+
|
|
269
|
+
expect(result.valid).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('handles leap seconds', () => {
|
|
273
|
+
// Simulate time going backward
|
|
274
|
+
MockDate.set(new Date('2024-06-30T23:59:60Z'));
|
|
275
|
+
|
|
276
|
+
const result = processScheduledTask();
|
|
277
|
+
|
|
278
|
+
expect(result.error).toBeUndefined();
|
|
279
|
+
MockDate.reset();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Chaos Monkey Implementation
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
class ChaosMonkey {
|
|
288
|
+
constructor(options = {}) {
|
|
289
|
+
this.enabled = options.enabled ?? false;
|
|
290
|
+
this.probability = options.probability ?? 0.1;
|
|
291
|
+
this.faults = options.faults ?? ['latency', 'error', 'timeout'];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
maybeInjectFault() {
|
|
295
|
+
if (!this.enabled) return null;
|
|
296
|
+
if (Math.random() > this.probability) return null;
|
|
297
|
+
|
|
298
|
+
const fault = this.faults[Math.floor(Math.random() * this.faults.length)];
|
|
299
|
+
|
|
300
|
+
switch (fault) {
|
|
301
|
+
case 'latency':
|
|
302
|
+
return { type: 'latency', delay: 1000 + Math.random() * 4000 };
|
|
303
|
+
case 'error':
|
|
304
|
+
return { type: 'error', code: 500, message: 'Chaos error' };
|
|
305
|
+
case 'timeout':
|
|
306
|
+
return { type: 'timeout', duration: 30000 };
|
|
307
|
+
default:
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
wrap(fn) {
|
|
313
|
+
return async (...args) => {
|
|
314
|
+
const fault = this.maybeInjectFault();
|
|
315
|
+
|
|
316
|
+
if (fault) {
|
|
317
|
+
switch (fault.type) {
|
|
318
|
+
case 'latency':
|
|
319
|
+
await new Promise(r => setTimeout(r, fault.delay));
|
|
320
|
+
break;
|
|
321
|
+
case 'error':
|
|
322
|
+
throw new Error(fault.message);
|
|
323
|
+
case 'timeout':
|
|
324
|
+
await new Promise(() => {}); // Never resolves
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return fn(...args);
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Usage
|
|
334
|
+
const chaos = new ChaosMonkey({
|
|
335
|
+
enabled: process.env.CHAOS_ENABLED === 'true',
|
|
336
|
+
probability: 0.05,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const fetchUsers = chaos.wrap(async () => {
|
|
340
|
+
return api.get('/users');
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Game Day Scenarios
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
describe('Game Day: Total Service Failure', () => {
|
|
348
|
+
it('survives primary database failure', async () => {
|
|
349
|
+
// Kill primary database
|
|
350
|
+
await killDatabase('primary');
|
|
351
|
+
|
|
352
|
+
// Application should failover
|
|
353
|
+
const result = await api.get('/users');
|
|
354
|
+
expect(result.status).toBe(200);
|
|
355
|
+
expect(result.headers['x-database']).toBe('replica');
|
|
356
|
+
|
|
357
|
+
// Restore
|
|
358
|
+
await restoreDatabase('primary');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('survives region failure', async () => {
|
|
362
|
+
// Simulate region failure
|
|
363
|
+
await disableRegion('us-east-1');
|
|
364
|
+
|
|
365
|
+
// Traffic should route to backup region
|
|
366
|
+
const result = await api.get('/health');
|
|
367
|
+
expect(result.region).toBe('us-west-2');
|
|
368
|
+
|
|
369
|
+
// Restore
|
|
370
|
+
await enableRegion('us-east-1');
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## When to Use
|
|
376
|
+
|
|
377
|
+
- Before major deployments
|
|
378
|
+
- After significant architecture changes
|
|
379
|
+
- Periodically in production (carefully!)
|
|
380
|
+
- During reliability engineering efforts
|
|
381
|
+
- When building distributed systems
|
|
382
|
+
|
|
383
|
+
## Anti-Patterns
|
|
384
|
+
|
|
385
|
+
1. **No Kill Switch**: Always have rollback mechanism
|
|
386
|
+
2. **Testing in Production Without Prep**: Start in staging
|
|
387
|
+
3. **No Observability**: Monitor during experiments
|
|
388
|
+
4. **Large Blast Radius**: Start small
|
|
389
|
+
5. **No Hypothesis**: Define expected behavior first
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing/comprehensive-testing
|
|
3
|
+
description: Comprehensive 4D testing methodology covering Accuracy, Performance, Security, and Accessibility for production-ready quality assurance
|
|
4
|
+
category: testing
|
|
5
|
+
tags:
|
|
6
|
+
- testing
|
|
7
|
+
- quality
|
|
8
|
+
- omega
|
|
9
|
+
- comprehensive
|
|
10
|
+
- methodology
|
|
11
|
+
related_skills:
|
|
12
|
+
- testing/vitest
|
|
13
|
+
- testing/property-testing
|
|
14
|
+
- testing/security-testing
|
|
15
|
+
- testing/performance-testing
|
|
16
|
+
- methodology/omega
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Omega Testing
|
|
20
|
+
|
|
21
|
+
Comprehensive 4-Dimensional testing methodology for production-ready quality assurance.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
// Run comprehensive Omega tests
|
|
27
|
+
npm run test:omega
|
|
28
|
+
|
|
29
|
+
// Or run individual dimensions
|
|
30
|
+
npm run test:accuracy // Unit, integration, E2E
|
|
31
|
+
npm run test:performance // Load, stress, benchmarks
|
|
32
|
+
npm run test:security // Vulnerability, injection, auth
|
|
33
|
+
npm run test:a11y // WCAG, keyboard, screen reader
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## The 4 Dimensions
|
|
37
|
+
|
|
38
|
+
### 1. Accuracy Testing
|
|
39
|
+
Ensures correctness of functionality.
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// Unit Tests - Isolated function testing
|
|
43
|
+
describe('calculateTotal', () => {
|
|
44
|
+
it('calculates sum correctly', () => {
|
|
45
|
+
expect(calculateTotal([10, 20, 30])).toBe(60);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('handles empty array', () => {
|
|
49
|
+
expect(calculateTotal([])).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('handles negative numbers', () => {
|
|
53
|
+
expect(calculateTotal([-10, 20])).toBe(10);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Integration Tests - Component interaction
|
|
58
|
+
describe('OrderService', () => {
|
|
59
|
+
it('creates order with payment', async () => {
|
|
60
|
+
const order = await orderService.create(cart, payment);
|
|
61
|
+
expect(order.status).toBe('confirmed');
|
|
62
|
+
expect(paymentService.charge).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// E2E Tests - Full user flows
|
|
67
|
+
test('user can complete checkout', async ({ page }) => {
|
|
68
|
+
await page.goto('/products');
|
|
69
|
+
await page.click('[data-testid="add-to-cart"]');
|
|
70
|
+
await page.click('[data-testid="checkout"]');
|
|
71
|
+
await page.fill('#email', 'user@example.com');
|
|
72
|
+
await page.click('[data-testid="place-order"]');
|
|
73
|
+
await expect(page.locator('.order-confirmation')).toBeVisible();
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. Performance Testing
|
|
78
|
+
Ensures speed and resource efficiency.
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// Benchmark Tests
|
|
82
|
+
describe('Performance Benchmarks', () => {
|
|
83
|
+
it('responds under 100ms', async () => {
|
|
84
|
+
const start = performance.now();
|
|
85
|
+
await api.fetch('/users');
|
|
86
|
+
const duration = performance.now() - start;
|
|
87
|
+
expect(duration).toBeLessThan(100);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('handles 1000 concurrent requests', async () => {
|
|
91
|
+
const requests = Array(1000).fill().map(() => api.fetch('/users'));
|
|
92
|
+
const results = await Promise.all(requests);
|
|
93
|
+
expect(results.every(r => r.status === 200)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Memory Tests
|
|
98
|
+
it('does not leak memory', async () => {
|
|
99
|
+
const before = process.memoryUsage().heapUsed;
|
|
100
|
+
for (let i = 0; i < 1000; i++) {
|
|
101
|
+
await processData(largeDataset);
|
|
102
|
+
}
|
|
103
|
+
global.gc();
|
|
104
|
+
const after = process.memoryUsage().heapUsed;
|
|
105
|
+
expect(after - before).toBeLessThan(10 * 1024 * 1024); // 10MB
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3. Security Testing
|
|
110
|
+
Ensures protection against vulnerabilities.
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
// Injection Tests
|
|
114
|
+
describe('SQL Injection Prevention', () => {
|
|
115
|
+
const maliciousInputs = [
|
|
116
|
+
"'; DROP TABLE users; --",
|
|
117
|
+
"1' OR '1'='1",
|
|
118
|
+
"admin'--",
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
maliciousInputs.forEach(input => {
|
|
122
|
+
it(`blocks injection: ${input.slice(0, 20)}...`, async () => {
|
|
123
|
+
const result = await db.query(
|
|
124
|
+
'SELECT * FROM users WHERE name = ?',
|
|
125
|
+
[input]
|
|
126
|
+
);
|
|
127
|
+
expect(result).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Authentication Tests
|
|
133
|
+
describe('Authentication', () => {
|
|
134
|
+
it('rejects expired tokens', async () => {
|
|
135
|
+
const expiredToken = generateToken({ exp: Date.now() - 1000 });
|
|
136
|
+
const response = await api.get('/protected', {
|
|
137
|
+
headers: { Authorization: `Bearer ${expiredToken}` },
|
|
138
|
+
});
|
|
139
|
+
expect(response.status).toBe(401);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('prevents brute force', async () => {
|
|
143
|
+
for (let i = 0; i < 10; i++) {
|
|
144
|
+
await api.post('/login', { password: 'wrong' });
|
|
145
|
+
}
|
|
146
|
+
const response = await api.post('/login', { password: 'correct' });
|
|
147
|
+
expect(response.status).toBe(429); // Rate limited
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 4. Accessibility Testing
|
|
153
|
+
Ensures usability for all users.
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
// WCAG Compliance
|
|
157
|
+
describe('Accessibility', () => {
|
|
158
|
+
it('has proper heading hierarchy', async () => {
|
|
159
|
+
const headings = await page.$$eval('h1, h2, h3, h4, h5, h6', els =>
|
|
160
|
+
els.map(el => ({ level: parseInt(el.tagName[1]), text: el.textContent }))
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
for (let i = 1; i < headings.length; i++) {
|
|
164
|
+
const skip = headings[i].level - headings[i-1].level;
|
|
165
|
+
expect(skip).toBeLessThanOrEqual(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('images have alt text', async () => {
|
|
170
|
+
const images = await page.$$('img');
|
|
171
|
+
for (const img of images) {
|
|
172
|
+
const alt = await img.getAttribute('alt');
|
|
173
|
+
expect(alt).toBeTruthy();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('is keyboard navigable', async () => {
|
|
178
|
+
await page.keyboard.press('Tab');
|
|
179
|
+
const focused = await page.evaluate(() => document.activeElement.tagName);
|
|
180
|
+
expect(['A', 'BUTTON', 'INPUT']).toContain(focused);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Screen Reader Testing
|
|
185
|
+
it('announces dynamic content', async () => {
|
|
186
|
+
await page.click('#load-more');
|
|
187
|
+
const liveRegion = await page.$('[aria-live="polite"]');
|
|
188
|
+
expect(await liveRegion.textContent()).toContain('Loaded');
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Coverage Requirements
|
|
193
|
+
|
|
194
|
+
| Dimension | Minimum | Target | Excellent |
|
|
195
|
+
|-----------|---------|--------|-----------|
|
|
196
|
+
| Accuracy (Unit) | 80% | 90% | 95% |
|
|
197
|
+
| Accuracy (Integration) | 60% | 75% | 85% |
|
|
198
|
+
| Performance | Pass SLAs | <100ms p95 | <50ms p95 |
|
|
199
|
+
| Security | No critical | No high | No medium |
|
|
200
|
+
| Accessibility | AA | AAA | Full AAA |
|
|
201
|
+
|
|
202
|
+
## Test Organization
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
tests/
|
|
206
|
+
├── accuracy/
|
|
207
|
+
│ ├── unit/ # Isolated unit tests
|
|
208
|
+
│ ├── integration/ # Component integration
|
|
209
|
+
│ └── e2e/ # End-to-end flows
|
|
210
|
+
├── performance/
|
|
211
|
+
│ ├── benchmarks/ # Speed benchmarks
|
|
212
|
+
│ ├── load/ # Load testing
|
|
213
|
+
│ └── stress/ # Stress testing
|
|
214
|
+
├── security/
|
|
215
|
+
│ ├── injection/ # Injection tests
|
|
216
|
+
│ ├── auth/ # Authentication
|
|
217
|
+
│ └── vulnerabilities/# Known vulns
|
|
218
|
+
└── accessibility/
|
|
219
|
+
├── wcag/ # WCAG compliance
|
|
220
|
+
├── keyboard/ # Keyboard nav
|
|
221
|
+
└── screen-reader/ # SR compatibility
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## F.I.R.S.T. Principles
|
|
225
|
+
|
|
226
|
+
- **Fast**: Tests run quickly (< 1ms for unit, < 100ms for integration)
|
|
227
|
+
- **Independent**: Tests don't depend on each other
|
|
228
|
+
- **Repeatable**: Same results every time
|
|
229
|
+
- **Self-Validating**: Pass or fail, no manual inspection
|
|
230
|
+
- **Timely**: Write tests before or with code
|
|
231
|
+
|
|
232
|
+
## Anti-Patterns to Avoid
|
|
233
|
+
|
|
234
|
+
1. **Testing Implementation**: Test behavior, not internals
|
|
235
|
+
2. **Flaky Tests**: Eliminate randomness and timing issues
|
|
236
|
+
3. **Over-Mocking**: Don't mock everything
|
|
237
|
+
4. **Ignoring Edge Cases**: Test boundaries and errors
|
|
238
|
+
5. **Copy-Paste Tests**: Use parameterized tests
|
|
239
|
+
6. **No Assertions**: Every test must assert something
|
|
240
|
+
7. **Testing Third-Party Code**: Focus on your code
|
|
241
|
+
|
|
242
|
+
## When to Use
|
|
243
|
+
|
|
244
|
+
- Starting a new project with quality focus
|
|
245
|
+
- Improving existing test coverage
|
|
246
|
+
- Preparing for production deployment
|
|
247
|
+
- Meeting compliance requirements (SOC2, GDPR)
|
|
248
|
+
- Building critical infrastructure
|