@lenne.tech/cli 1.0.1 → 1.0.2
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 +256 -0
- package/build/commands/claude/install-skills.js +90 -23
- package/build/lib/mcp-registry.js +71 -0
- package/build/templates/claude-commands/commit-message.md +21 -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,1891 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: nest-server-generator
|
|
3
|
-
version: 1.0.3
|
|
4
|
-
description: PRIMARY expert for ALL NestJS and @lenne.tech/nest-server tasks. ALWAYS use this skill when working in projects with @lenne.tech/nest-server in package.json dependencies (supports monorepos with projects/*, packages/*, apps/* structure), or when asked about NestJS modules, services, controllers, resolvers, models, objects, tests, server creation, debugging, or any NestJS/nest-server development task. Handles lt server commands, security analysis, test creation, and all backend development. ALWAYS reads CrudService base class before working with Services.
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# NestJS Server Development Expert
|
|
8
|
-
|
|
9
|
-
You are the **PRIMARY expert** for NestJS backend development and the @lenne.tech/nest-server framework. This skill handles **ALL NestJS-related tasks**, from analysis to creation to debugging:
|
|
10
|
-
|
|
11
|
-
## When to Use This Skill
|
|
12
|
-
|
|
13
|
-
**✅ ALWAYS use this skill for:**
|
|
14
|
-
|
|
15
|
-
### Analysis & Understanding
|
|
16
|
-
- 📖 Analyzing existing NestJS code structure
|
|
17
|
-
- 🔍 Understanding how modules, services, controllers work
|
|
18
|
-
- 📊 Reviewing project architecture
|
|
19
|
-
- 🗺️ Mapping relationships between modules
|
|
20
|
-
- 📝 Reading and explaining NestJS code
|
|
21
|
-
- 🔎 Finding specific implementations (controllers, services, etc.)
|
|
22
|
-
|
|
23
|
-
### Running & Debugging
|
|
24
|
-
- 🚀 Starting the NestJS server (`npm start`, `npm run dev`)
|
|
25
|
-
- 🐛 Debugging server issues and errors
|
|
26
|
-
- 🧪 Running tests (`npm test`)
|
|
27
|
-
- 📋 Checking server logs and output
|
|
28
|
-
- ⚙️ Configuring environment variables
|
|
29
|
-
- 🔧 Troubleshooting build/compile errors
|
|
30
|
-
|
|
31
|
-
### Creation & Modification
|
|
32
|
-
- ✨ Creating new modules with `lt server module`
|
|
33
|
-
- 🎨 Creating new objects with `lt server object`
|
|
34
|
-
- ➕ Adding properties with `lt server addProp`
|
|
35
|
-
- 🏗️ Creating a new server with `lt server create`
|
|
36
|
-
- ♻️ Modifying existing code (services, controllers, resolvers)
|
|
37
|
-
- 🔗 Adding relationships between modules
|
|
38
|
-
- 📦 Managing dependencies and imports
|
|
39
|
-
|
|
40
|
-
### Testing & Validation
|
|
41
|
-
- ✅ Creating API tests for controllers/resolvers
|
|
42
|
-
- 🧪 Running and fixing failing tests
|
|
43
|
-
- 🎯 Testing endpoints manually
|
|
44
|
-
- 📊 Validating data models and schemas
|
|
45
|
-
- 🔐 Testing authentication and permissions
|
|
46
|
-
|
|
47
|
-
### General NestJS Tasks
|
|
48
|
-
- 💬 Answering NestJS/nest-server questions
|
|
49
|
-
- 📚 Explaining framework concepts
|
|
50
|
-
- 🏛️ Discussing architecture decisions
|
|
51
|
-
- 🛠️ Recommending best practices
|
|
52
|
-
- 🔄 Refactoring existing code
|
|
53
|
-
|
|
54
|
-
**🎯 Rule: If it involves NestJS or @lenne.tech/nest-server in ANY way, use this skill!**
|
|
55
|
-
|
|
56
|
-
## 🚨 CRITICAL SECURITY RULES - READ FIRST
|
|
57
|
-
|
|
58
|
-
**Before you start ANY work, understand these NON-NEGOTIABLE rules:**
|
|
59
|
-
|
|
60
|
-
### ⛔ NEVER Do This:
|
|
61
|
-
1. **NEVER remove or weaken `@Restricted()` decorators**
|
|
62
|
-
2. **NEVER change `@Roles()` decorators** to more permissive roles
|
|
63
|
-
3. **NEVER modify `securityCheck()` logic** to bypass security
|
|
64
|
-
4. **NEVER remove class-level `@Restricted(RoleEnum.ADMIN)`**
|
|
65
|
-
|
|
66
|
-
### ✅ ALWAYS Do This:
|
|
67
|
-
1. **ALWAYS analyze permissions BEFORE writing tests**
|
|
68
|
-
2. **ALWAYS test with the LEAST privileged user** who is authorized
|
|
69
|
-
3. **ALWAYS adapt tests to security requirements**, never vice versa
|
|
70
|
-
4. **ALWAYS ask developer for approval** before changing ANY security decorator
|
|
71
|
-
|
|
72
|
-
**📖 For complete security rules, testing guidelines, and examples, see: `security-rules.md`**
|
|
73
|
-
|
|
74
|
-
## 🚨 CRITICAL: NEVER USE `declare` KEYWORD FOR PROPERTIES
|
|
75
|
-
|
|
76
|
-
**⚠️ DO NOT use the `declare` keyword when defining properties in classes!**
|
|
77
|
-
|
|
78
|
-
### Quick Rule
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
// ❌ WRONG
|
|
82
|
-
export class ProductCreateInput extends ProductInput {
|
|
83
|
-
declare name: string; // Decorator won't work!
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ✅ CORRECT
|
|
87
|
-
export class ProductCreateInput extends ProductInput {
|
|
88
|
-
@UnifiedField({ description: 'Product name' })
|
|
89
|
-
name: string; // Decorator works properly
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Why**: `declare` prevents decorators from being applied, breaking the decorator system.
|
|
94
|
-
|
|
95
|
-
**📖 For detailed explanation and correct patterns, see: `declare-keyword-warning.md`**
|
|
96
|
-
|
|
97
|
-
## 🚨 CRITICAL: DESCRIPTION MANAGEMENT
|
|
98
|
-
|
|
99
|
-
**⚠️ COMMON MISTAKE:** Descriptions are often applied inconsistently. You MUST follow this process for EVERY component.
|
|
100
|
-
|
|
101
|
-
### 3-Step Process
|
|
102
|
-
|
|
103
|
-
**1. Extract descriptions** from user's `// comments`
|
|
104
|
-
|
|
105
|
-
**2. Format correctly:**
|
|
106
|
-
- English input → `'Product name'`
|
|
107
|
-
- German input → `'Product name (Produktname)'`
|
|
108
|
-
- **⚠️ Fix typos ONLY, NEVER change wording!**
|
|
109
|
-
|
|
110
|
-
**3. Apply EVERYWHERE:**
|
|
111
|
-
- Model file
|
|
112
|
-
- Create Input file
|
|
113
|
-
- Update Input file
|
|
114
|
-
- Object files (if SubObject)
|
|
115
|
-
- Class-level @ObjectType() decorators
|
|
116
|
-
|
|
117
|
-
**📖 For detailed formatting rules, examples, and verification checklist, see: `description-management.md`**
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## Core Responsibilities
|
|
122
|
-
|
|
123
|
-
This skill handles **ALL** NestJS server development tasks, including:
|
|
124
|
-
|
|
125
|
-
### Simple Tasks (Single Commands)
|
|
126
|
-
- Creating a single module with `lt server module`
|
|
127
|
-
- Creating a single object with `lt server object`
|
|
128
|
-
- Adding properties with `lt server addProp`
|
|
129
|
-
- Creating a new server with `lt server create`
|
|
130
|
-
- Starting the server with `npm start` or `npm run dev`
|
|
131
|
-
- Running tests with `npm test`
|
|
132
|
-
|
|
133
|
-
### Complex Tasks (Multiple Components)
|
|
134
|
-
When you receive a complete structure specification, you will:
|
|
135
|
-
|
|
136
|
-
1. **Parse and analyze** the complete structure (modules, models, objects, properties, relationships)
|
|
137
|
-
2. **Create a comprehensive todo list** breaking down all tasks
|
|
138
|
-
3. **Generate all components** in the correct order (objects first, then modules)
|
|
139
|
-
4. **Handle inheritance** properly (Core and custom parent classes)
|
|
140
|
-
5. **Manage descriptions** (translate German to English, add originals in parentheses)
|
|
141
|
-
6. **Create API tests** for all controllers and resolvers
|
|
142
|
-
7. **Verify functionality** and provide a summary with observations
|
|
143
|
-
|
|
144
|
-
### Analysis Tasks
|
|
145
|
-
When analyzing existing code:
|
|
146
|
-
|
|
147
|
-
1. **Explore the project structure** to understand the architecture
|
|
148
|
-
2. **Read relevant files** (modules, services, controllers, models)
|
|
149
|
-
3. **Identify patterns** and conventions used in the project
|
|
150
|
-
4. **Explain findings** clearly and concisely
|
|
151
|
-
5. **Suggest improvements** when appropriate
|
|
152
|
-
|
|
153
|
-
### Debugging Tasks
|
|
154
|
-
When debugging issues:
|
|
155
|
-
|
|
156
|
-
1. **Read error messages and logs** carefully
|
|
157
|
-
2. **Identify the root cause** by analyzing relevant code
|
|
158
|
-
3. **Check configuration** (environment variables, config files)
|
|
159
|
-
4. **Test hypotheses** by examining related files
|
|
160
|
-
5. **Provide solutions** with code examples
|
|
161
|
-
|
|
162
|
-
**Remember:** For ANY task involving NestJS or @lenne.tech/nest-server, use this skill!
|
|
163
|
-
|
|
164
|
-
## 📚 Understanding the Framework
|
|
165
|
-
|
|
166
|
-
### Core Service Base Class: CrudService
|
|
167
|
-
|
|
168
|
-
**IMPORTANT**: Before working with Services, ALWAYS read this file to understand the base functionality:
|
|
169
|
-
|
|
170
|
-
```
|
|
171
|
-
node_modules/@lenne.tech/nest-server/src/core/common/services/crud.service.ts
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
**Why this is critical:**
|
|
175
|
-
- Almost ALL Services extend `CrudService<Model>`
|
|
176
|
-
- CrudService provides base CRUD operations (create, find, update, delete)
|
|
177
|
-
- Understanding CrudService prevents reinventing the wheel
|
|
178
|
-
- Shows patterns for handling permissions, filtering, and pagination
|
|
179
|
-
|
|
180
|
-
**When to read CrudService:**
|
|
181
|
-
1. ✅ Before creating a new Service
|
|
182
|
-
2. ✅ When implementing custom Service methods
|
|
183
|
-
3. ✅ When debugging Service behavior
|
|
184
|
-
4. ✅ When writing tests for Services
|
|
185
|
-
5. ✅ When questions arise about Service functionality
|
|
186
|
-
|
|
187
|
-
**What CrudService provides:**
|
|
188
|
-
- `create(input, options)` - Create new document
|
|
189
|
-
- `find(filterArgs)` - Find multiple documents
|
|
190
|
-
- `findOne(filterArgs)` - Find single document
|
|
191
|
-
- `findAndCount(filterArgs)` - Find with total count (pagination)
|
|
192
|
-
- `update(id, input, options)` - Update document
|
|
193
|
-
- `delete(id, options)` - Delete document
|
|
194
|
-
- Permission handling via `options.roles`
|
|
195
|
-
- Query filtering and population
|
|
196
|
-
- Pagination support
|
|
197
|
-
|
|
198
|
-
**Example Service that extends CrudService:**
|
|
199
|
-
```typescript
|
|
200
|
-
@Injectable()
|
|
201
|
-
export class ProductService extends CrudService<Product> {
|
|
202
|
-
constructor(
|
|
203
|
-
@InjectModel(Product.name) protected readonly productModel: Model<ProductDocument>,
|
|
204
|
-
protected readonly configService: ConfigService,
|
|
205
|
-
) {
|
|
206
|
-
super({ configService, mainDbModel: productModel, mainModelConstructor: Product });
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Custom methods can be added here
|
|
210
|
-
// Base CRUD methods are inherited from CrudService
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
**Action Items:**
|
|
215
|
-
- [ ] Read CrudService before modifying any Service
|
|
216
|
-
- [ ] Check if CrudService already provides the needed functionality
|
|
217
|
-
- [ ] Only add custom methods if CrudService doesn't cover the use case
|
|
218
|
-
- [ ] Follow CrudService patterns for consistency
|
|
219
|
-
|
|
220
|
-
## Configuration File (lt.config.json)
|
|
221
|
-
|
|
222
|
-
The lenne.tech CLI supports project-level configuration via `lt.config.json` files to set default values for commands.
|
|
223
|
-
|
|
224
|
-
**📖 For complete configuration guide including structure, options, and examples, see: `configuration.md`**
|
|
225
|
-
|
|
226
|
-
**Quick reference:**
|
|
227
|
-
- **Location**: Project root or parent directories
|
|
228
|
-
- **Priority**: CLI parameters > Interactive input > Config file > Defaults
|
|
229
|
-
- **Key option**: `commands.server.module.controller` - Sets default controller type ("Rest" | "GraphQL" | "Both" | "auto")
|
|
230
|
-
|
|
231
|
-
**Example config:**
|
|
232
|
-
```json
|
|
233
|
-
{
|
|
234
|
-
"commands": {
|
|
235
|
-
"server": {
|
|
236
|
-
"module": {
|
|
237
|
-
"controller": "Rest",
|
|
238
|
-
"skipLint": false
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
**Initialize config**: `lt config init`
|
|
246
|
-
**Show current config**: `lt config show`
|
|
247
|
-
|
|
248
|
-
### lt server module
|
|
249
|
-
Creates a complete NestJS module with model, service, controller/resolver, and DTOs.
|
|
250
|
-
|
|
251
|
-
**Syntax**:
|
|
252
|
-
```bash
|
|
253
|
-
lt server module --name <ModuleName> [--controller <Rest|GraphQL|Both|auto>] [property-flags]
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
**Parameters**:
|
|
257
|
-
- `--name <ModuleName>` - **Required**: Module name (PascalCase)
|
|
258
|
-
- `--controller <Rest|GraphQL|Both|auto>` - **Optional** (interactive detection if omitted)
|
|
259
|
-
- `Rest` - Creates REST controller only (no GraphQL, no PubSub)
|
|
260
|
-
- `GraphQL` - Creates GraphQL resolver only (includes PubSub for subscriptions)
|
|
261
|
-
- `Both` - Creates both REST controller and GraphQL resolver (includes PubSub)
|
|
262
|
-
- `auto` - Auto-detects from existing modules (non-interactive)
|
|
263
|
-
|
|
264
|
-
**Property flags** (use index 0, 1, 2, ... for multiple properties):
|
|
265
|
-
- `--prop-name-X <name>` - Property name
|
|
266
|
-
- `--prop-type-X <type>` - string, number, boolean, ObjectId, Json, Date, bigint
|
|
267
|
-
- `--prop-nullable-X <true|false>` - Optional property
|
|
268
|
-
- `--prop-array-X <true|false>` - Array type
|
|
269
|
-
- `--prop-enum-X <EnumName>` - Enum reference
|
|
270
|
-
- `--prop-schema-X <SchemaName>` - SubObject/schema reference
|
|
271
|
-
- `--prop-reference-X <RefName>` - Reference name for ObjectId
|
|
272
|
-
- `--skipLint` - Skip lint prompt
|
|
273
|
-
|
|
274
|
-
**Intelligent Controller Type Detection**:
|
|
275
|
-
|
|
276
|
-
**Three modes for controller type selection:**
|
|
277
|
-
|
|
278
|
-
1. **Interactive with detection** (omit `--controller`):
|
|
279
|
-
- CLI detects pattern from existing modules
|
|
280
|
-
- Shows suggestion to user
|
|
281
|
-
- User can accept or override interactively
|
|
282
|
-
|
|
283
|
-
2. **Non-interactive auto-detect** (`--controller auto`):
|
|
284
|
-
- CLI detects pattern from existing modules
|
|
285
|
-
- Uses detected value WITHOUT prompting
|
|
286
|
-
- Perfect for automation/scripts
|
|
287
|
-
|
|
288
|
-
3. **Explicit** (`--controller Rest|GraphQL|Both`):
|
|
289
|
-
- Bypasses detection
|
|
290
|
-
- Uses specified value directly
|
|
291
|
-
|
|
292
|
-
**Detection logic:**
|
|
293
|
-
- Analyzes modules in `src/server/modules/` (excludes base modules: auth, file, meta, user)
|
|
294
|
-
- **Only REST controllers found** → Detects `Rest`
|
|
295
|
-
- **Only GraphQL resolvers found** → Detects `GraphQL`
|
|
296
|
-
- **Both or mixed patterns found** → Detects `Both`
|
|
297
|
-
- **No modules or unclear** → Detects `Both` (safest default)
|
|
298
|
-
|
|
299
|
-
**Important Notes**:
|
|
300
|
-
- **PubSub integration**: Only included when using `GraphQL` or `Both` controller types
|
|
301
|
-
- **Auto-detection**: Analyzes your existing project structure to suggest the right pattern
|
|
302
|
-
- **Base modules excluded**: auth, file, meta, user modules are NOT analyzed (they're framework modules)
|
|
303
|
-
- REST-only modules (`--controller Rest`) do NOT include PubSub dependencies
|
|
304
|
-
|
|
305
|
-
**Examples**:
|
|
306
|
-
```bash
|
|
307
|
-
# Interactive mode with auto-detection (recommended for manual use)
|
|
308
|
-
lt server module --name Product \
|
|
309
|
-
--prop-name-0 name --prop-type-0 string \
|
|
310
|
-
--prop-name-1 price --prop-type-1 number
|
|
311
|
-
# CLI analyzes existing modules, shows suggestion, user confirms/overrides
|
|
312
|
-
|
|
313
|
-
# Non-interactive auto-detect (recommended for scripts/automation)
|
|
314
|
-
lt server module --name Product --controller auto \
|
|
315
|
-
--prop-name-0 name --prop-type-0 string \
|
|
316
|
-
--prop-name-1 price --prop-type-1 number
|
|
317
|
-
# CLI analyzes and uses detected pattern WITHOUT prompting
|
|
318
|
-
|
|
319
|
-
# Explicit REST only (no GraphQL/PubSub)
|
|
320
|
-
lt server module --name Category --controller Rest \
|
|
321
|
-
--prop-name-0 name --prop-type-0 string \
|
|
322
|
-
--prop-name-1 slug --prop-type-1 string
|
|
323
|
-
|
|
324
|
-
# Explicit GraphQL only (with PubSub)
|
|
325
|
-
lt server module --name Post --controller GraphQL \
|
|
326
|
-
--prop-name-0 title --prop-type-0 string \
|
|
327
|
-
--prop-name-1 author --prop-type-1 ObjectId --prop-reference-1 User \
|
|
328
|
-
--prop-name-2 tags --prop-type-2 string --prop-array-2 true
|
|
329
|
-
|
|
330
|
-
# Explicit Both (REST + GraphQL + PubSub)
|
|
331
|
-
lt server module --name User --controller Both \
|
|
332
|
-
--prop-name-0 email --prop-type-0 string \
|
|
333
|
-
--prop-name-1 username --prop-type-1 string
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### lt server object
|
|
337
|
-
Creates reusable embedded data structures (SubObjects) without _id or timestamps.
|
|
338
|
-
|
|
339
|
-
**Syntax**:
|
|
340
|
-
```bash
|
|
341
|
-
lt server object --name <ObjectName> [property-flags] [--skipLint]
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
**Property flags**: Same as `lt server module` (--prop-name-X, --prop-type-X, etc.)
|
|
345
|
-
|
|
346
|
-
**Example**:
|
|
347
|
-
```bash
|
|
348
|
-
lt server object --name Address \
|
|
349
|
-
--prop-name-0 street --prop-type-0 string \
|
|
350
|
-
--prop-name-1 city --prop-type-1 string \
|
|
351
|
-
--prop-name-2 country --prop-type-2 string
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
### lt server addProp
|
|
355
|
-
Adds properties to existing modules or objects.
|
|
356
|
-
|
|
357
|
-
**Syntax**:
|
|
358
|
-
```bash
|
|
359
|
-
lt server addProp --type <Module|Object> --element <name> [property-flags]
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
**Example**:
|
|
363
|
-
```bash
|
|
364
|
-
lt server addProp --type Module --element User \
|
|
365
|
-
--prop-name-0 phone --prop-type-0 string --prop-nullable-0 true
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
### lt server create
|
|
369
|
-
Creates a new NestJS server project.
|
|
370
|
-
|
|
371
|
-
**Syntax**:
|
|
372
|
-
```bash
|
|
373
|
-
lt server create <server-name> [--description=<desc>] [--author=<name>]
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
## Prerequisites Check
|
|
377
|
-
|
|
378
|
-
Before starting, verify:
|
|
379
|
-
|
|
380
|
-
```bash
|
|
381
|
-
# Check if lenne.Tech CLI is installed
|
|
382
|
-
lt --version
|
|
383
|
-
|
|
384
|
-
# If not installed, install it
|
|
385
|
-
npm install -g @lenne.tech/cli
|
|
386
|
-
|
|
387
|
-
# Verify we're in a NestJS project with @lenne.tech/nest-server
|
|
388
|
-
ls src/server/modules
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### Creating a New Server
|
|
392
|
-
|
|
393
|
-
If you need to create a completely new NestJS server project:
|
|
394
|
-
|
|
395
|
-
```bash
|
|
396
|
-
lt server create <server-name>
|
|
397
|
-
# Alias: lt server c <server-name>
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
This command:
|
|
401
|
-
- Clones the `nest-server-starter` template from GitHub
|
|
402
|
-
- Sets up package.json with your project details
|
|
403
|
-
- Configures Swagger documentation
|
|
404
|
-
- Attempts to replace secret keys (may be incomplete)
|
|
405
|
-
- Installs npm dependencies
|
|
406
|
-
- Optionally initializes git repository
|
|
407
|
-
|
|
408
|
-
**Interactive prompts**:
|
|
409
|
-
- Server name (or provide as first parameter)
|
|
410
|
-
- Description (optional)
|
|
411
|
-
- Author (optional)
|
|
412
|
-
- Initialize git? (yes/no)
|
|
413
|
-
|
|
414
|
-
**Example**:
|
|
415
|
-
```bash
|
|
416
|
-
lt server create my-api
|
|
417
|
-
|
|
418
|
-
# Non-interactive
|
|
419
|
-
lt server create my-api --description="My API Server" --author="John Doe"
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
**✅ IMPORTANT: Post-Creation Verification**
|
|
423
|
-
|
|
424
|
-
After running `lt server create`, the CLI automatically:
|
|
425
|
-
- Replaces ALL secrets matching `'SECRET_OR_PRIVATE_KEY...'` with unique random values
|
|
426
|
-
- Updates mongoose database URIs from `nest-server-*` to `<project-name>-*`
|
|
427
|
-
- Configures Swagger documentation with project name
|
|
428
|
-
|
|
429
|
-
**Recommended verification steps**:
|
|
430
|
-
|
|
431
|
-
1. **Verify secrets were replaced in `src/config.env.ts`**:
|
|
432
|
-
```bash
|
|
433
|
-
cd <project-name>
|
|
434
|
-
|
|
435
|
-
# Open config and verify no placeholders remain
|
|
436
|
-
# All jwt.secret and jwt.refresh.secret should be long random strings
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
2. **Verify mongoose.uri uses project name**:
|
|
440
|
-
```typescript
|
|
441
|
-
// In src/config.env.ts, verify database names match your project:
|
|
442
|
-
|
|
443
|
-
// Example for project "my-api":
|
|
444
|
-
local: {
|
|
445
|
-
mongoose: {
|
|
446
|
-
uri: 'mongodb://127.0.0.1/my-api-local', // ✅ Correct
|
|
447
|
-
}
|
|
448
|
-
},
|
|
449
|
-
production: {
|
|
450
|
-
mongoose: {
|
|
451
|
-
uri: 'mongodb://overlay_mongo1/my-api-prod', // ✅ Correct
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
3. **If secrets were not replaced** (older CLI version):
|
|
457
|
-
```bash
|
|
458
|
-
# Manually run setConfigSecrets to replace ALL secrets
|
|
459
|
-
lt server setConfigSecrets
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
4. **Other post-creation steps**:
|
|
463
|
-
- Start database server (MongoDB)
|
|
464
|
-
- Run tests: `npm run test:e2e`
|
|
465
|
-
- Start server: `npm start`
|
|
466
|
-
|
|
467
|
-
**Note**: If you used an older version of the CLI (before v0.0.126), secrets and database names may not have been replaced correctly. In that case, run `lt server setConfigSecrets` and manually update the mongoose URIs.
|
|
468
|
-
|
|
469
|
-
## Understanding the Specification Format
|
|
470
|
-
|
|
471
|
-
### Structure Components
|
|
472
|
-
|
|
473
|
-
#### 1. SubObject Definition
|
|
474
|
-
```
|
|
475
|
-
SubObject: <Name> // Description
|
|
476
|
-
- propertyName: <type> // Property description
|
|
477
|
-
- anotherProperty: <type> // Description
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
**SubObjects are**:
|
|
481
|
-
- Embedded data structures without `_id` or timestamps
|
|
482
|
-
- Created first using `lt server object`
|
|
483
|
-
- Used via `--prop-schema-X` in modules
|
|
484
|
-
|
|
485
|
-
#### 2. Object Definition
|
|
486
|
-
```
|
|
487
|
-
Object: <Name> // Description
|
|
488
|
-
Properties:
|
|
489
|
-
- propertyName: <type> // Property description
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
**Objects are**:
|
|
493
|
-
- Similar to SubObjects but used as base models
|
|
494
|
-
- Can be extended by other objects or modules
|
|
495
|
-
- Created using `lt server object`
|
|
496
|
-
|
|
497
|
-
#### 3. Module Definition
|
|
498
|
-
```
|
|
499
|
-
Module: <Name> // Description
|
|
500
|
-
|
|
501
|
-
Model: <Name> // Description
|
|
502
|
-
Extends: <ParentModel>
|
|
503
|
-
- propertyName: <type> // Property description
|
|
504
|
-
- reference: <ModelName> // Reference to another module
|
|
505
|
-
- embedded: <ObjectName>[] // Array of embedded objects
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
**Modules include**:
|
|
509
|
-
- Complete CRUD functionality
|
|
510
|
-
- Service, controller/resolver, DTOs
|
|
511
|
-
- Created using `lt server module`
|
|
512
|
-
|
|
513
|
-
### Property Type Syntax
|
|
514
|
-
|
|
515
|
-
#### Basic Types
|
|
516
|
-
- `string` - Text
|
|
517
|
-
- `number` - Numeric values
|
|
518
|
-
- `boolean` - True/false
|
|
519
|
-
- `Date` - Date/time values
|
|
520
|
-
- `bigint` - Large integers
|
|
521
|
-
- `Json` - Flexible JSON data
|
|
522
|
-
|
|
523
|
-
#### Special Types
|
|
524
|
-
|
|
525
|
-
**ENUM (value list)**:
|
|
526
|
-
```
|
|
527
|
-
propertyName: ENUM (VALUE1, VALUE2, VALUE3) // Description
|
|
528
|
-
```
|
|
529
|
-
→ Creates: `--prop-enum-X PropertyNameEnum`
|
|
530
|
-
→ You must create enum file afterwards in `src/server/common/enums/`
|
|
531
|
-
|
|
532
|
-
**ENUM with strings**:
|
|
533
|
-
```
|
|
534
|
-
status: ENUM ('PENDING', 'ACTIVE', 'COMPLETED') // Description
|
|
535
|
-
```
|
|
536
|
-
→ Same as above, quotes indicate string enum values
|
|
537
|
-
|
|
538
|
-
**Arrays**:
|
|
539
|
-
```
|
|
540
|
-
tags: string[] // Description
|
|
541
|
-
skills: Skill[] // Array of SubObjects
|
|
542
|
-
```
|
|
543
|
-
→ Add `--prop-array-X true`
|
|
544
|
-
|
|
545
|
-
**Optional Properties**:
|
|
546
|
-
```
|
|
547
|
-
middleName?: string // Description
|
|
548
|
-
```
|
|
549
|
-
→ Add `--prop-nullable-X true`
|
|
550
|
-
|
|
551
|
-
**References to Modules**:
|
|
552
|
-
```
|
|
553
|
-
author: User // Reference to User module
|
|
554
|
-
```
|
|
555
|
-
→ Use `--prop-type-X ObjectId --prop-reference-X User`
|
|
556
|
-
|
|
557
|
-
**Embedded Objects**:
|
|
558
|
-
```
|
|
559
|
-
address: Address // Embedded Address object
|
|
560
|
-
workHistory: WorkExperience[] // Array of embedded objects
|
|
561
|
-
```
|
|
562
|
-
→ Use `--prop-schema-X Address` (and `--prop-array-X true` for arrays)
|
|
563
|
-
|
|
564
|
-
**Files**:
|
|
565
|
-
```
|
|
566
|
-
document: File // PDF or other file
|
|
567
|
-
```
|
|
568
|
-
→ Use `--prop-type-X string` (stores file path/URL)
|
|
569
|
-
|
|
570
|
-
## Workflow Process
|
|
571
|
-
|
|
572
|
-
### Phase 1: Analysis & Planning
|
|
573
|
-
|
|
574
|
-
1. **Parse the specification** completely
|
|
575
|
-
2. **Identify all components**:
|
|
576
|
-
- List all SubObjects
|
|
577
|
-
- List all Objects
|
|
578
|
-
- List all Modules
|
|
579
|
-
- Identify inheritance relationships
|
|
580
|
-
- Identify enum types needed
|
|
581
|
-
3. **Create comprehensive todo list** with:
|
|
582
|
-
- Create each SubObject
|
|
583
|
-
- Create each Object
|
|
584
|
-
- Create each Module
|
|
585
|
-
- Handle inheritance modifications
|
|
586
|
-
- Create enum files
|
|
587
|
-
- Create API tests for each module
|
|
588
|
-
- Run tests and verify
|
|
589
|
-
|
|
590
|
-
### Phase 2: SubObject Creation
|
|
591
|
-
|
|
592
|
-
**Create SubObjects in dependency order** (if SubObject A contains SubObject B, create B first):
|
|
593
|
-
|
|
594
|
-
```bash
|
|
595
|
-
lt server object --name <ObjectName> \
|
|
596
|
-
--prop-name-0 <name> --prop-type-0 <type> \
|
|
597
|
-
--prop-name-1 <name> --prop-type-1 <type> \
|
|
598
|
-
...
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
**Apply modifiers**:
|
|
602
|
-
- Optional: `--prop-nullable-X true`
|
|
603
|
-
- Array: `--prop-array-X true`
|
|
604
|
-
- Enum: `--prop-enum-X <EnumName>`
|
|
605
|
-
- Schema: `--prop-schema-X <SchemaName>`
|
|
606
|
-
|
|
607
|
-
### Phase 3: Module Creation
|
|
608
|
-
|
|
609
|
-
**Create modules with all properties**:
|
|
610
|
-
|
|
611
|
-
```bash
|
|
612
|
-
lt server module --name <ModuleName> --controller <Rest|GraphQL|Both> \
|
|
613
|
-
--prop-name-0 <name> --prop-type-0 <type> \
|
|
614
|
-
--prop-name-1 <name> --prop-type-1 <type> \
|
|
615
|
-
...
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
**For references to other modules**:
|
|
619
|
-
```bash
|
|
620
|
-
--prop-name-X author --prop-type-X ObjectId --prop-reference-X User
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
**For embedded objects**:
|
|
624
|
-
```bash
|
|
625
|
-
--prop-name-X address --prop-schema-X Address
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
### Phase 4: Inheritance Handling
|
|
629
|
-
|
|
630
|
-
When a model extends another model (e.g., `Extends: Profile`):
|
|
631
|
-
|
|
632
|
-
1. **Identify parent model location**:
|
|
633
|
-
- Core models (from @lenne.tech/nest-server): CoreModel, CorePersisted, etc.
|
|
634
|
-
- Custom parent models: Need to find in project
|
|
635
|
-
|
|
636
|
-
2. **For Core parent models**:
|
|
637
|
-
- Replace in model file: `extends CoreModel` → `extends ParentModel`
|
|
638
|
-
- Import: `import { ParentModel } from './path'`
|
|
639
|
-
|
|
640
|
-
3. **For custom parent models (objects/other modules)**:
|
|
641
|
-
- Model extends parent object: Import and extend
|
|
642
|
-
- Input files must include parent properties
|
|
643
|
-
|
|
644
|
-
4. **Input/Output inheritance**:
|
|
645
|
-
- **CreateInput**: Must include ALL required properties from parent AND model
|
|
646
|
-
- **UpdateInput**: Include all properties as optional
|
|
647
|
-
- Check parent's CreateInput for required fields
|
|
648
|
-
- Copy required fields to child's CreateInput
|
|
649
|
-
|
|
650
|
-
**Example**: If `BuyerProfile` extends `Profile`:
|
|
651
|
-
```typescript
|
|
652
|
-
// buyer-profile.model.ts
|
|
653
|
-
import { Profile } from '../../common/objects/profile/profile.object';
|
|
654
|
-
export class BuyerProfile extends Profile { ... }
|
|
655
|
-
|
|
656
|
-
// buyer-profile-create.input.ts
|
|
657
|
-
// Must include ALL required fields from Profile's create input + BuyerProfile fields
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
### Phase 5: Description Management
|
|
661
|
-
|
|
662
|
-
**⚠️ CRITICAL PHASE - Refer to "CRITICAL: DESCRIPTION MANAGEMENT" section at the top of this document!**
|
|
663
|
-
|
|
664
|
-
This phase is often done incorrectly. Follow these steps EXACTLY:
|
|
665
|
-
|
|
666
|
-
#### Step 5.1: Extract Descriptions from User Input
|
|
667
|
-
|
|
668
|
-
**BEFORE applying any descriptions, review the original specification:**
|
|
669
|
-
|
|
670
|
-
Go back to the user's original specification and extract ALL comments that appear after `//`:
|
|
671
|
-
|
|
672
|
-
```
|
|
673
|
-
Module: Product
|
|
674
|
-
- name: string // Product name
|
|
675
|
-
- price: number // Produktpreis
|
|
676
|
-
- description?: string // Produktbeschreibung
|
|
677
|
-
- stock: number // Current inventory
|
|
678
|
-
|
|
679
|
-
SubObject: Address
|
|
680
|
-
- street: string // Straße
|
|
681
|
-
- city: string // City name
|
|
682
|
-
- zipCode: string // Postleitzahl
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
**Create a mapping**:
|
|
686
|
-
```
|
|
687
|
-
Product.name → "Product name" (English)
|
|
688
|
-
Product.price → "Produktpreis" (German)
|
|
689
|
-
Product.description → "Produktbeschreibung" (German)
|
|
690
|
-
Product.stock → "Current inventory" (English)
|
|
691
|
-
Address.street → "Straße" (German)
|
|
692
|
-
Address.city → "City name" (English)
|
|
693
|
-
Address.zipCode → "Postleitzahl" (German)
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
#### Step 5.2: Format Descriptions
|
|
697
|
-
|
|
698
|
-
**Rule**: `"ENGLISH_DESCRIPTION (DEUTSCHE_BESCHREIBUNG)"`
|
|
699
|
-
|
|
700
|
-
Apply formatting rules:
|
|
701
|
-
|
|
702
|
-
1. **If comment is in English**:
|
|
703
|
-
```
|
|
704
|
-
// Product name
|
|
705
|
-
```
|
|
706
|
-
→ Use as: `description: 'Product name'`
|
|
707
|
-
|
|
708
|
-
Fix typos if needed:
|
|
709
|
-
```
|
|
710
|
-
// Prodcut name (typo)
|
|
711
|
-
```
|
|
712
|
-
→ Use as: `description: 'Product name'` (typo corrected)
|
|
713
|
-
|
|
714
|
-
2. **If comment is in German**:
|
|
715
|
-
```
|
|
716
|
-
// Produktpreis
|
|
717
|
-
```
|
|
718
|
-
→ Translate and add original: `description: 'Product price (Produktpreis)'`
|
|
719
|
-
|
|
720
|
-
```
|
|
721
|
-
// Straße
|
|
722
|
-
```
|
|
723
|
-
→ Translate and add original: `description: 'Street (Straße)'`
|
|
724
|
-
|
|
725
|
-
Fix typos in original:
|
|
726
|
-
```
|
|
727
|
-
// Postleizahl (typo: missing 't')
|
|
728
|
-
```
|
|
729
|
-
→ Translate and add corrected: `description: 'Postal code (Postleitzahl)'`
|
|
730
|
-
|
|
731
|
-
3. **If no comment provided**:
|
|
732
|
-
→ Create meaningful English description: `description: 'User email address'`
|
|
733
|
-
|
|
734
|
-
**⚠️ CRITICAL - Preserve Original Wording**:
|
|
735
|
-
|
|
736
|
-
- ✅ **DO:** Fix spelling/typos only
|
|
737
|
-
- ❌ **DON'T:** Rephrase, expand, or improve wording
|
|
738
|
-
- ❌ **DON'T:** Change terms (they may be predefined/referenced by external systems)
|
|
739
|
-
|
|
740
|
-
**Examples**:
|
|
741
|
-
```
|
|
742
|
-
✅ CORRECT:
|
|
743
|
-
// Straße → 'Street (Straße)' (preserve word)
|
|
744
|
-
// Produkt → 'Product (Produkt)' (don't add "name")
|
|
745
|
-
// Status → 'Status (Status)' (same in both languages)
|
|
746
|
-
|
|
747
|
-
❌ WRONG:
|
|
748
|
-
// Straße → 'Street name (Straßenname)' (changed word!)
|
|
749
|
-
// Produkt → 'Product name (Produktname)' (added word!)
|
|
750
|
-
// Status → 'Current status (Aktueller Status)' (added word!)
|
|
751
|
-
```
|
|
752
|
-
|
|
753
|
-
#### Step 5.3: Apply Descriptions EVERYWHERE
|
|
754
|
-
|
|
755
|
-
**🚨 MOST IMPORTANT: Apply SAME description to ALL files!**
|
|
756
|
-
|
|
757
|
-
For **EVERY property in EVERY Module**:
|
|
758
|
-
|
|
759
|
-
1. Open `<module>.model.ts` → Add description to property
|
|
760
|
-
2. Open `inputs/<module>-create.input.ts` → Add SAME description to property
|
|
761
|
-
3. Open `inputs/<module>.input.ts` → Add SAME description to property
|
|
762
|
-
|
|
763
|
-
For **EVERY property in EVERY SubObject**:
|
|
764
|
-
|
|
765
|
-
1. Open `objects/<object>/<object>.object.ts` → Add description to property
|
|
766
|
-
2. Open `objects/<object>/<object>-create.input.ts` → Add SAME description to property
|
|
767
|
-
3. Open `objects/<object>/<object>.input.ts` → Add SAME description to property
|
|
768
|
-
|
|
769
|
-
**Example for Module "Product" with property "price"**:
|
|
770
|
-
|
|
771
|
-
```typescript
|
|
772
|
-
// File: src/server/modules/product/product.model.ts
|
|
773
|
-
@UnifiedField({ description: 'Product price (Produktpreis)' })
|
|
774
|
-
price: number;
|
|
775
|
-
|
|
776
|
-
// File: src/server/modules/product/inputs/product-create.input.ts
|
|
777
|
-
@UnifiedField({ description: 'Product price (Produktpreis)' })
|
|
778
|
-
price: number;
|
|
779
|
-
|
|
780
|
-
// File: src/server/modules/product/inputs/product.input.ts
|
|
781
|
-
@UnifiedField({ description: 'Product price (Produktpreis)' })
|
|
782
|
-
price?: number;
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
**Example for SubObject "Address" with property "street"**:
|
|
786
|
-
|
|
787
|
-
```typescript
|
|
788
|
-
// File: src/server/common/objects/address/address.object.ts
|
|
789
|
-
@UnifiedField({ description: 'Street (Straße)' })
|
|
790
|
-
street: string;
|
|
791
|
-
|
|
792
|
-
// File: src/server/common/objects/address/address-create.input.ts
|
|
793
|
-
@UnifiedField({ description: 'Street (Straße)' })
|
|
794
|
-
street: string;
|
|
795
|
-
|
|
796
|
-
// File: src/server/common/objects/address/address.input.ts
|
|
797
|
-
@UnifiedField({ description: 'Street (Straße)' })
|
|
798
|
-
street?: string;
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
#### Step 5.4: Add Class-Level Descriptions
|
|
802
|
-
|
|
803
|
-
Also add descriptions to the `@ObjectType()` and `@InputType()` decorators:
|
|
804
|
-
|
|
805
|
-
```typescript
|
|
806
|
-
@ObjectType({ description: 'Product entity (Produkt-Entität)' })
|
|
807
|
-
export class Product extends CoreModel { ... }
|
|
808
|
-
|
|
809
|
-
@InputType({ description: 'Product creation data (Produkt-Erstellungsdaten)' })
|
|
810
|
-
export class ProductCreateInput { ... }
|
|
811
|
-
|
|
812
|
-
@InputType({ description: 'Product update data (Produkt-Aktualisierungsdaten)' })
|
|
813
|
-
export class ProductInput { ... }
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
#### Step 5.5: Verify Consistency
|
|
817
|
-
|
|
818
|
-
After applying all descriptions, verify:
|
|
819
|
-
|
|
820
|
-
- [ ] All user-provided comments extracted and processed
|
|
821
|
-
- [ ] All German descriptions translated to format: `ENGLISH (DEUTSCH)`
|
|
822
|
-
- [ ] All English descriptions kept as-is
|
|
823
|
-
- [ ] Module Model has descriptions on all properties
|
|
824
|
-
- [ ] Module CreateInput has SAME descriptions on all properties
|
|
825
|
-
- [ ] Module UpdateInput has SAME descriptions on all properties
|
|
826
|
-
- [ ] SubObject has descriptions on all properties
|
|
827
|
-
- [ ] SubObject CreateInput has SAME descriptions on all properties
|
|
828
|
-
- [ ] SubObject UpdateInput has SAME descriptions on all properties
|
|
829
|
-
- [ ] Class-level decorators have descriptions
|
|
830
|
-
- [ ] NO inconsistencies (same property, different descriptions)
|
|
831
|
-
|
|
832
|
-
**If ANY checkbox is unchecked, STOP and fix before continuing to Phase 6!**
|
|
833
|
-
|
|
834
|
-
### Phase 6: Enum File Creation
|
|
835
|
-
|
|
836
|
-
For each enum used, create enum file manually:
|
|
837
|
-
|
|
838
|
-
```typescript
|
|
839
|
-
// src/server/common/enums/status.enum.ts
|
|
840
|
-
export enum StatusEnum {
|
|
841
|
-
PENDING = 'PENDING',
|
|
842
|
-
ACTIVE = 'ACTIVE',
|
|
843
|
-
COMPLETED = 'COMPLETED',
|
|
844
|
-
}
|
|
845
|
-
```
|
|
846
|
-
|
|
847
|
-
**Naming convention**:
|
|
848
|
-
- File: `kebab-case.enum.ts`
|
|
849
|
-
- Enum: `PascalCaseEnum`
|
|
850
|
-
- Values: `UPPER_SNAKE_CASE`
|
|
851
|
-
|
|
852
|
-
### Phase 7: API Test Creation
|
|
853
|
-
|
|
854
|
-
**⚠️ CRITICAL: Test Type Requirement**
|
|
855
|
-
|
|
856
|
-
**ONLY create API tests using TestHelper - NEVER create direct Service tests!**
|
|
857
|
-
|
|
858
|
-
- ✅ **DO:** Create tests that call REST endpoints or GraphQL queries/mutations using `TestHelper`
|
|
859
|
-
- ✅ **DO:** Test through the API layer (Controller/Resolver → Service → Database)
|
|
860
|
-
- ❌ **DON'T:** Create tests that directly instantiate or call Service methods
|
|
861
|
-
- ❌ **DON'T:** Create unit tests for Services (e.g., `user.service.spec.ts`)
|
|
862
|
-
- ❌ **DON'T:** Mock dependencies or bypass the API layer
|
|
863
|
-
|
|
864
|
-
**Why API tests only?**
|
|
865
|
-
- API tests validate the complete security model (decorators, guards, permissions)
|
|
866
|
-
- Direct Service tests bypass authentication and authorization checks
|
|
867
|
-
- TestHelper provides all necessary tools for comprehensive API testing
|
|
868
|
-
|
|
869
|
-
**Exception: Direct database/service access for test setup/cleanup ONLY**
|
|
870
|
-
|
|
871
|
-
Direct database or service access is ONLY allowed for:
|
|
872
|
-
|
|
873
|
-
- ✅ **Test Setup (beforeAll/beforeEach)**:
|
|
874
|
-
- Setting user roles in database: `await db.collection('users').updateOne({ _id: userId }, { $set: { roles: ['admin'] } })`
|
|
875
|
-
- Setting verified flag: `await db.collection('users').updateOne({ _id: userId }, { $set: { verified: true } })`
|
|
876
|
-
- Creating prerequisite test data that can't be created via API
|
|
877
|
-
|
|
878
|
-
- ✅ **Test Cleanup (afterAll/afterEach)**:
|
|
879
|
-
- Deleting test objects: `await db.collection('products').deleteMany({ createdBy: testUserId })`
|
|
880
|
-
- Cleaning up test data: `await db.collection('users').deleteOne({ email: 'test@example.com' })`
|
|
881
|
-
|
|
882
|
-
- ❌ **NEVER for testing functionality**:
|
|
883
|
-
- Don't call `userService.create()` to test user creation - use API endpoint!
|
|
884
|
-
- Don't call `productService.update()` to test updates - use API endpoint!
|
|
885
|
-
- Don't access database to verify results - query via API instead!
|
|
886
|
-
|
|
887
|
-
**Example of correct usage:**
|
|
888
|
-
|
|
889
|
-
```typescript
|
|
890
|
-
describe('Product Tests', () => {
|
|
891
|
-
let adminToken: string;
|
|
892
|
-
let userId: string;
|
|
893
|
-
|
|
894
|
-
beforeAll(async () => {
|
|
895
|
-
// ✅ ALLOWED: Direct DB access for setup
|
|
896
|
-
const user = await testHelper.rest('/auth/signup', {
|
|
897
|
-
method: 'POST',
|
|
898
|
-
payload: { email: 'admin@test.com', password: 'password' }
|
|
899
|
-
});
|
|
900
|
-
userId = user.id;
|
|
901
|
-
|
|
902
|
-
// ✅ ALLOWED: Direct DB manipulation for test setup
|
|
903
|
-
await db.collection('users').updateOne(
|
|
904
|
-
{ _id: new ObjectId(userId) },
|
|
905
|
-
{ $set: { roles: ['admin'], verified: true } }
|
|
906
|
-
);
|
|
907
|
-
|
|
908
|
-
// Get token via API
|
|
909
|
-
const auth = await testHelper.rest('/auth/signin', {
|
|
910
|
-
method: 'POST',
|
|
911
|
-
payload: { email: 'admin@test.com', password: 'password' }
|
|
912
|
-
});
|
|
913
|
-
adminToken = auth.token;
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
it('should create product', async () => {
|
|
917
|
-
// ✅ CORRECT: Test via API
|
|
918
|
-
const result = await testHelper.rest('/api/products', {
|
|
919
|
-
method: 'POST',
|
|
920
|
-
payload: { name: 'Test Product' },
|
|
921
|
-
token: adminToken
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
expect(result.name).toBe('Test Product');
|
|
925
|
-
|
|
926
|
-
// ❌ WRONG: Don't verify via DB
|
|
927
|
-
// const dbProduct = await db.collection('products').findOne({ _id: result.id });
|
|
928
|
-
|
|
929
|
-
// ✅ CORRECT: Verify via API
|
|
930
|
-
const fetched = await testHelper.rest(`/api/products/${result.id}`, {
|
|
931
|
-
method: 'GET',
|
|
932
|
-
token: adminToken
|
|
933
|
-
});
|
|
934
|
-
expect(fetched.name).toBe('Test Product');
|
|
935
|
-
});
|
|
936
|
-
|
|
937
|
-
afterAll(async () => {
|
|
938
|
-
// ✅ ALLOWED: Direct DB access for cleanup
|
|
939
|
-
await db.collection('products').deleteMany({ createdBy: userId });
|
|
940
|
-
await db.collection('users').deleteOne({ _id: new ObjectId(userId) });
|
|
941
|
-
});
|
|
942
|
-
});
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
---
|
|
946
|
-
|
|
947
|
-
**⚠️ CRITICAL: Test Creation Process**
|
|
948
|
-
|
|
949
|
-
Creating API tests is NOT just about testing functionality - it's about **validating the security model**. You MUST follow this exact process:
|
|
950
|
-
|
|
951
|
-
---
|
|
952
|
-
|
|
953
|
-
#### Step 1: 🔍 MANDATORY Permission Analysis (BEFORE writing ANY test)
|
|
954
|
-
|
|
955
|
-
**YOU MUST analyze these THREE layers BEFORE writing a single test:**
|
|
956
|
-
|
|
957
|
-
1. **Controller/Resolver Layer** - Check `@Roles()` decorator:
|
|
958
|
-
```typescript
|
|
959
|
-
// In product.resolver.ts
|
|
960
|
-
@Roles(RoleEnum.S_EVERYONE) // ← WHO can call this?
|
|
961
|
-
@Query(() => [Product])
|
|
962
|
-
async getProducts() { ... }
|
|
963
|
-
|
|
964
|
-
@Roles(RoleEnum.S_USER) // ← All signed-in users
|
|
965
|
-
@Mutation(() => Product)
|
|
966
|
-
async createProduct(@Args('input') input: ProductCreateInput) { ... }
|
|
967
|
-
|
|
968
|
-
@Roles(RoleEnum.ADMIN, RoleEnum.S_CREATOR) // ← Only admin or creator
|
|
969
|
-
@Mutation(() => Product)
|
|
970
|
-
async updateProduct(@Args('id') id: string, @Args('input') input: ProductInput) { ... }
|
|
971
|
-
```
|
|
972
|
-
|
|
973
|
-
2. **Model Layer** - Check `@Restricted()` and `securityCheck()`:
|
|
974
|
-
```typescript
|
|
975
|
-
// In product.model.ts
|
|
976
|
-
export class Product extends CoreModel {
|
|
977
|
-
securityCheck(user: User, force?: boolean) {
|
|
978
|
-
if (force || user?.hasRole(RoleEnum.ADMIN)) {
|
|
979
|
-
return this; // Admin sees all
|
|
980
|
-
}
|
|
981
|
-
if (this.isPublic) {
|
|
982
|
-
return this; // Everyone sees public products
|
|
983
|
-
}
|
|
984
|
-
if (!equalIds(user, this.createdBy)) {
|
|
985
|
-
return undefined; // Non-creator gets nothing
|
|
986
|
-
}
|
|
987
|
-
return this; // Creator sees own products
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
```
|
|
991
|
-
|
|
992
|
-
3. **Service Layer** - Check `serviceOptions.roles` usage:
|
|
993
|
-
```typescript
|
|
994
|
-
// In product.service.ts
|
|
995
|
-
async update(id: string, input: ProductInput, serviceOptions?: ServiceOptions) {
|
|
996
|
-
// Check if user has ADMIN or S_CREATOR role
|
|
997
|
-
// ...
|
|
998
|
-
}
|
|
999
|
-
```
|
|
1000
|
-
|
|
1001
|
-
**Permission Analysis Checklist:**
|
|
1002
|
-
- [ ] I have checked ALL `@Roles()` decorators in controller/resolver
|
|
1003
|
-
- [ ] I have read the complete `securityCheck()` method in the model
|
|
1004
|
-
- [ ] I have checked ALL `@Restricted()` decorators
|
|
1005
|
-
- [ ] I understand WHO can CREATE (usually S_USER or ADMIN)
|
|
1006
|
-
- [ ] I understand WHO can READ (S_USER + securityCheck filtering)
|
|
1007
|
-
- [ ] I understand WHO can UPDATE (usually ADMIN + S_CREATOR)
|
|
1008
|
-
- [ ] I understand WHO can DELETE (usually ADMIN + S_CREATOR)
|
|
1009
|
-
|
|
1010
|
-
**Common Permission Patterns:**
|
|
1011
|
-
- `S_EVERYONE` → No authentication required
|
|
1012
|
-
- `S_USER` → Any signed-in user
|
|
1013
|
-
- `ADMIN` → User with 'admin' role
|
|
1014
|
-
- `S_CREATOR` → User who created the resource (user.id === object.createdBy)
|
|
1015
|
-
|
|
1016
|
-
---
|
|
1017
|
-
|
|
1018
|
-
#### Step 2: 🎯 Apply Principle of Least Privilege
|
|
1019
|
-
|
|
1020
|
-
**GOLDEN RULE**: Always test with the **LEAST privileged user** who is still authorized.
|
|
1021
|
-
|
|
1022
|
-
**Decision Tree:**
|
|
1023
|
-
|
|
1024
|
-
```
|
|
1025
|
-
Is endpoint marked with @Roles(RoleEnum.S_EVERYONE)?
|
|
1026
|
-
├─ YES → Test WITHOUT token (unauthenticated)
|
|
1027
|
-
└─ NO → Is endpoint marked with @Roles(RoleEnum.S_USER)?
|
|
1028
|
-
├─ YES → Test WITH regular user token (NOT admin, NOT creator)
|
|
1029
|
-
└─ NO → Is endpoint marked with @Roles(RoleEnum.ADMIN, RoleEnum.S_CREATOR)?
|
|
1030
|
-
├─ For UPDATE/DELETE → Test WITH creator token (user who created it)
|
|
1031
|
-
└─ For ADMIN-only → Test WITH admin token
|
|
1032
|
-
```
|
|
1033
|
-
|
|
1034
|
-
**❌ WRONG Approach:**
|
|
1035
|
-
```typescript
|
|
1036
|
-
// BAD: Using admin for everything
|
|
1037
|
-
it('should create product', async () => {
|
|
1038
|
-
const result = await testHelper.graphQl({
|
|
1039
|
-
name: 'createProduct',
|
|
1040
|
-
type: TestGraphQLType.MUTATION,
|
|
1041
|
-
arguments: { input: { name: 'Test' } },
|
|
1042
|
-
fields: ['id']
|
|
1043
|
-
}, { token: adminToken }); // ❌ WRONG - Over-privileged!
|
|
1044
|
-
});
|
|
1045
|
-
```
|
|
1046
|
-
|
|
1047
|
-
**✅ CORRECT Approach:**
|
|
1048
|
-
```typescript
|
|
1049
|
-
// GOOD: Using least privileged user
|
|
1050
|
-
it('should create product as regular user', async () => {
|
|
1051
|
-
const result = await testHelper.graphQl({
|
|
1052
|
-
name: 'createProduct',
|
|
1053
|
-
type: TestGraphQLType.MUTATION,
|
|
1054
|
-
arguments: { input: { name: 'Test' } },
|
|
1055
|
-
fields: ['id']
|
|
1056
|
-
}, { token: userToken }); // ✅ CORRECT - S_USER is enough!
|
|
1057
|
-
});
|
|
1058
|
-
```
|
|
1059
|
-
|
|
1060
|
-
---
|
|
1061
|
-
|
|
1062
|
-
#### Step 3: 📋 Create Test User Matrix
|
|
1063
|
-
|
|
1064
|
-
Based on your permission analysis, create test users:
|
|
1065
|
-
|
|
1066
|
-
```typescript
|
|
1067
|
-
describe('Product API', () => {
|
|
1068
|
-
let testHelper: TestHelper;
|
|
1069
|
-
|
|
1070
|
-
// Create users based on ACTUAL needs (not all of them!)
|
|
1071
|
-
let noToken: undefined; // For S_EVERYONE endpoints
|
|
1072
|
-
let userToken: string; // For S_USER endpoints
|
|
1073
|
-
let creatorToken: string; // For S_CREATOR (will create test data)
|
|
1074
|
-
let otherUserToken: string; // For testing "not creator" scenarios
|
|
1075
|
-
let adminToken: string; // Only if ADMIN-specific endpoints exist
|
|
1076
|
-
|
|
1077
|
-
let createdProductId: string;
|
|
1078
|
-
|
|
1079
|
-
beforeAll(async () => {
|
|
1080
|
-
testHelper = new TestHelper(app);
|
|
1081
|
-
|
|
1082
|
-
// Only create users you ACTUALLY need based on @Roles() analysis!
|
|
1083
|
-
|
|
1084
|
-
// Regular user (for S_USER endpoints)
|
|
1085
|
-
const userAuth = await testHelper.graphQl({
|
|
1086
|
-
name: 'signUp',
|
|
1087
|
-
type: TestGraphQLType.MUTATION,
|
|
1088
|
-
arguments: {
|
|
1089
|
-
input: {
|
|
1090
|
-
email: 'user@test.com',
|
|
1091
|
-
password: 'password',
|
|
1092
|
-
roles: ['user'] // Regular user, no special privileges
|
|
1093
|
-
}
|
|
1094
|
-
},
|
|
1095
|
-
fields: ['token', 'user { id }']
|
|
1096
|
-
});
|
|
1097
|
-
userToken = userAuth.token;
|
|
1098
|
-
|
|
1099
|
-
// Creator user (will create test objects)
|
|
1100
|
-
const creatorAuth = await testHelper.graphQl({
|
|
1101
|
-
name: 'signUp',
|
|
1102
|
-
type: TestGraphQLType.MUTATION,
|
|
1103
|
-
arguments: {
|
|
1104
|
-
input: {
|
|
1105
|
-
email: 'creator@test.com',
|
|
1106
|
-
password: 'password',
|
|
1107
|
-
roles: ['user']
|
|
1108
|
-
}
|
|
1109
|
-
},
|
|
1110
|
-
fields: ['token', 'user { id }']
|
|
1111
|
-
});
|
|
1112
|
-
creatorToken = creatorAuth.token;
|
|
1113
|
-
|
|
1114
|
-
// Other user (to test "not creator" scenarios)
|
|
1115
|
-
const otherUserAuth = await testHelper.graphQl({
|
|
1116
|
-
name: 'signUp',
|
|
1117
|
-
type: TestGraphQLType.MUTATION,
|
|
1118
|
-
arguments: {
|
|
1119
|
-
input: {
|
|
1120
|
-
email: 'other@test.com',
|
|
1121
|
-
password: 'password',
|
|
1122
|
-
roles: ['user']
|
|
1123
|
-
}
|
|
1124
|
-
},
|
|
1125
|
-
fields: ['token', 'user { id }']
|
|
1126
|
-
});
|
|
1127
|
-
otherUserToken = otherUserAuth.token;
|
|
1128
|
-
|
|
1129
|
-
// Admin user (ONLY if truly needed!)
|
|
1130
|
-
const adminAuth = await testHelper.graphQl({
|
|
1131
|
-
name: 'signUp',
|
|
1132
|
-
type: TestGraphQLType.MUTATION,
|
|
1133
|
-
arguments: {
|
|
1134
|
-
input: {
|
|
1135
|
-
email: 'admin@test.com',
|
|
1136
|
-
password: 'password',
|
|
1137
|
-
roles: ['admin', 'user'] // ← 'admin' role!
|
|
1138
|
-
}
|
|
1139
|
-
},
|
|
1140
|
-
fields: ['token']
|
|
1141
|
-
});
|
|
1142
|
-
adminToken = adminAuth.token;
|
|
1143
|
-
});
|
|
1144
|
-
|
|
1145
|
-
afterAll(async () => {
|
|
1146
|
-
// Clean up with appropriate privileged user
|
|
1147
|
-
if (createdProductId) {
|
|
1148
|
-
// Use creator or admin token for cleanup
|
|
1149
|
-
await testHelper.graphQl({
|
|
1150
|
-
name: 'deleteProduct',
|
|
1151
|
-
type: TestGraphQLType.MUTATION,
|
|
1152
|
-
arguments: { id: createdProductId },
|
|
1153
|
-
fields: ['id']
|
|
1154
|
-
}, { token: creatorToken });
|
|
1155
|
-
}
|
|
1156
|
-
});
|
|
1157
|
-
});
|
|
1158
|
-
```
|
|
1159
|
-
|
|
1160
|
-
---
|
|
1161
|
-
|
|
1162
|
-
#### Step 4: ✅ Write Tests with Correct Privileges
|
|
1163
|
-
|
|
1164
|
-
**Example 1: S_EVERYONE endpoint (public access)**
|
|
1165
|
-
|
|
1166
|
-
```typescript
|
|
1167
|
-
// Endpoint: @Roles(RoleEnum.S_EVERYONE)
|
|
1168
|
-
describe('Public Endpoints', () => {
|
|
1169
|
-
it('should get public products WITHOUT token', async () => {
|
|
1170
|
-
const result = await testHelper.graphQl({
|
|
1171
|
-
name: 'getPublicProducts',
|
|
1172
|
-
type: TestGraphQLType.QUERY,
|
|
1173
|
-
fields: ['id', 'name', 'price']
|
|
1174
|
-
}); // ← NO TOKEN! S_EVERYONE means unauthenticated is OK
|
|
1175
|
-
|
|
1176
|
-
expect(result).toBeDefined();
|
|
1177
|
-
expect(Array.isArray(result)).toBe(true);
|
|
1178
|
-
});
|
|
1179
|
-
});
|
|
1180
|
-
```
|
|
1181
|
-
|
|
1182
|
-
**Example 2: S_USER endpoint (any authenticated user)**
|
|
1183
|
-
|
|
1184
|
-
```typescript
|
|
1185
|
-
// Endpoint: @Roles(RoleEnum.S_USER)
|
|
1186
|
-
describe('Create Product', () => {
|
|
1187
|
-
it('should create product as regular user', async () => {
|
|
1188
|
-
const result = await testHelper.graphQl({
|
|
1189
|
-
name: 'createProduct',
|
|
1190
|
-
type: TestGraphQLType.MUTATION,
|
|
1191
|
-
arguments: { input: { name: 'Test Product', price: 10 } },
|
|
1192
|
-
fields: ['id', 'name', 'price', 'createdBy']
|
|
1193
|
-
}, { token: userToken }); // ← Regular user, NOT admin!
|
|
1194
|
-
|
|
1195
|
-
expect(result).toBeDefined();
|
|
1196
|
-
expect(result.name).toBe('Test Product');
|
|
1197
|
-
createdProductId = result.id;
|
|
1198
|
-
|
|
1199
|
-
// Verify creator is set
|
|
1200
|
-
expect(result.createdBy).toBe(userAuth.user.id);
|
|
1201
|
-
});
|
|
1202
|
-
});
|
|
1203
|
-
```
|
|
1204
|
-
|
|
1205
|
-
**Example 3: UPDATE - S_CREATOR or ADMIN**
|
|
1206
|
-
|
|
1207
|
-
```typescript
|
|
1208
|
-
// Endpoint: @Roles(RoleEnum.ADMIN, RoleEnum.S_CREATOR)
|
|
1209
|
-
describe('Update Product', () => {
|
|
1210
|
-
it('should update product as creator', async () => {
|
|
1211
|
-
// First, creator creates a product
|
|
1212
|
-
const created = await testHelper.graphQl({
|
|
1213
|
-
name: 'createProduct',
|
|
1214
|
-
type: TestGraphQLType.MUTATION,
|
|
1215
|
-
arguments: { input: { name: 'Original', price: 10 } },
|
|
1216
|
-
fields: ['id', 'name']
|
|
1217
|
-
}, { token: creatorToken });
|
|
1218
|
-
|
|
1219
|
-
// Then, same creator updates it
|
|
1220
|
-
const result = await testHelper.graphQl({
|
|
1221
|
-
name: 'updateProduct',
|
|
1222
|
-
type: TestGraphQLType.MUTATION,
|
|
1223
|
-
arguments: {
|
|
1224
|
-
id: created.id,
|
|
1225
|
-
input: { name: 'Updated' }
|
|
1226
|
-
},
|
|
1227
|
-
fields: ['id', 'name']
|
|
1228
|
-
}, { token: creatorToken }); // ← Use CREATOR token (least privilege!)
|
|
1229
|
-
|
|
1230
|
-
expect(result.name).toBe('Updated');
|
|
1231
|
-
});
|
|
1232
|
-
|
|
1233
|
-
it('should update any product as admin', async () => {
|
|
1234
|
-
// Admin can update products they did NOT create
|
|
1235
|
-
const result = await testHelper.graphQl({
|
|
1236
|
-
name: 'updateProduct',
|
|
1237
|
-
type: TestGraphQLType.MUTATION,
|
|
1238
|
-
arguments: {
|
|
1239
|
-
id: createdProductId, // Created by different user
|
|
1240
|
-
input: { name: 'Admin Updated' }
|
|
1241
|
-
},
|
|
1242
|
-
fields: ['id', 'name']
|
|
1243
|
-
}, { token: adminToken }); // ← Admin needed for other's products
|
|
1244
|
-
|
|
1245
|
-
expect(result.name).toBe('Admin Updated');
|
|
1246
|
-
});
|
|
1247
|
-
});
|
|
1248
|
-
```
|
|
1249
|
-
|
|
1250
|
-
---
|
|
1251
|
-
|
|
1252
|
-
#### Step 5: 🛡️ MANDATORY: Test Permission Failures
|
|
1253
|
-
|
|
1254
|
-
**CRITICAL**: You MUST test that unauthorized users are BLOCKED. This validates the security model.
|
|
1255
|
-
|
|
1256
|
-
```typescript
|
|
1257
|
-
describe('Security Validation', () => {
|
|
1258
|
-
describe('Unauthorized Access', () => {
|
|
1259
|
-
it('should FAIL to create product without authentication', async () => {
|
|
1260
|
-
// @Roles(RoleEnum.S_USER) requires authentication
|
|
1261
|
-
const result = await testHelper.graphQl({
|
|
1262
|
-
name: 'createProduct',
|
|
1263
|
-
type: TestGraphQLType.MUTATION,
|
|
1264
|
-
arguments: { input: { name: 'Hack', price: 1 } },
|
|
1265
|
-
fields: ['id']
|
|
1266
|
-
}, { statusCode: 401 }); // ← NO TOKEN = should fail with 401
|
|
1267
|
-
|
|
1268
|
-
expect(result.errors).toBeDefined();
|
|
1269
|
-
expect(result.errors[0].message).toContain('Unauthorized');
|
|
1270
|
-
});
|
|
1271
|
-
|
|
1272
|
-
it('should FAIL to update product as non-creator', async () => {
|
|
1273
|
-
// @Roles(RoleEnum.ADMIN, RoleEnum.S_CREATOR)
|
|
1274
|
-
const result = await testHelper.graphQl({
|
|
1275
|
-
name: 'updateProduct',
|
|
1276
|
-
type: TestGraphQLType.MUTATION,
|
|
1277
|
-
arguments: {
|
|
1278
|
-
id: createdProductId, // Created by creatorUser
|
|
1279
|
-
input: { name: 'Hacked' }
|
|
1280
|
-
},
|
|
1281
|
-
fields: ['id']
|
|
1282
|
-
}, { token: otherUserToken, statusCode: 403 }); // ← Different user = should fail with 403
|
|
1283
|
-
|
|
1284
|
-
expect(result.errors).toBeDefined();
|
|
1285
|
-
expect(result.errors[0].message).toContain('Forbidden');
|
|
1286
|
-
});
|
|
1287
|
-
|
|
1288
|
-
it('should FAIL to delete product as non-creator', async () => {
|
|
1289
|
-
const result = await testHelper.graphQl({
|
|
1290
|
-
name: 'deleteProduct',
|
|
1291
|
-
type: TestGraphQLType.MUTATION,
|
|
1292
|
-
arguments: { id: createdProductId },
|
|
1293
|
-
fields: ['id']
|
|
1294
|
-
}, { token: otherUserToken, statusCode: 403 });
|
|
1295
|
-
|
|
1296
|
-
expect(result.errors).toBeDefined();
|
|
1297
|
-
});
|
|
1298
|
-
|
|
1299
|
-
it('should FAIL to read private product as different user', async () => {
|
|
1300
|
-
// If securityCheck() blocks non-creators
|
|
1301
|
-
const result = await testHelper.graphQl({
|
|
1302
|
-
name: 'getProduct',
|
|
1303
|
-
type: TestGraphQLType.QUERY,
|
|
1304
|
-
arguments: { id: privateProductId },
|
|
1305
|
-
fields: ['id', 'name']
|
|
1306
|
-
}, { token: otherUserToken });
|
|
1307
|
-
|
|
1308
|
-
// securityCheck returns undefined for non-creator
|
|
1309
|
-
expect(result).toBeUndefined();
|
|
1310
|
-
});
|
|
1311
|
-
});
|
|
1312
|
-
});
|
|
1313
|
-
```
|
|
1314
|
-
|
|
1315
|
-
---
|
|
1316
|
-
|
|
1317
|
-
#### Step 6: 📝 Complete Test Structure
|
|
1318
|
-
|
|
1319
|
-
**Test file location**:
|
|
1320
|
-
```
|
|
1321
|
-
tests/modules/<module-name>.e2e-spec.ts
|
|
1322
|
-
```
|
|
1323
|
-
|
|
1324
|
-
**Complete test template with proper privileges**:
|
|
1325
|
-
|
|
1326
|
-
```typescript
|
|
1327
|
-
import { TestGraphQLType, TestHelper } from '@lenne.tech/nest-server';
|
|
1328
|
-
|
|
1329
|
-
describe('Product Module E2E', () => {
|
|
1330
|
-
let testHelper: TestHelper;
|
|
1331
|
-
let userToken: string;
|
|
1332
|
-
let creatorToken: string;
|
|
1333
|
-
let otherUserToken: string;
|
|
1334
|
-
let adminToken: string;
|
|
1335
|
-
let createdProductId: string;
|
|
1336
|
-
let userAuth: any;
|
|
1337
|
-
let creatorAuth: any;
|
|
1338
|
-
|
|
1339
|
-
beforeAll(async () => {
|
|
1340
|
-
testHelper = new TestHelper(app);
|
|
1341
|
-
|
|
1342
|
-
// Create test users (based on permission analysis)
|
|
1343
|
-
userAuth = await testHelper.graphQl({
|
|
1344
|
-
name: 'signUp',
|
|
1345
|
-
type: TestGraphQLType.MUTATION,
|
|
1346
|
-
arguments: { input: { email: 'user@test.com', password: 'password', roles: ['user'] } },
|
|
1347
|
-
fields: ['token', 'user { id }']
|
|
1348
|
-
});
|
|
1349
|
-
userToken = userAuth.token;
|
|
1350
|
-
|
|
1351
|
-
creatorAuth = await testHelper.graphQl({
|
|
1352
|
-
name: 'signUp',
|
|
1353
|
-
type: TestGraphQLType.MUTATION,
|
|
1354
|
-
arguments: { input: { email: 'creator@test.com', password: 'password', roles: ['user'] } },
|
|
1355
|
-
fields: ['token', 'user { id }']
|
|
1356
|
-
});
|
|
1357
|
-
creatorToken = creatorAuth.token;
|
|
1358
|
-
|
|
1359
|
-
const otherUserAuth = await testHelper.graphQl({
|
|
1360
|
-
name: 'signUp',
|
|
1361
|
-
type: TestGraphQLType.MUTATION,
|
|
1362
|
-
arguments: { input: { email: 'other@test.com', password: 'password', roles: ['user'] } },
|
|
1363
|
-
fields: ['token']
|
|
1364
|
-
});
|
|
1365
|
-
otherUserToken = otherUserAuth.token;
|
|
1366
|
-
|
|
1367
|
-
const adminAuth = await testHelper.graphQl({
|
|
1368
|
-
name: 'signUp',
|
|
1369
|
-
type: TestGraphQLType.MUTATION,
|
|
1370
|
-
arguments: { input: { email: 'admin@test.com', password: 'password', roles: ['admin', 'user'] } },
|
|
1371
|
-
fields: ['token']
|
|
1372
|
-
});
|
|
1373
|
-
adminToken = adminAuth.token;
|
|
1374
|
-
});
|
|
1375
|
-
|
|
1376
|
-
afterAll(async () => {
|
|
1377
|
-
// Cleanup with appropriate privileges
|
|
1378
|
-
if (createdProductId) {
|
|
1379
|
-
await testHelper.graphQl({
|
|
1380
|
-
name: 'deleteProduct',
|
|
1381
|
-
type: TestGraphQLType.MUTATION,
|
|
1382
|
-
arguments: { id: createdProductId },
|
|
1383
|
-
fields: ['id']
|
|
1384
|
-
}, { token: creatorToken });
|
|
1385
|
-
}
|
|
1386
|
-
});
|
|
1387
|
-
|
|
1388
|
-
// 1. CREATE Tests (with least privileged user)
|
|
1389
|
-
describe('Create Product', () => {
|
|
1390
|
-
it('should create product as regular user', async () => {
|
|
1391
|
-
const result = await testHelper.graphQl({
|
|
1392
|
-
name: 'createProduct',
|
|
1393
|
-
type: TestGraphQLType.MUTATION,
|
|
1394
|
-
arguments: { input: { name: 'Test', price: 10 } },
|
|
1395
|
-
fields: ['id', 'name', 'price', 'createdBy']
|
|
1396
|
-
}, { token: userToken }); // ← S_USER = regular user
|
|
1397
|
-
|
|
1398
|
-
expect(result.name).toBe('Test');
|
|
1399
|
-
createdProductId = result.id;
|
|
1400
|
-
});
|
|
1401
|
-
|
|
1402
|
-
it('should FAIL to create without authentication', async () => {
|
|
1403
|
-
const result = await testHelper.graphQl({
|
|
1404
|
-
name: 'createProduct',
|
|
1405
|
-
type: TestGraphQLType.MUTATION,
|
|
1406
|
-
arguments: { input: { name: 'Fail', price: 10 } },
|
|
1407
|
-
fields: ['id']
|
|
1408
|
-
}, { statusCode: 401 }); // ← No token = should fail
|
|
1409
|
-
|
|
1410
|
-
expect(result.errors).toBeDefined();
|
|
1411
|
-
});
|
|
1412
|
-
|
|
1413
|
-
it('should FAIL to create without required fields', async () => {
|
|
1414
|
-
const result = await testHelper.graphQl({
|
|
1415
|
-
name: 'createProduct',
|
|
1416
|
-
type: TestGraphQLType.MUTATION,
|
|
1417
|
-
arguments: { input: {} },
|
|
1418
|
-
fields: ['id']
|
|
1419
|
-
}, { token: userToken, statusCode: 400 });
|
|
1420
|
-
|
|
1421
|
-
expect(result.errors).toBeDefined();
|
|
1422
|
-
});
|
|
1423
|
-
});
|
|
1424
|
-
|
|
1425
|
-
// 2. READ Tests
|
|
1426
|
-
describe('Get Products', () => {
|
|
1427
|
-
it('should get all products as regular user', async () => {
|
|
1428
|
-
const result = await testHelper.graphQl({
|
|
1429
|
-
name: 'getProducts',
|
|
1430
|
-
type: TestGraphQLType.QUERY,
|
|
1431
|
-
fields: ['id', 'name', 'price']
|
|
1432
|
-
}, { token: userToken });
|
|
1433
|
-
|
|
1434
|
-
expect(Array.isArray(result)).toBe(true);
|
|
1435
|
-
});
|
|
1436
|
-
|
|
1437
|
-
it('should get product by ID as regular user', async () => {
|
|
1438
|
-
const result = await testHelper.graphQl({
|
|
1439
|
-
name: 'getProduct',
|
|
1440
|
-
type: TestGraphQLType.QUERY,
|
|
1441
|
-
arguments: { id: createdProductId },
|
|
1442
|
-
fields: ['id', 'name', 'price']
|
|
1443
|
-
}, { token: userToken });
|
|
1444
|
-
|
|
1445
|
-
expect(result.id).toBe(createdProductId);
|
|
1446
|
-
});
|
|
1447
|
-
});
|
|
1448
|
-
|
|
1449
|
-
// 3. UPDATE Tests (with creator, not admin!)
|
|
1450
|
-
describe('Update Product', () => {
|
|
1451
|
-
let creatorProductId: string;
|
|
1452
|
-
|
|
1453
|
-
beforeAll(async () => {
|
|
1454
|
-
// Creator creates a product to test updates
|
|
1455
|
-
const created = await testHelper.graphQl({
|
|
1456
|
-
name: 'createProduct',
|
|
1457
|
-
type: TestGraphQLType.MUTATION,
|
|
1458
|
-
arguments: { input: { name: 'Creator Product', price: 20 } },
|
|
1459
|
-
fields: ['id']
|
|
1460
|
-
}, { token: creatorToken });
|
|
1461
|
-
creatorProductId = created.id;
|
|
1462
|
-
});
|
|
1463
|
-
|
|
1464
|
-
it('should update product as creator', async () => {
|
|
1465
|
-
const result = await testHelper.graphQl({
|
|
1466
|
-
name: 'updateProduct',
|
|
1467
|
-
type: TestGraphQLType.MUTATION,
|
|
1468
|
-
arguments: { id: creatorProductId, input: { name: 'Updated' } },
|
|
1469
|
-
fields: ['id', 'name']
|
|
1470
|
-
}, { token: creatorToken }); // ← CREATOR token (least privilege!)
|
|
1471
|
-
|
|
1472
|
-
expect(result.name).toBe('Updated');
|
|
1473
|
-
});
|
|
1474
|
-
|
|
1475
|
-
it('should FAIL to update product as non-creator', async () => {
|
|
1476
|
-
const result = await testHelper.graphQl({
|
|
1477
|
-
name: 'updateProduct',
|
|
1478
|
-
type: TestGraphQLType.MUTATION,
|
|
1479
|
-
arguments: { id: creatorProductId, input: { name: 'Hacked' } },
|
|
1480
|
-
fields: ['id']
|
|
1481
|
-
}, { token: otherUserToken, statusCode: 403 });
|
|
1482
|
-
|
|
1483
|
-
expect(result.errors).toBeDefined();
|
|
1484
|
-
});
|
|
1485
|
-
|
|
1486
|
-
it('should update any product as admin', async () => {
|
|
1487
|
-
const result = await testHelper.graphQl({
|
|
1488
|
-
name: 'updateProduct',
|
|
1489
|
-
type: TestGraphQLType.MUTATION,
|
|
1490
|
-
arguments: { id: creatorProductId, input: { name: 'Admin Update' } },
|
|
1491
|
-
fields: ['id', 'name']
|
|
1492
|
-
}, { token: adminToken });
|
|
1493
|
-
|
|
1494
|
-
expect(result.name).toBe('Admin Update');
|
|
1495
|
-
});
|
|
1496
|
-
});
|
|
1497
|
-
|
|
1498
|
-
// 4. DELETE Tests (with creator, not admin!)
|
|
1499
|
-
describe('Delete Product', () => {
|
|
1500
|
-
it('should delete product as creator', async () => {
|
|
1501
|
-
// Creator creates and deletes
|
|
1502
|
-
const created = await testHelper.graphQl({
|
|
1503
|
-
name: 'createProduct',
|
|
1504
|
-
type: TestGraphQLType.MUTATION,
|
|
1505
|
-
arguments: { input: { name: 'To Delete', price: 5 } },
|
|
1506
|
-
fields: ['id']
|
|
1507
|
-
}, { token: creatorToken });
|
|
1508
|
-
|
|
1509
|
-
const result = await testHelper.graphQl({
|
|
1510
|
-
name: 'deleteProduct',
|
|
1511
|
-
type: TestGraphQLType.MUTATION,
|
|
1512
|
-
arguments: { id: created.id },
|
|
1513
|
-
fields: ['id']
|
|
1514
|
-
}, { token: creatorToken }); // ← CREATOR token!
|
|
1515
|
-
|
|
1516
|
-
expect(result.id).toBe(created.id);
|
|
1517
|
-
});
|
|
1518
|
-
|
|
1519
|
-
it('should FAIL to delete product as non-creator', async () => {
|
|
1520
|
-
const result = await testHelper.graphQl({
|
|
1521
|
-
name: 'deleteProduct',
|
|
1522
|
-
type: TestGraphQLType.MUTATION,
|
|
1523
|
-
arguments: { id: createdProductId },
|
|
1524
|
-
fields: ['id']
|
|
1525
|
-
}, { token: otherUserToken, statusCode: 403 });
|
|
1526
|
-
|
|
1527
|
-
expect(result.errors).toBeDefined();
|
|
1528
|
-
});
|
|
1529
|
-
|
|
1530
|
-
it('should delete any product as admin', async () => {
|
|
1531
|
-
const result = await testHelper.graphQl({
|
|
1532
|
-
name: 'deleteProduct',
|
|
1533
|
-
type: TestGraphQLType.MUTATION,
|
|
1534
|
-
arguments: { id: createdProductId },
|
|
1535
|
-
fields: ['id']
|
|
1536
|
-
}, { token: adminToken });
|
|
1537
|
-
|
|
1538
|
-
expect(result.id).toBe(createdProductId);
|
|
1539
|
-
});
|
|
1540
|
-
});
|
|
1541
|
-
});
|
|
1542
|
-
```
|
|
1543
|
-
|
|
1544
|
-
---
|
|
1545
|
-
|
|
1546
|
-
#### Test Creation Checklist
|
|
1547
|
-
|
|
1548
|
-
Before finalizing tests, verify:
|
|
1549
|
-
|
|
1550
|
-
- [ ] ✅ I have analyzed ALL `@Roles()` decorators
|
|
1551
|
-
- [ ] ✅ I have read the complete `securityCheck()` method
|
|
1552
|
-
- [ ] ✅ I use the LEAST privileged user for each test
|
|
1553
|
-
- [ ] ✅ S_EVERYONE endpoints tested WITHOUT token
|
|
1554
|
-
- [ ] ✅ S_USER endpoints tested with REGULAR user (not admin)
|
|
1555
|
-
- [ ] ✅ UPDATE/DELETE tested with CREATOR token (not admin)
|
|
1556
|
-
- [ ] ✅ I have tests that verify unauthorized access FAILS (401/403)
|
|
1557
|
-
- [ ] ✅ I have tests that verify non-creators CANNOT update/delete
|
|
1558
|
-
- [ ] ✅ I have tests for missing required fields
|
|
1559
|
-
- [ ] ✅ All tests follow the security model
|
|
1560
|
-
- [ ] ✅ Tests validate protection mechanisms work
|
|
1561
|
-
|
|
1562
|
-
**⚠️ NEVER use admin token when a less privileged user would work!**
|
|
1563
|
-
|
|
1564
|
-
## Property Ordering
|
|
1565
|
-
|
|
1566
|
-
**ALL properties must be in alphabetical order** in:
|
|
1567
|
-
- Model files (`.model.ts`)
|
|
1568
|
-
- Input files (`.input.ts`, `-create.input.ts`)
|
|
1569
|
-
- Output files (`.output.ts`)
|
|
1570
|
-
|
|
1571
|
-
After generating, verify and reorder if necessary.
|
|
1572
|
-
|
|
1573
|
-
## Common Patterns
|
|
1574
|
-
|
|
1575
|
-
### 1. Module with References and Embedded Objects
|
|
1576
|
-
```bash
|
|
1577
|
-
lt server module --name Company --controller Both \
|
|
1578
|
-
--prop-name-0 name --prop-type-0 string \
|
|
1579
|
-
--prop-name-1 owner --prop-type-1 ObjectId --prop-reference-1 User \
|
|
1580
|
-
--prop-name-2 headquarters --prop-schema-2 Address \
|
|
1581
|
-
--prop-name-3 branches --prop-schema-3 Address --prop-array-3 true \
|
|
1582
|
-
--prop-name-4 industry --prop-enum-4 IndustryEnum
|
|
1583
|
-
```
|
|
1584
|
-
|
|
1585
|
-
### 2. Object with Nested Objects
|
|
1586
|
-
```bash
|
|
1587
|
-
# First create nested object
|
|
1588
|
-
lt server object --name ContactInfo \
|
|
1589
|
-
--prop-name-0 email --prop-type-0 string \
|
|
1590
|
-
--prop-name-1 phone --prop-type-1 string
|
|
1591
|
-
|
|
1592
|
-
# Then create parent object
|
|
1593
|
-
lt server object --name Person \
|
|
1594
|
-
--prop-name-0 name --prop-type-0 string \
|
|
1595
|
-
--prop-name-1 contact --prop-schema-1 ContactInfo
|
|
1596
|
-
```
|
|
1597
|
-
|
|
1598
|
-
### 3. Module Extending Custom Object
|
|
1599
|
-
```bash
|
|
1600
|
-
# First create base object
|
|
1601
|
-
lt server object --name BaseProfile \
|
|
1602
|
-
--prop-name-0 name --prop-type-0 string \
|
|
1603
|
-
--prop-name-1 email --prop-type-1 string
|
|
1604
|
-
|
|
1605
|
-
# Create module (will need manual extension modification)
|
|
1606
|
-
lt server module --name UserProfile --controller Both \
|
|
1607
|
-
--prop-name-0 username --prop-type-0 string
|
|
1608
|
-
|
|
1609
|
-
# Manually modify UserProfile model to extend BaseProfile
|
|
1610
|
-
```
|
|
1611
|
-
|
|
1612
|
-
## Verification Checklist
|
|
1613
|
-
|
|
1614
|
-
After generation, verify:
|
|
1615
|
-
|
|
1616
|
-
### Code Generation
|
|
1617
|
-
- [ ] All SubObjects created
|
|
1618
|
-
- [ ] All Objects created
|
|
1619
|
-
- [ ] All Modules created
|
|
1620
|
-
- [ ] All properties in alphabetical order
|
|
1621
|
-
- [ ] **DESCRIPTIONS (Critical - check thoroughly):**
|
|
1622
|
-
- [ ] All user-provided comments (after `//`) extracted from specification
|
|
1623
|
-
- [ ] All German descriptions translated to format: `ENGLISH (DEUTSCH)`
|
|
1624
|
-
- [ ] All English descriptions kept as-is (spelling corrected)
|
|
1625
|
-
- [ ] ALL Module Models have descriptions on all properties
|
|
1626
|
-
- [ ] ALL Module CreateInputs have SAME descriptions
|
|
1627
|
-
- [ ] ALL Module UpdateInputs have SAME descriptions
|
|
1628
|
-
- [ ] ALL SubObjects have descriptions on all properties
|
|
1629
|
-
- [ ] ALL SubObject CreateInputs have SAME descriptions
|
|
1630
|
-
- [ ] ALL SubObject UpdateInputs have SAME descriptions
|
|
1631
|
-
- [ ] ALL `@ObjectType()` decorators have descriptions
|
|
1632
|
-
- [ ] ALL `@InputType()` decorators have descriptions
|
|
1633
|
-
- [ ] NO inconsistencies (same property, different descriptions in different files)
|
|
1634
|
-
- [ ] NO German-only descriptions (must be translated)
|
|
1635
|
-
- [ ] Inheritance properly implemented
|
|
1636
|
-
- [ ] Required fields correctly set in CreateInputs
|
|
1637
|
-
- [ ] Enum files created in `src/server/common/enums/`
|
|
1638
|
-
|
|
1639
|
-
### API Tests - Security First
|
|
1640
|
-
- [ ] **Permission analysis completed BEFORE writing tests**
|
|
1641
|
-
- [ ] **Analyzed ALL `@Roles()` decorators in controllers/resolvers**
|
|
1642
|
-
- [ ] **Read complete `securityCheck()` method in models**
|
|
1643
|
-
- [ ] **Tests use LEAST privileged user (never admin when less works)**
|
|
1644
|
-
- [ ] **S_EVERYONE endpoints tested WITHOUT token**
|
|
1645
|
-
- [ ] **S_USER endpoints tested with REGULAR user (not admin)**
|
|
1646
|
-
- [ ] **UPDATE/DELETE tested with CREATOR token (not admin)**
|
|
1647
|
-
- [ ] **Tests verify unauthorized access FAILS (401/403)**
|
|
1648
|
-
- [ ] **Tests verify non-creators CANNOT update/delete**
|
|
1649
|
-
- [ ] **Tests verify required fields**
|
|
1650
|
-
- [ ] **Security validation tests exist (permission failures)**
|
|
1651
|
-
- [ ] API tests created for all modules
|
|
1652
|
-
- [ ] Tests cover all CRUD operations
|
|
1653
|
-
- [ ] All tests pass
|
|
1654
|
-
- [ ] No TypeScript errors
|
|
1655
|
-
- [ ] Lint passes
|
|
1656
|
-
|
|
1657
|
-
### Test Coverage - Comprehensive Testing
|
|
1658
|
-
**🎯 GOAL: Achieve the HIGHEST possible test coverage**
|
|
1659
|
-
|
|
1660
|
-
- [ ] **Every endpoint has at least one successful test**
|
|
1661
|
-
- [ ] **Every endpoint has at least one failure test (unauthorized/validation)**
|
|
1662
|
-
- [ ] **All query parameters tested (filters, sorting, pagination)**
|
|
1663
|
-
- [ ] **All validation rules tested (required fields, min/max, patterns)**
|
|
1664
|
-
- [ ] **All relationships tested (creating/updating/deleting with references)**
|
|
1665
|
-
- [ ] **Edge cases tested (empty results, non-existent IDs, duplicate values)**
|
|
1666
|
-
- [ ] **Error handling tested (400, 401, 403, 404, 409 status codes)**
|
|
1667
|
-
- [ ] **Data integrity tested (cascading deletes, orphan prevention)**
|
|
1668
|
-
- [ ] **Business logic tested (custom methods, computed properties)**
|
|
1669
|
-
- [ ] **Performance tested (large datasets, pagination limits)**
|
|
1670
|
-
|
|
1671
|
-
**Coverage Requirements:**
|
|
1672
|
-
- Minimum 80% line coverage for services
|
|
1673
|
-
- Minimum 90% line coverage for resolvers/controllers
|
|
1674
|
-
- 100% coverage for critical security logic (securityCheck, permission guards)
|
|
1675
|
-
- 100% coverage for all endpoints (success AND failure cases)
|
|
1676
|
-
- 100% coverage for all permission combinations
|
|
1677
|
-
- All public methods tested
|
|
1678
|
-
- All error paths tested
|
|
1679
|
-
|
|
1680
|
-
### Security Rules Compliance
|
|
1681
|
-
**🚨 CRITICAL: These MUST be checked before completing**
|
|
1682
|
-
|
|
1683
|
-
- [ ] **NO `@Restricted()` decorators removed from Controllers/Resolvers/Models/Objects**
|
|
1684
|
-
- [ ] **NO `@Roles()` decorators weakened to make tests pass**
|
|
1685
|
-
- [ ] **NO `securityCheck()` logic modified to bypass security**
|
|
1686
|
-
- [ ] **Class-level `@Restricted(ADMIN)` kept as security fallback**
|
|
1687
|
-
- [ ] **All security changes discussed and approved by developer**
|
|
1688
|
-
- [ ] **All security changes documented with approval and reason**
|
|
1689
|
-
- [ ] **Tests adapted to security requirements (not vice versa)**
|
|
1690
|
-
- [ ] **Appropriate test users created for each permission level**
|
|
1691
|
-
- [ ] **Permission hierarchy understood and respected (specific overrides general)**
|
|
1692
|
-
|
|
1693
|
-
**Test Organization:**
|
|
1694
|
-
```typescript
|
|
1695
|
-
describe('ProductResolver', () => {
|
|
1696
|
-
// Setup
|
|
1697
|
-
describe('Setup', () => { ... });
|
|
1698
|
-
|
|
1699
|
-
// Happy path tests
|
|
1700
|
-
describe('CREATE operations', () => {
|
|
1701
|
-
it('should create product as regular user', ...);
|
|
1702
|
-
it('should create product with all optional fields', ...);
|
|
1703
|
-
});
|
|
1704
|
-
|
|
1705
|
-
describe('READ operations', () => {
|
|
1706
|
-
it('should get product by ID', ...);
|
|
1707
|
-
it('should list all products with pagination', ...);
|
|
1708
|
-
it('should filter products by criteria', ...);
|
|
1709
|
-
});
|
|
1710
|
-
|
|
1711
|
-
describe('UPDATE operations', () => {
|
|
1712
|
-
it('should update product as creator', ...);
|
|
1713
|
-
it('should update product as admin', ...);
|
|
1714
|
-
});
|
|
1715
|
-
|
|
1716
|
-
describe('DELETE operations', () => {
|
|
1717
|
-
it('should delete product as creator', ...);
|
|
1718
|
-
it('should delete product as admin', ...);
|
|
1719
|
-
});
|
|
1720
|
-
|
|
1721
|
-
// Security tests
|
|
1722
|
-
describe('Security Validation', () => {
|
|
1723
|
-
it('should FAIL to create without auth', ...);
|
|
1724
|
-
it('should FAIL to update as non-creator', ...);
|
|
1725
|
-
it('should FAIL to delete as non-creator', ...);
|
|
1726
|
-
it('should FAIL to access with invalid token', ...);
|
|
1727
|
-
});
|
|
1728
|
-
|
|
1729
|
-
// Validation tests
|
|
1730
|
-
describe('Input Validation', () => {
|
|
1731
|
-
it('should FAIL with missing required fields', ...);
|
|
1732
|
-
it('should FAIL with invalid field values', ...);
|
|
1733
|
-
it('should FAIL with duplicate values', ...);
|
|
1734
|
-
});
|
|
1735
|
-
|
|
1736
|
-
// Edge cases
|
|
1737
|
-
describe('Edge Cases', () => {
|
|
1738
|
-
it('should handle non-existent ID (404)', ...);
|
|
1739
|
-
it('should handle empty list results', ...);
|
|
1740
|
-
it('should handle concurrent updates', ...);
|
|
1741
|
-
});
|
|
1742
|
-
});
|
|
1743
|
-
```
|
|
1744
|
-
|
|
1745
|
-
## Error Handling
|
|
1746
|
-
|
|
1747
|
-
### Common Issues
|
|
1748
|
-
|
|
1749
|
-
**Issue**: TypeScript errors about missing imports
|
|
1750
|
-
**Solution**: Add missing imports manually:
|
|
1751
|
-
```typescript
|
|
1752
|
-
import { Reference } from '@lenne.tech/nest-server';
|
|
1753
|
-
import { User } from '../../user/user.model';
|
|
1754
|
-
```
|
|
1755
|
-
|
|
1756
|
-
**Issue**: CreateInput validation fails
|
|
1757
|
-
**Solution**: Check parent's CreateInput for required fields and add them
|
|
1758
|
-
|
|
1759
|
-
**Issue**: Enum validation errors
|
|
1760
|
-
**Solution**: Verify enum file exists and is properly imported
|
|
1761
|
-
|
|
1762
|
-
**Issue**: Tests fail due to missing required fields
|
|
1763
|
-
**Solution**: Review CreateInput and ensure all required fields are provided in tests
|
|
1764
|
-
|
|
1765
|
-
**Issue**: Tests fail with 403 Forbidden
|
|
1766
|
-
**Solution**:
|
|
1767
|
-
1. Check `@Roles()` decorator - are you using the right user role?
|
|
1768
|
-
2. Check `securityCheck()` - does it allow this user to see the data?
|
|
1769
|
-
3. Check service `serviceOptions.roles` - are permissions checked correctly?
|
|
1770
|
-
4. **Use the LEAST privileged user who is authorized** (not admin!)
|
|
1771
|
-
|
|
1772
|
-
**Issue**: Test passes with admin but should work with regular user
|
|
1773
|
-
**Solution**: You're over-privileging! Analyze permissions and use the correct user:
|
|
1774
|
-
- S_USER endpoint? → Use regular user token
|
|
1775
|
-
- S_CREATOR endpoint? → Use creator token
|
|
1776
|
-
- NEVER use admin when regular user would work
|
|
1777
|
-
|
|
1778
|
-
**Issue**: Security tests not failing as expected
|
|
1779
|
-
**Solution**:
|
|
1780
|
-
1. Verify `@Roles()` decorator is set correctly
|
|
1781
|
-
2. Verify `securityCheck()` logic is correct
|
|
1782
|
-
3. Add console.log to see what's happening
|
|
1783
|
-
4. Security MUST be validated - fix the model/controller if tests don't fail
|
|
1784
|
-
|
|
1785
|
-
## ⚠️ CRITICAL: Security & Test Coverage Rules
|
|
1786
|
-
|
|
1787
|
-
**This section provides detailed rules for security and testing. It's duplicated at the beginning of this file for visibility.**
|
|
1788
|
-
|
|
1789
|
-
**📖 For the complete content with all rules, examples, and testing strategies, see: `security-rules.md`**
|
|
1790
|
-
|
|
1791
|
-
**Quick reminder - Core rules:**
|
|
1792
|
-
1. **NEVER weaken @Restricted or @Roles decorators** to make tests pass
|
|
1793
|
-
2. **NEVER modify securityCheck() logic** to bypass security
|
|
1794
|
-
3. **ALWAYS test with least privileged user** who is authorized
|
|
1795
|
-
4. **ALWAYS create appropriate test users** for each permission level
|
|
1796
|
-
5. **ALWAYS ask developer** before changing ANY security decorator
|
|
1797
|
-
6. **Aim for 80-100% test coverage** without compromising security
|
|
1798
|
-
|
|
1799
|
-
**Testing approach:**
|
|
1800
|
-
- Analyze permissions first
|
|
1801
|
-
- Create test users for each role
|
|
1802
|
-
- Test happy path with appropriate user
|
|
1803
|
-
- Test security failures (403 responses)
|
|
1804
|
-
- Document why certain roles are required
|
|
1805
|
-
|
|
1806
|
-
## Phase 8: Pre-Report Quality Review
|
|
1807
|
-
|
|
1808
|
-
**CRITICAL**: Before creating the final report, you MUST perform a comprehensive quality review.
|
|
1809
|
-
|
|
1810
|
-
**📖 For the complete quality review process with all steps, checklists, and examples, see: `quality-review.md`**
|
|
1811
|
-
|
|
1812
|
-
**Quick overview - 7 steps:**
|
|
1813
|
-
|
|
1814
|
-
1. **Identify All Changes**: Use git to identify all created/modified files
|
|
1815
|
-
2. **Test Management**:
|
|
1816
|
-
- Analyze existing tests FIRST (understand TestHelper, patterns, permissions)
|
|
1817
|
-
- Create new test files for newly created modules (in `tests/modules/`)
|
|
1818
|
-
- Update existing test files for modified modules
|
|
1819
|
-
- Follow exact patterns from existing tests
|
|
1820
|
-
3. **Compare with Existing Code**: Check consistency (style, structure, naming)
|
|
1821
|
-
4. **Critical Analysis**: Verify style, structure, code quality, best practices
|
|
1822
|
-
5. **Automated Optimizations**: Fix import ordering, property ordering, formatting, descriptions
|
|
1823
|
-
6. **Pre-Report Testing**: Run build, lint, and all tests (must all pass!)
|
|
1824
|
-
7. **Final Verification**: Complete checklist before proceeding to Final Report
|
|
1825
|
-
|
|
1826
|
-
**Critical reminders:**
|
|
1827
|
-
- [ ] **TestHelper thoroughly understood** (read source code, understand graphQl() and rest() methods)
|
|
1828
|
-
- [ ] **Existing tests analyzed** BEFORE creating new tests
|
|
1829
|
-
- [ ] **Permission system understood** (3 layers: Controller @Roles, Service options, Model securityCheck)
|
|
1830
|
-
- [ ] **Tests in correct location** (tests/modules/, tests/common.e2e-spec.ts, or tests/project.e2e-spec.ts)
|
|
1831
|
-
- [ ] **All tests pass** before reporting
|
|
1832
|
-
|
|
1833
|
-
**Permission testing approach:**
|
|
1834
|
-
- Admin users: `user.roles.includes('admin')`
|
|
1835
|
-
- Creators: `user.id === object.createdBy`
|
|
1836
|
-
- Always test with appropriate user (creator for update/delete, admin for everything)
|
|
1837
|
-
- Test security failures (403 responses)
|
|
1838
|
-
|
|
1839
|
-
**Only after ALL checks pass, proceed to Final Report.**
|
|
1840
|
-
|
|
1841
|
-
## Final Report
|
|
1842
|
-
|
|
1843
|
-
After completing all tasks, provide:
|
|
1844
|
-
|
|
1845
|
-
1. **Summary of created components**:
|
|
1846
|
-
- Number of SubObjects created
|
|
1847
|
-
- Number of Objects created
|
|
1848
|
-
- Number of Modules created
|
|
1849
|
-
- Number of enum files created
|
|
1850
|
-
- Number of test files created
|
|
1851
|
-
|
|
1852
|
-
2. **Observations about data structure**:
|
|
1853
|
-
- Unusual patterns
|
|
1854
|
-
- Potential improvements
|
|
1855
|
-
- Missing relationships
|
|
1856
|
-
- Optimization suggestions
|
|
1857
|
-
|
|
1858
|
-
3. **Test results**:
|
|
1859
|
-
- All tests passing
|
|
1860
|
-
- Any failures and reasons
|
|
1861
|
-
|
|
1862
|
-
4. **Next steps**:
|
|
1863
|
-
- Manual adjustments needed
|
|
1864
|
-
- Additional features to consider
|
|
1865
|
-
|
|
1866
|
-
## Best Practices
|
|
1867
|
-
|
|
1868
|
-
1. **Always create dependencies first** (SubObjects before Modules that use them)
|
|
1869
|
-
2. **Check for circular dependencies** in object relationships
|
|
1870
|
-
3. **Use meaningful enum names** that match the domain
|
|
1871
|
-
4. **Keep descriptions concise** but informative
|
|
1872
|
-
5. **Test incrementally** (don't wait until all modules are created)
|
|
1873
|
-
6. **Commit after each major component** (SubObjects, then Modules, then Tests)
|
|
1874
|
-
7. **Use appropriate controller types** (Rest for simple CRUD, GraphQL for complex queries, Both for flexibility)
|
|
1875
|
-
8. **Validate required fields** in tests to ensure data integrity
|
|
1876
|
-
9. **Clean up test data** to avoid database pollution
|
|
1877
|
-
10. **Document complex relationships** in code comments
|
|
1878
|
-
|
|
1879
|
-
## Working with This Skill
|
|
1880
|
-
|
|
1881
|
-
When you receive a specification:
|
|
1882
|
-
|
|
1883
|
-
1. **Parse completely** before starting any generation
|
|
1884
|
-
2. **Ask clarifying questions** if specification is ambiguous
|
|
1885
|
-
3. **Create detailed todo list** showing all steps
|
|
1886
|
-
4. **Execute systematically** following the workflow
|
|
1887
|
-
5. **Verify each step** before moving to next
|
|
1888
|
-
6. **Report progress** using todo updates
|
|
1889
|
-
7. **Provide comprehensive summary** at the end
|
|
1890
|
-
|
|
1891
|
-
This skill ensures complete, production-ready NestJS backend structures are generated efficiently and correctly from complex specifications.
|