@lenne.tech/cli 1.2.0 → 1.3.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/build/commands/claude/install-plugin.js +339 -0
- package/package.json +1 -1
- package/build/commands/claude/install-commands.js +0 -337
- package/build/commands/claude/install-mcps.js +0 -258
- package/build/commands/claude/install-skills.js +0 -693
- package/build/lib/mcp-registry.js +0 -80
- package/build/templates/claude-commands/code-cleanup.md +0 -82
- package/build/templates/claude-commands/commit-message.md +0 -21
- package/build/templates/claude-commands/create-story.md +0 -435
- package/build/templates/claude-commands/mr-description-clipboard.md +0 -48
- package/build/templates/claude-commands/mr-description.md +0 -33
- package/build/templates/claude-commands/sec-review.md +0 -62
- package/build/templates/claude-commands/skill-optimize.md +0 -481
- package/build/templates/claude-commands/test-generate.md +0 -45
- package/build/templates/claude-skills/building-stories-with-tdd/SKILL.md +0 -265
- package/build/templates/claude-skills/building-stories-with-tdd/code-quality.md +0 -276
- package/build/templates/claude-skills/building-stories-with-tdd/database-indexes.md +0 -182
- package/build/templates/claude-skills/building-stories-with-tdd/examples.md +0 -1383
- package/build/templates/claude-skills/building-stories-with-tdd/handling-existing-tests.md +0 -197
- package/build/templates/claude-skills/building-stories-with-tdd/reference.md +0 -1427
- package/build/templates/claude-skills/building-stories-with-tdd/security-review.md +0 -307
- package/build/templates/claude-skills/building-stories-with-tdd/workflow.md +0 -1004
- package/build/templates/claude-skills/generating-nest-servers/SKILL.md +0 -303
- package/build/templates/claude-skills/generating-nest-servers/configuration.md +0 -285
- package/build/templates/claude-skills/generating-nest-servers/declare-keyword-warning.md +0 -133
- package/build/templates/claude-skills/generating-nest-servers/description-management.md +0 -226
- package/build/templates/claude-skills/generating-nest-servers/examples.md +0 -893
- package/build/templates/claude-skills/generating-nest-servers/framework-guide.md +0 -259
- package/build/templates/claude-skills/generating-nest-servers/quality-review.md +0 -864
- package/build/templates/claude-skills/generating-nest-servers/reference.md +0 -487
- package/build/templates/claude-skills/generating-nest-servers/security-rules.md +0 -371
- package/build/templates/claude-skills/generating-nest-servers/verification-checklist.md +0 -262
- package/build/templates/claude-skills/generating-nest-servers/workflow-process.md +0 -1061
- package/build/templates/claude-skills/using-lt-cli/SKILL.md +0 -284
- package/build/templates/claude-skills/using-lt-cli/examples.md +0 -546
- package/build/templates/claude-skills/using-lt-cli/reference.md +0 -513
|
@@ -1,1427 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: story-tdd-reference
|
|
3
|
-
version: 1.0.1
|
|
4
|
-
description: Quick reference guide for Test-Driven Development workflow
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Story-Based TDD Quick Reference
|
|
8
|
-
|
|
9
|
-
## Table of Contents
|
|
10
|
-
- [The 7-Step Workflow](#the-7-step-workflow)
|
|
11
|
-
- [Commands Cheatsheet](#commands-cheatsheet)
|
|
12
|
-
- [Test File Organization](#test-file-organization)
|
|
13
|
-
- [Test File Template](#test-file-template)
|
|
14
|
-
- [Database Indexes with @UnifiedField](#database-indexes-with-unifiedfield)
|
|
15
|
-
- [REST API Testing Patterns](#rest-api-testing-patterns-using-testhelper)
|
|
16
|
-
- [GraphQL Testing Patterns](#graphql-testing-patterns-using-testhelper)
|
|
17
|
-
- [Common Test Assertions](#common-test-assertions)
|
|
18
|
-
- [ObjectId Conversion Utilities](#objectid-conversion-utilities)
|
|
19
|
-
- [Security Testing Checklist](#security-testing-checklist)
|
|
20
|
-
- [When to Ask Developer](#when-to-ask-developer)
|
|
21
|
-
- [Debugging Failed Tests](#debugging-failed-tests)
|
|
22
|
-
- [Decision Tree: Test Failure Analysis](#decision-tree-test-failure-analysis)
|
|
23
|
-
- [Code Quality, Security & Refactoring Check](#code-quality-security--refactoring-check)
|
|
24
|
-
- [Final Report Template](#final-report-template)
|
|
25
|
-
- [Handling Existing Tests](#-handling-existing-tests)
|
|
26
|
-
- [CRITICAL: Git Commits](#-critical-git-commits)
|
|
27
|
-
- [CRITICAL: Database Cleanup & Test Isolation](#-critical-database-cleanup--test-isolation)
|
|
28
|
-
- [User Authentication: signUp vs signIn](#user-authentication-signup-vs-signin)
|
|
29
|
-
- [Avoiding Test Interdependencies](#avoiding-test-interdependencies)
|
|
30
|
-
- [Async/Await Best Practices](#asyncawait-best-practices)
|
|
31
|
-
|
|
32
|
-
## The 7-Step Workflow
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
┌─────────────────────────────────────────────────────────┐
|
|
36
|
-
│ Step 1: Analyze Story & Clarify │
|
|
37
|
-
│ - Read requirements thoroughly │
|
|
38
|
-
│ - Check existing API structure │
|
|
39
|
-
│ - Identify contradictions │
|
|
40
|
-
│ - ASK DEVELOPER if anything unclear │
|
|
41
|
-
└─────────────────────────────────────────────────────────┘
|
|
42
|
-
↓
|
|
43
|
-
┌─────────────────────────────────────────────────────────┐
|
|
44
|
-
│ Step 2: Create Story Test │
|
|
45
|
-
│ - Location: tests/stories/feature-name.story.test.ts │
|
|
46
|
-
│ - Study existing test patterns │
|
|
47
|
-
│ - Write comprehensive test scenarios │
|
|
48
|
-
│ - Cover happy path, errors, edge cases │
|
|
49
|
-
└─────────────────────────────────────────────────────────┘
|
|
50
|
-
↓
|
|
51
|
-
┌─────────────────────────────────────────────────────────┐
|
|
52
|
-
│ Step 3: Run Tests & Analyze │
|
|
53
|
-
│ - npm test │
|
|
54
|
-
│ - Record failures and reasons │
|
|
55
|
-
│ - Decide: Test bug OR Missing implementation │
|
|
56
|
-
└─────────────────────────────────────────────────────────┘
|
|
57
|
-
↓
|
|
58
|
-
┌──────┴──────┐
|
|
59
|
-
│ │
|
|
60
|
-
┌──────────▼─┐ ┌───▼────────────┐
|
|
61
|
-
│ Step 3a: │ │ Step 4: │
|
|
62
|
-
│ Fix Test │ │ Implement Code │
|
|
63
|
-
│ Errors │ │ (Use nest- │
|
|
64
|
-
│ │ │ server- │
|
|
65
|
-
│ │ │ generator) │
|
|
66
|
-
└──────┬─────┘ └───┬────────────┘
|
|
67
|
-
│ │
|
|
68
|
-
└────────┬────────┘
|
|
69
|
-
↓
|
|
70
|
-
┌─────────────────────────────────────────────────────────┐
|
|
71
|
-
│ Step 5: Validate │
|
|
72
|
-
│ - Run ALL tests │
|
|
73
|
-
│ - All pass? → Go to Step 5a │
|
|
74
|
-
│ - Some fail? → Back to Step 3 │
|
|
75
|
-
└─────────────────────────────────────────────────────────┘
|
|
76
|
-
↓
|
|
77
|
-
┌─────────────────────────────────────────────────────────┐
|
|
78
|
-
│ Step 5a: Code Quality, Security & Refactoring Check │
|
|
79
|
-
│ - Check for code duplication │
|
|
80
|
-
│ - Extract common functionality │
|
|
81
|
-
│ - Consolidate similar code paths │
|
|
82
|
-
│ - Review for consistency │
|
|
83
|
-
│ - Check database indexes │
|
|
84
|
-
│ - 🔐 SECURITY REVIEW (CRITICAL) │
|
|
85
|
-
│ - Run tests after refactoring │
|
|
86
|
-
└─────────────────────────────────────────────────────────┘
|
|
87
|
-
↓
|
|
88
|
-
┌─────────────────────────────────────────────────────────┐
|
|
89
|
-
│ Step 5b: Final Validation │
|
|
90
|
-
│ - Run ALL tests one final time │
|
|
91
|
-
│ - Generate report → DONE! 🎉 │
|
|
92
|
-
└─────────────────────────────────────────────────────────┘
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Commands Cheatsheet
|
|
96
|
-
|
|
97
|
-
### Running Tests
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
# Run all tests
|
|
101
|
-
npm test
|
|
102
|
-
|
|
103
|
-
# Run specific story test
|
|
104
|
-
npm test -- tests/stories/feature-name.story.test.ts
|
|
105
|
-
|
|
106
|
-
# Run tests with coverage
|
|
107
|
-
npm run test:cov
|
|
108
|
-
|
|
109
|
-
# Run tests in watch mode
|
|
110
|
-
npm run test:watch
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Using nest-server-generator Skill
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
# Create module
|
|
117
|
-
lt server module ModuleName --no-interactive
|
|
118
|
-
|
|
119
|
-
# Create object
|
|
120
|
-
lt server object ObjectName --no-interactive
|
|
121
|
-
|
|
122
|
-
# Add property
|
|
123
|
-
lt server addProp ModuleName propertyName:type --no-interactive
|
|
124
|
-
|
|
125
|
-
# Examples:
|
|
126
|
-
lt server module Review --no-interactive
|
|
127
|
-
lt server addProp Review rating:number --no-interactive
|
|
128
|
-
lt server addProp Review comment:string? --no-interactive
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Test File Organization
|
|
132
|
-
|
|
133
|
-
### Structuring Tests with Subfolders
|
|
134
|
-
|
|
135
|
-
When many test files accumulate in `tests/stories/`, consider organizing them into subfolders for better clarity:
|
|
136
|
-
|
|
137
|
-
**✅ DO use subfolders when:**
|
|
138
|
-
- Multiple tests can be logically grouped (e.g., by feature, module, or domain)
|
|
139
|
-
- Each subfolder contains at least 3-5 related test files
|
|
140
|
-
- The grouping improves discoverability and navigation
|
|
141
|
-
|
|
142
|
-
**❌ DON'T use subfolders when:**
|
|
143
|
-
- Only 1-2 files would end up in each subfolder (defeats the purpose)
|
|
144
|
-
- The grouping is arbitrary or unclear
|
|
145
|
-
- Tests are already easy to find
|
|
146
|
-
|
|
147
|
-
**Example folder structure:**
|
|
148
|
-
|
|
149
|
-
```
|
|
150
|
-
tests/stories/
|
|
151
|
-
├── user-management/ # ✅ Good: 4 related tests
|
|
152
|
-
│ ├── user-registration.story.test.ts
|
|
153
|
-
│ ├── user-profile.story.test.ts
|
|
154
|
-
│ ├── user-roles.story.test.ts
|
|
155
|
-
│ └── user-deletion.story.test.ts
|
|
156
|
-
├── orders/ # ✅ Good: 3 related tests
|
|
157
|
-
│ ├── order-creation.story.test.ts
|
|
158
|
-
│ ├── order-fulfillment.story.test.ts
|
|
159
|
-
│ └── order-cancellation.story.test.ts
|
|
160
|
-
├── auth/ # ❌ Bad: Only 1 file, should stay in root
|
|
161
|
-
│ └── login.story.test.ts
|
|
162
|
-
└── simple-feature.story.test.ts # ✅ OK: Single file stays in root
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**Rule of thumb:** If you can't fill a subfolder with at least 3 thematically related test files, keep them in the root `tests/stories/` directory.
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## Test File Template
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
import {
|
|
173
|
-
ConfigService,
|
|
174
|
-
HttpExceptionLogFilter,
|
|
175
|
-
TestGraphQLType,
|
|
176
|
-
TestHelper,
|
|
177
|
-
} from '@lenne.tech/nest-server';
|
|
178
|
-
import { Test, TestingModule } from '@nestjs/testing';
|
|
179
|
-
import { PubSub } from 'graphql-subscriptions';
|
|
180
|
-
import { MongoClient, ObjectId } from 'mongodb';
|
|
181
|
-
|
|
182
|
-
import envConfig from '../../src/config.env';
|
|
183
|
-
import { RoleEnum } from '../../src/server/common/enums/role.enum';
|
|
184
|
-
import { imports, ServerModule } from '../../src/server/server.module';
|
|
185
|
-
|
|
186
|
-
// ⚠️ IMPORTANT: Do NOT import Services!
|
|
187
|
-
// Tests must ONLY use API endpoints via TestHelper.
|
|
188
|
-
// Services are accessed indirectly through Controllers/Resolvers.
|
|
189
|
-
|
|
190
|
-
describe('[Feature Name] Story', () => {
|
|
191
|
-
// Test environment properties
|
|
192
|
-
let app;
|
|
193
|
-
let testHelper: TestHelper;
|
|
194
|
-
|
|
195
|
-
// Database (only for setup/cleanup and setting roles/verified status)
|
|
196
|
-
let connection;
|
|
197
|
-
let db;
|
|
198
|
-
|
|
199
|
-
// Global test data
|
|
200
|
-
let gUserToken: string;
|
|
201
|
-
let gUserId: string;
|
|
202
|
-
|
|
203
|
-
// Track created entities for cleanup
|
|
204
|
-
let createdEntityIds: string[] = [];
|
|
205
|
-
|
|
206
|
-
beforeAll(async () => {
|
|
207
|
-
// Start server for testing
|
|
208
|
-
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
209
|
-
imports: [...imports, ServerModule],
|
|
210
|
-
providers: [
|
|
211
|
-
{
|
|
212
|
-
provide: 'PUB_SUB',
|
|
213
|
-
useValue: new PubSub(),
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
}).compile();
|
|
217
|
-
|
|
218
|
-
app = moduleFixture.createNestApplication();
|
|
219
|
-
app.useGlobalFilters(new HttpExceptionLogFilter());
|
|
220
|
-
app.setBaseViewsDir(envConfig.templates.path);
|
|
221
|
-
app.setViewEngine(envConfig.templates.engine);
|
|
222
|
-
await app.init();
|
|
223
|
-
|
|
224
|
-
testHelper = new TestHelper(app);
|
|
225
|
-
|
|
226
|
-
// Connection to database (ONLY for cleanup and setting roles/verified)
|
|
227
|
-
connection = await MongoClient.connect(envConfig.mongoose.uri);
|
|
228
|
-
db = await connection.db();
|
|
229
|
-
|
|
230
|
-
// 🚨 CRITICAL: Create test user with @test.com email
|
|
231
|
-
const password = Math.random().toString(36).substring(7);
|
|
232
|
-
// ✅ MUST end with @test.com for e2e.brevo.exclude filtering
|
|
233
|
-
// Use timestamp + random suffix for guaranteed uniqueness
|
|
234
|
-
const email = `test-${Date.now()}-${Math.random().toString(36).substring(2, 8)}@test.com`;
|
|
235
|
-
const signUp = await testHelper.graphQl({
|
|
236
|
-
arguments: {
|
|
237
|
-
input: {
|
|
238
|
-
email,
|
|
239
|
-
firstName: 'Test',
|
|
240
|
-
password,
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
fields: ['token', { user: ['id', 'email'] }],
|
|
244
|
-
name: 'signUp',
|
|
245
|
-
type: TestGraphQLType.MUTATION,
|
|
246
|
-
});
|
|
247
|
-
gUserId = signUp.user.id;
|
|
248
|
-
gUserToken = signUp.token;
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
afterAll(async () => {
|
|
252
|
-
// 🧹 CLEANUP: Delete all test data created during tests
|
|
253
|
-
try {
|
|
254
|
-
if (createdEntityIds.length > 0) {
|
|
255
|
-
await db.collection('entities').deleteMany({
|
|
256
|
-
_id: { $in: createdEntityIds.map(id => new ObjectId(id)) }
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Delete test user
|
|
261
|
-
if (gUserId) {
|
|
262
|
-
await db.collection('users').deleteOne({ _id: new ObjectId(gUserId) });
|
|
263
|
-
}
|
|
264
|
-
} catch (error) {
|
|
265
|
-
console.error('Cleanup failed:', error);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
await connection.close();
|
|
269
|
-
await app.close();
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
describe('Happy Path', () => {
|
|
273
|
-
it('should [expected behavior]', async () => {
|
|
274
|
-
// Arrange
|
|
275
|
-
// 🚨 IMPORTANT: Make data unique per test file to avoid conflicts
|
|
276
|
-
const data = {
|
|
277
|
-
email: `entity-feature-test-${Date.now()}-${Math.random().toString(36).substring(2, 8)}@test.com`, // ✅ @test.com + unique
|
|
278
|
-
name: `Entity-FeatureTest-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`, // ✅ Unique per test file
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
// Act - Using REST
|
|
282
|
-
const result = await testHelper.rest('/api/endpoint', {
|
|
283
|
-
method: 'POST',
|
|
284
|
-
payload: data,
|
|
285
|
-
token: gUserToken,
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// Assert
|
|
289
|
-
expect(result).toMatchObject({
|
|
290
|
-
// expected properties
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
// ✅ Track for cleanup (CRITICAL for parallel-safe tests)
|
|
294
|
-
createdEntityIds.push(result.id);
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
describe('Error Cases', () => {
|
|
299
|
-
it('should reject invalid input', async () => {
|
|
300
|
-
await testHelper.rest('/api/endpoint', {
|
|
301
|
-
method: 'POST',
|
|
302
|
-
payload: { /* invalid data */ },
|
|
303
|
-
statusCode: 400,
|
|
304
|
-
token: gUserToken,
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it('should require authentication', async () => {
|
|
309
|
-
await testHelper.rest('/api/endpoint', {
|
|
310
|
-
method: 'POST',
|
|
311
|
-
payload: { /* data */ },
|
|
312
|
-
statusCode: 401,
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
## Database Indexes with @UnifiedField
|
|
320
|
-
|
|
321
|
-
### When to Add Indexes
|
|
322
|
-
|
|
323
|
-
**🔍 ALWAYS define indexes in @UnifiedField decorator via mongoose option!**
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
// ✅ CORRECT: Index in decorator mongoose option
|
|
327
|
-
@UnifiedField({
|
|
328
|
-
description: 'User email',
|
|
329
|
-
mongoose: { index: true, unique: true, type: String }
|
|
330
|
-
})
|
|
331
|
-
email: string;
|
|
332
|
-
|
|
333
|
-
// ❌ WRONG: Separate schema index (hard to find)
|
|
334
|
-
UserSchema.index({ email: 1 }, { unique: true });
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### Common Index Patterns
|
|
338
|
-
|
|
339
|
-
**Single Field Index:**
|
|
340
|
-
```typescript
|
|
341
|
-
@UnifiedField({
|
|
342
|
-
description: 'Product category',
|
|
343
|
-
mongoose: { index: true, type: String } // For queries like: find({ category: 'electronics' })
|
|
344
|
-
})
|
|
345
|
-
category: string;
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
**Unique Index:**
|
|
349
|
-
```typescript
|
|
350
|
-
@UnifiedField({
|
|
351
|
-
description: 'Username',
|
|
352
|
-
mongoose: { index: true, unique: true, type: String } // Prevents duplicates
|
|
353
|
-
})
|
|
354
|
-
username: string;
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
**Foreign Key Index:**
|
|
358
|
-
```typescript
|
|
359
|
-
@UnifiedField({
|
|
360
|
-
description: 'User who created this',
|
|
361
|
-
mongoose: { index: true, type: String } // For JOIN/population operations
|
|
362
|
-
})
|
|
363
|
-
createdBy: string;
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
**Multiple Indexed Fields:**
|
|
367
|
-
```typescript
|
|
368
|
-
@UnifiedField({
|
|
369
|
-
description: 'Customer reference',
|
|
370
|
-
mongoose: { index: true, type: String } // Indexed individually
|
|
371
|
-
})
|
|
372
|
-
customerId: string;
|
|
373
|
-
|
|
374
|
-
@UnifiedField({
|
|
375
|
-
description: 'Order status',
|
|
376
|
-
mongoose: { index: true, type: String } // Indexed individually
|
|
377
|
-
})
|
|
378
|
-
status: string;
|
|
379
|
-
|
|
380
|
-
// Both indexed for flexible querying
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
**Text Search Index:**
|
|
384
|
-
```typescript
|
|
385
|
-
@UnifiedField({
|
|
386
|
-
description: 'Product name',
|
|
387
|
-
mongoose: { type: String, text: true } // For full-text search
|
|
388
|
-
})
|
|
389
|
-
name: string;
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Index Checklist
|
|
393
|
-
|
|
394
|
-
Before marking complete, verify:
|
|
395
|
-
|
|
396
|
-
- [ ] Fields used in `find()` queries have indexes
|
|
397
|
-
- [ ] Foreign keys (userId, productId, etc.) have indexes
|
|
398
|
-
- [ ] Unique fields (email, username) marked with `unique: true`
|
|
399
|
-
- [ ] Fields used in sorting have indexes
|
|
400
|
-
- [ ] Compound queries use compound indexes
|
|
401
|
-
- [ ] All indexes in @UnifiedField decorator (NOT separate schema)
|
|
402
|
-
|
|
403
|
-
## REST API Testing Patterns (using TestHelper)
|
|
404
|
-
|
|
405
|
-
**🔍 IMPORTANT: Before writing tests, read the TestHelper source file to understand all available features:**
|
|
406
|
-
|
|
407
|
-
```
|
|
408
|
-
node_modules/@lenne.tech/nest-server/src/test/test.helper.ts
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
This file documents all TestHelper capabilities including:
|
|
412
|
-
- File uploads via `attachments` option
|
|
413
|
-
- Debugging with `log` and `logError` options in `TestRestOptions`
|
|
414
|
-
- Custom headers, status code validation, and more
|
|
415
|
-
|
|
416
|
-
```typescript
|
|
417
|
-
// GET request
|
|
418
|
-
const result = await testHelper.rest('/api/resource/123', {
|
|
419
|
-
token: userToken,
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// GET request (public endpoint, no auth)
|
|
423
|
-
const result = await testHelper.rest('/api/public');
|
|
424
|
-
|
|
425
|
-
// POST request
|
|
426
|
-
const result = await testHelper.rest('/api/resource', {
|
|
427
|
-
method: 'POST',
|
|
428
|
-
payload: data,
|
|
429
|
-
token: userToken,
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// PUT request
|
|
433
|
-
const result = await testHelper.rest('/api/resource/123', {
|
|
434
|
-
method: 'PUT',
|
|
435
|
-
payload: updates,
|
|
436
|
-
token: userToken,
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
// DELETE request
|
|
440
|
-
const result = await testHelper.rest('/api/resource/123', {
|
|
441
|
-
method: 'DELETE',
|
|
442
|
-
token: userToken,
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// Expect specific status code
|
|
446
|
-
await testHelper.rest('/api/resource', {
|
|
447
|
-
method: 'POST',
|
|
448
|
-
payload: invalidData,
|
|
449
|
-
statusCode: 400,
|
|
450
|
-
token: userToken,
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// With custom headers
|
|
454
|
-
const result = await testHelper.rest('/api/resource', {
|
|
455
|
-
headers: {
|
|
456
|
-
'Content-Type': 'application/json',
|
|
457
|
-
'X-Custom-Header': 'value',
|
|
458
|
-
},
|
|
459
|
-
token: userToken,
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// File upload via attachments
|
|
463
|
-
const result = await testHelper.rest('/api/upload', {
|
|
464
|
-
method: 'POST',
|
|
465
|
-
attachments: [
|
|
466
|
-
{ name: 'file', path: '/path/to/file.pdf' },
|
|
467
|
-
{ name: 'image', path: '/path/to/image.png' },
|
|
468
|
-
],
|
|
469
|
-
token: userToken,
|
|
470
|
-
});
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
## GraphQL Testing Patterns (using TestHelper)
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
|
-
import { TestGraphQLType, TestHelper } from '@lenne.tech/nest-server';
|
|
477
|
-
|
|
478
|
-
// GraphQL Query
|
|
479
|
-
const user = await testHelper.graphQl({
|
|
480
|
-
arguments: {
|
|
481
|
-
id: userId,
|
|
482
|
-
},
|
|
483
|
-
fields: ['id', 'email', 'firstName', { profile: ['bio', 'avatar'] }],
|
|
484
|
-
name: 'getUser',
|
|
485
|
-
type: TestGraphQLType.QUERY,
|
|
486
|
-
}, { token: userToken });
|
|
487
|
-
|
|
488
|
-
expect(user).toMatchObject({
|
|
489
|
-
id: userId,
|
|
490
|
-
email: 'test@example.com',
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
// GraphQL Mutation
|
|
494
|
-
const result = await testHelper.graphQl({
|
|
495
|
-
arguments: {
|
|
496
|
-
input: {
|
|
497
|
-
firstName: 'Updated',
|
|
498
|
-
lastName: 'Name',
|
|
499
|
-
},
|
|
500
|
-
},
|
|
501
|
-
fields: ['id', 'firstName', 'lastName'],
|
|
502
|
-
name: 'updateUser',
|
|
503
|
-
type: TestGraphQLType.MUTATION,
|
|
504
|
-
}, { token: userToken });
|
|
505
|
-
|
|
506
|
-
// GraphQL Mutation with nested objects
|
|
507
|
-
const created = await testHelper.graphQl({
|
|
508
|
-
arguments: {
|
|
509
|
-
input: {
|
|
510
|
-
title: 'New Post',
|
|
511
|
-
content: 'Post content',
|
|
512
|
-
tags: ['tag1', 'tag2'],
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
fields: ['id', 'title', { author: ['id', 'email'] }, 'tags'],
|
|
516
|
-
name: 'createPost',
|
|
517
|
-
type: TestGraphQLType.MUTATION,
|
|
518
|
-
}, { token: userToken });
|
|
519
|
-
|
|
520
|
-
// GraphQL Query without auth (public)
|
|
521
|
-
const publicData = await testHelper.graphQl({
|
|
522
|
-
arguments: {},
|
|
523
|
-
fields: ['version', 'status'],
|
|
524
|
-
name: 'getPublicInfo',
|
|
525
|
-
type: TestGraphQLType.QUERY,
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
// Expecting errors (e.g., unauthorized)
|
|
529
|
-
const result = await testHelper.graphQl({
|
|
530
|
-
arguments: { id: otherUserId },
|
|
531
|
-
fields: ['id', 'email'],
|
|
532
|
-
name: 'getUser',
|
|
533
|
-
type: TestGraphQLType.QUERY,
|
|
534
|
-
}, { token: userToken, statusCode: 200 });
|
|
535
|
-
|
|
536
|
-
expect(result.errors).toBeDefined();
|
|
537
|
-
expect(result.errors[0].message).toContain('Forbidden');
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
## Common Test Assertions
|
|
541
|
-
|
|
542
|
-
```typescript
|
|
543
|
-
// Object matching
|
|
544
|
-
expect(result).toMatchObject({ key: value });
|
|
545
|
-
|
|
546
|
-
// Exact equality
|
|
547
|
-
expect(result).toEqual(expected);
|
|
548
|
-
|
|
549
|
-
// Array checks
|
|
550
|
-
expect(array).toHaveLength(3);
|
|
551
|
-
expect(array).toContain(item);
|
|
552
|
-
expect(array).toBeInstanceOf(Array);
|
|
553
|
-
|
|
554
|
-
// Existence checks
|
|
555
|
-
expect(value).toBeDefined();
|
|
556
|
-
expect(value).toBeUndefined();
|
|
557
|
-
expect(value).toBeNull();
|
|
558
|
-
expect(value).toBeTruthy();
|
|
559
|
-
expect(value).toBeFalsy();
|
|
560
|
-
|
|
561
|
-
// Number comparisons
|
|
562
|
-
expect(number).toBeGreaterThan(5);
|
|
563
|
-
expect(number).toBeLessThan(10);
|
|
564
|
-
expect(number).toBeCloseTo(3.14, 2);
|
|
565
|
-
|
|
566
|
-
// String matching
|
|
567
|
-
expect(string).toContain('substring');
|
|
568
|
-
expect(string).toMatch(/regex/);
|
|
569
|
-
|
|
570
|
-
// Error checking
|
|
571
|
-
expect(() => fn()).toThrow();
|
|
572
|
-
expect(() => fn()).toThrow('error message');
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
## ObjectId Conversion Utilities
|
|
576
|
-
|
|
577
|
-
**Use the utility functions from @lenne.tech/nest-server for ObjectId conversions:**
|
|
578
|
-
|
|
579
|
-
```typescript
|
|
580
|
-
import { getStringIds, getObjectIds } from '@lenne.tech/nest-server';
|
|
581
|
-
|
|
582
|
-
// Convert ObjectIds to strings (works with arrays OR single values)
|
|
583
|
-
const stringIds = getStringIds(objectIds); // ObjectId[] → string[]
|
|
584
|
-
const stringId = getStringIds(singleObjectId); // ObjectId → string
|
|
585
|
-
|
|
586
|
-
// Convert strings to ObjectIds (works with arrays OR single values)
|
|
587
|
-
const objectIds = getObjectIds(stringIds); // string[] → ObjectId[]
|
|
588
|
-
const objectId = getObjectIds(singleStringId); // string → ObjectId
|
|
589
|
-
|
|
590
|
-
// Pass objects directly - the functions extract IDs automatically!
|
|
591
|
-
const stringIds = getStringIds(documents); // Extracts _id from each document
|
|
592
|
-
const objectIds = getObjectIds(documents); // Extracts _id/id and converts
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
**✅ ALWAYS use these utilities instead of manual conversion:**
|
|
596
|
-
|
|
597
|
-
```typescript
|
|
598
|
-
// ✅ CORRECT: Use utility functions
|
|
599
|
-
import { getStringIds, getObjectIds } from '@lenne.tech/nest-server';
|
|
600
|
-
|
|
601
|
-
// For arrays of objects (no mapping needed - IDs are extracted automatically!)
|
|
602
|
-
const stringIds = getStringIds(documents);
|
|
603
|
-
const objectIds = getObjectIds(users);
|
|
604
|
-
|
|
605
|
-
// For single values (no array needed!)
|
|
606
|
-
const objectId = getObjectIds(userId);
|
|
607
|
-
const stringId = getStringIds(document);
|
|
608
|
-
|
|
609
|
-
// ❌ WRONG: Manual conversion
|
|
610
|
-
const stringIds = documents.map(d => d._id.toString());
|
|
611
|
-
const objectIds = inputIds.map(id => new ObjectId(id));
|
|
612
|
-
const objectId = new ObjectId(userId);
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
**Why use these utilities:**
|
|
616
|
-
- Consistent behavior across the codebase
|
|
617
|
-
- Works with both arrays and single values
|
|
618
|
-
- Extracts IDs from objects automatically (no `.map()` needed)
|
|
619
|
-
- Handles edge cases (null, undefined, invalid IDs)
|
|
620
|
-
- Type-safe conversions
|
|
621
|
-
- Easier to maintain and test
|
|
622
|
-
|
|
623
|
-
## Security Testing Checklist
|
|
624
|
-
|
|
625
|
-
```typescript
|
|
626
|
-
// ✅ Create users with correct roles using TestHelper
|
|
627
|
-
const userSignUp = await testHelper.graphQl({
|
|
628
|
-
arguments: {
|
|
629
|
-
input: {
|
|
630
|
-
email: 'user@test.com',
|
|
631
|
-
password: 'password123',
|
|
632
|
-
firstName: 'Test',
|
|
633
|
-
},
|
|
634
|
-
},
|
|
635
|
-
fields: ['token', { user: ['id'] }],
|
|
636
|
-
name: 'signUp',
|
|
637
|
-
type: TestGraphQLType.MUTATION,
|
|
638
|
-
});
|
|
639
|
-
const userToken = userSignUp.token;
|
|
640
|
-
|
|
641
|
-
// ✅ Test with correct role
|
|
642
|
-
await testHelper.rest('/api/resource', {
|
|
643
|
-
token: userToken,
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
// ✅ Test without authentication
|
|
647
|
-
await testHelper.rest('/api/resource', {
|
|
648
|
-
statusCode: 401,
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
// ✅ Test with insufficient permissions
|
|
652
|
-
await testHelper.rest('/api/admin/resource', {
|
|
653
|
-
statusCode: 403,
|
|
654
|
-
token: userToken, // Normal user trying admin endpoint
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
// ✅ Test access to own resources only
|
|
658
|
-
await testHelper.rest(`/api/users/${userSignUp.user.id}/profile`, {
|
|
659
|
-
method: 'PUT',
|
|
660
|
-
payload: { firstName: 'Updated' },
|
|
661
|
-
token: userToken,
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
await testHelper.rest(`/api/users/${otherUserId}/profile`, {
|
|
665
|
-
method: 'PUT',
|
|
666
|
-
payload: { firstName: 'Hacker' },
|
|
667
|
-
statusCode: 403,
|
|
668
|
-
token: userToken,
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
// ❌ NEVER do this
|
|
672
|
-
// Don't remove @Restricted decorators
|
|
673
|
-
// Don't change @Roles to more permissive
|
|
674
|
-
// Don't disable security checks
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
## When to Ask Developer
|
|
678
|
-
|
|
679
|
-
```
|
|
680
|
-
❓ ASK when:
|
|
681
|
-
- Story has contradictions or ambiguities
|
|
682
|
-
- Need to change security decorators
|
|
683
|
-
- Need to add new npm package
|
|
684
|
-
- Multiple valid architectural approaches
|
|
685
|
-
- Tests keep failing for unclear reasons
|
|
686
|
-
|
|
687
|
-
✅ DON'T ASK when:
|
|
688
|
-
- Creating test files
|
|
689
|
-
- Running tests
|
|
690
|
-
- Analyzing failures
|
|
691
|
-
- Implementing obvious features
|
|
692
|
-
- Using nest-server-generator
|
|
693
|
-
```
|
|
694
|
-
|
|
695
|
-
## Debugging Failed Tests
|
|
696
|
-
|
|
697
|
-
When tests fail, use these debugging tools to analyze the issue:
|
|
698
|
-
|
|
699
|
-
### 1. TestHelper Logging Options
|
|
700
|
-
|
|
701
|
-
```typescript
|
|
702
|
-
// Enable detailed request/response logging
|
|
703
|
-
const result = await testHelper.graphQl({
|
|
704
|
-
arguments: { id: userId },
|
|
705
|
-
fields: ['id', 'email'],
|
|
706
|
-
name: 'getUser',
|
|
707
|
-
type: TestGraphQLType.QUERY,
|
|
708
|
-
}, {
|
|
709
|
-
token: userToken,
|
|
710
|
-
log: true, // Logs request details
|
|
711
|
-
logError: true, // Logs errors when status >= 400
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
// For REST requests
|
|
715
|
-
const result = await testHelper.rest('/api/endpoint', {
|
|
716
|
-
method: 'POST',
|
|
717
|
-
payload: data,
|
|
718
|
-
token: userToken,
|
|
719
|
-
log: true,
|
|
720
|
-
logError: true,
|
|
721
|
-
});
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
### 2. Server Exception Logging
|
|
725
|
-
|
|
726
|
-
Enable in `src/config.env.ts`:
|
|
727
|
-
|
|
728
|
-
```typescript
|
|
729
|
-
export default {
|
|
730
|
-
// ... other config
|
|
731
|
-
logExceptions: true, // Log all exceptions with stack traces
|
|
732
|
-
// ...
|
|
733
|
-
};
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
### 3. Validation Debug Logging
|
|
737
|
-
|
|
738
|
-
Enable validation debugging via environment variable:
|
|
739
|
-
|
|
740
|
-
```bash
|
|
741
|
-
# In your terminal or test script
|
|
742
|
-
DEBUG_VALIDATION=true npm test
|
|
743
|
-
|
|
744
|
-
# Or in your test file
|
|
745
|
-
process.env.DEBUG_VALIDATION = 'true';
|
|
746
|
-
```
|
|
747
|
-
|
|
748
|
-
This activates console.debug statements in MapAndValidatePipe (automatically activated via CoreModule - see `node_modules/@lenne.tech/nest-server/src/core/common/pipes/map-and-validate.pipe.ts`) to show detailed validation errors.
|
|
749
|
-
|
|
750
|
-
### 4. Combined Debugging Setup
|
|
751
|
-
|
|
752
|
-
For comprehensive debugging, combine all three:
|
|
753
|
-
|
|
754
|
-
```typescript
|
|
755
|
-
// In your test file beforeAll
|
|
756
|
-
process.env.DEBUG_VALIDATION = 'true';
|
|
757
|
-
|
|
758
|
-
// In src/config.env.ts
|
|
759
|
-
export default {
|
|
760
|
-
logExceptions: true,
|
|
761
|
-
// ...
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
// In your tests
|
|
765
|
-
const result = await testHelper.graphQl({
|
|
766
|
-
// ... your test
|
|
767
|
-
}, {
|
|
768
|
-
log: true,
|
|
769
|
-
logError: true,
|
|
770
|
-
});
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
## Decision Tree: Test Failure Analysis
|
|
774
|
-
|
|
775
|
-
```
|
|
776
|
-
Test fails
|
|
777
|
-
│
|
|
778
|
-
├─► Missing implementation?
|
|
779
|
-
│ └─► Go to Step 4 (Implement)
|
|
780
|
-
│
|
|
781
|
-
├─► Test has bugs/errors?
|
|
782
|
-
│ └─► Go to Step 3a (Fix test)
|
|
783
|
-
│
|
|
784
|
-
├─► Security blocking correctly?
|
|
785
|
-
│ └─► Fix test to use proper auth
|
|
786
|
-
│
|
|
787
|
-
├─► Unclear error message?
|
|
788
|
-
│ └─► Enable debugging (log, logError, logExceptions, DEBUG_VALIDATION)
|
|
789
|
-
│
|
|
790
|
-
└─► Still unclear why failing?
|
|
791
|
-
└─► Ask developer
|
|
792
|
-
```
|
|
793
|
-
|
|
794
|
-
## Code Quality, Security & Refactoring Check
|
|
795
|
-
|
|
796
|
-
### Quick Review Guide
|
|
797
|
-
|
|
798
|
-
**Before marking complete, check for:**
|
|
799
|
-
|
|
800
|
-
1. **Code Duplication:**
|
|
801
|
-
- Repeated validation logic → Extract to private method
|
|
802
|
-
- Similar calculations in multiple places → Create helper function
|
|
803
|
-
- Duplicated query patterns → Consolidate into flexible method
|
|
804
|
-
|
|
805
|
-
2. **Common Functionality:**
|
|
806
|
-
- Extract repeated data transformations
|
|
807
|
-
- Create shared validation helpers
|
|
808
|
-
- Consolidate similar query builders
|
|
809
|
-
|
|
810
|
-
2a. **🔐 Guards in Controllers:**
|
|
811
|
-
- DO NOT add `@UseGuards(AuthGuard(AuthGuardStrategy.JWT))` manually
|
|
812
|
-
- `@Roles()` decorator automatically activates JWT authentication
|
|
813
|
-
- `@Restricted()` decorator also activates guards automatically
|
|
814
|
-
- Manual guards are redundant and create duplicates
|
|
815
|
-
```typescript
|
|
816
|
-
// ✅ CORRECT
|
|
817
|
-
@Roles(RoleEnum.ADMIN)
|
|
818
|
-
@Get()
|
|
819
|
-
async findAll() { ... }
|
|
820
|
-
|
|
821
|
-
// ❌ WRONG: Redundant guard
|
|
822
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
823
|
-
@Roles(RoleEnum.ADMIN)
|
|
824
|
-
@Get()
|
|
825
|
-
async findAll() { ... }
|
|
826
|
-
```
|
|
827
|
-
|
|
828
|
-
3. **Database Indexes:**
|
|
829
|
-
- Fields used in queries → Add `mongoose: { index: true, type: String }` to @UnifiedField
|
|
830
|
-
- Foreign keys → Add index via mongoose option
|
|
831
|
-
- Unique fields → Add `mongoose: { index: true, unique: true, type: String }`
|
|
832
|
-
- Multiple query fields → Index each individually
|
|
833
|
-
|
|
834
|
-
4. **🔐 Security Review (CRITICAL):**
|
|
835
|
-
- @Restricted/@Roles decorators NOT removed or weakened
|
|
836
|
-
- Ownership checks in place for user data
|
|
837
|
-
- All inputs validated with DTOs
|
|
838
|
-
- Sensitive fields marked with `hideField: true`
|
|
839
|
-
- No injection vulnerabilities
|
|
840
|
-
- Error messages don't leak sensitive data
|
|
841
|
-
- Authorization tests pass
|
|
842
|
-
|
|
843
|
-
5. **Refactoring Decision:**
|
|
844
|
-
```
|
|
845
|
-
Used in 2+ places? → Extract to private method
|
|
846
|
-
Used across services? → Consider utility class
|
|
847
|
-
Only 1 usage? → Leave as-is (don't over-engineer)
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
6. **After Refactoring & Security Review:**
|
|
851
|
-
```bash
|
|
852
|
-
npm test # MUST still pass!
|
|
853
|
-
```
|
|
854
|
-
|
|
855
|
-
### Code Quality Checklist
|
|
856
|
-
|
|
857
|
-
Before marking complete:
|
|
858
|
-
|
|
859
|
-
- [ ] All tests passing
|
|
860
|
-
- [ ] **No obvious code duplication**
|
|
861
|
-
- [ ] **Common functionality extracted to helpers**
|
|
862
|
-
- [ ] **Consistent patterns throughout**
|
|
863
|
-
- [ ] **Database indexes added to @UnifiedField decorators**
|
|
864
|
-
- [ ] **Indexes match query patterns in services**
|
|
865
|
-
- [ ] Test coverage adequate (80%+)
|
|
866
|
-
- [ ] Code follows existing patterns
|
|
867
|
-
- [ ] No unnecessary dependencies added
|
|
868
|
-
- [ ] Proper error handling
|
|
869
|
-
- [ ] Input validation implemented
|
|
870
|
-
- [ ] Documentation/comments where needed
|
|
871
|
-
- [ ] **Tests still pass after refactoring**
|
|
872
|
-
|
|
873
|
-
**🔐 Security Checklist:**
|
|
874
|
-
|
|
875
|
-
- [ ] **@Restricted/@Roles decorators NOT removed or weakened**
|
|
876
|
-
- [ ] **Ownership checks in place (users can only access own data)**
|
|
877
|
-
- [ ] **All inputs validated with proper DTOs**
|
|
878
|
-
- [ ] **Sensitive fields marked with hideField: true**
|
|
879
|
-
- [ ] **No SQL/NoSQL injection vulnerabilities**
|
|
880
|
-
- [ ] **Error messages don't expose sensitive data**
|
|
881
|
-
- [ ] **checkSecurity methods implemented in models**
|
|
882
|
-
- [ ] **Authorization tests pass**
|
|
883
|
-
- [ ] **No hardcoded secrets or credentials**
|
|
884
|
-
|
|
885
|
-
## Final Report Template
|
|
886
|
-
|
|
887
|
-
```markdown
|
|
888
|
-
# Story Implementation Complete ✅
|
|
889
|
-
|
|
890
|
-
## Story: [Name]
|
|
891
|
-
|
|
892
|
-
### Tests Created
|
|
893
|
-
- Location: tests/stories/[filename].story.test.ts
|
|
894
|
-
- Test cases: X scenarios
|
|
895
|
-
- Coverage: X%
|
|
896
|
-
|
|
897
|
-
### Implementation Summary
|
|
898
|
-
- Modules: [list]
|
|
899
|
-
- Objects: [list]
|
|
900
|
-
- Properties: [list]
|
|
901
|
-
- Other: [list]
|
|
902
|
-
|
|
903
|
-
### Test Results
|
|
904
|
-
✅ All X tests passing
|
|
905
|
-
|
|
906
|
-
### Code Quality
|
|
907
|
-
- Patterns followed: ✅
|
|
908
|
-
- Security preserved: ✅
|
|
909
|
-
- Dependencies: None added ✅
|
|
910
|
-
- Code duplication checked: ✅
|
|
911
|
-
- Database indexes added: ✅
|
|
912
|
-
- Refactoring performed: [Yes/No]
|
|
913
|
-
|
|
914
|
-
### Security Review
|
|
915
|
-
- Authentication/Authorization: ✅
|
|
916
|
-
- Input validation: ✅
|
|
917
|
-
- Data exposure prevented: ✅
|
|
918
|
-
- Ownership checks: ✅
|
|
919
|
-
- Injection prevention: ✅
|
|
920
|
-
- Authorization tests pass: ✅
|
|
921
|
-
|
|
922
|
-
### Refactoring (if performed)
|
|
923
|
-
- Extracted helper functions: [list]
|
|
924
|
-
- Consolidated code paths: [describe]
|
|
925
|
-
- Removed duplication: [describe]
|
|
926
|
-
- Tests still passing: ✅
|
|
927
|
-
|
|
928
|
-
### Files Modified
|
|
929
|
-
1. path/to/file.ts - description
|
|
930
|
-
2. path/to/file.ts - description
|
|
931
|
-
```
|
|
932
|
-
|
|
933
|
-
## 🔄 Handling Existing Tests
|
|
934
|
-
|
|
935
|
-
**When your changes break existing tests:**
|
|
936
|
-
|
|
937
|
-
### Decision Tree
|
|
938
|
-
|
|
939
|
-
```
|
|
940
|
-
Existing test fails
|
|
941
|
-
├─► Intentional breaking change? (e.g., added required field)
|
|
942
|
-
│ └─► ✅ Update test to match new behavior
|
|
943
|
-
│
|
|
944
|
-
└─► Unclear/unintended side effect?
|
|
945
|
-
├─► 🔍 Use git to investigate:
|
|
946
|
-
│ - git show HEAD:path/to/file.ts
|
|
947
|
-
│ - git diff HEAD path/to/file.ts
|
|
948
|
-
│
|
|
949
|
-
└─► ⚠️ Fix code to satisfy BOTH old AND new tests
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
### Git Analysis (ALLOWED)
|
|
953
|
-
|
|
954
|
-
```bash
|
|
955
|
-
# View old version of file
|
|
956
|
-
git show HEAD:src/server/modules/user/user.service.ts
|
|
957
|
-
|
|
958
|
-
# See what changed
|
|
959
|
-
git diff HEAD src/server/modules/user/user.service.ts
|
|
960
|
-
|
|
961
|
-
# View commit history
|
|
962
|
-
git log -p --follow path/to/file.ts
|
|
963
|
-
```
|
|
964
|
-
|
|
965
|
-
### Guidelines
|
|
966
|
-
|
|
967
|
-
**✅ Update tests when:**
|
|
968
|
-
- Intentional API contract change
|
|
969
|
-
- Removed deprecated functionality
|
|
970
|
-
- Renamed fields/methods
|
|
971
|
-
- Documented in story requirements
|
|
972
|
-
|
|
973
|
-
**❌ Don't update tests when:**
|
|
974
|
-
- Unclear why they're failing
|
|
975
|
-
- Unrelated to your story
|
|
976
|
-
- Multiple unrelated tests breaking
|
|
977
|
-
- Testing important existing functionality
|
|
978
|
-
|
|
979
|
-
**🚩 Red flags (investigate, don't update):**
|
|
980
|
-
- Tests in different modules failing
|
|
981
|
-
- Security/auth tests failing
|
|
982
|
-
- 3+ unrelated tests failing
|
|
983
|
-
|
|
984
|
-
**Remember:**
|
|
985
|
-
- Existing tests = documentation of expected behavior
|
|
986
|
-
- Use git freely for investigation (NOT commits!)
|
|
987
|
-
- When in doubt, preserve backward compatibility
|
|
988
|
-
|
|
989
|
-
## ⛔ CRITICAL: Git Commits
|
|
990
|
-
|
|
991
|
-
**🚨 NEVER create git commits unless explicitly requested by the developer.**
|
|
992
|
-
|
|
993
|
-
- ❌ DO NOT use `git add`, `git commit`, or `git push` automatically
|
|
994
|
-
- ❌ DO NOT commit changes when tests pass
|
|
995
|
-
- ❌ DO NOT assume developer wants changes committed
|
|
996
|
-
- ✅ ONLY commit when developer explicitly asks: "commit these changes"
|
|
997
|
-
|
|
998
|
-
**Why:** Developers may want to review changes, commit in specific chunks, or have custom workflows.
|
|
999
|
-
|
|
1000
|
-
**Your job:**
|
|
1001
|
-
- ✅ Create/modify files
|
|
1002
|
-
- ✅ Run tests
|
|
1003
|
-
- ✅ Use git for analysis (git show, git diff, git log)
|
|
1004
|
-
- ✅ Provide comprehensive report
|
|
1005
|
-
- ❌ Never commit to git (unless explicitly requested)
|
|
1006
|
-
|
|
1007
|
-
## 🚨 CRITICAL: Database Cleanup & Test Isolation
|
|
1008
|
-
|
|
1009
|
-
**ALWAYS implement comprehensive cleanup in your story tests!**
|
|
1010
|
-
|
|
1011
|
-
Tests run in parallel, so improper test data management causes:
|
|
1012
|
-
- Conflicts between parallel tests (duplicate keys, race conditions)
|
|
1013
|
-
- False positives/negatives in tests
|
|
1014
|
-
- Flaky tests that pass/fail randomly
|
|
1015
|
-
- Contaminated test database
|
|
1016
|
-
- Hard-to-debug test failures
|
|
1017
|
-
|
|
1018
|
-
**📋 GOLDEN RULES for Parallel-Safe Test Data:**
|
|
1019
|
-
|
|
1020
|
-
1. **Email Addresses Must End with @test.com**
|
|
1021
|
-
- Configuration in `src/config.env.ts` uses `e2e.brevo.exclude` to filter @test.com
|
|
1022
|
-
- External services (email, etc.) will exclude these addresses
|
|
1023
|
-
- Use timestamp + random suffix for guaranteed uniqueness
|
|
1024
|
-
```typescript
|
|
1025
|
-
// ✅ CORRECT: Timestamp + 6-char random suffix
|
|
1026
|
-
const email = `user-${Date.now()}-${Math.random().toString(36).substring(2, 8)}@test.com`;
|
|
1027
|
-
|
|
1028
|
-
// ⚠️ LESS SAFE: Only timestamp (collision risk in same millisecond)
|
|
1029
|
-
const email = `user-${Date.now()}@test.com`;
|
|
1030
|
-
|
|
1031
|
-
// ❌ WRONG: No @test.com suffix
|
|
1032
|
-
const email = 'testuser@example.com';
|
|
1033
|
-
```
|
|
1034
|
-
|
|
1035
|
-
2. **NEVER Reuse Same Data Across Test Files**
|
|
1036
|
-
- Tests run in parallel = same data causes conflicts
|
|
1037
|
-
- Make ALL data unique (emails, usernames, product names, etc.)
|
|
1038
|
-
- Always use timestamp + random suffix
|
|
1039
|
-
```typescript
|
|
1040
|
-
// ✅ CORRECT: Unique per test file with timestamp + random suffix
|
|
1041
|
-
const email = `admin-product-test-${Date.now()}-${Math.random().toString(36).substring(2, 8)}@test.com`;
|
|
1042
|
-
|
|
1043
|
-
// ⚠️ LESS SAFE: Only timestamp
|
|
1044
|
-
const email = `admin-product-test-${Date.now()}@test.com`;
|
|
1045
|
-
|
|
1046
|
-
// ❌ WRONG: Reused across multiple test files
|
|
1047
|
-
const email = 'admin@test.com';
|
|
1048
|
-
```
|
|
1049
|
-
|
|
1050
|
-
3. **ONLY Delete Entities Created in This Test File**
|
|
1051
|
-
- Track created IDs explicitly
|
|
1052
|
-
- Delete ONLY tracked entities, not by pattern
|
|
1053
|
-
```typescript
|
|
1054
|
-
// ✅ CORRECT: Only delete what we created
|
|
1055
|
-
await db.collection('users').deleteMany({
|
|
1056
|
-
_id: { $in: createdUserIds.map(id => new ObjectId(id)) }
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
|
-
// ❌ WRONG: Deletes ALL test users (breaks parallel tests)
|
|
1060
|
-
await db.collection('users').deleteMany({ email: /@test\.com$/ });
|
|
1061
|
-
```
|
|
1062
|
-
|
|
1063
|
-
4. **ALL Created Entities Must Be Cleaned Up**
|
|
1064
|
-
- Track every created entity ID immediately
|
|
1065
|
-
- Clean up in correct order (children before parents)
|
|
1066
|
-
- Prevents side effects on future test runs
|
|
1067
|
-
|
|
1068
|
-
5. **NEVER Use Fixed Port Numbers**
|
|
1069
|
-
- NestJS assigns random ports automatically for parallel execution
|
|
1070
|
-
- Always use TestHelper - it abstracts port handling
|
|
1071
|
-
```typescript
|
|
1072
|
-
// ✅ CORRECT: No port specified, TestHelper handles it
|
|
1073
|
-
await app.init();
|
|
1074
|
-
const result = await testHelper.rest('/api/users', { ... });
|
|
1075
|
-
|
|
1076
|
-
// ❌ WRONG: Fixed port causes conflicts
|
|
1077
|
-
await app.listen(3000);
|
|
1078
|
-
const response = await fetch('http://localhost:3000/api/users');
|
|
1079
|
-
```
|
|
1080
|
-
|
|
1081
|
-
### Between Test Suites - RECOMMENDED APPROACH
|
|
1082
|
-
|
|
1083
|
-
**Track all created entities and delete them explicitly:**
|
|
1084
|
-
|
|
1085
|
-
```typescript
|
|
1086
|
-
describe('Feature Story', () => {
|
|
1087
|
-
// Track created entities
|
|
1088
|
-
let createdUserIds: string[] = [];
|
|
1089
|
-
let createdProductIds: string[] = [];
|
|
1090
|
-
let createdOrderIds: string[] = [];
|
|
1091
|
-
|
|
1092
|
-
// In your tests, track IDs immediately after creation
|
|
1093
|
-
it('should create product', async () => {
|
|
1094
|
-
// 🚨 IMPORTANT: Use unique data per test file + @test.com for emails
|
|
1095
|
-
const productData = {
|
|
1096
|
-
name: `Product-FeatureStory-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`, // ✅ Unique per test file
|
|
1097
|
-
ownerEmail: `owner-feature-${Date.now()}-${Math.random().toString(36).substring(2, 8)}@test.com`, // ✅ @test.com + unique
|
|
1098
|
-
};
|
|
1099
|
-
|
|
1100
|
-
const product = await testHelper.rest('/api/products', {
|
|
1101
|
-
method: 'POST',
|
|
1102
|
-
payload: productData,
|
|
1103
|
-
token: adminToken,
|
|
1104
|
-
});
|
|
1105
|
-
|
|
1106
|
-
// ✅ Track for cleanup (ONLY delete what we created)
|
|
1107
|
-
createdProductIds.push(product.id);
|
|
1108
|
-
});
|
|
1109
|
-
|
|
1110
|
-
afterAll(async () => {
|
|
1111
|
-
// 🧹 CLEANUP: Delete ALL test data created during tests
|
|
1112
|
-
try {
|
|
1113
|
-
// Delete in correct order (child entities first)
|
|
1114
|
-
if (createdOrderIds.length > 0) {
|
|
1115
|
-
await db.collection('orders').deleteMany({
|
|
1116
|
-
_id: { $in: createdOrderIds.map(id => new ObjectId(id)) }
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
if (createdProductIds.length > 0) {
|
|
1121
|
-
await db.collection('products').deleteMany({
|
|
1122
|
-
_id: { $in: createdProductIds.map(id => new ObjectId(id)) }
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
if (createdUserIds.length > 0) {
|
|
1127
|
-
await db.collection('users').deleteMany({
|
|
1128
|
-
_id: { $in: createdUserIds.map(id => new ObjectId(id)) }
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
|
-
} catch (error) {
|
|
1132
|
-
console.error('Cleanup failed:', error);
|
|
1133
|
-
// Don't throw - cleanup failures shouldn't fail the test suite
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
await connection.close();
|
|
1137
|
-
await app.close();
|
|
1138
|
-
});
|
|
1139
|
-
});
|
|
1140
|
-
```
|
|
1141
|
-
|
|
1142
|
-
### Alternative: Pattern-Based Cleanup (AVOID - Not Parallel-Safe!)
|
|
1143
|
-
|
|
1144
|
-
**❌ DO NOT USE pattern-based cleanup - it breaks parallel test execution!**
|
|
1145
|
-
|
|
1146
|
-
```typescript
|
|
1147
|
-
// ❌ WRONG: Deletes ALL test users, even from parallel tests!
|
|
1148
|
-
afterAll(async () => {
|
|
1149
|
-
await db.collection('users').deleteMany({ email: /@test\.com$/ });
|
|
1150
|
-
await db.collection('products').deleteMany({ name: /^Test/ });
|
|
1151
|
-
|
|
1152
|
-
await connection.close();
|
|
1153
|
-
await app.close();
|
|
1154
|
-
});
|
|
1155
|
-
```
|
|
1156
|
-
|
|
1157
|
-
**⚠️ Why This is Dangerous:**
|
|
1158
|
-
- **Breaks parallel tests:** Deletes entities from other tests that are still running
|
|
1159
|
-
- **Race conditions:** Unpredictable failures when tests run simultaneously
|
|
1160
|
-
- **Flaky tests:** Tests pass/fail randomly depending on execution order
|
|
1161
|
-
- **Hard to debug:** Unclear why tests fail intermittently
|
|
1162
|
-
|
|
1163
|
-
**✅ ALWAYS use ID-based cleanup instead:**
|
|
1164
|
-
```typescript
|
|
1165
|
-
// ✅ CORRECT: Only deletes entities created in THIS test file
|
|
1166
|
-
if (createdUserIds.length > 0) {
|
|
1167
|
-
await db.collection('users').deleteMany({
|
|
1168
|
-
_id: { $in: createdUserIds.map(id => new ObjectId(id)) }
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
```
|
|
1172
|
-
|
|
1173
|
-
### Between Individual Tests
|
|
1174
|
-
|
|
1175
|
-
Use `beforeEach`/`afterEach` only when necessary:
|
|
1176
|
-
|
|
1177
|
-
```typescript
|
|
1178
|
-
describe('Feature Tests', () => {
|
|
1179
|
-
let sharedResource;
|
|
1180
|
-
|
|
1181
|
-
beforeEach(async () => {
|
|
1182
|
-
// Reset state before each test if needed
|
|
1183
|
-
sharedResource = await createFreshResource();
|
|
1184
|
-
});
|
|
1185
|
-
|
|
1186
|
-
afterEach(async () => {
|
|
1187
|
-
// Clean up after each test if needed
|
|
1188
|
-
await deleteResource(sharedResource.id);
|
|
1189
|
-
});
|
|
1190
|
-
});
|
|
1191
|
-
```
|
|
1192
|
-
|
|
1193
|
-
## User Authentication: signUp vs signIn
|
|
1194
|
-
|
|
1195
|
-
### When to use signUp
|
|
1196
|
-
|
|
1197
|
-
- Creating new users in tests
|
|
1198
|
-
- Full control over user data needed
|
|
1199
|
-
- Testing user registration flows
|
|
1200
|
-
- Most common in story tests
|
|
1201
|
-
|
|
1202
|
-
```typescript
|
|
1203
|
-
const signUp = await testHelper.graphQl({
|
|
1204
|
-
arguments: {
|
|
1205
|
-
input: {
|
|
1206
|
-
// 🚨 CRITICAL: MUST end with @test.com for e2e.brevo.exclude
|
|
1207
|
-
// Use timestamp + random suffix for guaranteed uniqueness
|
|
1208
|
-
email: `test-${Date.now()}-${Math.random().toString(36).substring(2, 8)}@test.com`, // ✅ Unique + @test.com
|
|
1209
|
-
password: 'testpass123',
|
|
1210
|
-
firstName: 'Test',
|
|
1211
|
-
},
|
|
1212
|
-
},
|
|
1213
|
-
fields: ['token', { user: ['id', 'email'] }],
|
|
1214
|
-
name: 'signUp',
|
|
1215
|
-
type: TestGraphQLType.MUTATION,
|
|
1216
|
-
});
|
|
1217
|
-
const token = signUp.token;
|
|
1218
|
-
|
|
1219
|
-
// ✅ Track for cleanup
|
|
1220
|
-
createdUserIds.push(signUp.user.id);
|
|
1221
|
-
```
|
|
1222
|
-
|
|
1223
|
-
### When to use signIn
|
|
1224
|
-
|
|
1225
|
-
- Authenticating existing users
|
|
1226
|
-
- User already exists in database
|
|
1227
|
-
- Testing login flows
|
|
1228
|
-
|
|
1229
|
-
```typescript
|
|
1230
|
-
const signIn = await testHelper.rest('/auth/signin', {
|
|
1231
|
-
method: 'POST',
|
|
1232
|
-
payload: {
|
|
1233
|
-
email: existingUserEmail,
|
|
1234
|
-
password: existingUserPassword,
|
|
1235
|
-
},
|
|
1236
|
-
});
|
|
1237
|
-
const token = signIn.token;
|
|
1238
|
-
```
|
|
1239
|
-
|
|
1240
|
-
## Avoiding Test Interdependencies
|
|
1241
|
-
|
|
1242
|
-
### ❌ DON'T: Shared state between tests
|
|
1243
|
-
|
|
1244
|
-
```typescript
|
|
1245
|
-
// ❌ BAD: Test 2 depends on Test 1
|
|
1246
|
-
let createdUserId;
|
|
1247
|
-
|
|
1248
|
-
it('should create user', async () => {
|
|
1249
|
-
const user = await createUser(...);
|
|
1250
|
-
createdUserId = user.id; // ❌ Shared state!
|
|
1251
|
-
});
|
|
1252
|
-
|
|
1253
|
-
it('should update user', async () => {
|
|
1254
|
-
await updateUser(createdUserId, ...); // ❌ Depends on Test 1!
|
|
1255
|
-
});
|
|
1256
|
-
```
|
|
1257
|
-
|
|
1258
|
-
### ✅ DO: Independent tests
|
|
1259
|
-
|
|
1260
|
-
```typescript
|
|
1261
|
-
// ✅ GOOD: Each test is independent
|
|
1262
|
-
describe('User CRUD', () => {
|
|
1263
|
-
let testUserId;
|
|
1264
|
-
|
|
1265
|
-
beforeEach(async () => {
|
|
1266
|
-
// Create fresh user for EACH test
|
|
1267
|
-
const user = await createUser(...);
|
|
1268
|
-
testUserId = user.id;
|
|
1269
|
-
});
|
|
1270
|
-
|
|
1271
|
-
afterEach(async () => {
|
|
1272
|
-
// Clean up after each test
|
|
1273
|
-
await deleteUser(testUserId);
|
|
1274
|
-
});
|
|
1275
|
-
|
|
1276
|
-
it('should update user', async () => {
|
|
1277
|
-
await updateUser(testUserId, ...); // ✅ Independent!
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
it('should delete user', async () => {
|
|
1281
|
-
await deleteUser(testUserId, ...); // ✅ Independent!
|
|
1282
|
-
});
|
|
1283
|
-
});
|
|
1284
|
-
```
|
|
1285
|
-
|
|
1286
|
-
## Async/Await Best Practices
|
|
1287
|
-
|
|
1288
|
-
### Always await async operations
|
|
1289
|
-
|
|
1290
|
-
```typescript
|
|
1291
|
-
// ❌ WRONG: Forgotten await
|
|
1292
|
-
const user = testHelper.graphQl({...}); // Returns Promise, not user!
|
|
1293
|
-
expect(user.email).toBe('test@example.com'); // FAILS!
|
|
1294
|
-
|
|
1295
|
-
// ✅ CORRECT: With await
|
|
1296
|
-
const user = await testHelper.graphQl({...});
|
|
1297
|
-
expect(user.email).toBe('test@example.com'); // Works!
|
|
1298
|
-
```
|
|
1299
|
-
|
|
1300
|
-
### Parallel vs Sequential execution
|
|
1301
|
-
|
|
1302
|
-
```typescript
|
|
1303
|
-
// ✅ Parallel execution (independent operations)
|
|
1304
|
-
const [user1, user2, product] = await Promise.all([
|
|
1305
|
-
testHelper.graphQl({...}), // Create user 1
|
|
1306
|
-
testHelper.graphQl({...}), // Create user 2
|
|
1307
|
-
testHelper.rest('/api/products', {...}), // Create product
|
|
1308
|
-
]);
|
|
1309
|
-
|
|
1310
|
-
// ✅ Sequential execution (dependent operations)
|
|
1311
|
-
const user = await testHelper.graphQl({...});
|
|
1312
|
-
const product = await testHelper.rest('/api/products', {
|
|
1313
|
-
token: user.token, // Depends on user being created first
|
|
1314
|
-
payload: {...},
|
|
1315
|
-
method: 'POST',
|
|
1316
|
-
});
|
|
1317
|
-
|
|
1318
|
-
// ❌ WRONG: Sequential when parallel is possible (slower)
|
|
1319
|
-
const user1 = await testHelper.graphQl({...});
|
|
1320
|
-
const user2 = await testHelper.graphQl({...}); // Could run in parallel!
|
|
1321
|
-
const product = await testHelper.rest('/api/products', {...});
|
|
1322
|
-
```
|
|
1323
|
-
|
|
1324
|
-
### Handling errors with async/await
|
|
1325
|
-
|
|
1326
|
-
```typescript
|
|
1327
|
-
// Test that async operation throws error
|
|
1328
|
-
await expect(async () => {
|
|
1329
|
-
await testHelper.rest('/api/resource', {
|
|
1330
|
-
payload: invalidData,
|
|
1331
|
-
token: userToken,
|
|
1332
|
-
});
|
|
1333
|
-
}).rejects.toThrow();
|
|
1334
|
-
|
|
1335
|
-
// Or use statusCode option
|
|
1336
|
-
await testHelper.rest('/api/resource', {
|
|
1337
|
-
payload: invalidData,
|
|
1338
|
-
statusCode: 400,
|
|
1339
|
-
token: userToken,
|
|
1340
|
-
});
|
|
1341
|
-
```
|
|
1342
|
-
|
|
1343
|
-
## Common Pitfalls to Avoid
|
|
1344
|
-
|
|
1345
|
-
❌ **Don't:**
|
|
1346
|
-
- Write code before tests
|
|
1347
|
-
- Skip test analysis step
|
|
1348
|
-
- **Weaken security for passing tests**
|
|
1349
|
-
- **Remove or weaken @Restricted/@Roles decorators**
|
|
1350
|
-
- **Add @UseGuards(AuthGuard(...)) manually (redundant with @Roles)**
|
|
1351
|
-
- **Skip security review before marking complete**
|
|
1352
|
-
- Add dependencies without checking existing
|
|
1353
|
-
- Ignore existing code patterns
|
|
1354
|
-
- Batch test completions (mark complete immediately)
|
|
1355
|
-
- Work on multiple tasks simultaneously
|
|
1356
|
-
- **Create git commits without explicit request**
|
|
1357
|
-
- Forget `await` on async calls
|
|
1358
|
-
- Create test interdependencies
|
|
1359
|
-
- **Forget to implement cleanup in afterAll**
|
|
1360
|
-
- **Forget to track created entity IDs for cleanup**
|
|
1361
|
-
- **Use pattern-based cleanup (deletes entities from parallel tests!)**
|
|
1362
|
-
- **Reuse same test data across test files (causes parallel conflicts)**
|
|
1363
|
-
- **Use emails without @test.com suffix (won't be excluded from external services)**
|
|
1364
|
-
- **Use fixed port numbers (breaks parallel test execution)**
|
|
1365
|
-
- Clean up too aggressively (breaking other tests)
|
|
1366
|
-
- **Skip code quality check before marking complete**
|
|
1367
|
-
- **Leave obvious code duplication in place**
|
|
1368
|
-
- Over-engineer by extracting single-use code
|
|
1369
|
-
- **Define indexes separately in schema files**
|
|
1370
|
-
- **Forget to add indexes for queried fields**
|
|
1371
|
-
- Add indexes to fields that are never queried
|
|
1372
|
-
- **Expose sensitive fields without hideField**
|
|
1373
|
-
- **Allow users to access others' data without checks**
|
|
1374
|
-
- **Use 'any' type instead of proper DTOs**
|
|
1375
|
-
|
|
1376
|
-
✅ **Do:**
|
|
1377
|
-
- Follow the 7-step process strictly (including Step 5a security & refactoring check)
|
|
1378
|
-
- Ask for clarification early
|
|
1379
|
-
- **Preserve all security mechanisms (CRITICAL)**
|
|
1380
|
-
- **Perform security review before marking complete**
|
|
1381
|
-
- Study existing code first
|
|
1382
|
-
- Match existing patterns
|
|
1383
|
-
- Mark todos complete as you finish them
|
|
1384
|
-
- Focus on one step at a time
|
|
1385
|
-
- **Wait for developer to commit changes**
|
|
1386
|
-
- Always use `await` with async operations
|
|
1387
|
-
- Make tests independent
|
|
1388
|
-
- Use `beforeEach`/`afterEach` for test isolation
|
|
1389
|
-
- Use Promise.all() for parallel operations
|
|
1390
|
-
- **ALWAYS implement comprehensive cleanup in afterAll**
|
|
1391
|
-
- **Track all created entity IDs immediately after creation**
|
|
1392
|
-
- **ONLY delete entities created in THIS test file (parallel-safe)**
|
|
1393
|
-
- **Use @test.com suffix for ALL test emails (e2e.brevo.exclude)**
|
|
1394
|
-
- **Make ALL test data unique per test file (avoid parallel conflicts)**
|
|
1395
|
-
- **NEVER use fixed ports - let NestJS assign random ports automatically**
|
|
1396
|
-
- **Always use TestHelper for API calls (handles ports automatically)**
|
|
1397
|
-
- Delete entities in correct order (children before parents)
|
|
1398
|
-
- **Check for code duplication before marking complete**
|
|
1399
|
-
- **Extract common functionality to helpers when used 2+ times**
|
|
1400
|
-
- **Run tests again after refactoring**
|
|
1401
|
-
- **Verify ownership checks for user data access**
|
|
1402
|
-
- **Mark sensitive fields with hideField: true**
|
|
1403
|
-
- **Use proper DTOs with validation decorators**
|
|
1404
|
-
- **Ensure authorization tests pass**
|
|
1405
|
-
|
|
1406
|
-
## Integration Points
|
|
1407
|
-
|
|
1408
|
-
### With nest-server-generator
|
|
1409
|
-
- Use for creating modules, objects, properties
|
|
1410
|
-
- Use for understanding NestJS patterns
|
|
1411
|
-
- Use for reading CrudService implementations
|
|
1412
|
-
|
|
1413
|
-
### With Existing Tests
|
|
1414
|
-
- Study patterns in test/ directory
|
|
1415
|
-
- Copy authentication setup approach
|
|
1416
|
-
- Use same helper functions
|
|
1417
|
-
- Match assertion style
|
|
1418
|
-
|
|
1419
|
-
### With API Documentation
|
|
1420
|
-
- Check Controllers for REST endpoints
|
|
1421
|
-
- Review Swagger annotations
|
|
1422
|
-
- Understand existing data models
|
|
1423
|
-
- Verify GraphQL schema if applicable
|
|
1424
|
-
|
|
1425
|
-
---
|
|
1426
|
-
|
|
1427
|
-
**Remember:** Tests first, code second. Iterate until green. **Security review mandatory.** Refactor before done. Quality over speed.
|