@assertrx/dino 1.0.10 → 1.0.11

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.
Files changed (34) hide show
  1. package/package.json +1 -1
  2. package/prompts/generate-k6-test-from-endpoint.md +68 -0
  3. package/prompts/generate-k6-test-from-openapi.md +85 -0
  4. package/prompts/setup-k6.md +130 -0
  5. package/scripts/api-server.mjs +23 -0
  6. package/skills/analyzing-test-results/SKILL.md +150 -0
  7. package/skills/analyzing-test-results/reference/bottleneck-patterns.md +130 -0
  8. package/skills/analyzing-test-results/reference/metrics.md +134 -0
  9. package/skills/analyzing-test-results/reference/visualization.md +152 -0
  10. package/skills/designing-test-scenarios/SKILL.md +247 -0
  11. package/skills/designing-test-scenarios/reference/executors.md +215 -0
  12. package/skills/designing-test-scenarios/reference/test-types.md +215 -0
  13. package/skills/designing-test-scenarios/reference/thresholds.md +208 -0
  14. package/skills/generating-api-load-tests/SKILL.md +199 -0
  15. package/skills/generating-api-load-tests/reference/data-parameterization.md +343 -0
  16. package/skills/generating-api-load-tests/reference/grpc.md +315 -0
  17. package/skills/generating-api-load-tests/reference/http.md +380 -0
  18. package/skills/generating-api-load-tests/reference/websocket.md +369 -0
  19. package/skills/generating-browser-tests/SKILL.md +269 -0
  20. package/skills/generating-browser-tests/reference/browser-api.md +251 -0
  21. package/skills/generating-browser-tests/reference/web-vitals.md +148 -0
  22. package/skills/generating-tests-from-code/SKILL.md +142 -0
  23. package/skills/generating-tests-from-code/reference/auth-analysis.md +220 -0
  24. package/skills/generating-tests-from-code/reference/framework-patterns.md +242 -0
  25. package/skills/generating-tests-from-openapi/SKILL.md +234 -0
  26. package/skills/generating-tests-from-openapi/reference/openapi-auth-mapping.md +247 -0
  27. package/skills/generating-tests-from-openapi/reference/openapi-parsing.md +194 -0
  28. package/skills/operating-k6-in-ci-cd/SKILL.md +98 -0
  29. package/skills/operating-k6-in-ci-cd/reference/ci-cd-pipelines.md +265 -0
  30. package/skills/operating-k6-in-ci-cd/reference/extensions.md +144 -0
  31. package/skills/operating-k6-in-ci-cd/reference/k6-operator.md +214 -0
  32. package/skills/testing-resilience/SKILL.md +230 -0
  33. package/skills/testing-resilience/reference/fault-injection.md +153 -0
  34. package/skills/testing-resilience/reference/resilience-patterns.md +500 -0
@@ -0,0 +1,500 @@
1
+ # Resilience Test Patterns Reference
2
+
3
+ ## Pattern 1: Graceful Degradation
4
+
5
+ Verify the system degrades gracefully under partial failure instead of cascading.
6
+
7
+ ```javascript
8
+ import http from 'k6/http';
9
+ import { check, sleep } from 'k6';
10
+ import { ServiceDisruptor } from 'k6/x/disruptor';
11
+
12
+ export const options = {
13
+ scenarios: {
14
+ load: {
15
+ executor: 'constant-arrival-rate',
16
+ rate: 100,
17
+ timeUnit: '1s',
18
+ duration: '3m',
19
+ preAllocatedVUs: 50,
20
+ maxVUs: 100,
21
+ },
22
+ },
23
+ thresholds: {
24
+ http_req_failed: ['rate<0.5'], // Allow up to 50% errors
25
+ http_req_duration: ['p(95)<5000'], // 5s max during degradation
26
+ 'http_req_duration{phase:recovery}': ['p(95)<500'], // Back to normal after
27
+ },
28
+ };
29
+
30
+ export function setup() {
31
+ const disruptor = new ServiceDisruptor('backend-api', 'default');
32
+ disruptor.injectHTTPFaults({
33
+ averageDelay: '1000ms',
34
+ delayVariation: '200ms',
35
+ errorRate: 0.3,
36
+ errorCode: 503,
37
+ exclude: '/health',
38
+ }, '90s'); // Fault for 90s, test runs 3m → 90s recovery
39
+ }
40
+
41
+ export default function () {
42
+ const res = http.get('http://backend-api.default.svc.cluster.local:8080/api/data');
43
+ check(res, {
44
+ 'returns valid response': (r) => r.status === 200 || r.status === 503,
45
+ 'no cascading failure': (r) => r.status !== 502,
46
+ 'responds within timeout': (r) => r.timings.duration < 10000,
47
+ });
48
+ sleep(0.1);
49
+ }
50
+ ```
51
+
52
+ **Success criteria:**
53
+ - System returns 503 (expected) not 502 (cascade)
54
+ - Response times degrade but stay within upper bound
55
+ - No complete service outage
56
+ - Metrics return to baseline during recovery phase
57
+
58
+ ---
59
+
60
+ ## Pattern 2: Circuit Breaker Validation
61
+
62
+ Verify circuit breaker activates under sustained errors and returns fallback responses.
63
+
64
+ ```javascript
65
+ import http from 'k6/http';
66
+ import { check, sleep } from 'k6';
67
+ import { Counter, Trend } from 'k6/metrics';
68
+ import { ServiceDisruptor } from 'k6/x/disruptor';
69
+
70
+ const circuitOpenResponses = new Counter('circuit_open_responses');
71
+ const responseTime = new Trend('response_time_trend');
72
+
73
+ export const options = {
74
+ scenarios: {
75
+ load: {
76
+ executor: 'constant-arrival-rate',
77
+ rate: 50,
78
+ timeUnit: '1s',
79
+ duration: '4m',
80
+ preAllocatedVUs: 30,
81
+ maxVUs: 60,
82
+ },
83
+ },
84
+ thresholds: {
85
+ circuit_open_responses: ['count>0'], // Circuit breaker MUST activate
86
+ http_req_duration: ['p(95)<3000'], // Fast fallback expected
87
+ },
88
+ };
89
+
90
+ export function setup() {
91
+ const disruptor = new ServiceDisruptor('downstream-service', 'default');
92
+ // High error rate to trigger circuit breaker
93
+ disruptor.injectHTTPFaults({
94
+ errorRate: 0.8,
95
+ errorCode: 500,
96
+ averageDelay: '3000ms',
97
+ }, '120s'); // 2 min of faults, then 2 min recovery
98
+ }
99
+
100
+ export default function () {
101
+ const res = http.get('http://api-gateway.default.svc.cluster.local:8080/api/data');
102
+ responseTime.add(res.timings.duration);
103
+
104
+ // Detect circuit breaker activation via fallback response
105
+ const isFallback = res.status === 200 && res.json() &&
106
+ (res.json().source === 'cache' || res.json().fallback === true);
107
+
108
+ if (isFallback) {
109
+ circuitOpenResponses.add(1);
110
+ }
111
+
112
+ check(res, {
113
+ 'not a timeout': (r) => r.timings.duration < 10000,
114
+ 'valid response or fallback': (r) => r.status === 200 || r.status === 503,
115
+ });
116
+ sleep(0.1);
117
+ }
118
+ ```
119
+
120
+ **Success criteria:**
121
+ - `circuit_open_responses` count > 0 (circuit breaker activated)
122
+ - Response times drop after circuit opens (fast fallback)
123
+ - After fault removal, circuit closes and normal responses resume
124
+
125
+ **Phases observed:**
126
+ ```
127
+ |-- Closed --|-- Open (fallback) --|-- Half-Open --|-- Closed --|
128
+ | Errors | Fast fallback | Trial reqs | Normal |
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Pattern 3: Recovery Time Measurement
134
+
135
+ Measure how quickly the system recovers after faults are removed.
136
+
137
+ ```javascript
138
+ import http from 'k6/http';
139
+ import { check, sleep } from 'k6';
140
+ import { Trend, Rate } from 'k6/metrics';
141
+ import { ServiceDisruptor } from 'k6/x/disruptor';
142
+
143
+ const recoveryLatency = new Trend('recovery_phase_latency');
144
+ const recoveryErrors = new Rate('recovery_phase_errors');
145
+
146
+ export const options = {
147
+ scenarios: {
148
+ load: {
149
+ executor: 'constant-arrival-rate',
150
+ rate: 50,
151
+ timeUnit: '1s',
152
+ duration: '5m',
153
+ preAllocatedVUs: 30,
154
+ maxVUs: 60,
155
+ },
156
+ },
157
+ thresholds: {
158
+ recovery_phase_latency: ['p(95)<500'], // Must recover to baseline
159
+ recovery_phase_errors: ['rate<0.01'], // Near-zero errors after recovery
160
+ http_req_duration: ['p(95)<5000'], // Overall upper bound
161
+ },
162
+ };
163
+
164
+ export function setup() {
165
+ const disruptor = new ServiceDisruptor('my-service', 'default');
166
+ // Fault for 2 min → 3 min recovery observation
167
+ disruptor.injectHTTPFaults({
168
+ averageDelay: '2000ms',
169
+ delayVariation: '500ms',
170
+ errorRate: 0.5,
171
+ errorCode: 503,
172
+ }, '120s');
173
+
174
+ return { faultEndTime: Date.now() + 120000 };
175
+ }
176
+
177
+ export default function (data) {
178
+ const res = http.get('http://my-service.default.svc.cluster.local:8080/api');
179
+ const isRecoveryPhase = Date.now() > data.faultEndTime;
180
+
181
+ if (isRecoveryPhase) {
182
+ recoveryLatency.add(res.timings.duration);
183
+ recoveryErrors.add(res.status >= 500 ? 1 : 0);
184
+ }
185
+
186
+ check(res, {
187
+ 'status ok or expected error': (r) => r.status === 200 || r.status === 503,
188
+ });
189
+ sleep(0.1);
190
+ }
191
+ ```
192
+
193
+ **Success criteria:**
194
+ - `recovery_phase_latency` p(95) returns to baseline (< 500ms)
195
+ - `recovery_phase_errors` rate drops to near zero
196
+ - Recovery happens within acceptable window (e.g., 30 seconds)
197
+
198
+ ---
199
+
200
+ ## Pattern 4: Pod Failure and Auto-Scaling
201
+
202
+ Test system behavior when pods are terminated and verify Kubernetes auto-scaling replaces them.
203
+
204
+ ```javascript
205
+ import http from 'k6/http';
206
+ import { check, sleep } from 'k6';
207
+ import { Rate, Trend } from 'k6/metrics';
208
+ import { ServiceDisruptor } from 'k6/x/disruptor';
209
+
210
+ const errorRate = new Rate('error_rate');
211
+ const latency = new Trend('request_latency');
212
+
213
+ export const options = {
214
+ scenarios: {
215
+ load: {
216
+ executor: 'constant-arrival-rate',
217
+ rate: 100,
218
+ timeUnit: '1s',
219
+ duration: '5m',
220
+ preAllocatedVUs: 50,
221
+ maxVUs: 100,
222
+ },
223
+ disrupt: {
224
+ executor: 'shared-iterations',
225
+ iterations: 1,
226
+ vus: 1,
227
+ exec: 'disrupt',
228
+ startTime: '30s', // Start disruption after 30s baseline
229
+ },
230
+ },
231
+ thresholds: {
232
+ error_rate: ['rate<0.3'], // Allow transient errors
233
+ request_latency: ['p(99)<10000'], // Upper bound during disruption
234
+ },
235
+ };
236
+
237
+ export function disrupt() {
238
+ const disruptor = new ServiceDisruptor('my-service', 'default');
239
+ // Terminate 1 pod every 30 seconds
240
+ disruptor.terminatePods({
241
+ count: 1,
242
+ interval: '30s',
243
+ });
244
+ }
245
+
246
+ export default function () {
247
+ const res = http.get('http://my-service.default.svc.cluster.local:8080/api');
248
+ errorRate.add(res.status >= 500 || res.status === 0 ? 1 : 0);
249
+ latency.add(res.timings.duration);
250
+
251
+ check(res, {
252
+ 'service available': (r) => r.status !== 0,
253
+ 'not server error': (r) => r.status < 500,
254
+ });
255
+ sleep(0.1);
256
+ }
257
+ ```
258
+
259
+ **Success criteria:**
260
+ - Error rate stays below threshold despite pod termination
261
+ - Kubernetes replaces terminated pods (check with `kubectl get pods -w`)
262
+ - Service remains available throughout (no total outage)
263
+ - Response times stabilize after replacement pods are ready
264
+
265
+ ---
266
+
267
+ ## Pattern 5: Failover Testing
268
+
269
+ Test traffic routing to healthy instances when some become unavailable.
270
+
271
+ ```javascript
272
+ import http from 'k6/http';
273
+ import { check, sleep } from 'k6';
274
+ import { ServiceDisruptor } from 'k6/x/disruptor';
275
+
276
+ export const options = {
277
+ scenarios: {
278
+ baseline: {
279
+ executor: 'constant-arrival-rate',
280
+ rate: 50,
281
+ timeUnit: '1s',
282
+ duration: '1m',
283
+ preAllocatedVUs: 30,
284
+ exec: 'loadTest',
285
+ startTime: '0s',
286
+ },
287
+ failover: {
288
+ executor: 'constant-arrival-rate',
289
+ rate: 50,
290
+ timeUnit: '1s',
291
+ duration: '2m',
292
+ preAllocatedVUs: 30,
293
+ exec: 'loadTest',
294
+ startTime: '1m', // Start after baseline
295
+ },
296
+ inject: {
297
+ executor: 'shared-iterations',
298
+ iterations: 1,
299
+ vus: 1,
300
+ exec: 'injectFault',
301
+ startTime: '1m', // Inject at same time as failover phase
302
+ },
303
+ },
304
+ thresholds: {
305
+ 'http_req_failed{scenario:baseline}': ['rate<0.01'], // Baseline: near-zero errors
306
+ 'http_req_failed{scenario:failover}': ['rate<0.1'], // Failover: some errors ok
307
+ 'http_req_duration{scenario:failover}': ['p(95)<2000'],
308
+ },
309
+ };
310
+
311
+ export function injectFault() {
312
+ const disruptor = new ServiceDisruptor('primary-service', 'default');
313
+ // Make primary service return errors
314
+ disruptor.injectHTTPFaults({
315
+ errorRate: 1.0,
316
+ errorCode: 503,
317
+ }, '120s');
318
+ }
319
+
320
+ export function loadTest() {
321
+ // Request goes through a load balancer or gateway that should failover
322
+ const res = http.get('http://api-gateway.default.svc.cluster.local:8080/api/data');
323
+ check(res, {
324
+ 'response received': (r) => r.status !== 0,
325
+ 'successful response': (r) => r.status === 200,
326
+ });
327
+ sleep(0.1);
328
+ }
329
+ ```
330
+
331
+ **Success criteria:**
332
+ - Baseline phase has near-zero errors
333
+ - After primary failure, traffic routes to secondary/healthy instances
334
+ - Error rate during failover stays within threshold
335
+ - No sustained outage period
336
+
337
+ ---
338
+
339
+ ## Pattern 6: Baseline Comparison with SKIP_FAULTS
340
+
341
+ Run the same test with and without faults to measure exact degradation impact.
342
+
343
+ ```javascript
344
+ import http from 'k6/http';
345
+ import { check, sleep } from 'k6';
346
+ import { ServiceDisruptor } from 'k6/x/disruptor';
347
+
348
+ const SKIP_FAULTS = (__ENV.SKIP_FAULTS === '1');
349
+
350
+ export const options = {
351
+ scenarios: {
352
+ load: {
353
+ executor: 'constant-arrival-rate',
354
+ rate: 100,
355
+ timeUnit: '1s',
356
+ duration: '30s',
357
+ preAllocatedVUs: 10,
358
+ maxVUs: 100,
359
+ },
360
+ disrupt: {
361
+ executor: 'shared-iterations',
362
+ iterations: 1,
363
+ vus: 1,
364
+ exec: 'disrupt',
365
+ startTime: '0s',
366
+ },
367
+ },
368
+ };
369
+
370
+ export function disrupt() {
371
+ if (SKIP_FAULTS) {
372
+ console.log('Skipping fault injection (baseline run)');
373
+ return;
374
+ }
375
+
376
+ const disruptor = new ServiceDisruptor('my-app', 'default');
377
+ disruptor.injectHTTPFaults({
378
+ averageDelay: '50ms',
379
+ errorCode: 500,
380
+ errorRate: 0.1,
381
+ }, '30s');
382
+ }
383
+
384
+ export default function () {
385
+ const res = http.get('http://my-app.default.svc.cluster.local:8080/api');
386
+ check(res, {
387
+ 'status is 200': (r) => r.status === 200,
388
+ });
389
+ sleep(0.1);
390
+ }
391
+ ```
392
+
393
+ **Usage:**
394
+ ```bash
395
+ # Run baseline (no faults)
396
+ SKIP_FAULTS=1 k6 run test.js --out json=baseline.json
397
+
398
+ # Run with faults
399
+ k6 run test.js --out json=fault.json
400
+
401
+ # Compare results
402
+ # Baseline: ~100ms p95, 0% errors
403
+ # With faults: ~150ms p95, 10% errors
404
+ ```
405
+
406
+ **What to compare:**
407
+ | Metric | Baseline | With Faults | Acceptable Delta |
408
+ |--------|----------|-------------|------------------|
409
+ | p(95) latency | 100ms | 150ms | < 2x baseline |
410
+ | Error rate | 0% | 10% | = injected rate |
411
+ | Throughput | 100 req/s | 90 req/s | > 70% baseline |
412
+
413
+ ---
414
+
415
+ ## Multi-Scenario Architecture
416
+
417
+ Use separate `exec` functions for load generation and fault injection in the same test:
418
+
419
+ ```javascript
420
+ export const options = {
421
+ scenarios: {
422
+ // Load generation runs the entire test
423
+ load: {
424
+ executor: 'constant-arrival-rate',
425
+ rate: 100,
426
+ timeUnit: '1s',
427
+ duration: '5m',
428
+ preAllocatedVUs: 50,
429
+ maxVUs: 100,
430
+ exec: 'default',
431
+ },
432
+ // Fault injection runs once at a specific time
433
+ disrupt: {
434
+ executor: 'shared-iterations',
435
+ iterations: 1,
436
+ vus: 1,
437
+ exec: 'disrupt',
438
+ startTime: '30s', // Start after 30s baseline
439
+ },
440
+ },
441
+ };
442
+ ```
443
+
444
+ **Timeline:**
445
+ ```
446
+ 0s 30s 2m30s 5m
447
+ |---------|---------------------|------------------|
448
+ Baseline Fault Active Recovery
449
+ (clean) (degraded) (back to normal)
450
+ ```
451
+
452
+ This pattern is preferred over `setup()` injection because:
453
+ - Captures clean baseline metrics before faults
454
+ - Fault injection timing is precise via `startTime`
455
+ - Load and disruption scenarios run independently
456
+
457
+ ---
458
+
459
+ ## Threshold Strategy for Resilience Tests
460
+
461
+ ### Relaxed Overall + Strict Recovery
462
+
463
+ ```javascript
464
+ export const options = {
465
+ thresholds: {
466
+ // Overall: relaxed (includes chaos phase)
467
+ http_req_failed: ['rate<0.5'],
468
+ http_req_duration: ['p(95)<5000'],
469
+
470
+ // Tag-based: strict for recovery phase
471
+ 'http_req_duration{phase:recovery}': ['p(95)<500'],
472
+ 'http_req_failed{phase:recovery}': ['rate<0.01'],
473
+ },
474
+ };
475
+ ```
476
+
477
+ ### Abort on Catastrophic Failure
478
+
479
+ ```javascript
480
+ export const options = {
481
+ thresholds: {
482
+ // Abort if system completely fails
483
+ http_req_failed: [{
484
+ threshold: 'rate<0.95',
485
+ abortOnFail: true,
486
+ delayAbortEval: '10s',
487
+ }],
488
+ },
489
+ };
490
+ ```
491
+
492
+ ### Recommended Thresholds by Pattern
493
+
494
+ | Pattern | Error Rate | p(95) Latency | Notes |
495
+ |---------|-----------|---------------|-------|
496
+ | Graceful Degradation | < 50% | < 5s | Errors expected, no cascade |
497
+ | Circuit Breaker | < 30% | < 3s | Fast fallback expected |
498
+ | Recovery | < 1% (recovery) | < 500ms (recovery) | Strict after fault removal |
499
+ | Pod Failure | < 30% | < 10s | Transient errors during reschedule |
500
+ | Failover | < 10% | < 2s | Quick reroute expected |