digiqagent 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +29 -0
- package/README.md +197 -0
- package/agents/code-reviewer.md +48 -0
- package/bin/cli.js +210 -0
- package/package.json +41 -0
- package/skills/playwright-test-generator/SKILL.md +563 -0
- package/skills/playwright-test-generator/interaction-test-patterns.md +987 -0
- package/skills/playwright-test-generator/page-object-patterns.md +833 -0
- package/skills/postman-collection-generator/SKILL.md +310 -0
- package/skills/postman-collection-generator/collection-patterns.md +493 -0
- package/skills/postman-test-suite-generator/SKILL.md +653 -0
- package/skills/postman-test-suite-generator/test-scenario-patterns.md +612 -0
- package/skills/receiving-code-review/SKILL.md +213 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +146 -0
- package/skills/review-prompts/code-quality-reviewer-prompt.md +26 -0
- package/skills/review-prompts/spec-reviewer-prompt.md +61 -0
- package/skills/swagger-generator/SKILL.md +238 -0
- package/skills/swagger-generator/openapi-patterns.md +667 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +296 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +371 -0
- package/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/skills/verification-before-completion/SKILL.md +139 -0
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: postman-test-suite-generator
|
|
3
|
+
description: Use when generating Postman collection test scripts with positive and negative test scenarios that developers can run manually to verify API behavior
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Postman Test Suite Generator
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Produce a Postman collection with comprehensive test scripts covering both positive (happy path) and negative (error/edge case) scenarios for every endpoint. Developers import and run it in Postman to verify API behavior with zero test-writing effort.
|
|
11
|
+
|
|
12
|
+
**Core principle:** Every endpoint gets tested for what should work AND what should fail. If you can't prove the API rejects bad input, you don't know if it validates anything.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- API needs test coverage that developers can run manually
|
|
17
|
+
- After generating a Postman collection with `digiqagent:postman-collection-generator`
|
|
18
|
+
- QA handoff — provide runnable test scenarios
|
|
19
|
+
- Pre-release verification — structured test pass before deployment
|
|
20
|
+
- Onboarding — new developers see expected API behavior through tests
|
|
21
|
+
|
|
22
|
+
## Input Detection
|
|
23
|
+
|
|
24
|
+
### Preferred — Existing Postman Collection
|
|
25
|
+
|
|
26
|
+
Look for `postman_collection.json` in the project. Build test scripts on top of existing requests.
|
|
27
|
+
|
|
28
|
+
If no collection exists, recommend running `digiqagent:postman-collection-generator` first.
|
|
29
|
+
|
|
30
|
+
### Alternative — OpenAPI Spec
|
|
31
|
+
|
|
32
|
+
Parse `openapi.yaml` / `swagger.yaml` directly to build both requests and tests in one pass.
|
|
33
|
+
|
|
34
|
+
### Alternative — Codebase Scan
|
|
35
|
+
|
|
36
|
+
If neither exists, scan the codebase for endpoints, validation rules, and auth requirements. Generate a complete test collection from code analysis.
|
|
37
|
+
|
|
38
|
+
## Generation Process
|
|
39
|
+
|
|
40
|
+
### Step 1 — Analyze Endpoints
|
|
41
|
+
|
|
42
|
+
For each endpoint, identify:
|
|
43
|
+
- **Expected success behavior** — status code, response shape, side effects
|
|
44
|
+
- **Required fields** — which fields trigger 400 if missing
|
|
45
|
+
- **Field validations** — types, formats, min/max, patterns, enums
|
|
46
|
+
- **Auth requirements** — which roles/permissions are needed
|
|
47
|
+
- **Business rules** — uniqueness constraints, state transitions, dependencies
|
|
48
|
+
- **Edge cases** — empty collections, boundary values, concurrent access
|
|
49
|
+
|
|
50
|
+
Build a **scenario matrix** per endpoint:
|
|
51
|
+
|
|
52
|
+
| Endpoint | Positive Scenarios | Negative Scenarios |
|
|
53
|
+
|----------|-------------------|-------------------|
|
|
54
|
+
| POST /users | Valid creation, minimal fields, all fields | Missing email, invalid email, duplicate email, short password, missing name, invalid role |
|
|
55
|
+
| GET /users/:id | Valid ID returns user | Non-existent ID, invalid ID format, unauthorized |
|
|
56
|
+
| PUT /users/:id | Valid update, partial update | Non-existent ID, invalid fields, unauthorized, forbidden |
|
|
57
|
+
| DELETE /users/:id | Valid deletion | Non-existent ID, unauthorized, already deleted |
|
|
58
|
+
|
|
59
|
+
### Step 2 — Generate Positive Test Scenarios
|
|
60
|
+
|
|
61
|
+
For each endpoint, create happy-path tests that verify:
|
|
62
|
+
|
|
63
|
+
**Status code:**
|
|
64
|
+
```javascript
|
|
65
|
+
pm.test("Status code is 201", function () {
|
|
66
|
+
pm.response.to.have.status(201);
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Response time:**
|
|
71
|
+
```javascript
|
|
72
|
+
pm.test("Response time is under 2000ms", function () {
|
|
73
|
+
pm.expect(pm.response.responseTime).to.be.below(2000);
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Response body structure:**
|
|
78
|
+
```javascript
|
|
79
|
+
pm.test("Response has correct structure", function () {
|
|
80
|
+
const response = pm.response.json();
|
|
81
|
+
pm.expect(response).to.have.property("id");
|
|
82
|
+
pm.expect(response).to.have.property("name");
|
|
83
|
+
pm.expect(response).to.have.property("email");
|
|
84
|
+
pm.expect(response).to.have.property("createdAt");
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Response data types:**
|
|
89
|
+
```javascript
|
|
90
|
+
pm.test("Response fields have correct types", function () {
|
|
91
|
+
const response = pm.response.json();
|
|
92
|
+
pm.expect(response.id).to.be.a("string");
|
|
93
|
+
pm.expect(response.name).to.be.a("string");
|
|
94
|
+
pm.expect(response.email).to.be.a("string");
|
|
95
|
+
pm.expect(response.active).to.be.a("boolean");
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Response content matches request:**
|
|
100
|
+
```javascript
|
|
101
|
+
pm.test("Created user matches request data", function () {
|
|
102
|
+
const request = JSON.parse(pm.request.body.raw);
|
|
103
|
+
const response = pm.response.json();
|
|
104
|
+
pm.expect(response.name).to.eql(request.name);
|
|
105
|
+
pm.expect(response.email).to.eql(request.email);
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Headers:**
|
|
110
|
+
```javascript
|
|
111
|
+
pm.test("Content-Type is application/json", function () {
|
|
112
|
+
pm.response.to.have.header("Content-Type");
|
|
113
|
+
pm.expect(pm.response.headers.get("Content-Type")).to.include("application/json");
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Pagination (for list endpoints):**
|
|
118
|
+
```javascript
|
|
119
|
+
pm.test("Response has pagination metadata", function () {
|
|
120
|
+
const response = pm.response.json();
|
|
121
|
+
pm.expect(response).to.have.property("meta");
|
|
122
|
+
pm.expect(response.meta).to.have.property("page");
|
|
123
|
+
pm.expect(response.meta).to.have.property("limit");
|
|
124
|
+
pm.expect(response.meta).to.have.property("totalItems");
|
|
125
|
+
pm.expect(response.meta).to.have.property("totalPages");
|
|
126
|
+
pm.expect(response.meta.page).to.be.a("number");
|
|
127
|
+
pm.expect(response.data).to.be.an("array");
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Variable capture for chaining:**
|
|
132
|
+
```javascript
|
|
133
|
+
if (pm.response.code === 201) {
|
|
134
|
+
const response = pm.response.json();
|
|
135
|
+
pm.environment.set("createdUserId", response.id);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Step 3 — Generate Negative Test Scenarios
|
|
140
|
+
|
|
141
|
+
For each endpoint, **systematically** cover these categories:
|
|
142
|
+
|
|
143
|
+
#### 3a. Missing Required Fields (one at a time)
|
|
144
|
+
|
|
145
|
+
For a POST /users endpoint with required fields `name`, `email`, `password`:
|
|
146
|
+
|
|
147
|
+
**Missing name:**
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"name": "Negative - Create User - Missing Name",
|
|
151
|
+
"request": {
|
|
152
|
+
"method": "POST",
|
|
153
|
+
"body": {
|
|
154
|
+
"mode": "raw",
|
|
155
|
+
"raw": "{\n \"email\": \"jane@example.com\",\n \"password\": \"SecureP@ss123\"\n}"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"event": [{
|
|
159
|
+
"listen": "test",
|
|
160
|
+
"script": {
|
|
161
|
+
"exec": [
|
|
162
|
+
"pm.test('Returns 400 for missing name', function () {",
|
|
163
|
+
" pm.response.to.have.status(400);",
|
|
164
|
+
"});",
|
|
165
|
+
"pm.test('Error message mentions name field', function () {",
|
|
166
|
+
" const response = pm.response.json();",
|
|
167
|
+
" const message = Array.isArray(response.message) ? response.message.join(' ') : response.message;",
|
|
168
|
+
" pm.expect(message.toLowerCase()).to.include('name');",
|
|
169
|
+
"});"
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
}]
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Repeat for each required field:** Remove one field at a time, verify 400 response and field-specific error message.
|
|
177
|
+
|
|
178
|
+
#### 3b. Invalid Data Types
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
// String where number expected
|
|
182
|
+
pm.test("Returns 400 for string price", function () {
|
|
183
|
+
pm.response.to.have.status(400);
|
|
184
|
+
});
|
|
185
|
+
// Body: { "price": "not-a-number" }
|
|
186
|
+
|
|
187
|
+
// Number where string expected
|
|
188
|
+
// Body: { "name": 12345 }
|
|
189
|
+
|
|
190
|
+
// Boolean where string expected
|
|
191
|
+
// Body: { "email": true }
|
|
192
|
+
|
|
193
|
+
// Array where object expected
|
|
194
|
+
// Body: []
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### 3c. Boundary Values
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
// Empty string
|
|
201
|
+
pm.test("Returns 400 for empty name", function () {
|
|
202
|
+
pm.response.to.have.status(400);
|
|
203
|
+
});
|
|
204
|
+
// Body: { "name": "", "email": "jane@example.com", "password": "SecureP@ss123" }
|
|
205
|
+
|
|
206
|
+
// Exceeds max length (if maxLength: 100)
|
|
207
|
+
// Body: { "name": "A".repeat(101) }
|
|
208
|
+
|
|
209
|
+
// Below minimum (if minimum: 0)
|
|
210
|
+
// Body: { "price": -1 }
|
|
211
|
+
|
|
212
|
+
// Zero value
|
|
213
|
+
// Body: { "quantity": 0 }
|
|
214
|
+
|
|
215
|
+
// Whitespace only
|
|
216
|
+
// Body: { "name": " " }
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### 3d. Unauthorized Access
|
|
220
|
+
|
|
221
|
+
**No token:**
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"name": "Negative - Get Users - No Auth Token",
|
|
225
|
+
"request": {
|
|
226
|
+
"method": "GET",
|
|
227
|
+
"header": [],
|
|
228
|
+
"url": { "raw": "{{baseUrl}}/api/users" }
|
|
229
|
+
},
|
|
230
|
+
"event": [{
|
|
231
|
+
"listen": "test",
|
|
232
|
+
"script": {
|
|
233
|
+
"exec": [
|
|
234
|
+
"pm.test('Returns 401 without auth token', function () {",
|
|
235
|
+
" pm.response.to.have.status(401);",
|
|
236
|
+
"});",
|
|
237
|
+
"pm.test('Error indicates authentication required', function () {",
|
|
238
|
+
" const response = pm.response.json();",
|
|
239
|
+
" pm.expect(response.error || response.message).to.be.a('string');",
|
|
240
|
+
"});"
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
}]
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Invalid token:**
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"name": "Negative - Get Users - Invalid Token",
|
|
251
|
+
"request": {
|
|
252
|
+
"method": "GET",
|
|
253
|
+
"header": [
|
|
254
|
+
{ "key": "Authorization", "value": "Bearer invalid.token.value" }
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Expired token (simulated):**
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"name": "Negative - Get Users - Expired Token",
|
|
264
|
+
"request": {
|
|
265
|
+
"method": "GET",
|
|
266
|
+
"header": [
|
|
267
|
+
{ "key": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxMDAwMDAwMDAwfQ.invalid" }
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Wrong role/insufficient permissions:**
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"name": "Negative - Delete User - Insufficient Permissions",
|
|
277
|
+
"request": {
|
|
278
|
+
"method": "DELETE",
|
|
279
|
+
"header": [
|
|
280
|
+
{ "key": "Authorization", "value": "Bearer {{regularUserToken}}" }
|
|
281
|
+
]
|
|
282
|
+
},
|
|
283
|
+
"event": [{
|
|
284
|
+
"listen": "test",
|
|
285
|
+
"script": {
|
|
286
|
+
"exec": [
|
|
287
|
+
"pm.test('Returns 403 for insufficient permissions', function () {",
|
|
288
|
+
" pm.response.to.have.status(403);",
|
|
289
|
+
"});"
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
}]
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### 3e. Not Found
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
{
|
|
300
|
+
"name": "Negative - Get User - Non-Existent ID",
|
|
301
|
+
"request": {
|
|
302
|
+
"method": "GET",
|
|
303
|
+
"url": { "raw": "{{baseUrl}}/api/users/00000000-0000-0000-0000-000000000000" }
|
|
304
|
+
},
|
|
305
|
+
"event": [{
|
|
306
|
+
"listen": "test",
|
|
307
|
+
"script": {
|
|
308
|
+
"exec": [
|
|
309
|
+
"pm.test('Returns 404 for non-existent user', function () {",
|
|
310
|
+
" pm.response.to.have.status(404);",
|
|
311
|
+
"});",
|
|
312
|
+
"pm.test('Error message indicates resource not found', function () {",
|
|
313
|
+
" const response = pm.response.json();",
|
|
314
|
+
" const msg = (response.message || response.error || '').toLowerCase();",
|
|
315
|
+
" pm.expect(msg).to.include('not found');",
|
|
316
|
+
"});"
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
}]
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Invalid ID format:**
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"name": "Negative - Get User - Invalid ID Format",
|
|
327
|
+
"request": {
|
|
328
|
+
"method": "GET",
|
|
329
|
+
"url": { "raw": "{{baseUrl}}/api/users/not-a-valid-id!" }
|
|
330
|
+
},
|
|
331
|
+
"event": [{
|
|
332
|
+
"listen": "test",
|
|
333
|
+
"script": {
|
|
334
|
+
"exec": [
|
|
335
|
+
"pm.test('Returns 400 or 404 for invalid ID format', function () {",
|
|
336
|
+
" pm.expect(pm.response.code).to.be.oneOf([400, 404]);",
|
|
337
|
+
"});"
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
}]
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### 3f. Duplicate / Conflict
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"name": "Negative - Create User - Duplicate Email",
|
|
349
|
+
"event": [
|
|
350
|
+
{
|
|
351
|
+
"listen": "prerequest",
|
|
352
|
+
"script": {
|
|
353
|
+
"exec": [
|
|
354
|
+
"// Uses the same email as the previously created user"
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"listen": "test",
|
|
360
|
+
"script": {
|
|
361
|
+
"exec": [
|
|
362
|
+
"pm.test('Returns 409 for duplicate email', function () {",
|
|
363
|
+
" pm.response.to.have.status(409);",
|
|
364
|
+
"});",
|
|
365
|
+
"pm.test('Error indicates duplicate resource', function () {",
|
|
366
|
+
" const response = pm.response.json();",
|
|
367
|
+
" const msg = (response.message || '').toLowerCase();",
|
|
368
|
+
" pm.expect(msg).to.satisfy(function(m) {",
|
|
369
|
+
" return m.includes('already exists') || m.includes('duplicate') || m.includes('conflict');",
|
|
370
|
+
" });",
|
|
371
|
+
"});"
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
],
|
|
376
|
+
"request": {
|
|
377
|
+
"method": "POST",
|
|
378
|
+
"body": {
|
|
379
|
+
"mode": "raw",
|
|
380
|
+
"raw": "{\n \"name\": \"Another User\",\n \"email\": \"jane.smith@example.com\",\n \"password\": \"SecureP@ss123\"\n}"
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
#### 3g. Invalid Content Type
|
|
387
|
+
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"name": "Negative - Create User - Wrong Content-Type",
|
|
391
|
+
"request": {
|
|
392
|
+
"method": "POST",
|
|
393
|
+
"header": [
|
|
394
|
+
{ "key": "Content-Type", "value": "text/plain" },
|
|
395
|
+
{ "key": "Authorization", "value": "Bearer {{authToken}}" }
|
|
396
|
+
],
|
|
397
|
+
"body": {
|
|
398
|
+
"mode": "raw",
|
|
399
|
+
"raw": "this is not json"
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
"event": [{
|
|
403
|
+
"listen": "test",
|
|
404
|
+
"script": {
|
|
405
|
+
"exec": [
|
|
406
|
+
"pm.test('Returns 400 or 415 for wrong content type', function () {",
|
|
407
|
+
" pm.expect(pm.response.code).to.be.oneOf([400, 415]);",
|
|
408
|
+
"});"
|
|
409
|
+
]
|
|
410
|
+
}
|
|
411
|
+
}]
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### 3h. Malformed Request Body
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{
|
|
419
|
+
"name": "Negative - Create User - Malformed JSON",
|
|
420
|
+
"request": {
|
|
421
|
+
"method": "POST",
|
|
422
|
+
"header": [
|
|
423
|
+
{ "key": "Content-Type", "value": "application/json" },
|
|
424
|
+
{ "key": "Authorization", "value": "Bearer {{authToken}}" }
|
|
425
|
+
],
|
|
426
|
+
"body": {
|
|
427
|
+
"mode": "raw",
|
|
428
|
+
"raw": "{invalid json content"
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
"event": [{
|
|
432
|
+
"listen": "test",
|
|
433
|
+
"script": {
|
|
434
|
+
"exec": [
|
|
435
|
+
"pm.test('Returns 400 for malformed JSON', function () {",
|
|
436
|
+
" pm.response.to.have.status(400);",
|
|
437
|
+
"});"
|
|
438
|
+
]
|
|
439
|
+
}
|
|
440
|
+
}]
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Step 4 — Write Test Scripts
|
|
445
|
+
|
|
446
|
+
Embed `pm.test()` blocks in the `event` array of each request item:
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"event": [
|
|
451
|
+
{
|
|
452
|
+
"listen": "test",
|
|
453
|
+
"script": {
|
|
454
|
+
"type": "text/javascript",
|
|
455
|
+
"exec": [
|
|
456
|
+
"// line 1",
|
|
457
|
+
"// line 2"
|
|
458
|
+
]
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Each `exec` array entry is a line of JavaScript. Keep lines short and readable.
|
|
466
|
+
|
|
467
|
+
### Step 5 — Organize into Folders
|
|
468
|
+
|
|
469
|
+
Structure the collection with clear separation:
|
|
470
|
+
|
|
471
|
+
```
|
|
472
|
+
Test Suite Root
|
|
473
|
+
├── Setup
|
|
474
|
+
│ ├── Register Test User
|
|
475
|
+
│ └── Login (capture token)
|
|
476
|
+
├── Users
|
|
477
|
+
│ ├── Positive Tests
|
|
478
|
+
│ │ ├── Create User - Valid Data
|
|
479
|
+
│ │ ├── List Users - Default Pagination
|
|
480
|
+
│ │ ├── List Users - Custom Page Size
|
|
481
|
+
│ │ ├── Get User by ID
|
|
482
|
+
│ │ ├── Update User - Full Update
|
|
483
|
+
│ │ ├── Update User - Partial Update
|
|
484
|
+
│ │ └── Delete User
|
|
485
|
+
│ └── Negative Tests
|
|
486
|
+
│ ├── Create User - Missing Name
|
|
487
|
+
│ ├── Create User - Missing Email
|
|
488
|
+
│ ├── Create User - Invalid Email Format
|
|
489
|
+
│ ├── Create User - Short Password
|
|
490
|
+
│ ├── Create User - Duplicate Email
|
|
491
|
+
│ ├── Get User - Non-Existent ID
|
|
492
|
+
│ ├── Get User - Invalid ID Format
|
|
493
|
+
│ ├── Update User - Not Found
|
|
494
|
+
│ ├── Delete User - Not Found
|
|
495
|
+
│ ├── Delete User - Insufficient Permissions
|
|
496
|
+
│ ├── Access Without Auth Token
|
|
497
|
+
│ ├── Access With Invalid Token
|
|
498
|
+
│ ├── Create User - Malformed JSON
|
|
499
|
+
│ └── Create User - Wrong Content-Type
|
|
500
|
+
├── Products
|
|
501
|
+
│ ├── Positive Tests
|
|
502
|
+
│ │ └── ...
|
|
503
|
+
│ └── Negative Tests
|
|
504
|
+
│ └── ...
|
|
505
|
+
└── Teardown
|
|
506
|
+
└── Delete Test User
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Step 6 — Add Data-Driven Tests
|
|
510
|
+
|
|
511
|
+
For endpoints with many validation scenarios, suggest data file usage:
|
|
512
|
+
|
|
513
|
+
**CSV data file format (`test_data_users.csv`):**
|
|
514
|
+
```csv
|
|
515
|
+
name,email,password,expectedStatus,testDescription
|
|
516
|
+
,jane@example.com,SecureP@ss123,400,Missing name
|
|
517
|
+
Jane Smith,,SecureP@ss123,400,Missing email
|
|
518
|
+
Jane Smith,not-an-email,SecureP@ss123,400,Invalid email format
|
|
519
|
+
Jane Smith,jane@example.com,short,400,Password too short
|
|
520
|
+
Jane Smith,jane@example.com,SecureP@ss123,201,Valid data
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Data-driven test script:**
|
|
524
|
+
```javascript
|
|
525
|
+
pm.test(pm.iterationData.get("testDescription"), function () {
|
|
526
|
+
pm.response.to.have.status(parseInt(pm.iterationData.get("expectedStatus")));
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Include a note in the collection description explaining how to use data files with Postman Runner.
|
|
531
|
+
|
|
532
|
+
### Step 7 — Write File
|
|
533
|
+
|
|
534
|
+
Output as: `postman_test_collection.json` at project root (or user-specified path).
|
|
535
|
+
|
|
536
|
+
The file follows the same Postman Collection v2.1 schema as the base collection, with test scripts embedded in each request's `event` array.
|
|
537
|
+
|
|
538
|
+
### Step 8 — Verify
|
|
539
|
+
|
|
540
|
+
Before claiming the test suite is complete:
|
|
541
|
+
|
|
542
|
+
1. **Coverage check** — every endpoint has at least one positive and multiple negative scenarios
|
|
543
|
+
2. **JSON validity** — file parses without errors
|
|
544
|
+
3. **Script syntax** — all `pm.test()` blocks are syntactically correct JavaScript
|
|
545
|
+
4. **Variable consistency** — all `{{variables}}` used in tests are defined or captured by prior requests
|
|
546
|
+
5. **Ordering** — setup (auth) runs before tests that need tokens; create runs before get-by-id
|
|
547
|
+
6. Apply `digiqagent:verification-before-completion` — evidence before claims
|
|
548
|
+
|
|
549
|
+
## Negative Scenario Matrix
|
|
550
|
+
|
|
551
|
+
Use this matrix to ensure systematic coverage. Every cell should have at least one test:
|
|
552
|
+
|
|
553
|
+
| Scenario Category | POST (Create) | GET (Read) | PUT (Update) | DELETE |
|
|
554
|
+
|-------------------|--------------|------------|-------------|--------|
|
|
555
|
+
| Missing required fields | One test per field | N/A | One test per field | N/A |
|
|
556
|
+
| Invalid data types | Per typed field | N/A | Per typed field | N/A |
|
|
557
|
+
| Empty/blank values | Per string field | N/A | Per string field | N/A |
|
|
558
|
+
| Boundary values | Per constrained field | N/A | Per constrained field | N/A |
|
|
559
|
+
| Invalid format | Email, date, UUID | Invalid ID format | Email, date, UUID | Invalid ID format |
|
|
560
|
+
| No auth token | Yes | Yes | Yes | Yes |
|
|
561
|
+
| Invalid auth token | Yes | Yes | Yes | Yes |
|
|
562
|
+
| Insufficient permissions | If role-based | If role-based | If role-based | If role-based |
|
|
563
|
+
| Resource not found | N/A | Yes | Yes | Yes |
|
|
564
|
+
| Duplicate/conflict | If unique constraints | N/A | If unique constraints | N/A |
|
|
565
|
+
| Wrong content type | Yes | N/A | Yes | N/A |
|
|
566
|
+
| Malformed body | Yes | N/A | Yes | N/A |
|
|
567
|
+
|
|
568
|
+
## Test Assertion Quick Reference
|
|
569
|
+
|
|
570
|
+
| What to Assert | pm.test Code |
|
|
571
|
+
|---------------|-------------|
|
|
572
|
+
| Status code | `pm.response.to.have.status(200)` |
|
|
573
|
+
| Status code one of | `pm.expect(pm.response.code).to.be.oneOf([200, 201])` |
|
|
574
|
+
| Response time | `pm.expect(pm.response.responseTime).to.be.below(2000)` |
|
|
575
|
+
| Has property | `pm.expect(json).to.have.property("id")` |
|
|
576
|
+
| Property type | `pm.expect(json.id).to.be.a("string")` |
|
|
577
|
+
| Property value | `pm.expect(json.status).to.eql("active")` |
|
|
578
|
+
| Array length | `pm.expect(json.data).to.have.lengthOf.at.least(1)` |
|
|
579
|
+
| Array type | `pm.expect(json.data).to.be.an("array")` |
|
|
580
|
+
| String contains | `pm.expect(json.message).to.include("not found")` |
|
|
581
|
+
| Header exists | `pm.response.to.have.header("Content-Type")` |
|
|
582
|
+
| Header value | `pm.expect(pm.response.headers.get("Content-Type")).to.include("json")` |
|
|
583
|
+
| Not empty | `pm.expect(json.id).to.not.be.empty` |
|
|
584
|
+
| Null check | `pm.expect(json.deletedAt).to.be.null` |
|
|
585
|
+
| Nested property | `pm.expect(json.user.profile.name).to.eql("Jane")` |
|
|
586
|
+
| JSON Schema | `pm.response.to.have.jsonSchema(schema)` |
|
|
587
|
+
| Set variable | `pm.environment.set("key", json.value)` |
|
|
588
|
+
| Get variable | `pm.environment.get("key")` |
|
|
589
|
+
|
|
590
|
+
## Red Flags — STOP and Fix
|
|
591
|
+
|
|
592
|
+
| Problem | Fix |
|
|
593
|
+
|---------|-----|
|
|
594
|
+
| Endpoint has only positive tests | Add negative scenarios from the matrix |
|
|
595
|
+
| Negative test doesn't check error message | Add assertion on error body content |
|
|
596
|
+
| Tests depend on hardcoded IDs | Use environment variables from setup requests |
|
|
597
|
+
| No setup folder for auth | Add login request at the start |
|
|
598
|
+
| Test scripts have syntax errors | Validate JavaScript before writing file |
|
|
599
|
+
| Missing coverage for auth scenarios | Add no-token, invalid-token, wrong-role tests |
|
|
600
|
+
| All negative tests in one request | Separate: one scenario per request for clear reporting |
|
|
601
|
+
| Test names are generic | Use descriptive names: "Returns 400 when email is missing" |
|
|
602
|
+
|
|
603
|
+
## Verification Checklist
|
|
604
|
+
|
|
605
|
+
Before marking generation complete:
|
|
606
|
+
|
|
607
|
+
- [ ] Every endpoint has at least one positive test scenario
|
|
608
|
+
- [ ] Every mutating endpoint (POST/PUT/PATCH) has missing-field tests
|
|
609
|
+
- [ ] Every authenticated endpoint has no-auth and invalid-auth tests
|
|
610
|
+
- [ ] Every endpoint with path params has not-found and invalid-ID tests
|
|
611
|
+
- [ ] Tests organized into Positive/Negative folders per resource
|
|
612
|
+
- [ ] Setup folder with auth requests runs first
|
|
613
|
+
- [ ] All `pm.test()` scripts are syntactically valid JavaScript
|
|
614
|
+
- [ ] Environment variables are captured before they're used
|
|
615
|
+
- [ ] Collection JSON is valid and imports into Postman
|
|
616
|
+
- [ ] File written and path confirmed to user
|
|
617
|
+
|
|
618
|
+
Can't check all boxes? Fix the test suite. Incomplete negative coverage means unknown API behavior.
|
|
619
|
+
|
|
620
|
+
## Common Patterns
|
|
621
|
+
|
|
622
|
+
See @test-scenario-patterns.md for:
|
|
623
|
+
- Complete CRUD test suite example
|
|
624
|
+
- Auth test patterns
|
|
625
|
+
- Schema validation patterns
|
|
626
|
+
- Chained test patterns
|
|
627
|
+
- Idempotency and rate limiting tests
|
|
628
|
+
|
|
629
|
+
## Developer Usage
|
|
630
|
+
|
|
631
|
+
Include in the collection description:
|
|
632
|
+
|
|
633
|
+
```
|
|
634
|
+
## Running the Test Suite
|
|
635
|
+
|
|
636
|
+
1. Import `postman_test_collection.json` into Postman
|
|
637
|
+
2. Import `postman_environment.json` (from collection generator)
|
|
638
|
+
3. Select the environment in the top-right dropdown
|
|
639
|
+
4. Ensure your API server is running at the configured baseUrl
|
|
640
|
+
5. Run the "Setup" folder first to populate auth tokens
|
|
641
|
+
6. Run individual folders or the entire collection via Postman Runner
|
|
642
|
+
|
|
643
|
+
## Understanding Results
|
|
644
|
+
- Green: Test passed — API behaves as expected
|
|
645
|
+
- Red: Test failed — investigate the endpoint
|
|
646
|
+
- Positive test failures: API is broken
|
|
647
|
+
- Negative test failures: API may be missing validation
|
|
648
|
+
|
|
649
|
+
## Using Data-Driven Tests
|
|
650
|
+
1. Open Postman Runner
|
|
651
|
+
2. Select a data file (CSV/JSON) for parameterized scenarios
|
|
652
|
+
3. Run — each row becomes a separate test iteration
|
|
653
|
+
```
|