@lenne.tech/cli 1.0.1 → 1.1.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-commands.js +10 -5
- package/build/commands/claude/install-mcps.js +258 -0
- package/build/commands/claude/install-skills.js +90 -23
- package/build/lib/mcp-registry.js +80 -0
- package/build/templates/claude-commands/commit-message.md +21 -0
- package/build/templates/claude-commands/create-story.md +407 -0
- package/build/templates/claude-commands/skill-optimize.md +431 -90
- package/build/templates/claude-skills/building-stories-with-tdd/SKILL.md +265 -0
- package/build/templates/claude-skills/{story-tdd → building-stories-with-tdd}/code-quality.md +10 -0
- package/build/templates/claude-skills/{story-tdd → building-stories-with-tdd}/database-indexes.md +9 -0
- package/build/templates/claude-skills/{story-tdd → building-stories-with-tdd}/examples.md +115 -64
- package/build/templates/claude-skills/building-stories-with-tdd/handling-existing-tests.md +197 -0
- package/build/templates/claude-skills/{story-tdd → building-stories-with-tdd}/reference.md +276 -29
- package/build/templates/claude-skills/{story-tdd → building-stories-with-tdd}/security-review.md +8 -0
- package/build/templates/claude-skills/building-stories-with-tdd/workflow.md +1004 -0
- package/build/templates/claude-skills/generating-nest-servers/SKILL.md +303 -0
- package/build/templates/claude-skills/{nest-server-generator → generating-nest-servers}/configuration.md +6 -0
- package/build/templates/claude-skills/{nest-server-generator → generating-nest-servers}/declare-keyword-warning.md +9 -0
- package/build/templates/claude-skills/{nest-server-generator → generating-nest-servers}/description-management.md +9 -0
- package/build/templates/claude-skills/{nest-server-generator → generating-nest-servers}/examples.md +7 -0
- package/build/templates/claude-skills/generating-nest-servers/framework-guide.md +259 -0
- package/build/templates/claude-skills/{nest-server-generator → generating-nest-servers}/quality-review.md +9 -0
- package/build/templates/claude-skills/{nest-server-generator → generating-nest-servers}/reference.md +16 -0
- package/build/templates/claude-skills/{nest-server-generator → generating-nest-servers}/security-rules.md +13 -0
- package/build/templates/claude-skills/generating-nest-servers/verification-checklist.md +262 -0
- package/build/templates/claude-skills/generating-nest-servers/workflow-process.md +1061 -0
- package/build/templates/claude-skills/{lt-cli → using-lt-cli}/SKILL.md +22 -10
- package/build/templates/claude-skills/{lt-cli → using-lt-cli}/examples.md +7 -3
- package/build/templates/claude-skills/{lt-cli → using-lt-cli}/reference.md +10 -3
- package/package.json +2 -2
- package/build/templates/claude-skills/nest-server-generator/SKILL.md +0 -1891
- package/build/templates/claude-skills/story-tdd/SKILL.md +0 -1173
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: story-tdd-reference
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: Quick reference guide for Test-Driven Development workflow
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Story-Based TDD Quick Reference
|
|
8
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
|
+
|
|
9
32
|
## The 7-Step Workflow
|
|
10
33
|
|
|
11
34
|
```
|
|
@@ -19,7 +42,7 @@ description: Quick reference guide for Test-Driven Development workflow
|
|
|
19
42
|
↓
|
|
20
43
|
┌─────────────────────────────────────────────────────────┐
|
|
21
44
|
│ Step 2: Create Story Test │
|
|
22
|
-
│ - Location:
|
|
45
|
+
│ - Location: tests/stories/feature-name.story.test.ts │
|
|
23
46
|
│ - Study existing test patterns │
|
|
24
47
|
│ - Write comprehensive test scenarios │
|
|
25
48
|
│ - Cover happy path, errors, edge cases │
|
|
@@ -78,7 +101,7 @@ description: Quick reference guide for Test-Driven Development workflow
|
|
|
78
101
|
npm test
|
|
79
102
|
|
|
80
103
|
# Run specific story test
|
|
81
|
-
npm test --
|
|
104
|
+
npm test -- tests/stories/feature-name.story.test.ts
|
|
82
105
|
|
|
83
106
|
# Run tests with coverage
|
|
84
107
|
npm run test:cov
|
|
@@ -105,6 +128,44 @@ lt server addProp Review rating:number --no-interactive
|
|
|
105
128
|
lt server addProp Review comment:string? --no-interactive
|
|
106
129
|
```
|
|
107
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
|
+
|
|
108
169
|
## Test File Template
|
|
109
170
|
|
|
110
171
|
```typescript
|
|
@@ -116,25 +177,25 @@ import {
|
|
|
116
177
|
} from '@lenne.tech/nest-server';
|
|
117
178
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
118
179
|
import { PubSub } from 'graphql-subscriptions';
|
|
119
|
-
import { MongoClient } from 'mongodb';
|
|
180
|
+
import { MongoClient, ObjectId } from 'mongodb';
|
|
120
181
|
|
|
121
182
|
import envConfig from '../../src/config.env';
|
|
122
183
|
import { RoleEnum } from '../../src/server/common/enums/role.enum';
|
|
123
|
-
import { YourService } from '../../src/server/modules/your-module/your.service';
|
|
124
184
|
import { imports, ServerModule } from '../../src/server/server.module';
|
|
125
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
|
+
|
|
126
190
|
describe('[Feature Name] Story', () => {
|
|
127
191
|
// Test environment properties
|
|
128
192
|
let app;
|
|
129
193
|
let testHelper: TestHelper;
|
|
130
194
|
|
|
131
|
-
// Database
|
|
195
|
+
// Database (only for setup/cleanup and setting roles/verified status)
|
|
132
196
|
let connection;
|
|
133
197
|
let db;
|
|
134
198
|
|
|
135
|
-
// Services
|
|
136
|
-
let yourService: YourService;
|
|
137
|
-
|
|
138
199
|
// Global test data
|
|
139
200
|
let gUserToken: string;
|
|
140
201
|
let gUserId: string;
|
|
@@ -147,7 +208,6 @@ describe('[Feature Name] Story', () => {
|
|
|
147
208
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
148
209
|
imports: [...imports, ServerModule],
|
|
149
210
|
providers: [
|
|
150
|
-
YourService,
|
|
151
211
|
{
|
|
152
212
|
provide: 'PUB_SUB',
|
|
153
213
|
useValue: new PubSub(),
|
|
@@ -162,15 +222,16 @@ describe('[Feature Name] Story', () => {
|
|
|
162
222
|
await app.init();
|
|
163
223
|
|
|
164
224
|
testHelper = new TestHelper(app);
|
|
165
|
-
yourService = moduleFixture.get(YourService);
|
|
166
225
|
|
|
167
|
-
// Connection to database
|
|
226
|
+
// Connection to database (ONLY for cleanup and setting roles/verified)
|
|
168
227
|
connection = await MongoClient.connect(envConfig.mongoose.uri);
|
|
169
228
|
db = await connection.db();
|
|
170
229
|
|
|
171
|
-
// Create test user
|
|
230
|
+
// 🚨 CRITICAL: Create test user with @test.com email
|
|
172
231
|
const password = Math.random().toString(36).substring(7);
|
|
173
|
-
|
|
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`;
|
|
174
235
|
const signUp = await testHelper.graphQl({
|
|
175
236
|
arguments: {
|
|
176
237
|
input: {
|
|
@@ -211,7 +272,11 @@ describe('[Feature Name] Story', () => {
|
|
|
211
272
|
describe('Happy Path', () => {
|
|
212
273
|
it('should [expected behavior]', async () => {
|
|
213
274
|
// Arrange
|
|
214
|
-
|
|
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
|
+
};
|
|
215
280
|
|
|
216
281
|
// Act - Using REST
|
|
217
282
|
const result = await testHelper.rest('/api/endpoint', {
|
|
@@ -225,7 +290,7 @@ describe('[Feature Name] Story', () => {
|
|
|
225
290
|
// expected properties
|
|
226
291
|
});
|
|
227
292
|
|
|
228
|
-
// Track for cleanup
|
|
293
|
+
// ✅ Track for cleanup (CRITICAL for parallel-safe tests)
|
|
229
294
|
createdEntityIds.push(result.id);
|
|
230
295
|
});
|
|
231
296
|
});
|
|
@@ -337,6 +402,17 @@ Before marking complete, verify:
|
|
|
337
402
|
|
|
338
403
|
## REST API Testing Patterns (using TestHelper)
|
|
339
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
|
+
|
|
340
416
|
```typescript
|
|
341
417
|
// GET request
|
|
342
418
|
const result = await testHelper.rest('/api/resource/123', {
|
|
@@ -382,6 +458,16 @@ const result = await testHelper.rest('/api/resource', {
|
|
|
382
458
|
},
|
|
383
459
|
token: userToken,
|
|
384
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
|
+
});
|
|
385
471
|
```
|
|
386
472
|
|
|
387
473
|
## GraphQL Testing Patterns (using TestHelper)
|
|
@@ -486,6 +572,54 @@ expect(() => fn()).toThrow();
|
|
|
486
572
|
expect(() => fn()).toThrow('error message');
|
|
487
573
|
```
|
|
488
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
|
+
|
|
489
623
|
## Security Testing Checklist
|
|
490
624
|
|
|
491
625
|
```typescript
|
|
@@ -611,7 +745,7 @@ DEBUG_VALIDATION=true npm test
|
|
|
611
745
|
process.env.DEBUG_VALIDATION = 'true';
|
|
612
746
|
```
|
|
613
747
|
|
|
614
|
-
This activates console.debug statements in MapAndValidatePipe (`node_modules/@lenne.tech/nest-server/src/core/common/pipes/map-and-validate.pipe.ts`) to show detailed validation errors.
|
|
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.
|
|
615
749
|
|
|
616
750
|
### 4. Combined Debugging Setup
|
|
617
751
|
|
|
@@ -673,6 +807,24 @@ Test fails
|
|
|
673
807
|
- Create shared validation helpers
|
|
674
808
|
- Consolidate similar query builders
|
|
675
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
|
+
|
|
676
828
|
3. **Database Indexes:**
|
|
677
829
|
- Fields used in queries → Add `mongoose: { index: true, type: String }` to @UnifiedField
|
|
678
830
|
- Foreign keys → Add index via mongoose option
|
|
@@ -738,7 +890,7 @@ Before marking complete:
|
|
|
738
890
|
## Story: [Name]
|
|
739
891
|
|
|
740
892
|
### Tests Created
|
|
741
|
-
- Location:
|
|
893
|
+
- Location: tests/stories/[filename].story.test.ts
|
|
742
894
|
- Test cases: X scenarios
|
|
743
895
|
- Coverage: X%
|
|
744
896
|
|
|
@@ -856,12 +1008,76 @@ git log -p --follow path/to/file.ts
|
|
|
856
1008
|
|
|
857
1009
|
**ALWAYS implement comprehensive cleanup in your story tests!**
|
|
858
1010
|
|
|
859
|
-
|
|
1011
|
+
Tests run in parallel, so improper test data management causes:
|
|
1012
|
+
- Conflicts between parallel tests (duplicate keys, race conditions)
|
|
860
1013
|
- False positives/negatives in tests
|
|
861
1014
|
- Flaky tests that pass/fail randomly
|
|
862
1015
|
- Contaminated test database
|
|
863
1016
|
- Hard-to-debug test failures
|
|
864
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
|
+
|
|
865
1081
|
### Between Test Suites - RECOMMENDED APPROACH
|
|
866
1082
|
|
|
867
1083
|
**Track all created entities and delete them explicitly:**
|
|
@@ -875,13 +1091,19 @@ describe('Feature Story', () => {
|
|
|
875
1091
|
|
|
876
1092
|
// In your tests, track IDs immediately after creation
|
|
877
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
|
+
|
|
878
1100
|
const product = await testHelper.rest('/api/products', {
|
|
879
1101
|
method: 'POST',
|
|
880
1102
|
payload: productData,
|
|
881
1103
|
token: adminToken,
|
|
882
1104
|
});
|
|
883
1105
|
|
|
884
|
-
// ✅ Track for cleanup
|
|
1106
|
+
// ✅ Track for cleanup (ONLY delete what we created)
|
|
885
1107
|
createdProductIds.push(product.id);
|
|
886
1108
|
});
|
|
887
1109
|
|
|
@@ -917,13 +1139,13 @@ describe('Feature Story', () => {
|
|
|
917
1139
|
});
|
|
918
1140
|
```
|
|
919
1141
|
|
|
920
|
-
### Alternative: Pattern-Based Cleanup (
|
|
1142
|
+
### Alternative: Pattern-Based Cleanup (AVOID - Not Parallel-Safe!)
|
|
921
1143
|
|
|
922
|
-
|
|
1144
|
+
**❌ DO NOT USE pattern-based cleanup - it breaks parallel test execution!**
|
|
923
1145
|
|
|
924
1146
|
```typescript
|
|
1147
|
+
// ❌ WRONG: Deletes ALL test users, even from parallel tests!
|
|
925
1148
|
afterAll(async () => {
|
|
926
|
-
// Clean up test data by pattern (less reliable)
|
|
927
1149
|
await db.collection('users').deleteMany({ email: /@test\.com$/ });
|
|
928
1150
|
await db.collection('products').deleteMany({ name: /^Test/ });
|
|
929
1151
|
|
|
@@ -932,10 +1154,21 @@ afterAll(async () => {
|
|
|
932
1154
|
});
|
|
933
1155
|
```
|
|
934
1156
|
|
|
935
|
-
**⚠️
|
|
936
|
-
-
|
|
937
|
-
-
|
|
938
|
-
-
|
|
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
|
+
```
|
|
939
1172
|
|
|
940
1173
|
### Between Individual Tests
|
|
941
1174
|
|
|
@@ -970,7 +1203,9 @@ describe('Feature Tests', () => {
|
|
|
970
1203
|
const signUp = await testHelper.graphQl({
|
|
971
1204
|
arguments: {
|
|
972
1205
|
input: {
|
|
973
|
-
|
|
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
|
|
974
1209
|
password: 'testpass123',
|
|
975
1210
|
firstName: 'Test',
|
|
976
1211
|
},
|
|
@@ -980,6 +1215,9 @@ const signUp = await testHelper.graphQl({
|
|
|
980
1215
|
type: TestGraphQLType.MUTATION,
|
|
981
1216
|
});
|
|
982
1217
|
const token = signUp.token;
|
|
1218
|
+
|
|
1219
|
+
// ✅ Track for cleanup
|
|
1220
|
+
createdUserIds.push(signUp.user.id);
|
|
983
1221
|
```
|
|
984
1222
|
|
|
985
1223
|
### When to use signIn
|
|
@@ -1109,6 +1347,7 @@ await testHelper.rest('/api/resource', {
|
|
|
1109
1347
|
- Skip test analysis step
|
|
1110
1348
|
- **Weaken security for passing tests**
|
|
1111
1349
|
- **Remove or weaken @Restricted/@Roles decorators**
|
|
1350
|
+
- **Add @UseGuards(AuthGuard(...)) manually (redundant with @Roles)**
|
|
1112
1351
|
- **Skip security review before marking complete**
|
|
1113
1352
|
- Add dependencies without checking existing
|
|
1114
1353
|
- Ignore existing code patterns
|
|
@@ -1119,8 +1358,11 @@ await testHelper.rest('/api/resource', {
|
|
|
1119
1358
|
- Create test interdependencies
|
|
1120
1359
|
- **Forget to implement cleanup in afterAll**
|
|
1121
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)**
|
|
1122
1365
|
- Clean up too aggressively (breaking other tests)
|
|
1123
|
-
- Use pattern-based cleanup when ID tracking is possible
|
|
1124
1366
|
- **Skip code quality check before marking complete**
|
|
1125
1367
|
- **Leave obvious code duplication in place**
|
|
1126
1368
|
- Over-engineer by extracting single-use code
|
|
@@ -1147,6 +1389,11 @@ await testHelper.rest('/api/resource', {
|
|
|
1147
1389
|
- Use Promise.all() for parallel operations
|
|
1148
1390
|
- **ALWAYS implement comprehensive cleanup in afterAll**
|
|
1149
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)**
|
|
1150
1397
|
- Delete entities in correct order (children before parents)
|
|
1151
1398
|
- **Check for code duplication before marking complete**
|
|
1152
1399
|
- **Extract common functionality to helpers when used 2+ times**
|
package/build/templates/claude-skills/{story-tdd → building-stories-with-tdd}/security-review.md
RENAMED
|
@@ -6,6 +6,14 @@ description: Security review checklist for Test-Driven Development - ensures no
|
|
|
6
6
|
|
|
7
7
|
# 🔐 Security Review Checklist
|
|
8
8
|
|
|
9
|
+
## Table of Contents
|
|
10
|
+
- [Security Checklist](#security-checklist)
|
|
11
|
+
- [Security Decision Tree](#security-decision-tree)
|
|
12
|
+
- [Red Flags - STOP and Review](#red-flags---stop-and-review)
|
|
13
|
+
- [If ANY Red Flag Found](#if-any-red-flag-found)
|
|
14
|
+
- [Remember](#remember)
|
|
15
|
+
- [Quick Security Checklist](#quick-security-checklist)
|
|
16
|
+
|
|
9
17
|
**CRITICAL: Perform security review before final testing!**
|
|
10
18
|
|
|
11
19
|
**ALWAYS review all code changes for security vulnerabilities before marking complete.**
|