@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.
Files changed (36) hide show
  1. package/build/commands/claude/install-plugin.js +339 -0
  2. package/package.json +1 -1
  3. package/build/commands/claude/install-commands.js +0 -337
  4. package/build/commands/claude/install-mcps.js +0 -258
  5. package/build/commands/claude/install-skills.js +0 -693
  6. package/build/lib/mcp-registry.js +0 -80
  7. package/build/templates/claude-commands/code-cleanup.md +0 -82
  8. package/build/templates/claude-commands/commit-message.md +0 -21
  9. package/build/templates/claude-commands/create-story.md +0 -435
  10. package/build/templates/claude-commands/mr-description-clipboard.md +0 -48
  11. package/build/templates/claude-commands/mr-description.md +0 -33
  12. package/build/templates/claude-commands/sec-review.md +0 -62
  13. package/build/templates/claude-commands/skill-optimize.md +0 -481
  14. package/build/templates/claude-commands/test-generate.md +0 -45
  15. package/build/templates/claude-skills/building-stories-with-tdd/SKILL.md +0 -265
  16. package/build/templates/claude-skills/building-stories-with-tdd/code-quality.md +0 -276
  17. package/build/templates/claude-skills/building-stories-with-tdd/database-indexes.md +0 -182
  18. package/build/templates/claude-skills/building-stories-with-tdd/examples.md +0 -1383
  19. package/build/templates/claude-skills/building-stories-with-tdd/handling-existing-tests.md +0 -197
  20. package/build/templates/claude-skills/building-stories-with-tdd/reference.md +0 -1427
  21. package/build/templates/claude-skills/building-stories-with-tdd/security-review.md +0 -307
  22. package/build/templates/claude-skills/building-stories-with-tdd/workflow.md +0 -1004
  23. package/build/templates/claude-skills/generating-nest-servers/SKILL.md +0 -303
  24. package/build/templates/claude-skills/generating-nest-servers/configuration.md +0 -285
  25. package/build/templates/claude-skills/generating-nest-servers/declare-keyword-warning.md +0 -133
  26. package/build/templates/claude-skills/generating-nest-servers/description-management.md +0 -226
  27. package/build/templates/claude-skills/generating-nest-servers/examples.md +0 -893
  28. package/build/templates/claude-skills/generating-nest-servers/framework-guide.md +0 -259
  29. package/build/templates/claude-skills/generating-nest-servers/quality-review.md +0 -864
  30. package/build/templates/claude-skills/generating-nest-servers/reference.md +0 -487
  31. package/build/templates/claude-skills/generating-nest-servers/security-rules.md +0 -371
  32. package/build/templates/claude-skills/generating-nest-servers/verification-checklist.md +0 -262
  33. package/build/templates/claude-skills/generating-nest-servers/workflow-process.md +0 -1061
  34. package/build/templates/claude-skills/using-lt-cli/SKILL.md +0 -284
  35. package/build/templates/claude-skills/using-lt-cli/examples.md +0 -546
  36. package/build/templates/claude-skills/using-lt-cli/reference.md +0 -513
@@ -1,1061 +0,0 @@
1
- ---
2
- name: nest-server-generator-workflow
3
- version: 1.0.0
4
- description: Complete 7-phase workflow for NestJS module/object generation - from analysis to testing, including SubObject creation, inheritance handling, description management, enum files, and comprehensive API testing with security validation
5
- ---
6
-
7
- # Workflow Process
8
-
9
- ## Table of Contents
10
- - [Phase 1: Analysis & Planning](#phase-1-analysis--planning)
11
- - [Phase 2: SubObject Creation](#phase-2-subobject-creation)
12
- - [Phase 3: Module Creation](#phase-3-module-creation)
13
- - [Phase 4: Inheritance Handling](#phase-4-inheritance-handling)
14
- - [Phase 5: Description Management](#phase-5-description-management)
15
- - [Phase 6: Enum File Creation](#phase-6-enum-file-creation)
16
- - [Phase 7: API Test Creation](#phase-7-api-test-creation)
17
-
18
- ### Phase 1: Analysis & Planning
19
-
20
- 1. **Parse the specification** completely
21
- 2. **Identify all components**:
22
- - List all SubObjects
23
- - List all Objects
24
- - List all Modules
25
- - Identify inheritance relationships
26
- - Identify enum types needed
27
- 3. **Create comprehensive todo list** with:
28
- - Create each SubObject
29
- - Create each Object
30
- - Create each Module
31
- - Handle inheritance modifications
32
- - Create enum files
33
- - Create API tests for each module
34
- - Run tests and verify
35
-
36
- **Phase 1 Checklist:**
37
- - [ ] Specification completely parsed
38
- - [ ] All components identified (SubObjects, Objects, Modules)
39
- - [ ] Inheritance relationships documented
40
- - [ ] Enum types listed
41
- - [ ] Comprehensive todo list created
42
- - [ ] Ready for Phase 2
43
-
44
- ### Phase 2: SubObject Creation
45
-
46
- **Create SubObjects in dependency order** (if SubObject A contains SubObject B, create B first):
47
-
48
- ```bash
49
- lt server object --name <ObjectName> \
50
- --prop-name-0 <name> --prop-type-0 <type> \
51
- --prop-name-1 <name> --prop-type-1 <type> \
52
- ...
53
- ```
54
-
55
- **Apply modifiers**:
56
- - Optional: `--prop-nullable-X true`
57
- - Array: `--prop-array-X true`
58
- - Enum: `--prop-enum-X <EnumName>`
59
- - Schema: `--prop-schema-X <SchemaName>`
60
-
61
- **Phase 2 Checklist:**
62
- - [ ] All SubObjects created in correct dependency order
63
- - [ ] All modifiers applied (nullable, array, enum, schema)
64
- - [ ] Properties in alphabetical order
65
- - [ ] No circular dependencies
66
- - [ ] Ready for Phase 3
67
-
68
- ### Phase 3: Module Creation
69
-
70
- **Create modules with all properties**:
71
-
72
- ```bash
73
- lt server module --name <ModuleName> --controller <Rest|GraphQL|Both> \
74
- --prop-name-0 <name> --prop-type-0 <type> \
75
- --prop-name-1 <name> --prop-type-1 <type> \
76
- ...
77
- ```
78
-
79
- **For references to other modules**:
80
- ```bash
81
- --prop-name-X author --prop-type-X ObjectId --prop-reference-X User
82
- ```
83
-
84
- **For embedded objects**:
85
- ```bash
86
- --prop-name-X address --prop-schema-X Address
87
- ```
88
-
89
- **Phase 3 Checklist:**
90
- - [ ] All modules created with correct properties
91
- - [ ] References correctly set (ObjectId with --prop-reference-X)
92
- - [ ] Embedded objects correctly referenced (--prop-schema-X)
93
- - [ ] Properties in alphabetical order
94
- - [ ] All required imports present
95
- - [ ] Ready for Phase 4
96
-
97
- ### Phase 4: Inheritance Handling
98
-
99
- When a model extends another model (e.g., `Extends: Profile`):
100
-
101
- 1. **Identify parent model location**:
102
- - Core models (from @lenne.tech/nest-server): CoreModel, CorePersisted, etc.
103
- - Custom parent models: Need to find in project
104
-
105
- 2. **For Core parent models**:
106
- - Replace in model file: `extends CoreModel` → `extends ParentModel`
107
- - Import: `import { ParentModel } from './path'`
108
-
109
- 3. **For custom parent models (objects/other modules)**:
110
- - Model extends parent object: Import and extend
111
- - Input files must include parent properties
112
-
113
- 4. **Input/Output inheritance**:
114
- - **CreateInput**: Must include ALL required properties from parent AND model
115
- - **UpdateInput**: Include all properties as optional
116
- - Check parent's CreateInput for required fields
117
- - Copy required fields to child's CreateInput
118
-
119
- **Example**: If `BuyerProfile` extends `Profile`:
120
- ```typescript
121
- // buyer-profile.model.ts
122
- import { Profile } from '../../common/objects/profile/profile.object';
123
- export class BuyerProfile extends Profile { ... }
124
-
125
- // buyer-profile-create.input.ts
126
- // Must include ALL required fields from Profile's create input + BuyerProfile fields
127
- ```
128
-
129
- **Phase 4 Checklist:**
130
- - [ ] All parent models identified (Core or custom)
131
- - [ ] Model extends correct parent class
132
- - [ ] Imports updated correctly
133
- - [ ] CreateInput includes ALL parent required fields
134
- - [ ] UpdateInput includes all properties as optional
135
- - [ ] No missing required fields
136
- - [ ] Ready for Phase 5
137
-
138
- ### Phase 5: Description Management
139
-
140
- **⚠️ CRITICAL PHASE - Refer to "CRITICAL: DESCRIPTION MANAGEMENT" section at the top of this document!**
141
-
142
- This phase is often done incorrectly. Follow these steps EXACTLY:
143
-
144
- #### Step 5.1: Extract Descriptions from User Input
145
-
146
- **BEFORE applying any descriptions, review the original specification:**
147
-
148
- Go back to the user's original specification and extract ALL comments that appear after `//`:
149
-
150
- ```
151
- Module: Product
152
- - name: string // Product name
153
- - price: number // Produktpreis
154
- - description?: string // Produktbeschreibung
155
- - stock: number // Current inventory
156
-
157
- SubObject: Address
158
- - street: string // Straße
159
- - city: string // City name
160
- - zipCode: string // Postleitzahl
161
- ```
162
-
163
- **Create a mapping**:
164
- ```
165
- Product.name → "Product name" (English)
166
- Product.price → "Produktpreis" (German)
167
- Product.description → "Produktbeschreibung" (German)
168
- Product.stock → "Current inventory" (English)
169
- Address.street → "Straße" (German)
170
- Address.city → "City name" (English)
171
- Address.zipCode → "Postleitzahl" (German)
172
- ```
173
-
174
- #### Step 5.2: Format Descriptions
175
-
176
- **Rule**: `"ENGLISH_DESCRIPTION (DEUTSCHE_BESCHREIBUNG)"`
177
-
178
- Apply formatting rules:
179
-
180
- 1. **If comment is in English**:
181
- ```
182
- // Product name
183
- ```
184
- → Use as: `description: 'Product name'`
185
-
186
- Fix typos if needed:
187
- ```
188
- // Prodcut name (typo)
189
- ```
190
- → Use as: `description: 'Product name'` (typo corrected)
191
-
192
- 2. **If comment is in German**:
193
- ```
194
- // Produktpreis
195
- ```
196
- → Translate and add original: `description: 'Product price (Produktpreis)'`
197
-
198
- ```
199
- // Straße
200
- ```
201
- → Translate and add original: `description: 'Street (Straße)'`
202
-
203
- Fix typos in original:
204
- ```
205
- // Postleizahl (typo: missing 't')
206
- ```
207
- → Translate and add corrected: `description: 'Postal code (Postleitzahl)'`
208
-
209
- 3. **If no comment provided**:
210
- → Create meaningful English description: `description: 'User email address'`
211
-
212
- **⚠️ CRITICAL - Preserve Original Wording**:
213
-
214
- - ✅ **DO:** Fix spelling/typos only
215
- - ❌ **DON'T:** Rephrase, expand, or improve wording
216
- - ❌ **DON'T:** Change terms (they may be predefined/referenced by external systems)
217
-
218
- **Examples**:
219
- ```
220
- ✅ CORRECT:
221
- // Straße → 'Street (Straße)' (preserve word)
222
- // Produkt → 'Product (Produkt)' (don't add "name")
223
- // Status → 'Status (Status)' (same in both languages)
224
-
225
- ❌ WRONG:
226
- // Straße → 'Street name (Straßenname)' (changed word!)
227
- // Produkt → 'Product name (Produktname)' (added word!)
228
- // Status → 'Current status (Aktueller Status)' (added word!)
229
- ```
230
-
231
- #### Step 5.3: Apply Descriptions EVERYWHERE
232
-
233
- **🚨 MOST IMPORTANT: Apply SAME description to ALL files!**
234
-
235
- For **EVERY property in EVERY Module**:
236
-
237
- 1. Open `<module>.model.ts` → Add description to property
238
- 2. Open `inputs/<module>-create.input.ts` → Add SAME description to property
239
- 3. Open `inputs/<module>.input.ts` → Add SAME description to property
240
-
241
- For **EVERY property in EVERY SubObject**:
242
-
243
- 1. Open `objects/<object>/<object>.object.ts` → Add description to property
244
- 2. Open `objects/<object>/<object>-create.input.ts` → Add SAME description to property
245
- 3. Open `objects/<object>/<object>.input.ts` → Add SAME description to property
246
-
247
- **Example for Module "Product" with property "price"**:
248
-
249
- ```typescript
250
- // File: src/server/modules/product/product.model.ts
251
- @UnifiedField({ description: 'Product price (Produktpreis)' })
252
- price: number;
253
-
254
- // File: src/server/modules/product/inputs/product-create.input.ts
255
- @UnifiedField({ description: 'Product price (Produktpreis)' })
256
- price: number;
257
-
258
- // File: src/server/modules/product/inputs/product.input.ts
259
- @UnifiedField({ description: 'Product price (Produktpreis)' })
260
- price?: number;
261
- ```
262
-
263
- **Example for SubObject "Address" with property "street"**:
264
-
265
- ```typescript
266
- // File: src/server/common/objects/address/address.object.ts
267
- @UnifiedField({ description: 'Street (Straße)' })
268
- street: string;
269
-
270
- // File: src/server/common/objects/address/address-create.input.ts
271
- @UnifiedField({ description: 'Street (Straße)' })
272
- street: string;
273
-
274
- // File: src/server/common/objects/address/address.input.ts
275
- @UnifiedField({ description: 'Street (Straße)' })
276
- street?: string;
277
- ```
278
-
279
- #### Step 5.4: Add Class-Level Descriptions
280
-
281
- Also add descriptions to the `@ObjectType()` and `@InputType()` decorators:
282
-
283
- ```typescript
284
- @ObjectType({ description: 'Product entity (Produkt-Entität)' })
285
- export class Product extends CoreModel { ... }
286
-
287
- @InputType({ description: 'Product creation data (Produkt-Erstellungsdaten)' })
288
- export class ProductCreateInput { ... }
289
-
290
- @InputType({ description: 'Product update data (Produkt-Aktualisierungsdaten)' })
291
- export class ProductInput { ... }
292
- ```
293
-
294
- #### Step 5.5: Verify Consistency
295
-
296
- After applying all descriptions, verify:
297
-
298
- - [ ] All user-provided comments extracted and processed
299
- - [ ] All German descriptions translated to format: `ENGLISH (DEUTSCH)`
300
- - [ ] All English descriptions kept as-is
301
- - [ ] Module Model has descriptions on all properties
302
- - [ ] Module CreateInput has SAME descriptions on all properties
303
- - [ ] Module UpdateInput has SAME descriptions on all properties
304
- - [ ] SubObject has descriptions on all properties
305
- - [ ] SubObject CreateInput has SAME descriptions on all properties
306
- - [ ] SubObject UpdateInput has SAME descriptions on all properties
307
- - [ ] Class-level decorators have descriptions
308
- - [ ] NO inconsistencies (same property, different descriptions)
309
-
310
- **If ANY checkbox is unchecked, STOP and fix before continuing to Phase 6!**
311
-
312
- **Phase 5 Checklist:**
313
- - [ ] All user-provided comments extracted and processed
314
- - [ ] All German descriptions translated to format: ENGLISH (DEUTSCH)
315
- - [ ] All English descriptions kept as-is (typos fixed only)
316
- - [ ] Descriptions applied to ALL Model properties
317
- - [ ] Descriptions applied to ALL CreateInput properties
318
- - [ ] Descriptions applied to ALL UpdateInput properties
319
- - [ ] Descriptions applied to ALL SubObject properties
320
- - [ ] Class-level decorators have descriptions
321
- - [ ] NO inconsistencies (same property different descriptions)
322
- - [ ] Ready for Phase 6
323
-
324
- ### Phase 6: Enum File Creation
325
-
326
- For each enum used, create enum file manually:
327
-
328
- ```typescript
329
- // src/server/common/enums/status.enum.ts
330
- export enum StatusEnum {
331
- PENDING = 'PENDING',
332
- ACTIVE = 'ACTIVE',
333
- COMPLETED = 'COMPLETED',
334
- }
335
- ```
336
-
337
- **Naming convention**:
338
- - File: `kebab-case.enum.ts`
339
- - Enum: `PascalCaseEnum`
340
- - Values: `UPPER_SNAKE_CASE`
341
-
342
- **Phase 6 Checklist:**
343
- - [ ] All enum files created in src/server/common/enums/
344
- - [ ] File naming follows kebab-case.enum.ts
345
- - [ ] Enum naming follows PascalCaseEnum
346
- - [ ] Values follow UPPER_SNAKE_CASE
347
- - [ ] All enums properly imported where used
348
- - [ ] Ready for Phase 7
349
-
350
- ### Phase 7: API Test Creation
351
-
352
- **⚠️ CRITICAL: Test Type Requirement**
353
-
354
- **ONLY create API tests using TestHelper - NEVER create direct Service tests!**
355
-
356
- - ✅ **DO:** Create tests that call REST endpoints or GraphQL queries/mutations using `TestHelper`
357
- - ✅ **DO:** Test through the API layer (Controller/Resolver → Service → Database)
358
- - ❌ **DON'T:** Create tests that directly instantiate or call Service methods
359
- - ❌ **DON'T:** Create unit tests for Services (e.g., `user.service.spec.ts`)
360
- - ❌ **DON'T:** Mock dependencies or bypass the API layer
361
-
362
- **Why API tests only?**
363
- - API tests validate the complete security model (decorators, guards, permissions)
364
- - Direct Service tests bypass authentication and authorization checks
365
- - TestHelper provides all necessary tools for comprehensive API testing
366
-
367
- **Exception: Direct database/service access for test setup/cleanup ONLY**
368
-
369
- Direct database or service access is ONLY allowed for:
370
-
371
- - ✅ **Test Setup (beforeAll/beforeEach)**:
372
- - Setting user roles in database: `await db.collection('users').updateOne({ _id: userId }, { $set: { roles: ['admin'] } })`
373
- - Setting verified flag: `await db.collection('users').updateOne({ _id: userId }, { $set: { verified: true } })`
374
- - Creating prerequisite test data that can't be created via API
375
-
376
- - ✅ **Test Cleanup (afterAll/afterEach)**:
377
- - Deleting test objects: `await db.collection('products').deleteMany({ createdBy: testUserId })`
378
- - Cleaning up test data: `await db.collection('users').deleteOne({ email: 'test@example.com' })`
379
-
380
- - ❌ **NEVER for testing functionality**:
381
- - Don't call `userService.create()` to test user creation - use API endpoint!
382
- - Don't call `productService.update()` to test updates - use API endpoint!
383
- - Don't access database to verify results - query via API instead!
384
-
385
- **Example of correct usage:**
386
-
387
- ```typescript
388
- describe('Product Tests', () => {
389
- let adminToken: string;
390
- let userId: string;
391
-
392
- beforeAll(async () => {
393
- // ✅ ALLOWED: Direct DB access for setup
394
- const user = await testHelper.rest('/auth/signup', {
395
- method: 'POST',
396
- payload: { email: 'admin@test.com', password: 'password' }
397
- });
398
- userId = user.id;
399
-
400
- // ✅ ALLOWED: Direct DB manipulation for test setup
401
- await db.collection('users').updateOne(
402
- { _id: new ObjectId(userId) },
403
- { $set: { roles: ['admin'], verified: true } }
404
- );
405
-
406
- // Get token via API
407
- const auth = await testHelper.rest('/auth/signin', {
408
- method: 'POST',
409
- payload: { email: 'admin@test.com', password: 'password' }
410
- });
411
- adminToken = auth.token;
412
- });
413
-
414
- it('should create product', async () => {
415
- // ✅ CORRECT: Test via API
416
- const result = await testHelper.rest('/api/products', {
417
- method: 'POST',
418
- payload: { name: 'Test Product' },
419
- token: adminToken
420
- });
421
-
422
- expect(result.name).toBe('Test Product');
423
-
424
- // ❌ WRONG: Don't verify via DB
425
- // const dbProduct = await db.collection('products').findOne({ _id: result.id });
426
-
427
- // ✅ CORRECT: Verify via API
428
- const fetched = await testHelper.rest(`/api/products/${result.id}`, {
429
- method: 'GET',
430
- token: adminToken
431
- });
432
- expect(fetched.name).toBe('Test Product');
433
- });
434
-
435
- afterAll(async () => {
436
- // ✅ ALLOWED: Direct DB access for cleanup
437
- await db.collection('products').deleteMany({ createdBy: userId });
438
- await db.collection('users').deleteOne({ _id: new ObjectId(userId) });
439
- });
440
- });
441
- ```
442
-
443
- ---
444
-
445
- **⚠️ CRITICAL: Test Creation Process**
446
-
447
- Creating API tests is NOT just about testing functionality - it's about **validating the security model**. You MUST follow this exact process:
448
-
449
- ---
450
-
451
- #### Step 1: 🔍 MANDATORY Permission Analysis (BEFORE writing ANY test)
452
-
453
- **YOU MUST analyze these THREE layers BEFORE writing a single test:**
454
-
455
- 1. **Controller/Resolver Layer** - Check `@Roles()` decorator:
456
- ```typescript
457
- // In product.resolver.ts
458
- @Roles(RoleEnum.S_EVERYONE) // ← WHO can call this?
459
- @Query(() => [Product])
460
- async getProducts() { ... }
461
-
462
- @Roles(RoleEnum.S_USER) // ← All signed-in users
463
- @Mutation(() => Product)
464
- async createProduct(@Args('input') input: ProductCreateInput) { ... }
465
-
466
- @Roles(RoleEnum.ADMIN, RoleEnum.S_CREATOR) // ← Only admin or creator
467
- @Mutation(() => Product)
468
- async updateProduct(@Args('id') id: string, @Args('input') input: ProductInput) { ... }
469
- ```
470
-
471
- 2. **Model Layer** - Check `@Restricted()` and `securityCheck()`:
472
- ```typescript
473
- // In product.model.ts
474
- export class Product extends CoreModel {
475
- securityCheck(user: User, force?: boolean) {
476
- if (force || user?.hasRole(RoleEnum.ADMIN)) {
477
- return this; // Admin sees all
478
- }
479
- if (this.isPublic) {
480
- return this; // Everyone sees public products
481
- }
482
- if (!equalIds(user, this.createdBy)) {
483
- return undefined; // Non-creator gets nothing
484
- }
485
- return this; // Creator sees own products
486
- }
487
- }
488
- ```
489
-
490
- 3. **Service Layer** - Check `serviceOptions.roles` usage:
491
- ```typescript
492
- // In product.service.ts
493
- async update(id: string, input: ProductInput, serviceOptions?: ServiceOptions) {
494
- // Check if user has ADMIN or S_CREATOR role
495
- // ...
496
- }
497
- ```
498
-
499
- **Permission Analysis Checklist:**
500
- - [ ] I have checked ALL `@Roles()` decorators in controller/resolver
501
- - [ ] I have read the complete `securityCheck()` method in the model
502
- - [ ] I have checked ALL `@Restricted()` decorators
503
- - [ ] I understand WHO can CREATE (usually S_USER or ADMIN)
504
- - [ ] I understand WHO can READ (S_USER + securityCheck filtering)
505
- - [ ] I understand WHO can UPDATE (usually ADMIN + S_CREATOR)
506
- - [ ] I understand WHO can DELETE (usually ADMIN + S_CREATOR)
507
-
508
- **Common Permission Patterns:**
509
- - `S_EVERYONE` → No authentication required
510
- - `S_USER` → Any signed-in user
511
- - `ADMIN` → User with 'admin' role
512
- - `S_CREATOR` → User who created the resource (user.id === object.createdBy)
513
-
514
- ---
515
-
516
- #### Step 2: 🎯 Apply Principle of Least Privilege
517
-
518
- **GOLDEN RULE**: Always test with the **LEAST privileged user** who is still authorized.
519
-
520
- **Decision Tree:**
521
-
522
- ```
523
- Is endpoint marked with @Roles(RoleEnum.S_EVERYONE)?
524
- ├─ YES → Test WITHOUT token (unauthenticated)
525
- └─ NO → Is endpoint marked with @Roles(RoleEnum.S_USER)?
526
- ├─ YES → Test WITH regular user token (NOT admin, NOT creator)
527
- └─ NO → Is endpoint marked with @Roles(RoleEnum.ADMIN, RoleEnum.S_CREATOR)?
528
- ├─ For UPDATE/DELETE → Test WITH creator token (user who created it)
529
- └─ For ADMIN-only → Test WITH admin token
530
- ```
531
-
532
- **❌ WRONG Approach:**
533
- ```typescript
534
- // BAD: Using admin for everything
535
- it('should create product', async () => {
536
- const result = await testHelper.graphQl({
537
- name: 'createProduct',
538
- type: TestGraphQLType.MUTATION,
539
- arguments: { input: { name: 'Test' } },
540
- fields: ['id']
541
- }, { token: adminToken }); // ❌ WRONG - Over-privileged!
542
- });
543
- ```
544
-
545
- **✅ CORRECT Approach:**
546
- ```typescript
547
- // GOOD: Using least privileged user
548
- it('should create product as regular user', async () => {
549
- const result = await testHelper.graphQl({
550
- name: 'createProduct',
551
- type: TestGraphQLType.MUTATION,
552
- arguments: { input: { name: 'Test' } },
553
- fields: ['id']
554
- }, { token: userToken }); // ✅ CORRECT - S_USER is enough!
555
- });
556
- ```
557
-
558
- ---
559
-
560
- #### Step 3: 📋 Create Test User Matrix
561
-
562
- Based on your permission analysis, create test users:
563
-
564
- ```typescript
565
- describe('Product API', () => {
566
- let testHelper: TestHelper;
567
-
568
- // Create users based on ACTUAL needs (not all of them!)
569
- let noToken: undefined; // For S_EVERYONE endpoints
570
- let userToken: string; // For S_USER endpoints
571
- let creatorToken: string; // For S_CREATOR (will create test data)
572
- let otherUserToken: string; // For testing "not creator" scenarios
573
- let adminToken: string; // Only if ADMIN-specific endpoints exist
574
-
575
- let createdProductId: string;
576
-
577
- beforeAll(async () => {
578
- testHelper = new TestHelper(app);
579
-
580
- // Only create users you ACTUALLY need based on @Roles() analysis!
581
-
582
- // Regular user (for S_USER endpoints)
583
- const userAuth = await testHelper.graphQl({
584
- name: 'signUp',
585
- type: TestGraphQLType.MUTATION,
586
- arguments: {
587
- input: {
588
- email: 'user@test.com',
589
- password: 'password',
590
- roles: ['user'] // Regular user, no special privileges
591
- }
592
- },
593
- fields: ['token', 'user { id }']
594
- });
595
- userToken = userAuth.token;
596
-
597
- // Creator user (will create test objects)
598
- const creatorAuth = await testHelper.graphQl({
599
- name: 'signUp',
600
- type: TestGraphQLType.MUTATION,
601
- arguments: {
602
- input: {
603
- email: 'creator@test.com',
604
- password: 'password',
605
- roles: ['user']
606
- }
607
- },
608
- fields: ['token', 'user { id }']
609
- });
610
- creatorToken = creatorAuth.token;
611
-
612
- // Other user (to test "not creator" scenarios)
613
- const otherUserAuth = await testHelper.graphQl({
614
- name: 'signUp',
615
- type: TestGraphQLType.MUTATION,
616
- arguments: {
617
- input: {
618
- email: 'other@test.com',
619
- password: 'password',
620
- roles: ['user']
621
- }
622
- },
623
- fields: ['token', 'user { id }']
624
- });
625
- otherUserToken = otherUserAuth.token;
626
-
627
- // Admin user (ONLY if truly needed!)
628
- const adminAuth = await testHelper.graphQl({
629
- name: 'signUp',
630
- type: TestGraphQLType.MUTATION,
631
- arguments: {
632
- input: {
633
- email: 'admin@test.com',
634
- password: 'password',
635
- roles: ['admin', 'user'] // ← 'admin' role!
636
- }
637
- },
638
- fields: ['token']
639
- });
640
- adminToken = adminAuth.token;
641
- });
642
-
643
- afterAll(async () => {
644
- // Clean up with appropriate privileged user
645
- if (createdProductId) {
646
- // Use creator or admin token for cleanup
647
- await testHelper.graphQl({
648
- name: 'deleteProduct',
649
- type: TestGraphQLType.MUTATION,
650
- arguments: { id: createdProductId },
651
- fields: ['id']
652
- }, { token: creatorToken });
653
- }
654
- });
655
- });
656
- ```
657
-
658
- ---
659
-
660
- #### Step 4: ✅ Write Tests with Correct Privileges
661
-
662
- **Example 1: S_EVERYONE endpoint (public access)**
663
-
664
- ```typescript
665
- // Endpoint: @Roles(RoleEnum.S_EVERYONE)
666
- describe('Public Endpoints', () => {
667
- it('should get public products WITHOUT token', async () => {
668
- const result = await testHelper.graphQl({
669
- name: 'getPublicProducts',
670
- type: TestGraphQLType.QUERY,
671
- fields: ['id', 'name', 'price']
672
- }); // ← NO TOKEN! S_EVERYONE means unauthenticated is OK
673
-
674
- expect(result).toBeDefined();
675
- expect(Array.isArray(result)).toBe(true);
676
- });
677
- });
678
- ```
679
-
680
- **Example 2: S_USER endpoint (any authenticated user)**
681
-
682
- ```typescript
683
- // Endpoint: @Roles(RoleEnum.S_USER)
684
- describe('Create Product', () => {
685
- it('should create product as regular user', async () => {
686
- const result = await testHelper.graphQl({
687
- name: 'createProduct',
688
- type: TestGraphQLType.MUTATION,
689
- arguments: { input: { name: 'Test Product', price: 10 } },
690
- fields: ['id', 'name', 'price', 'createdBy']
691
- }, { token: userToken }); // ← Regular user, NOT admin!
692
-
693
- expect(result).toBeDefined();
694
- expect(result.name).toBe('Test Product');
695
- createdProductId = result.id;
696
-
697
- // Verify creator is set
698
- expect(result.createdBy).toBe(userAuth.user.id);
699
- });
700
- });
701
- ```
702
-
703
- **Example 3: UPDATE - S_CREATOR or ADMIN**
704
-
705
- ```typescript
706
- // Endpoint: @Roles(RoleEnum.ADMIN, RoleEnum.S_CREATOR)
707
- describe('Update Product', () => {
708
- it('should update product as creator', async () => {
709
- // First, creator creates a product
710
- const created = await testHelper.graphQl({
711
- name: 'createProduct',
712
- type: TestGraphQLType.MUTATION,
713
- arguments: { input: { name: 'Original', price: 10 } },
714
- fields: ['id', 'name']
715
- }, { token: creatorToken });
716
-
717
- // Then, same creator updates it
718
- const result = await testHelper.graphQl({
719
- name: 'updateProduct',
720
- type: TestGraphQLType.MUTATION,
721
- arguments: {
722
- id: created.id,
723
- input: { name: 'Updated' }
724
- },
725
- fields: ['id', 'name']
726
- }, { token: creatorToken }); // ← Use CREATOR token (least privilege!)
727
-
728
- expect(result.name).toBe('Updated');
729
- });
730
-
731
- it('should update any product as admin', async () => {
732
- // Admin can update products they did NOT create
733
- const result = await testHelper.graphQl({
734
- name: 'updateProduct',
735
- type: TestGraphQLType.MUTATION,
736
- arguments: {
737
- id: createdProductId, // Created by different user
738
- input: { name: 'Admin Updated' }
739
- },
740
- fields: ['id', 'name']
741
- }, { token: adminToken }); // ← Admin needed for other's products
742
-
743
- expect(result.name).toBe('Admin Updated');
744
- });
745
- });
746
- ```
747
-
748
- ---
749
-
750
- #### Step 5: 🛡️ MANDATORY: Test Permission Failures
751
-
752
- **CRITICAL**: You MUST test that unauthorized users are BLOCKED. This validates the security model.
753
-
754
- ```typescript
755
- describe('Security Validation', () => {
756
- describe('Unauthorized Access', () => {
757
- it('should FAIL to create product without authentication', async () => {
758
- // @Roles(RoleEnum.S_USER) requires authentication
759
- const result = await testHelper.graphQl({
760
- name: 'createProduct',
761
- type: TestGraphQLType.MUTATION,
762
- arguments: { input: { name: 'Hack', price: 1 } },
763
- fields: ['id']
764
- }, { statusCode: 401 }); // ← NO TOKEN = should fail with 401
765
-
766
- expect(result.errors).toBeDefined();
767
- expect(result.errors[0].message).toContain('Unauthorized');
768
- });
769
-
770
- it('should FAIL to update product as non-creator', async () => {
771
- // @Roles(RoleEnum.ADMIN, RoleEnum.S_CREATOR)
772
- const result = await testHelper.graphQl({
773
- name: 'updateProduct',
774
- type: TestGraphQLType.MUTATION,
775
- arguments: {
776
- id: createdProductId, // Created by creatorUser
777
- input: { name: 'Hacked' }
778
- },
779
- fields: ['id']
780
- }, { token: otherUserToken, statusCode: 403 }); // ← Different user = should fail with 403
781
-
782
- expect(result.errors).toBeDefined();
783
- expect(result.errors[0].message).toContain('Forbidden');
784
- });
785
-
786
- it('should FAIL to delete product as non-creator', async () => {
787
- const result = await testHelper.graphQl({
788
- name: 'deleteProduct',
789
- type: TestGraphQLType.MUTATION,
790
- arguments: { id: createdProductId },
791
- fields: ['id']
792
- }, { token: otherUserToken, statusCode: 403 });
793
-
794
- expect(result.errors).toBeDefined();
795
- });
796
-
797
- it('should FAIL to read private product as different user', async () => {
798
- // If securityCheck() blocks non-creators
799
- const result = await testHelper.graphQl({
800
- name: 'getProduct',
801
- type: TestGraphQLType.QUERY,
802
- arguments: { id: privateProductId },
803
- fields: ['id', 'name']
804
- }, { token: otherUserToken });
805
-
806
- // securityCheck returns undefined for non-creator
807
- expect(result).toBeUndefined();
808
- });
809
- });
810
- });
811
- ```
812
-
813
- ---
814
-
815
- #### Step 6: 📝 Complete Test Structure
816
-
817
- **Test file location**:
818
- ```
819
- tests/modules/<module-name>.e2e-spec.ts
820
- ```
821
-
822
- **Complete test template with proper privileges**:
823
-
824
- ```typescript
825
- import { TestGraphQLType, TestHelper } from '@lenne.tech/nest-server';
826
-
827
- describe('Product Module E2E', () => {
828
- let testHelper: TestHelper;
829
- let userToken: string;
830
- let creatorToken: string;
831
- let otherUserToken: string;
832
- let adminToken: string;
833
- let createdProductId: string;
834
- let userAuth: any;
835
- let creatorAuth: any;
836
-
837
- beforeAll(async () => {
838
- testHelper = new TestHelper(app);
839
-
840
- // Create test users (based on permission analysis)
841
- userAuth = await testHelper.graphQl({
842
- name: 'signUp',
843
- type: TestGraphQLType.MUTATION,
844
- arguments: { input: { email: 'user@test.com', password: 'password', roles: ['user'] } },
845
- fields: ['token', 'user { id }']
846
- });
847
- userToken = userAuth.token;
848
-
849
- creatorAuth = await testHelper.graphQl({
850
- name: 'signUp',
851
- type: TestGraphQLType.MUTATION,
852
- arguments: { input: { email: 'creator@test.com', password: 'password', roles: ['user'] } },
853
- fields: ['token', 'user { id }']
854
- });
855
- creatorToken = creatorAuth.token;
856
-
857
- const otherUserAuth = await testHelper.graphQl({
858
- name: 'signUp',
859
- type: TestGraphQLType.MUTATION,
860
- arguments: { input: { email: 'other@test.com', password: 'password', roles: ['user'] } },
861
- fields: ['token']
862
- });
863
- otherUserToken = otherUserAuth.token;
864
-
865
- const adminAuth = await testHelper.graphQl({
866
- name: 'signUp',
867
- type: TestGraphQLType.MUTATION,
868
- arguments: { input: { email: 'admin@test.com', password: 'password', roles: ['admin', 'user'] } },
869
- fields: ['token']
870
- });
871
- adminToken = adminAuth.token;
872
- });
873
-
874
- afterAll(async () => {
875
- // Cleanup with appropriate privileges
876
- if (createdProductId) {
877
- await testHelper.graphQl({
878
- name: 'deleteProduct',
879
- type: TestGraphQLType.MUTATION,
880
- arguments: { id: createdProductId },
881
- fields: ['id']
882
- }, { token: creatorToken });
883
- }
884
- });
885
-
886
- // 1. CREATE Tests (with least privileged user)
887
- describe('Create Product', () => {
888
- it('should create product as regular user', async () => {
889
- const result = await testHelper.graphQl({
890
- name: 'createProduct',
891
- type: TestGraphQLType.MUTATION,
892
- arguments: { input: { name: 'Test', price: 10 } },
893
- fields: ['id', 'name', 'price', 'createdBy']
894
- }, { token: userToken }); // ← S_USER = regular user
895
-
896
- expect(result.name).toBe('Test');
897
- createdProductId = result.id;
898
- });
899
-
900
- it('should FAIL to create without authentication', async () => {
901
- const result = await testHelper.graphQl({
902
- name: 'createProduct',
903
- type: TestGraphQLType.MUTATION,
904
- arguments: { input: { name: 'Fail', price: 10 } },
905
- fields: ['id']
906
- }, { statusCode: 401 }); // ← No token = should fail
907
-
908
- expect(result.errors).toBeDefined();
909
- });
910
-
911
- it('should FAIL to create without required fields', async () => {
912
- const result = await testHelper.graphQl({
913
- name: 'createProduct',
914
- type: TestGraphQLType.MUTATION,
915
- arguments: { input: {} },
916
- fields: ['id']
917
- }, { token: userToken, statusCode: 400 });
918
-
919
- expect(result.errors).toBeDefined();
920
- });
921
- });
922
-
923
- // 2. READ Tests
924
- describe('Get Products', () => {
925
- it('should get all products as regular user', async () => {
926
- const result = await testHelper.graphQl({
927
- name: 'getProducts',
928
- type: TestGraphQLType.QUERY,
929
- fields: ['id', 'name', 'price']
930
- }, { token: userToken });
931
-
932
- expect(Array.isArray(result)).toBe(true);
933
- });
934
-
935
- it('should get product by ID as regular user', async () => {
936
- const result = await testHelper.graphQl({
937
- name: 'getProduct',
938
- type: TestGraphQLType.QUERY,
939
- arguments: { id: createdProductId },
940
- fields: ['id', 'name', 'price']
941
- }, { token: userToken });
942
-
943
- expect(result.id).toBe(createdProductId);
944
- });
945
- });
946
-
947
- // 3. UPDATE Tests (with creator, not admin!)
948
- describe('Update Product', () => {
949
- let creatorProductId: string;
950
-
951
- beforeAll(async () => {
952
- // Creator creates a product to test updates
953
- const created = await testHelper.graphQl({
954
- name: 'createProduct',
955
- type: TestGraphQLType.MUTATION,
956
- arguments: { input: { name: 'Creator Product', price: 20 } },
957
- fields: ['id']
958
- }, { token: creatorToken });
959
- creatorProductId = created.id;
960
- });
961
-
962
- it('should update product as creator', async () => {
963
- const result = await testHelper.graphQl({
964
- name: 'updateProduct',
965
- type: TestGraphQLType.MUTATION,
966
- arguments: { id: creatorProductId, input: { name: 'Updated' } },
967
- fields: ['id', 'name']
968
- }, { token: creatorToken }); // ← CREATOR token (least privilege!)
969
-
970
- expect(result.name).toBe('Updated');
971
- });
972
-
973
- it('should FAIL to update product as non-creator', async () => {
974
- const result = await testHelper.graphQl({
975
- name: 'updateProduct',
976
- type: TestGraphQLType.MUTATION,
977
- arguments: { id: creatorProductId, input: { name: 'Hacked' } },
978
- fields: ['id']
979
- }, { token: otherUserToken, statusCode: 403 });
980
-
981
- expect(result.errors).toBeDefined();
982
- });
983
-
984
- it('should update any product as admin', async () => {
985
- const result = await testHelper.graphQl({
986
- name: 'updateProduct',
987
- type: TestGraphQLType.MUTATION,
988
- arguments: { id: creatorProductId, input: { name: 'Admin Update' } },
989
- fields: ['id', 'name']
990
- }, { token: adminToken });
991
-
992
- expect(result.name).toBe('Admin Update');
993
- });
994
- });
995
-
996
- // 4. DELETE Tests (with creator, not admin!)
997
- describe('Delete Product', () => {
998
- it('should delete product as creator', async () => {
999
- // Creator creates and deletes
1000
- const created = await testHelper.graphQl({
1001
- name: 'createProduct',
1002
- type: TestGraphQLType.MUTATION,
1003
- arguments: { input: { name: 'To Delete', price: 5 } },
1004
- fields: ['id']
1005
- }, { token: creatorToken });
1006
-
1007
- const result = await testHelper.graphQl({
1008
- name: 'deleteProduct',
1009
- type: TestGraphQLType.MUTATION,
1010
- arguments: { id: created.id },
1011
- fields: ['id']
1012
- }, { token: creatorToken }); // ← CREATOR token!
1013
-
1014
- expect(result.id).toBe(created.id);
1015
- });
1016
-
1017
- it('should FAIL to delete product as non-creator', async () => {
1018
- const result = await testHelper.graphQl({
1019
- name: 'deleteProduct',
1020
- type: TestGraphQLType.MUTATION,
1021
- arguments: { id: createdProductId },
1022
- fields: ['id']
1023
- }, { token: otherUserToken, statusCode: 403 });
1024
-
1025
- expect(result.errors).toBeDefined();
1026
- });
1027
-
1028
- it('should delete any product as admin', async () => {
1029
- const result = await testHelper.graphQl({
1030
- name: 'deleteProduct',
1031
- type: TestGraphQLType.MUTATION,
1032
- arguments: { id: createdProductId },
1033
- fields: ['id']
1034
- }, { token: adminToken });
1035
-
1036
- expect(result.id).toBe(createdProductId);
1037
- });
1038
- });
1039
- });
1040
- ```
1041
-
1042
- ---
1043
-
1044
- #### Test Creation Checklist
1045
-
1046
- Before finalizing tests, verify:
1047
-
1048
- - [ ] ✅ I have analyzed ALL `@Roles()` decorators
1049
- - [ ] ✅ I have read the complete `securityCheck()` method
1050
- - [ ] ✅ I use the LEAST privileged user for each test
1051
- - [ ] ✅ S_EVERYONE endpoints tested WITHOUT token
1052
- - [ ] ✅ S_USER endpoints tested with REGULAR user (not admin)
1053
- - [ ] ✅ UPDATE/DELETE tested with CREATOR token (not admin)
1054
- - [ ] ✅ I have tests that verify unauthorized access FAILS (401/403)
1055
- - [ ] ✅ I have tests that verify non-creators CANNOT update/delete
1056
- - [ ] ✅ I have tests for missing required fields
1057
- - [ ] ✅ All tests follow the security model
1058
- - [ ] ✅ Tests validate protection mechanisms work
1059
-
1060
- **⚠️ NEVER use admin token when a less privileged user would work!**
1061
-