@oalacea/demon 1.0.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/CHANGELOG.md +38 -0
- package/LICENSE +23 -0
- package/README.md +103 -0
- package/agents/deps-analyzer.js +366 -0
- package/agents/detector.js +570 -0
- package/agents/fix-engine.js +305 -0
- package/agents/perf-analyzer.js +294 -0
- package/agents/test-generator.js +387 -0
- package/agents/test-runner.js +318 -0
- package/bin/Dockerfile +65 -0
- package/bin/cli.js +455 -0
- package/lib/config.js +237 -0
- package/lib/docker.js +207 -0
- package/lib/reporter.js +297 -0
- package/package.json +34 -0
- package/prompts/DEPS_EFFICIENCY.md +558 -0
- package/prompts/E2E.md +491 -0
- package/prompts/EXECUTE.md +782 -0
- package/prompts/INTEGRATION_API.md +484 -0
- package/prompts/INTEGRATION_DB.md +425 -0
- package/prompts/PERF_API.md +433 -0
- package/prompts/PERF_DB.md +430 -0
- package/prompts/REMEDIATION.md +482 -0
- package/prompts/UNIT.md +260 -0
- package/scripts/dev.js +106 -0
- package/templates/README.md +22 -0
- package/templates/k6/load-test.js +54 -0
- package/templates/playwright/e2e.spec.ts +61 -0
- package/templates/vitest/api.test.ts +51 -0
- package/templates/vitest/component.test.ts +27 -0
- package/templates/vitest/hook.test.ts +36 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# Performance Test Guide (k6)
|
|
2
|
+
|
|
3
|
+
This prompt is included by EXECUTE.md. It provides detailed guidance for API performance testing.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## k6 Setup
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// tests/performance/lib/config.js
|
|
11
|
+
export const config = {
|
|
12
|
+
BASE_URL: __ENV.BASE_URL || 'http://host.docker.internal:3000',
|
|
13
|
+
TIMEOUT: '30s',
|
|
14
|
+
};
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Load Testing Patterns
|
|
20
|
+
|
|
21
|
+
### Basic Load Test
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// tests/performance/scenarios/basic-load.js
|
|
25
|
+
import http from 'k6/http';
|
|
26
|
+
import { check, sleep } from 'k6';
|
|
27
|
+
import { Rate } from 'k6/metrics';
|
|
28
|
+
|
|
29
|
+
// Custom error rate
|
|
30
|
+
const errorRate = new Rate('errors');
|
|
31
|
+
|
|
32
|
+
export const options = {
|
|
33
|
+
stages: [
|
|
34
|
+
{ duration: '30s', target: 10 }, // Ramp up to 10 users
|
|
35
|
+
{ duration: '1m', target: 10 }, // Stay at 10 users
|
|
36
|
+
{ duration: '30s', target: 50 }, // Ramp up to 50 users
|
|
37
|
+
{ duration: '2m', target: 50 }, // Stay at 50 users
|
|
38
|
+
{ duration: '30s', target: 100 }, // Spike to 100 users
|
|
39
|
+
{ duration: '1m', target: 100 }, // Stay at 100 users
|
|
40
|
+
{ duration: '30s', target: 0 }, // Ramp down
|
|
41
|
+
],
|
|
42
|
+
thresholds: {
|
|
43
|
+
http_req_duration: ['p(95)<200', 'p(99)<500'],
|
|
44
|
+
http_req_failed: ['rate<0.01'],
|
|
45
|
+
errors: ['rate<0.05'],
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const BASE_URL = 'http://host.docker.internal:3000';
|
|
50
|
+
|
|
51
|
+
export default function () {
|
|
52
|
+
// Test homepage
|
|
53
|
+
let res = http.get(`${BASE_URL}/`);
|
|
54
|
+
check(res, {
|
|
55
|
+
'homepage status 200': (r) => r.status === 200,
|
|
56
|
+
'homepage response time < 200ms': (r) => r.timings.duration < 200,
|
|
57
|
+
}) || errorRate.add(1);
|
|
58
|
+
|
|
59
|
+
sleep(1);
|
|
60
|
+
|
|
61
|
+
// Test API
|
|
62
|
+
res = http.get(`${BASE_URL}/api/users`);
|
|
63
|
+
check(res, {
|
|
64
|
+
'users API status 200': (r) => r.status === 200,
|
|
65
|
+
'users response time < 200ms': (r) => r.timings.duration < 200,
|
|
66
|
+
}) || errorRate.add(1);
|
|
67
|
+
|
|
68
|
+
sleep(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function handleSummary(data) {
|
|
72
|
+
return {
|
|
73
|
+
'stdout': JSON.stringify(data, null, 2),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Stress Test
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// tests/performance/scenarios/stress.js
|
|
82
|
+
import http from 'k6/http';
|
|
83
|
+
import { check, sleep } from 'k6';
|
|
84
|
+
|
|
85
|
+
export const options = {
|
|
86
|
+
stages: [
|
|
87
|
+
{ duration: '2m', target: 100 }, // Ramp up to 100
|
|
88
|
+
{ duration: '5m', target: 100 }, // Stay at 100
|
|
89
|
+
{ duration: '2m', target: 200 }, // Ramp to 200
|
|
90
|
+
{ duration: '5m', target: 200 }, // Stay at 200
|
|
91
|
+
{ duration: '2m', target: 300 }, // Ramp to 300
|
|
92
|
+
{ duration: '5m', target: 300 }, // Stay at 300
|
|
93
|
+
{ duration: '10m', target: 0 }, // Recovery
|
|
94
|
+
],
|
|
95
|
+
thresholds: {
|
|
96
|
+
http_req_duration: ['p(95)<500', 'p(99)<1000'],
|
|
97
|
+
http_req_failed: ['rate<0.05'], // Allow more errors during stress
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const BASE_URL = 'http://host.docker.internal:3000';
|
|
102
|
+
|
|
103
|
+
export default function () {
|
|
104
|
+
const responses = http.batch([
|
|
105
|
+
['GET', `${BASE_URL}/api/users`],
|
|
106
|
+
['GET', `${BASE_URL}/api/posts`],
|
|
107
|
+
['GET', `${BASE_URL}/api/comments`],
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
responses.forEach((res) => {
|
|
111
|
+
check(res, {
|
|
112
|
+
'status 200': (r) => r.status === 200,
|
|
113
|
+
'not rate limited': (r) => r.status !== 429,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
sleep(1);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Spike Test
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
// tests/performance/scenarios/spike.js
|
|
125
|
+
import http from 'k6/http';
|
|
126
|
+
import { check } from 'k6';
|
|
127
|
+
|
|
128
|
+
export const options = {
|
|
129
|
+
stages: [
|
|
130
|
+
{ duration: '2m', target: 10 }, // Normal load
|
|
131
|
+
{ duration: '10s', target: 500 }, // Sudden spike!
|
|
132
|
+
{ duration: '2m', target: 500 }, // Stay at spike
|
|
133
|
+
{ duration: '2m', target: 10 }, // Recovery
|
|
134
|
+
],
|
|
135
|
+
thresholds: {
|
|
136
|
+
http_req_duration: ['p(95)<1000'],
|
|
137
|
+
http_req_failed: ['rate<0.10'], // Allow some failures during spike
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const BASE_URL = 'http://host.docker.internal:3000';
|
|
142
|
+
|
|
143
|
+
export default function () {
|
|
144
|
+
const res = http.get(`${BASE_URL}/api/users`);
|
|
145
|
+
check(res, {
|
|
146
|
+
'status acceptable': (r) => r.status < 500,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## API Endpoint Testing
|
|
154
|
+
|
|
155
|
+
### CRUD Operations
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
// tests/performance/scenarios/api-crud.js
|
|
159
|
+
import http from 'k6/http';
|
|
160
|
+
import { check, group } from 'k6';
|
|
161
|
+
import { randomString } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
|
|
162
|
+
|
|
163
|
+
export const options = {
|
|
164
|
+
stages: [
|
|
165
|
+
{ duration: '1m', target: 20 },
|
|
166
|
+
{ duration: '3m', target: 20 },
|
|
167
|
+
{ duration: '1m', target: 0 },
|
|
168
|
+
],
|
|
169
|
+
thresholds: {
|
|
170
|
+
http_req_duration: ['p(95)<300'],
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const BASE_URL = 'http://host.docker.internal:3000';
|
|
175
|
+
|
|
176
|
+
export default function () {
|
|
177
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
178
|
+
|
|
179
|
+
group('Create', () => {
|
|
180
|
+
const payload = JSON.stringify({
|
|
181
|
+
email: `${randomString(10)}@example.com`,
|
|
182
|
+
name: randomString(10),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const res = http.post(`${BASE_URL}/api/users`, payload, { headers });
|
|
186
|
+
|
|
187
|
+
check(res, {
|
|
188
|
+
'create status 201': (r) => r.status === 201,
|
|
189
|
+
'create response time < 300ms': (r) => r.timings.duration < 300,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
group('Read', () => {
|
|
194
|
+
const res = http.get(`${BASE_URL}/api/users`);
|
|
195
|
+
|
|
196
|
+
check(res, {
|
|
197
|
+
'read status 200': (r) => r.status === 200,
|
|
198
|
+
'read has data': (r) => JSON.parse(r.body).users.length > 0,
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
group('Update', () => {
|
|
203
|
+
const payload = JSON.stringify({ name: randomString(10) });
|
|
204
|
+
const res = http.patch(`${BASE_URL}/api/users/1`, payload, { headers });
|
|
205
|
+
|
|
206
|
+
check(res, {
|
|
207
|
+
'update status 200': (r) => r.status === 200,
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
group('Delete', () => {
|
|
212
|
+
const res = http.del(`${BASE_URL}/api/users/1`);
|
|
213
|
+
|
|
214
|
+
check(res, {
|
|
215
|
+
'delete status 204': (r) => r.status === 204,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Authenticated Requests
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
// tests/performance/scenarios/authenticated.js
|
|
225
|
+
import http from 'k6/http';
|
|
226
|
+
import { check } from 'k6';
|
|
227
|
+
|
|
228
|
+
export const options = {
|
|
229
|
+
stages: [
|
|
230
|
+
{ duration: '1m', target: 30 },
|
|
231
|
+
{ duration: '3m', target: 30 },
|
|
232
|
+
{ duration: '1m', target: 0 },
|
|
233
|
+
],
|
|
234
|
+
thresholds: {
|
|
235
|
+
http_req_duration: ['p(95)<250'],
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const BASE_URL = 'http://host.docker.internal:3000';
|
|
240
|
+
|
|
241
|
+
// Get auth token (run once per VU)
|
|
242
|
+
let authToken;
|
|
243
|
+
|
|
244
|
+
export function setup() {
|
|
245
|
+
const loginRes = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
|
|
246
|
+
email: 'test@example.com',
|
|
247
|
+
password: 'password123',
|
|
248
|
+
}), {
|
|
249
|
+
headers: { 'Content-Type': 'application/json' },
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return { token: loginRes.json('token') };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export default function (data) {
|
|
256
|
+
const headers = {
|
|
257
|
+
'Content-Type': 'application/json',
|
|
258
|
+
'Authorization': `Bearer ${data.token}`,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const res = http.get(`${BASE_URL}/api/protected`, { headers });
|
|
262
|
+
|
|
263
|
+
check(res, {
|
|
264
|
+
'status 200': (r) => r.status === 200,
|
|
265
|
+
'has data': (r) => JSON.parse(r.body).data !== undefined,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Scenario Testing
|
|
273
|
+
|
|
274
|
+
### User Journey
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
// tests/performance/scenarios/user-journey.js
|
|
278
|
+
import http from 'k6/http';
|
|
279
|
+
import { check, group } from 'k6';
|
|
280
|
+
|
|
281
|
+
export const options = {
|
|
282
|
+
stages: [
|
|
283
|
+
{ duration: '2m', target: 50 },
|
|
284
|
+
{ duration: '5m', target: 50 },
|
|
285
|
+
{ duration: '2m', target: 0 },
|
|
286
|
+
],
|
|
287
|
+
thresholds: {
|
|
288
|
+
http_req_duration: ['p(95)<400'],
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const BASE_URL = 'http://host.docker.internal:3000';
|
|
293
|
+
|
|
294
|
+
export default function () {
|
|
295
|
+
group('Browse Homepage', () => {
|
|
296
|
+
const res = http.get(`${BASE_URL}/`);
|
|
297
|
+
check(res, { 'homepage OK': (r) => r.status === 200 });
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
group('Search Products', () => {
|
|
301
|
+
const res = http.get(`${BASE_URL}/api/products?search=test`);
|
|
302
|
+
check(res, {
|
|
303
|
+
'search OK': (r) => r.status === 200,
|
|
304
|
+
'has results': (r) => JSON.parse(r.body).products.length > 0,
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
group('View Product', () => {
|
|
309
|
+
const res = http.get(`${BASE_URL}/products/1`);
|
|
310
|
+
check(res, {
|
|
311
|
+
'product OK': (r) => r.status === 200,
|
|
312
|
+
'has price': (r) => JSON.parse(r.body).price !== undefined,
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
group('Add to Cart', () => {
|
|
317
|
+
const res = http.post(`${BASE_URL}/api/cart/items`, JSON.stringify({
|
|
318
|
+
productId: 1,
|
|
319
|
+
quantity: 1,
|
|
320
|
+
}), {
|
|
321
|
+
headers: { 'Content-Type': 'application/json' },
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
check(res, {
|
|
325
|
+
'added to cart': (r) => r.status === 201,
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Data Management Tests
|
|
334
|
+
|
|
335
|
+
### Concurrent Writes
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
// tests/performance/scenarios/concurrent-writes.js
|
|
339
|
+
import http from 'k6/http';
|
|
340
|
+
import { check } from 'k6';
|
|
341
|
+
import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
|
|
342
|
+
|
|
343
|
+
export const options = {
|
|
344
|
+
scenarios: {
|
|
345
|
+
concurrent_writes: {
|
|
346
|
+
executor: 'constant-vus',
|
|
347
|
+
vus: 50,
|
|
348
|
+
duration: '2m',
|
|
349
|
+
gracefulStop: '10s',
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
thresholds: {
|
|
353
|
+
http_req_duration: ['p(95)<500'],
|
|
354
|
+
http_req_failed: ['rate<0.02'],
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const BASE_URL = 'http://host.docker.internal:3000';
|
|
359
|
+
|
|
360
|
+
export default function () {
|
|
361
|
+
const userId = randomIntBetween(1, 1000);
|
|
362
|
+
const payload = JSON.stringify({
|
|
363
|
+
userId,
|
|
364
|
+
action: 'update',
|
|
365
|
+
timestamp: Date.now(),
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const res = http.post(`${BASE_URL}/api/actions`, payload, {
|
|
369
|
+
headers: { 'Content-Type': 'application/json' },
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
check(res, {
|
|
373
|
+
'write successful': (r) => r.status === 201 || r.status === 200,
|
|
374
|
+
'no conflict': (r) => r.status !== 409,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Cache Performance
|
|
380
|
+
|
|
381
|
+
```javascript
|
|
382
|
+
// tests/performance/scenarios/cache.js
|
|
383
|
+
import http from 'k6/http';
|
|
384
|
+
import { check } from 'k6';
|
|
385
|
+
|
|
386
|
+
export const options = {
|
|
387
|
+
stages: [
|
|
388
|
+
{ duration: '1m', target: 10 },
|
|
389
|
+
{ duration: '3m', target: 10 },
|
|
390
|
+
{ duration: '1m', target: 0 },
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const BASE_URL = 'http://host.docker.internal:3000';
|
|
395
|
+
const ENDPOINT = '/api/users';
|
|
396
|
+
|
|
397
|
+
export default function () {
|
|
398
|
+
// First request (cache miss expected)
|
|
399
|
+
const miss = http.get(`${BASE_URL}${ENDPOINT}`);
|
|
400
|
+
|
|
401
|
+
check(miss, {
|
|
402
|
+
'miss status OK': (r) => r.status === 200,
|
|
403
|
+
'miss time < 500ms': (r) => r.timings.duration < 500,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Second request (cache hit expected)
|
|
407
|
+
const hit = http.get(`${BASE_URL}${ENDPOINT}`);
|
|
408
|
+
|
|
409
|
+
check(hit, {
|
|
410
|
+
'hit status OK': (r) => r.status === 200,
|
|
411
|
+
'hit faster than miss': (r) => r.timings.duration < miss.timings.duration,
|
|
412
|
+
'hit time < 100ms': (r) => r.timings.duration < 100,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Running Performance Tests
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# Run basic load test
|
|
423
|
+
docker exec demon-tools k6 run tests/performance/scenarios/basic-load.js
|
|
424
|
+
|
|
425
|
+
# Run with environment variables
|
|
426
|
+
docker exec demon-tools k6 run -e BASE_URL=http://host.docker.internal:3000 tests/performance/scenarios/basic-load.js
|
|
427
|
+
|
|
428
|
+
# Run with output file
|
|
429
|
+
docker exec demon-tools k6 run --out json=results.json tests/performance/scenarios/basic-load.js
|
|
430
|
+
|
|
431
|
+
# Run specific stage
|
|
432
|
+
docker exec demon-tools k6 run --execution-segment "0:2m,5m:8m" tests/performance/scenarios/basic-load.js
|
|
433
|
+
```
|