@oalacea/daemon 0.7.2 → 0.7.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.
Files changed (38) hide show
  1. package/dist/cli/commands/init.command.js +1 -1
  2. package/dist/cli/commands/init.command.js.map +1 -1
  3. package/dist/prompts/DEPS_EFFICIENCY.md +558 -0
  4. package/dist/prompts/E2E.md +491 -0
  5. package/dist/prompts/EXECUTE.md +1060 -0
  6. package/dist/prompts/INTEGRATION_API.md +484 -0
  7. package/dist/prompts/INTEGRATION_DB.md +425 -0
  8. package/dist/prompts/PERF_API.md +433 -0
  9. package/dist/prompts/PERF_DB.md +430 -0
  10. package/dist/prompts/PERF_FRONT.md +357 -0
  11. package/dist/prompts/REMEDIATION.md +482 -0
  12. package/dist/prompts/UNIT.md +260 -0
  13. package/dist/templates/README.md +221 -0
  14. package/dist/templates/k6/load-test.js +54 -0
  15. package/dist/templates/nestjs/controller.spec.ts +203 -0
  16. package/dist/templates/nestjs/e2e/api.e2e-spec.ts +451 -0
  17. package/dist/templates/nestjs/e2e/auth.e2e-spec.ts +533 -0
  18. package/dist/templates/nestjs/fixtures/test-module.ts +311 -0
  19. package/dist/templates/nestjs/guard.spec.ts +314 -0
  20. package/dist/templates/nestjs/interceptor.spec.ts +458 -0
  21. package/dist/templates/nestjs/module.spec.ts +173 -0
  22. package/dist/templates/nestjs/pipe.spec.ts +474 -0
  23. package/dist/templates/nestjs/service.spec.ts +296 -0
  24. package/dist/templates/playwright/e2e.spec.ts +61 -0
  25. package/dist/templates/rust/Cargo.toml +72 -0
  26. package/dist/templates/rust/actix-controller.test.rs +114 -0
  27. package/dist/templates/rust/axum-handler.test.rs +117 -0
  28. package/dist/templates/rust/integration.test.rs +63 -0
  29. package/dist/templates/rust/rocket-route.test.rs +106 -0
  30. package/dist/templates/rust/unit.test.rs +38 -0
  31. package/dist/templates/vitest/angular-component.test.ts +38 -0
  32. package/dist/templates/vitest/api.test.ts +51 -0
  33. package/dist/templates/vitest/component.test.ts +27 -0
  34. package/dist/templates/vitest/hook.test.ts +36 -0
  35. package/dist/templates/vitest/solid-component.test.ts +34 -0
  36. package/dist/templates/vitest/svelte-component.test.ts +33 -0
  37. package/dist/templates/vitest/vue-component.test.ts +39 -0
  38. package/package.json +2 -2
@@ -0,0 +1,203 @@
1
+ /**
2
+ * NestJS Controller Test Template
3
+ *
4
+ * Tests for NestJS controllers following best practices:
5
+ * - Isolated unit tests (no actual HTTP)
6
+ * - Mocked services and providers
7
+ * - Proper DTO validation
8
+ * - Error handling coverage
9
+ *
10
+ * @package test
11
+ */
12
+
13
+ import { Test, TestingModule } from '@nestjs/testing';
14
+ import { GameController } from './game.controller';
15
+ import { GameService } from './game.service';
16
+ import { CreateGameDto, UpdateGameDto } from './dto';
17
+ import { NotFoundException, BadRequestException } from '@nestjs/common';
18
+
19
+ describe('GameController', () => {
20
+ let controller: GameController;
21
+ let service: GameService;
22
+
23
+ // Mock service
24
+ const mockGameService = {
25
+ findAll: jest.fn(),
26
+ findOne: jest.fn(),
27
+ create: jest.fn(),
28
+ update: jest.fn(),
29
+ remove: jest.fn(),
30
+ };
31
+
32
+ beforeEach(async () => {
33
+ const module: TestingModule = await Test.createTestingModule({
34
+ controllers: [GameController],
35
+ providers: [
36
+ {
37
+ provide: GameService,
38
+ useValue: mockGameService,
39
+ },
40
+ ],
41
+ }).compile();
42
+
43
+ controller = module.get<GameController>(GameController);
44
+ service = module.get<GameService>(GameService);
45
+ });
46
+
47
+ afterEach(() => {
48
+ jest.clearAllMocks();
49
+ });
50
+
51
+ it('should be defined', () => {
52
+ expect(controller).toBeDefined();
53
+ });
54
+
55
+ describe('findAll', () => {
56
+ it('should return an array of games', async () => {
57
+ const expectedGames = [
58
+ { id: '1', name: 'Game 1' },
59
+ { id: '2', name: 'Game 2' },
60
+ ];
61
+
62
+ mockGameService.findAll.mockResolvedValue(expectedGames);
63
+
64
+ const result = await controller.findAll();
65
+
66
+ expect(result).toEqual(expectedGames);
67
+ expect(service.findAll).toHaveBeenCalledTimes(1);
68
+ });
69
+
70
+ it('should handle empty results', async () => {
71
+ mockGameService.findAll.mockResolvedValue([]);
72
+
73
+ const result = await controller.findAll();
74
+
75
+ expect(result).toEqual([]);
76
+ expect(service.findAll).toHaveBeenCalledTimes(1);
77
+ });
78
+ });
79
+
80
+ describe('findOne', () => {
81
+ it('should return a single game by id', async () => {
82
+ const expectedGame = { id: '1', name: 'Game 1' };
83
+
84
+ mockGameService.findOne.mockResolvedValue(expectedGame);
85
+
86
+ const result = await controller.findOne('1');
87
+
88
+ expect(result).toEqual(expectedGame);
89
+ expect(service.findOne).toHaveBeenCalledWith('1');
90
+ });
91
+
92
+ it('should throw NotFoundException when game not found', async () => {
93
+ mockGameService.findOne.mockRejectedValue(
94
+ new NotFoundException('Game not found')
95
+ );
96
+
97
+ await expect(controller.findOne('999')).rejects.toThrow(
98
+ NotFoundException
99
+ );
100
+ expect(service.findOne).toHaveBeenCalledWith('999');
101
+ });
102
+ });
103
+
104
+ describe('create', () => {
105
+ it('should create a new game', async () => {
106
+ const createGameDto: CreateGameDto = {
107
+ name: 'New Game',
108
+ type: 'action',
109
+ };
110
+
111
+ const createdGame = { id: '1', ...createGameDto };
112
+
113
+ mockGameService.create.mockResolvedValue(createdGame);
114
+
115
+ const result = await controller.create(createGameDto);
116
+
117
+ expect(result).toEqual(createdGame);
118
+ expect(service.create).toHaveBeenCalledWith(createGameDto);
119
+ });
120
+
121
+ it('should throw BadRequestException for invalid DTO', async () => {
122
+ const invalidDto = { name: '' };
123
+
124
+ mockGameService.create.mockRejectedValue(
125
+ new BadRequestException('Invalid data')
126
+ );
127
+
128
+ await expect(controller.create(invalidDto as any)).rejects.toThrow(
129
+ BadRequestException
130
+ );
131
+ });
132
+ });
133
+
134
+ describe('update', () => {
135
+ it('should update an existing game', async () => {
136
+ const updateGameDto: UpdateGameDto = {
137
+ name: 'Updated Game',
138
+ };
139
+
140
+ const updatedGame = { id: '1', ...updateGameDto };
141
+
142
+ mockGameService.update.mockResolvedValue(updatedGame);
143
+
144
+ const result = await controller.update('1', updateGameDto);
145
+
146
+ expect(result).toEqual(updatedGame);
147
+ expect(service.update).toHaveBeenCalledWith('1', updateGameDto);
148
+ });
149
+
150
+ it('should throw NotFoundException when updating non-existent game', async () => {
151
+ const updateGameDto: UpdateGameDto = { name: 'Updated' };
152
+
153
+ mockGameService.update.mockRejectedValue(
154
+ new NotFoundException('Game not found')
155
+ );
156
+
157
+ await expect(controller.update('999', updateGameDto)).rejects.toThrow(
158
+ NotFoundException
159
+ );
160
+ });
161
+ });
162
+
163
+ describe('remove', () => {
164
+ it('should delete a game', async () => {
165
+ mockGameService.remove.mockResolvedValue(undefined);
166
+
167
+ await controller.remove('1');
168
+
169
+ expect(service.remove).toHaveBeenCalledWith('1');
170
+ });
171
+
172
+ it('should throw NotFoundException when deleting non-existent game', async () => {
173
+ mockGameService.remove.mockRejectedValue(
174
+ new NotFoundException('Game not found')
175
+ );
176
+
177
+ await expect(controller.remove('999')).rejects.toThrow(
178
+ NotFoundException
179
+ );
180
+ });
181
+ });
182
+
183
+ describe('query parameters', () => {
184
+ it('should handle query parameters correctly', async () => {
185
+ const expectedGames = [{ id: '1', name: 'Filtered Game' }];
186
+
187
+ mockGameService.findAll.mockResolvedValue(expectedGames);
188
+
189
+ const result = await controller.findAll({
190
+ page: '1',
191
+ limit: '10',
192
+ search: 'test',
193
+ } as any);
194
+
195
+ expect(result).toEqual(expectedGames);
196
+ expect(service.findAll).toHaveBeenCalledWith({
197
+ page: 1,
198
+ limit: 10,
199
+ search: 'test',
200
+ });
201
+ });
202
+ });
203
+ });
@@ -0,0 +1,451 @@
1
+ /**
2
+ * NestJS E2E API Test Template
3
+ *
4
+ * End-to-end tests for NestJS API endpoints:
5
+ * - Full HTTP request/response cycle
6
+ * - Authentication flows
7
+ * - Database integration
8
+ * - API contract validation
9
+ *
10
+ * @package test
11
+ */
12
+
13
+ import { Test, TestingModule } from '@nestjs/testing';
14
+ import { INestApplication, ValidationPipe } from '@nestjs/common';
15
+ import * as request from 'supertest';
16
+ import { AppModule } from './../src/app.module';
17
+
18
+ describe('API E2E Tests', () => {
19
+ let app: INestApplication;
20
+ let authToken: string;
21
+
22
+ beforeAll(async () => {
23
+ const moduleFixture: TestingModule = await Test.createTestingModule({
24
+ imports: [AppModule],
25
+ }).compile();
26
+
27
+ app = moduleFixture.createNestApplication();
28
+
29
+ // Apply global pipes
30
+ app.useGlobalPipes(
31
+ new ValidationPipe({
32
+ whitelist: true,
33
+ transform: true,
34
+ forbidNonWhitelisted: true,
35
+ })
36
+ );
37
+
38
+ // Enable shutdown hooks
39
+ app.enableShutdownHooks();
40
+
41
+ await app.init();
42
+ });
43
+
44
+ afterAll(async () => {
45
+ await app.close();
46
+ });
47
+
48
+ describe('Authentication (e2e)', () => {
49
+ describe('POST /auth/register', () => {
50
+ it('should register a new user', () => {
51
+ return request(app.getHttpServer())
52
+ .post('/auth/register')
53
+ .send({
54
+ email: `test-${Date.now()}@example.com`,
55
+ password: 'SecurePass123!',
56
+ name: 'Test User',
57
+ })
58
+ .expect(201)
59
+ .expect((res) => {
60
+ expect(res.body).toHaveProperty('id');
61
+ expect(res.body).toHaveProperty('email');
62
+ expect(res.body).not.toHaveProperty('password');
63
+ });
64
+ });
65
+
66
+ it('should reject duplicate email', () => {
67
+ const email = `duplicate-${Date.now()}@example.com`;
68
+
69
+ return request(app.getHttpServer())
70
+ .post('/auth/register')
71
+ .send({
72
+ email,
73
+ password: 'SecurePass123!',
74
+ name: 'Test User',
75
+ })
76
+ .expect(201)
77
+ .then(() => {
78
+ return request(app.getHttpServer())
79
+ .post('/auth/register')
80
+ .send({
81
+ email,
82
+ password: 'SecurePass123!',
83
+ name: 'Test User',
84
+ })
85
+ .expect(409);
86
+ });
87
+ });
88
+
89
+ it('should reject invalid email format', () => {
90
+ return request(app.getHttpServer())
91
+ .post('/auth/register')
92
+ .send({
93
+ email: 'invalid-email',
94
+ password: 'SecurePass123!',
95
+ name: 'Test User',
96
+ })
97
+ .expect(400);
98
+ });
99
+
100
+ it('should reject weak password', () => {
101
+ return request(app.getHttpServer())
102
+ .post('/auth/register')
103
+ .send({
104
+ email: `test-${Date.now()}@example.com`,
105
+ password: 'weak',
106
+ name: 'Test User',
107
+ })
108
+ .expect(400);
109
+ });
110
+ });
111
+
112
+ describe('POST /auth/login', () => {
113
+ it('should login with valid credentials', () => {
114
+ return request(app.getHttpServer())
115
+ .post('/auth/login')
116
+ .send({
117
+ email: 'test@example.com',
118
+ password: 'SecurePass123!',
119
+ })
120
+ .expect(200)
121
+ .expect((res) => {
122
+ expect(res.body).toHaveProperty('access_token');
123
+ expect(res.body).toHaveProperty('refresh_token');
124
+ authToken = res.body.access_token;
125
+ });
126
+ });
127
+
128
+ it('should reject invalid credentials', () => {
129
+ return request(app.getHttpServer())
130
+ .post('/auth/login')
131
+ .send({
132
+ email: 'test@example.com',
133
+ password: 'WrongPassword123!',
134
+ })
135
+ .expect(401);
136
+ });
137
+
138
+ it('should reject non-existent user', () => {
139
+ return request(app.getHttpServer())
140
+ .post('/auth/login')
141
+ .send({
142
+ email: `nonexistent-${Date.now()}@example.com`,
143
+ password: 'SecurePass123!',
144
+ })
145
+ .expect(401);
146
+ });
147
+ });
148
+
149
+ describe('POST /auth/refresh', () => {
150
+ it('should refresh access token', () => {
151
+ return request(app.getHttpServer())
152
+ .post('/auth/refresh')
153
+ .send({ refresh_token: 'valid-refresh-token' })
154
+ .expect(200)
155
+ .expect((res) => {
156
+ expect(res.body).toHaveProperty('access_token');
157
+ });
158
+ });
159
+
160
+ it('should reject invalid refresh token', () => {
161
+ return request(app.getHttpServer())
162
+ .post('/auth/refresh')
163
+ .send({ refresh_token: 'invalid-token' })
164
+ .expect(401);
165
+ });
166
+ });
167
+ });
168
+
169
+ describe('Resources (e2e)', () => {
170
+ let resourceId: string;
171
+
172
+ describe('GET /resources', () => {
173
+ it('should return array of resources', () => {
174
+ return request(app.getHttpServer())
175
+ .get('/resources')
176
+ .set('Authorization', `Bearer ${authToken}`)
177
+ .expect(200)
178
+ .expect((res) => {
179
+ expect(Array.isArray(res.body)).toBe(true);
180
+ expect(res.body).toHaveProperty('data');
181
+ });
182
+ });
183
+
184
+ it('should support pagination', () => {
185
+ return request(app.getHttpServer())
186
+ .get('/resources?page=1&limit=10')
187
+ .set('Authorization', `Bearer ${authToken}`)
188
+ .expect(200)
189
+ .expect((res) => {
190
+ expect(res.body).toHaveProperty('meta');
191
+ expect(res.body.meta).toHaveProperty('page');
192
+ expect(res.body.meta).toHaveProperty('limit');
193
+ });
194
+ });
195
+
196
+ it('should support filtering', () => {
197
+ return request(app.getHttpServer())
198
+ .get('/resources?status=active')
199
+ .set('Authorization', `Bearer ${authToken}`)
200
+ .expect(200)
201
+ .expect((res) => {
202
+ expect(res.body.data.every((item: any) => item.status === 'active')).toBe(true);
203
+ });
204
+ });
205
+
206
+ it('should support sorting', () => {
207
+ return request(app.getHttpServer())
208
+ .get('/resources?sort=name&order=asc')
209
+ .set('Authorization', `Bearer ${authToken}`)
210
+ .expect(200)
211
+ .expect((res) => {
212
+ const names = res.body.data.map((item: any) => item.name);
213
+ const sortedNames = [...names].sort();
214
+ expect(names).toEqual(sortedNames);
215
+ });
216
+ });
217
+
218
+ it('should require authentication', () => {
219
+ return request(app.getHttpServer())
220
+ .get('/resources')
221
+ .expect(401);
222
+ });
223
+ });
224
+
225
+ describe('POST /resources', () => {
226
+ it('should create a new resource', () => {
227
+ const newResource = {
228
+ name: 'Test Resource',
229
+ description: 'Test Description',
230
+ status: 'active',
231
+ };
232
+
233
+ return request(app.getHttpServer())
234
+ .post('/resources')
235
+ .set('Authorization', `Bearer ${authToken}`)
236
+ .send(newResource)
237
+ .expect(201)
238
+ .expect((res) => {
239
+ expect(res.body).toHaveProperty('id');
240
+ expect(res.body.name).toBe(newResource.name);
241
+ resourceId = res.body.id;
242
+ });
243
+ });
244
+
245
+ it('should reject invalid data', () => {
246
+ return request(app.getHttpServer())
247
+ .post('/resources')
248
+ .set('Authorization', `Bearer ${authToken}`)
249
+ .send({
250
+ name: '', // Invalid: empty name
251
+ status: 'invalid-status',
252
+ })
253
+ .expect(400);
254
+ });
255
+
256
+ it('should strip non-whitelisted properties', () => {
257
+ return request(app.getHttpServer())
258
+ .post('/resources')
259
+ .set('Authorization', `Bearer ${authToken}`)
260
+ .send({
261
+ name: 'Test Resource',
262
+ maliciousProperty: 'should be stripped',
263
+ })
264
+ .expect(201)
265
+ .expect((res) => {
266
+ expect(res.body).not.toHaveProperty('maliciousProperty');
267
+ });
268
+ });
269
+ });
270
+
271
+ describe('GET /resources/:id', () => {
272
+ it('should return a single resource', () => {
273
+ return request(app.getHttpServer())
274
+ .get(`/resources/${resourceId}`)
275
+ .set('Authorization', `Bearer ${authToken}`)
276
+ .expect(200)
277
+ .expect((res) => {
278
+ expect(res.body).toHaveProperty('id');
279
+ expect(res.body.id).toBe(resourceId);
280
+ });
281
+ });
282
+
283
+ it('should return 404 for non-existent resource', () => {
284
+ return request(app.getHttpServer())
285
+ .get('/resources/non-existent-id')
286
+ .set('Authorization', `Bearer ${authToken}`)
287
+ .expect(404);
288
+ });
289
+
290
+ it('should validate UUID format', () => {
291
+ return request(app.getHttpServer())
292
+ .get('/resources/invalid-uuid-format')
293
+ .set('Authorization', `Bearer ${authToken}`)
294
+ .expect(400);
295
+ });
296
+ });
297
+
298
+ describe('PATCH /resources/:id', () => {
299
+ it('should update a resource', () => {
300
+ const updates = {
301
+ name: 'Updated Resource Name',
302
+ };
303
+
304
+ return request(app.getHttpServer())
305
+ .patch(`/resources/${resourceId}`)
306
+ .set('Authorization', `Bearer ${authToken}`)
307
+ .send(updates)
308
+ .expect(200)
309
+ .expect((res) => {
310
+ expect(res.body.name).toBe(updates.name);
311
+ });
312
+ });
313
+
314
+ it('should return 404 for non-existent resource', () => {
315
+ return request(app.getHttpServer())
316
+ .patch('/resources/non-existent-id')
317
+ .set('Authorization', `Bearer ${authToken}`)
318
+ .send({ name: 'Updated' })
319
+ .expect(404);
320
+ });
321
+
322
+ it('should reject invalid updates', () => {
323
+ return request(app.getHttpServer())
324
+ .patch(`/resources/${resourceId}`)
325
+ .set('Authorization', `Bearer ${authToken}`)
326
+ .send({
327
+ status: 'invalid-status-value',
328
+ })
329
+ .expect(400);
330
+ });
331
+ });
332
+
333
+ describe('DELETE /resources/:id', () => {
334
+ it('should delete a resource', () => {
335
+ return request(app.getHttpServer())
336
+ .delete(`/resources/${resourceId}`)
337
+ .set('Authorization', `Bearer ${authToken}`)
338
+ .expect(204);
339
+ });
340
+
341
+ it('should return 404 for already deleted resource', () => {
342
+ return request(app.getHttpServer())
343
+ .delete(`/resources/${resourceId}`)
344
+ .set('Authorization', `Bearer ${authToken}`)
345
+ .expect(404);
346
+ });
347
+ });
348
+ });
349
+
350
+ describe('Error Handling (e2e)', () => {
351
+ it('should return consistent error format', () => {
352
+ return request(app.getHttpServer())
353
+ .get('/resources/non-existent')
354
+ .set('Authorization', `Bearer ${authToken}`)
355
+ .expect(404)
356
+ .expect((res) => {
357
+ expect(res.body).toHaveProperty('statusCode');
358
+ expect(res.body).toHaveProperty('message');
359
+ expect(res.body).toHaveProperty('error');
360
+ });
361
+ });
362
+
363
+ it('should handle validation errors consistently', () => {
364
+ return request(app.getHttpServer())
365
+ .post('/resources')
366
+ .set('Authorization', `Bearer ${authToken}`)
367
+ .send({})
368
+ .expect(400)
369
+ .expect((res) => {
370
+ expect(res.body).toHaveProperty('message');
371
+ expect(Array.isArray(res.body.message)).toBe(true);
372
+ });
373
+ });
374
+
375
+ it('should include request ID in error responses', () => {
376
+ return request(app.getHttpServer())
377
+ .get('/resources/non-existent')
378
+ .set('Authorization', `Bearer ${authToken}`)
379
+ .set('X-Request-ID', 'test-request-id')
380
+ .expect(404)
381
+ .expect((res) => {
382
+ expect(res.headers['x-request-id']).toBeDefined();
383
+ });
384
+ });
385
+ });
386
+
387
+ describe('Performance (e2e)', () => {
388
+ it('should respond to simple GET within 200ms', async () => {
389
+ const start = Date.now();
390
+
391
+ await request(app.getHttpServer())
392
+ .get('/resources')
393
+ .set('Authorization', `Bearer ${authToken}`)
394
+ .expect(200);
395
+
396
+ const duration = Date.now() - start;
397
+ expect(duration).toBeLessThan(200);
398
+ });
399
+
400
+ it('should handle concurrent requests', async () => {
401
+ const requests = Array.from({ length: 10 }, () =>
402
+ request(app.getHttpServer())
403
+ .get('/resources')
404
+ .set('Authorization', `Bearer ${authToken}`)
405
+ );
406
+
407
+ const responses = await Promise.all(requests);
408
+
409
+ responses.forEach((res) => {
410
+ expect(res.status).toBe(200);
411
+ });
412
+ });
413
+ });
414
+
415
+ describe('CORS (e2e)', () => {
416
+ it('should include CORS headers', () => {
417
+ return request(app.getHttpServer())
418
+ .options('/resources')
419
+ .set('Origin', 'https://example.com')
420
+ .expect(204)
421
+ .expect((res) => {
422
+ expect(res.headers['access-control-allow-origin']).toBeDefined();
423
+ });
424
+ });
425
+ });
426
+
427
+ describe('Rate Limiting (e2e)', () => {
428
+ it('should allow requests within rate limit', () => {
429
+ return request(app.getHttpServer())
430
+ .get('/resources')
431
+ .set('Authorization', `Bearer ${authToken}`)
432
+ .expect(200);
433
+ });
434
+
435
+ it('should block requests exceeding rate limit', async () => {
436
+ const requests = Array.from({ length: 105 }, () =>
437
+ request(app.getHttpServer())
438
+ .get('/resources')
439
+ .set('Authorization', `Bearer ${authToken}`)
440
+ );
441
+
442
+ const responses = await Promise.allSettled(requests);
443
+
444
+ const rateLimited = responses.some(
445
+ (r) => r.status === 'fulfilled' && r.value.status === 429
446
+ );
447
+
448
+ expect(rateLimited).toBe(true);
449
+ });
450
+ });
451
+ });