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,612 @@
|
|
|
1
|
+
# Test Scenario Patterns
|
|
2
|
+
|
|
3
|
+
Reference patterns for Postman test scripts. Copy and adapt for generated test suites.
|
|
4
|
+
|
|
5
|
+
## Complete CRUD Test Suite Example — `/api/users`
|
|
6
|
+
|
|
7
|
+
A full working test suite for a Users resource showing both positive and negative scenarios.
|
|
8
|
+
|
|
9
|
+
### Setup — Login
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
// Test script for POST /api/auth/login
|
|
13
|
+
pm.test("Login successful", function () {
|
|
14
|
+
pm.response.to.have.status(200);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
pm.test("Response contains token", function () {
|
|
18
|
+
const response = pm.response.json();
|
|
19
|
+
pm.expect(response).to.have.property("accessToken");
|
|
20
|
+
pm.environment.set("authToken", response.accessToken);
|
|
21
|
+
if (response.user) {
|
|
22
|
+
pm.environment.set("currentUserId", response.user.id);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Positive — Create User
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
// Test script for POST /api/users
|
|
31
|
+
// Body: { "name": "Jane Smith", "email": "jane.smith@example.com", "password": "SecureP@ss123", "role": "user" }
|
|
32
|
+
|
|
33
|
+
pm.test("Status code is 201", function () {
|
|
34
|
+
pm.response.to.have.status(201);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
pm.test("Response time is acceptable", function () {
|
|
38
|
+
pm.expect(pm.response.responseTime).to.be.below(2000);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
pm.test("Response has user object with all expected fields", function () {
|
|
42
|
+
const user = pm.response.json();
|
|
43
|
+
pm.expect(user).to.have.property("id");
|
|
44
|
+
pm.expect(user).to.have.property("name");
|
|
45
|
+
pm.expect(user).to.have.property("email");
|
|
46
|
+
pm.expect(user).to.have.property("role");
|
|
47
|
+
pm.expect(user).to.have.property("createdAt");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
pm.test("Response data matches request", function () {
|
|
51
|
+
const request = JSON.parse(pm.request.body.raw);
|
|
52
|
+
const user = pm.response.json();
|
|
53
|
+
pm.expect(user.name).to.eql(request.name);
|
|
54
|
+
pm.expect(user.email).to.eql(request.email);
|
|
55
|
+
pm.expect(user.role).to.eql(request.role);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
pm.test("Password is not returned in response", function () {
|
|
59
|
+
const user = pm.response.json();
|
|
60
|
+
pm.expect(user).to.not.have.property("password");
|
|
61
|
+
pm.expect(user).to.not.have.property("passwordHash");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
pm.test("ID is a valid format", function () {
|
|
65
|
+
const user = pm.response.json();
|
|
66
|
+
pm.expect(user.id).to.be.a("string");
|
|
67
|
+
pm.expect(user.id).to.not.be.empty;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Capture for subsequent tests
|
|
71
|
+
if (pm.response.code === 201) {
|
|
72
|
+
pm.environment.set("testUserId", pm.response.json().id);
|
|
73
|
+
pm.environment.set("testUserEmail", pm.response.json().email);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Positive — List Users
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
// Test script for GET /api/users?page=1&limit=10
|
|
81
|
+
|
|
82
|
+
pm.test("Status code is 200", function () {
|
|
83
|
+
pm.response.to.have.status(200);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
pm.test("Response is paginated array", function () {
|
|
87
|
+
const response = pm.response.json();
|
|
88
|
+
pm.expect(response).to.have.property("data");
|
|
89
|
+
pm.expect(response.data).to.be.an("array");
|
|
90
|
+
pm.expect(response).to.have.property("meta");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
pm.test("Pagination metadata is correct", function () {
|
|
94
|
+
const meta = pm.response.json().meta;
|
|
95
|
+
pm.expect(meta.page).to.be.a("number");
|
|
96
|
+
pm.expect(meta.limit).to.be.a("number");
|
|
97
|
+
pm.expect(meta.totalItems).to.be.a("number");
|
|
98
|
+
pm.expect(meta.totalPages).to.be.a("number");
|
|
99
|
+
pm.expect(meta.page).to.eql(1);
|
|
100
|
+
pm.expect(meta.limit).to.eql(10);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
pm.test("Each user has expected fields", function () {
|
|
104
|
+
const users = pm.response.json().data;
|
|
105
|
+
if (users.length > 0) {
|
|
106
|
+
users.forEach(function (user) {
|
|
107
|
+
pm.expect(user).to.have.property("id");
|
|
108
|
+
pm.expect(user).to.have.property("name");
|
|
109
|
+
pm.expect(user).to.have.property("email");
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
pm.test("Previously created user is in the list", function () {
|
|
115
|
+
const users = pm.response.json().data;
|
|
116
|
+
const testUserId = pm.environment.get("testUserId");
|
|
117
|
+
if (testUserId) {
|
|
118
|
+
const found = users.some(u => u.id === testUserId);
|
|
119
|
+
pm.expect(found).to.be.true;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Positive — Get User by ID
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// Test script for GET /api/users/{{testUserId}}
|
|
128
|
+
|
|
129
|
+
pm.test("Status code is 200", function () {
|
|
130
|
+
pm.response.to.have.status(200);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
pm.test("Returns the correct user", function () {
|
|
134
|
+
const user = pm.response.json();
|
|
135
|
+
pm.expect(user.id).to.eql(pm.environment.get("testUserId"));
|
|
136
|
+
pm.expect(user.email).to.eql(pm.environment.get("testUserEmail"));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
pm.test("User has all expected fields", function () {
|
|
140
|
+
const user = pm.response.json();
|
|
141
|
+
pm.expect(user).to.have.all.keys("id", "name", "email", "role", "createdAt", "updatedAt");
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Positive — Update User
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
// Test script for PUT /api/users/{{testUserId}}
|
|
149
|
+
// Body: { "name": "Jane Smith Updated" }
|
|
150
|
+
|
|
151
|
+
pm.test("Status code is 200", function () {
|
|
152
|
+
pm.response.to.have.status(200);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
pm.test("Name is updated", function () {
|
|
156
|
+
const user = pm.response.json();
|
|
157
|
+
pm.expect(user.name).to.eql("Jane Smith Updated");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
pm.test("Other fields unchanged", function () {
|
|
161
|
+
const user = pm.response.json();
|
|
162
|
+
pm.expect(user.email).to.eql(pm.environment.get("testUserEmail"));
|
|
163
|
+
pm.expect(user.id).to.eql(pm.environment.get("testUserId"));
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
pm.test("updatedAt is after createdAt", function () {
|
|
167
|
+
const user = pm.response.json();
|
|
168
|
+
if (user.updatedAt && user.createdAt) {
|
|
169
|
+
pm.expect(new Date(user.updatedAt).getTime()).to.be.at.least(new Date(user.createdAt).getTime());
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Positive — Delete User
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
// Test script for DELETE /api/users/{{testUserId}}
|
|
178
|
+
|
|
179
|
+
pm.test("Status code is 200 or 204", function () {
|
|
180
|
+
pm.expect(pm.response.code).to.be.oneOf([200, 204]);
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Positive — Verify Deletion
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
// Test script for GET /api/users/{{testUserId}} after deletion
|
|
188
|
+
|
|
189
|
+
pm.test("Returns 404 after deletion", function () {
|
|
190
|
+
pm.response.to.have.status(404);
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Negative — Missing Required Fields
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// POST /api/users — missing "name"
|
|
198
|
+
// Body: { "email": "test@example.com", "password": "SecureP@ss123" }
|
|
199
|
+
|
|
200
|
+
pm.test("Returns 400 for missing name", function () {
|
|
201
|
+
pm.response.to.have.status(400);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
pm.test("Error response has expected structure", function () {
|
|
205
|
+
const error = pm.response.json();
|
|
206
|
+
pm.expect(error).to.have.property("message");
|
|
207
|
+
pm.expect(error).to.have.property("error");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
pm.test("Error message references the missing field", function () {
|
|
211
|
+
const error = pm.response.json();
|
|
212
|
+
const msg = Array.isArray(error.message) ? error.message.join(" ") : error.message;
|
|
213
|
+
pm.expect(msg.toLowerCase()).to.include("name");
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
// POST /api/users — missing "email"
|
|
219
|
+
// Body: { "name": "Jane Smith", "password": "SecureP@ss123" }
|
|
220
|
+
|
|
221
|
+
pm.test("Returns 400 for missing email", function () {
|
|
222
|
+
pm.response.to.have.status(400);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
pm.test("Error message references email", function () {
|
|
226
|
+
const error = pm.response.json();
|
|
227
|
+
const msg = Array.isArray(error.message) ? error.message.join(" ") : error.message;
|
|
228
|
+
pm.expect(msg.toLowerCase()).to.include("email");
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
// POST /api/users — missing "password"
|
|
234
|
+
// Body: { "name": "Jane Smith", "email": "test@example.com" }
|
|
235
|
+
|
|
236
|
+
pm.test("Returns 400 for missing password", function () {
|
|
237
|
+
pm.response.to.have.status(400);
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Negative — Invalid Email Format
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
// POST /api/users
|
|
245
|
+
// Body: { "name": "Jane", "email": "not-an-email", "password": "SecureP@ss123" }
|
|
246
|
+
|
|
247
|
+
pm.test("Returns 400 for invalid email format", function () {
|
|
248
|
+
pm.response.to.have.status(400);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
pm.test("Error mentions email validation", function () {
|
|
252
|
+
const error = pm.response.json();
|
|
253
|
+
const msg = Array.isArray(error.message) ? error.message.join(" ") : error.message;
|
|
254
|
+
pm.expect(msg.toLowerCase()).to.satisfy(function (m) {
|
|
255
|
+
return m.includes("email") || m.includes("valid");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Negative — Password Too Short
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
// POST /api/users
|
|
264
|
+
// Body: { "name": "Jane", "email": "jane@example.com", "password": "ab" }
|
|
265
|
+
|
|
266
|
+
pm.test("Returns 400 for short password", function () {
|
|
267
|
+
pm.response.to.have.status(400);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
pm.test("Error mentions password requirements", function () {
|
|
271
|
+
const error = pm.response.json();
|
|
272
|
+
const msg = Array.isArray(error.message) ? error.message.join(" ") : error.message;
|
|
273
|
+
pm.expect(msg.toLowerCase()).to.satisfy(function (m) {
|
|
274
|
+
return m.includes("password") || m.includes("length") || m.includes("characters");
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Negative — Empty String Values
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
// POST /api/users
|
|
283
|
+
// Body: { "name": "", "email": "jane@example.com", "password": "SecureP@ss123" }
|
|
284
|
+
|
|
285
|
+
pm.test("Returns 400 for empty name", function () {
|
|
286
|
+
pm.response.to.have.status(400);
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
// POST /api/users
|
|
292
|
+
// Body: { "name": " ", "email": "jane@example.com", "password": "SecureP@ss123" }
|
|
293
|
+
|
|
294
|
+
pm.test("Returns 400 for whitespace-only name", function () {
|
|
295
|
+
pm.response.to.have.status(400);
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Auth Test Patterns
|
|
300
|
+
|
|
301
|
+
### Valid Token
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
pm.test("Authenticated request succeeds", function () {
|
|
305
|
+
pm.response.to.have.status(200);
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### No Token
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
// Remove Authorization header entirely
|
|
313
|
+
|
|
314
|
+
pm.test("Returns 401 without auth token", function () {
|
|
315
|
+
pm.response.to.have.status(401);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
pm.test("Error indicates authentication is required", function () {
|
|
319
|
+
const error = pm.response.json();
|
|
320
|
+
const msg = (error.message || error.error || "").toLowerCase();
|
|
321
|
+
pm.expect(msg).to.satisfy(function (m) {
|
|
322
|
+
return m.includes("unauthorized") || m.includes("token") || m.includes("authentication");
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Malformed Token
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
// Authorization: Bearer not-a-jwt-token
|
|
331
|
+
|
|
332
|
+
pm.test("Returns 401 for malformed token", function () {
|
|
333
|
+
pm.response.to.have.status(401);
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Expired Token
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
// Authorization: Bearer <expired-jwt>
|
|
341
|
+
|
|
342
|
+
pm.test("Returns 401 for expired token", function () {
|
|
343
|
+
pm.response.to.have.status(401);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
pm.test("Error indicates token is expired", function () {
|
|
347
|
+
const error = pm.response.json();
|
|
348
|
+
const msg = (error.message || "").toLowerCase();
|
|
349
|
+
pm.expect(msg).to.satisfy(function (m) {
|
|
350
|
+
return m.includes("expired") || m.includes("invalid") || m.includes("unauthorized");
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Insufficient Permissions (Role-Based)
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
// Use a regular user token for an admin-only endpoint
|
|
359
|
+
|
|
360
|
+
pm.test("Returns 403 for insufficient permissions", function () {
|
|
361
|
+
pm.response.to.have.status(403);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
pm.test("Error indicates permission denied", function () {
|
|
365
|
+
const error = pm.response.json();
|
|
366
|
+
const msg = (error.message || error.error || "").toLowerCase();
|
|
367
|
+
pm.expect(msg).to.satisfy(function (m) {
|
|
368
|
+
return m.includes("forbidden") || m.includes("permission") || m.includes("access denied");
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Schema Validation Patterns
|
|
374
|
+
|
|
375
|
+
### Inline Schema Validation
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
const userSchema = {
|
|
379
|
+
type: "object",
|
|
380
|
+
required: ["id", "name", "email", "createdAt"],
|
|
381
|
+
properties: {
|
|
382
|
+
id: { type: "string" },
|
|
383
|
+
name: { type: "string" },
|
|
384
|
+
email: { type: "string" },
|
|
385
|
+
role: { type: "string", enum: ["admin", "user", "moderator"] },
|
|
386
|
+
createdAt: { type: "string" },
|
|
387
|
+
updatedAt: { type: "string" }
|
|
388
|
+
},
|
|
389
|
+
additionalProperties: false
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
pm.test("Response matches user schema", function () {
|
|
393
|
+
pm.response.to.have.jsonSchema(userSchema);
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### List Response Schema Validation
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
const paginatedSchema = {
|
|
401
|
+
type: "object",
|
|
402
|
+
required: ["data", "meta"],
|
|
403
|
+
properties: {
|
|
404
|
+
data: {
|
|
405
|
+
type: "array",
|
|
406
|
+
items: {
|
|
407
|
+
type: "object",
|
|
408
|
+
required: ["id", "name", "email"],
|
|
409
|
+
properties: {
|
|
410
|
+
id: { type: "string" },
|
|
411
|
+
name: { type: "string" },
|
|
412
|
+
email: { type: "string" }
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
meta: {
|
|
417
|
+
type: "object",
|
|
418
|
+
required: ["page", "limit", "totalItems", "totalPages"],
|
|
419
|
+
properties: {
|
|
420
|
+
page: { type: "number" },
|
|
421
|
+
limit: { type: "number" },
|
|
422
|
+
totalItems: { type: "number" },
|
|
423
|
+
totalPages: { type: "number" }
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
pm.test("List response matches paginated schema", function () {
|
|
430
|
+
pm.response.to.have.jsonSchema(paginatedSchema);
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Error Response Schema Validation
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
const errorSchema = {
|
|
438
|
+
type: "object",
|
|
439
|
+
required: ["statusCode", "message", "error"],
|
|
440
|
+
properties: {
|
|
441
|
+
statusCode: { type: "number" },
|
|
442
|
+
message: {
|
|
443
|
+
oneOf: [
|
|
444
|
+
{ type: "string" },
|
|
445
|
+
{ type: "array", items: { type: "string" } }
|
|
446
|
+
]
|
|
447
|
+
},
|
|
448
|
+
error: { type: "string" }
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
pm.test("Error response matches error schema", function () {
|
|
453
|
+
pm.response.to.have.jsonSchema(errorSchema);
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Chained Test Patterns
|
|
458
|
+
|
|
459
|
+
### Create -> Read -> Update -> Read -> Delete -> Read (Verify Gone)
|
|
460
|
+
|
|
461
|
+
This sequence must run in order. Each step captures or uses variables.
|
|
462
|
+
|
|
463
|
+
```
|
|
464
|
+
Request 1: POST /api/products → capture productId
|
|
465
|
+
Assert: 201, has id, matches input
|
|
466
|
+
|
|
467
|
+
Request 2: GET /api/products/:id → uses productId
|
|
468
|
+
Assert: 200, matches created data
|
|
469
|
+
|
|
470
|
+
Request 3: PUT /api/products/:id → uses productId
|
|
471
|
+
Assert: 200, fields updated
|
|
472
|
+
|
|
473
|
+
Request 4: GET /api/products/:id → uses productId
|
|
474
|
+
Assert: 200, reflects update
|
|
475
|
+
|
|
476
|
+
Request 5: DELETE /api/products/:id → uses productId
|
|
477
|
+
Assert: 200 or 204
|
|
478
|
+
|
|
479
|
+
Request 6: GET /api/products/:id → uses productId
|
|
480
|
+
Assert: 404
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Parent-Child Chain
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
Request 1: POST /api/categories → capture categoryId
|
|
487
|
+
Request 2: POST /api/products → uses categoryId in body
|
|
488
|
+
Body: { "categoryId": "{{categoryId}}", ... }
|
|
489
|
+
Assert: 201, product.categoryId matches
|
|
490
|
+
|
|
491
|
+
Request 3: GET /api/categories/:id/products → uses categoryId
|
|
492
|
+
Assert: 200, array includes created product
|
|
493
|
+
|
|
494
|
+
Request 4: DELETE /api/categories/:id → uses categoryId
|
|
495
|
+
Assert: 409 (has children) OR 204 (cascade delete, verify products gone)
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## Idempotency Test Patterns
|
|
499
|
+
|
|
500
|
+
### PUT Is Idempotent
|
|
501
|
+
|
|
502
|
+
```javascript
|
|
503
|
+
// Run the same PUT request twice in sequence
|
|
504
|
+
|
|
505
|
+
// First PUT
|
|
506
|
+
pm.test("First PUT returns 200", function () {
|
|
507
|
+
pm.response.to.have.status(200);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Second PUT (same data)
|
|
511
|
+
pm.test("Second PUT also returns 200 (idempotent)", function () {
|
|
512
|
+
pm.response.to.have.status(200);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
pm.test("Response is identical to first PUT", function () {
|
|
516
|
+
const response = pm.response.json();
|
|
517
|
+
pm.expect(response.name).to.eql("Updated Name");
|
|
518
|
+
});
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### DELETE Is Idempotent (or Not)
|
|
522
|
+
|
|
523
|
+
```javascript
|
|
524
|
+
// First DELETE
|
|
525
|
+
pm.test("First DELETE succeeds", function () {
|
|
526
|
+
pm.expect(pm.response.code).to.be.oneOf([200, 204]);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// Second DELETE (same resource)
|
|
530
|
+
pm.test("Second DELETE returns 404 (already deleted)", function () {
|
|
531
|
+
pm.response.to.have.status(404);
|
|
532
|
+
});
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## Rate Limiting Test Patterns
|
|
536
|
+
|
|
537
|
+
```javascript
|
|
538
|
+
pm.test("Rate limit headers are present", function () {
|
|
539
|
+
pm.response.to.have.header("X-RateLimit-Limit");
|
|
540
|
+
pm.response.to.have.header("X-RateLimit-Remaining");
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
pm.test("Rate limit remaining decreases", function () {
|
|
544
|
+
const remaining = parseInt(pm.response.headers.get("X-RateLimit-Remaining"));
|
|
545
|
+
pm.expect(remaining).to.be.a("number");
|
|
546
|
+
pm.expect(remaining).to.be.at.least(0);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// After exceeding the limit
|
|
550
|
+
pm.test("Returns 429 when rate limit exceeded", function () {
|
|
551
|
+
pm.response.to.have.status(429);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
pm.test("Rate limit response includes retry information", function () {
|
|
555
|
+
const response = pm.response.json();
|
|
556
|
+
const msg = (response.message || "").toLowerCase();
|
|
557
|
+
pm.expect(msg).to.satisfy(function (m) {
|
|
558
|
+
return m.includes("rate limit") || m.includes("too many requests");
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
## Response Time Benchmarks
|
|
564
|
+
|
|
565
|
+
```javascript
|
|
566
|
+
pm.test("GET responds under 500ms", function () {
|
|
567
|
+
pm.expect(pm.response.responseTime).to.be.below(500);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
pm.test("POST responds under 1000ms", function () {
|
|
571
|
+
pm.expect(pm.response.responseTime).to.be.below(1000);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
pm.test("List with pagination responds under 1000ms", function () {
|
|
575
|
+
pm.expect(pm.response.responseTime).to.be.below(1000);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
pm.test("Search/filter responds under 2000ms", function () {
|
|
579
|
+
pm.expect(pm.response.responseTime).to.be.below(2000);
|
|
580
|
+
});
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## Environment Variable Helpers
|
|
584
|
+
|
|
585
|
+
### Capture and Log
|
|
586
|
+
|
|
587
|
+
```javascript
|
|
588
|
+
const response = pm.response.json();
|
|
589
|
+
const id = response.id || response.data?.id;
|
|
590
|
+
pm.environment.set("lastCreatedId", id);
|
|
591
|
+
console.log("Captured ID:", id);
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Clear After Test Suite
|
|
595
|
+
|
|
596
|
+
```javascript
|
|
597
|
+
// Place in Teardown folder's test script
|
|
598
|
+
pm.environment.unset("testUserId");
|
|
599
|
+
pm.environment.unset("testUserEmail");
|
|
600
|
+
pm.environment.unset("authToken");
|
|
601
|
+
console.log("Test environment variables cleared");
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### Generate Unique Test Data
|
|
605
|
+
|
|
606
|
+
```javascript
|
|
607
|
+
// Pre-request script for unique data per run
|
|
608
|
+
const timestamp = Date.now();
|
|
609
|
+
pm.environment.set("testEmail", `test_${timestamp}@example.com`);
|
|
610
|
+
pm.environment.set("testName", `TestUser_${timestamp}`);
|
|
611
|
+
pm.environment.set("testSku", `SKU-${timestamp}`);
|
|
612
|
+
```
|