@schmock/core 1.0.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/dist/builder.d.ts +62 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +432 -0
- package/dist/errors.d.ts +56 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +92 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/parser.d.ts +19 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +40 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +39 -0
- package/src/builder.d.ts.map +1 -0
- package/src/builder.test.ts +289 -0
- package/src/builder.ts +580 -0
- package/src/debug.test.ts +241 -0
- package/src/delay.test.ts +319 -0
- package/src/errors.d.ts.map +1 -0
- package/src/errors.test.ts +223 -0
- package/src/errors.ts +124 -0
- package/src/factory.test.ts +133 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +80 -0
- package/src/namespace.test.ts +273 -0
- package/src/parser.d.ts.map +1 -0
- package/src/parser.test.ts +131 -0
- package/src/parser.ts +61 -0
- package/src/plugin-system.test.ts +511 -0
- package/src/response-parsing.test.ts +255 -0
- package/src/route-matching.test.ts +351 -0
- package/src/smart-defaults.test.ts +361 -0
- package/src/steps/async-support.steps.ts +427 -0
- package/src/steps/basic-usage.steps.ts +316 -0
- package/src/steps/developer-experience.steps.ts +439 -0
- package/src/steps/error-handling.steps.ts +387 -0
- package/src/steps/fluent-api.steps.ts +252 -0
- package/src/steps/http-methods.steps.ts +397 -0
- package/src/steps/performance-reliability.steps.ts +459 -0
- package/src/steps/plugin-integration.steps.ts +279 -0
- package/src/steps/route-key-format.steps.ts +118 -0
- package/src/steps/state-concurrency.steps.ts +643 -0
- package/src/steps/stateful-workflows.steps.ts +351 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.ts +17 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { describeFeature, loadFeature } from "@amiceli/vitest-cucumber";
|
|
2
|
+
import { expect } from "vitest";
|
|
3
|
+
import { schmock } from "../index";
|
|
4
|
+
import type { MockInstance } from "../types";
|
|
5
|
+
|
|
6
|
+
const feature = await loadFeature("../../features/performance-reliability.feature");
|
|
7
|
+
|
|
8
|
+
describeFeature(feature, ({ Scenario }) => {
|
|
9
|
+
let mock: MockInstance<any>;
|
|
10
|
+
let responses: any[] = [];
|
|
11
|
+
let responsesTimes: number[] = [];
|
|
12
|
+
|
|
13
|
+
Scenario("High-volume concurrent requests", ({ Given, When, Then, And }) => {
|
|
14
|
+
responses = [];
|
|
15
|
+
responsesTimes = [];
|
|
16
|
+
|
|
17
|
+
Given("I create a mock for load testing:", (_, docString: string) => {
|
|
18
|
+
// Create mock with new callable API for load testing
|
|
19
|
+
mock = schmock();
|
|
20
|
+
|
|
21
|
+
mock('GET /api/health', () => ({
|
|
22
|
+
status: 'healthy',
|
|
23
|
+
timestamp: Date.now()
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
mock('GET /api/data/:id', ({ params }) => ({
|
|
27
|
+
id: params.id,
|
|
28
|
+
data: Array.from({ length: 50 }, (_, i) => ({
|
|
29
|
+
index: i,
|
|
30
|
+
value: Math.random()
|
|
31
|
+
})),
|
|
32
|
+
generated_at: new Date().toISOString()
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
mock('POST /api/process', ({ body }) => {
|
|
36
|
+
// Simulate processing time
|
|
37
|
+
const items = Array.isArray(body) ? body : [body];
|
|
38
|
+
return {
|
|
39
|
+
processed: items.length,
|
|
40
|
+
results: items.map(item => ({ ...item, processed: true })),
|
|
41
|
+
batch_id: Math.random().toString(36)
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
When("I send {int} concurrent {string} requests", async (_, count: number, request: string) => {
|
|
47
|
+
const [method, path] = request.split(" ");
|
|
48
|
+
responses = [];
|
|
49
|
+
responsesTimes = [];
|
|
50
|
+
|
|
51
|
+
const promises = Array.from({ length: count }, async () => {
|
|
52
|
+
const startTime = Date.now();
|
|
53
|
+
const response = await mock.handle(method as any, path);
|
|
54
|
+
const elapsed = Date.now() - startTime;
|
|
55
|
+
return { response, elapsed };
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const results = await Promise.all(promises);
|
|
59
|
+
responses = results.map(r => r.response);
|
|
60
|
+
responsesTimes = results.map(r => r.elapsed);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
Then("all concurrent requests should complete successfully", () => {
|
|
64
|
+
const expectedCount = responses.length;
|
|
65
|
+
expect(responses).toHaveLength(expectedCount);
|
|
66
|
+
for (const response of responses) {
|
|
67
|
+
expect(response.status).toBe(200);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
And("the average response time should be under {int}ms", (_, maxTime: number) => {
|
|
72
|
+
const avgTime = responsesTimes.reduce((a, b) => a + b, 0) / responsesTimes.length;
|
|
73
|
+
expect(avgTime).toBeLessThan(maxTime);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
And("no requests should timeout", () => {
|
|
77
|
+
// All requests completed if we got here, so no timeouts
|
|
78
|
+
expect(responses.length).toBeGreaterThan(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
When("I send {int} concurrent requests to different {string} endpoints", async (_, count: number, pathPattern: string) => {
|
|
82
|
+
responses = [];
|
|
83
|
+
|
|
84
|
+
const promises = Array.from({ length: count }, async (_, i) => {
|
|
85
|
+
const path = pathPattern.replace(":id", `id${i}`);
|
|
86
|
+
return await mock.handle("GET", path);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
responses = await Promise.all(promises);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
Then("all responses should be unique based on ID", () => {
|
|
93
|
+
const ids = responses.map(r => r.body.id);
|
|
94
|
+
const uniqueIds = new Set(ids);
|
|
95
|
+
expect(uniqueIds.size).toBe(responses.length);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
And("each response should contain {int} data items", (_, expectedCount: number) => {
|
|
99
|
+
for (const response of responses) {
|
|
100
|
+
expect(response.body.data).toHaveLength(expectedCount);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
When("I send {int} concurrent {string} requests with different payloads", async (_, count: number, request: string) => {
|
|
105
|
+
const [method, path] = request.split(" ");
|
|
106
|
+
responses = [];
|
|
107
|
+
|
|
108
|
+
const promises = Array.from({ length: count }, async (_, i) => {
|
|
109
|
+
return await mock.handle(method as any, path, {
|
|
110
|
+
body: { id: i, data: `payload-${i}` }
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
responses = await Promise.all(promises);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
And("all concurrent requests should complete successfully", () => {
|
|
118
|
+
const expectedCount = responses.length;
|
|
119
|
+
expect(responses).toHaveLength(expectedCount);
|
|
120
|
+
for (const response of responses) {
|
|
121
|
+
expect(response.status).toBe(200);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
And("each response should have a unique batch_id", () => {
|
|
126
|
+
const batchIds = responses.map(r => r.body.batch_id);
|
|
127
|
+
const uniqueBatchIds = new Set(batchIds);
|
|
128
|
+
expect(uniqueBatchIds.size).toBe(responses.length);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
Scenario("Memory usage under sustained load", ({ Given, When, Then, And }) => {
|
|
133
|
+
Given("I create a mock with potential memory concerns:", (_, docString: string) => {
|
|
134
|
+
// Create mock with routes that handle large data
|
|
135
|
+
mock = schmock();
|
|
136
|
+
|
|
137
|
+
mock('POST /api/large-data', ({ body }) => {
|
|
138
|
+
// Create large response data
|
|
139
|
+
const largeArray = Array.from({ length: 1000 }, (_, i) => ({
|
|
140
|
+
id: i,
|
|
141
|
+
data: 'x'.repeat(100), // 100 chars per item
|
|
142
|
+
timestamp: Date.now(),
|
|
143
|
+
payload: body
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
results: largeArray,
|
|
148
|
+
items: largeArray,
|
|
149
|
+
total_size: largeArray.length,
|
|
150
|
+
size: 'large',
|
|
151
|
+
memory_usage: process.memoryUsage ? process.memoryUsage() : null
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
mock('GET /api/accumulate/:count', ({ params }) => {
|
|
156
|
+
const count = parseInt(params.count);
|
|
157
|
+
const items = Array.from({ length: count }, (_, i) => ({
|
|
158
|
+
id: i,
|
|
159
|
+
value: Math.random(),
|
|
160
|
+
timestamp: Date.now()
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
items: items,
|
|
165
|
+
accumulated: items,
|
|
166
|
+
total: items.length,
|
|
167
|
+
count: items.length,
|
|
168
|
+
memory_usage: process.memoryUsage ? process.memoryUsage() : null
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
When("I send {int} requests to {string} with {int}KB payloads", async (_, count: number, request: string, payloadSize: number) => {
|
|
174
|
+
const [method, path] = request.split(" ");
|
|
175
|
+
responses = [];
|
|
176
|
+
|
|
177
|
+
const largePayload = { data: 'x'.repeat(payloadSize * 1024) };
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < count; i++) {
|
|
180
|
+
const response = await mock.handle(method as any, path, { body: largePayload });
|
|
181
|
+
responses.push(response);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
Then("all requests should complete without memory errors", () => {
|
|
186
|
+
expect(responses).toHaveLength(20);
|
|
187
|
+
for (const response of responses) {
|
|
188
|
+
expect(response.status).toBe(200);
|
|
189
|
+
expect(response.body.items).toHaveLength(1000);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
And("the mock should handle the load gracefully", () => {
|
|
194
|
+
// If we got here without throwing, the mock handled the load
|
|
195
|
+
expect(responses.every(r => r.body.size === 'large')).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
When("I request {string} multiple times", async (_, request: string) => {
|
|
199
|
+
const [method, path] = request.split(" ");
|
|
200
|
+
responses = [];
|
|
201
|
+
|
|
202
|
+
for (let i = 0; i < 5; i++) {
|
|
203
|
+
const response = await mock.handle(method as any, path);
|
|
204
|
+
responses.push(response);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
Then("each response should contain {int} accumulated items", (_, expectedCount: number) => {
|
|
209
|
+
for (const response of responses) {
|
|
210
|
+
expect(response.body.accumulated).toHaveLength(expectedCount);
|
|
211
|
+
expect(response.body.total).toBe(expectedCount);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
And("the memory usage should remain stable", () => {
|
|
216
|
+
// Memory stability is tested by not crashing during multiple large requests
|
|
217
|
+
expect(responses).toHaveLength(5);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
Scenario("Error resilience and recovery", ({ Given, When, Then, And }) => {
|
|
222
|
+
responses = [];
|
|
223
|
+
|
|
224
|
+
Given("I create a mock with intermittent failures:", (_, docString: string) => {
|
|
225
|
+
// Create mock with intermittent failure simulation
|
|
226
|
+
mock = schmock();
|
|
227
|
+
|
|
228
|
+
let requestCount = 0;
|
|
229
|
+
|
|
230
|
+
mock('POST /api/unreliable', ({ body }) => {
|
|
231
|
+
requestCount++;
|
|
232
|
+
|
|
233
|
+
// Simulate 20% failure rate
|
|
234
|
+
if (requestCount % 5 === 0) {
|
|
235
|
+
return [500, { error: 'Simulated server error', request_id: requestCount }];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return [200, { success: true, data: body, request_id: requestCount }];
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
mock('GET /api/flaky', () => {
|
|
242
|
+
requestCount++;
|
|
243
|
+
|
|
244
|
+
// Simulate 20% failure rate (1 in 5 requests fail)
|
|
245
|
+
if (requestCount % 5 === 0) {
|
|
246
|
+
return [500, { error: 'Flaky service error', request_id: requestCount }];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return [200, { success: true, request_id: requestCount }];
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
mock('POST /api/validate-strict', ({ body }) => {
|
|
253
|
+
if (!body || typeof body !== 'object') {
|
|
254
|
+
return [400, { error: 'Request body is required and must be an object', code: 'INVALID_BODY' }];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!body.name || typeof body.name !== 'string') {
|
|
258
|
+
return [422, { error: 'Name field is required and must be a string', code: 'INVALID_NAME' }];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!body.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(body.email)) {
|
|
262
|
+
return [422, { error: 'Valid email address is required', code: 'INVALID_EMAIL' }];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return [200, { message: 'Validation successful', data: body }];
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
When("I send {int} requests to {string}", async (_, count: number, request: string) => {
|
|
270
|
+
const [method, path] = request.split(" ");
|
|
271
|
+
responses = [];
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < count; i++) {
|
|
274
|
+
const response = await mock.handle(method as any, path);
|
|
275
|
+
responses.push(response);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
Then("some requests should succeed and some should fail", () => {
|
|
280
|
+
const successCount = responses.filter(r => r.status === 200).length;
|
|
281
|
+
const errorCount = responses.filter(r => r.status >= 400).length;
|
|
282
|
+
|
|
283
|
+
expect(successCount).toBeGreaterThan(0);
|
|
284
|
+
expect(errorCount).toBeGreaterThan(0);
|
|
285
|
+
expect(successCount + errorCount).toBe(responses.length);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
And("the success rate should be approximately {int}%", (_, expectedRate: number) => {
|
|
289
|
+
const successCount = responses.filter(r => r.status === 200).length;
|
|
290
|
+
const actualRate = (successCount / responses.length) * 100;
|
|
291
|
+
|
|
292
|
+
// Allow for some variance due to randomness
|
|
293
|
+
expect(actualRate).toBeGreaterThan(expectedRate - 10);
|
|
294
|
+
expect(actualRate).toBeLessThan(expectedRate + 10);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
And("error responses should have appropriate status codes", () => {
|
|
298
|
+
const errorResponses = responses.filter(r => r.status >= 400);
|
|
299
|
+
const validErrorCodes = [429, 500, 503];
|
|
300
|
+
|
|
301
|
+
for (const response of errorResponses) {
|
|
302
|
+
expect(validErrorCodes).toContain(response.status);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
When("I send requests to {string} with various invalid inputs", async (_, request: string) => {
|
|
307
|
+
const [method, path] = request.split(" ");
|
|
308
|
+
responses = [];
|
|
309
|
+
|
|
310
|
+
// Test various invalid scenarios
|
|
311
|
+
const testCases = [
|
|
312
|
+
{ headers: {}, body: null }, // No content-type, no body
|
|
313
|
+
{ headers: { 'content-type': 'application/json' }, body: null }, // No body
|
|
314
|
+
{ headers: { 'content-type': 'application/json' }, body: "invalid" }, // Invalid body type
|
|
315
|
+
{ headers: { 'content-type': 'application/json' }, body: {} }, // Missing required field
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
for (const testCase of testCases) {
|
|
319
|
+
const response = await mock.handle(method as any, path, testCase);
|
|
320
|
+
responses.push(response);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
Then("each error should have a specific, helpful error message", () => {
|
|
325
|
+
for (const response of responses) {
|
|
326
|
+
expect(response.status).toBeGreaterThanOrEqual(400);
|
|
327
|
+
expect(response.body.error).toBeDefined();
|
|
328
|
+
expect(typeof response.body.error).toBe('string');
|
|
329
|
+
expect(response.body.error.length).toBeGreaterThan(0);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
And("the error codes should correctly identify the validation issue", () => {
|
|
334
|
+
expect(responses[0].status).toBe(400); // No content-type
|
|
335
|
+
expect(responses[1].status).toBe(400); // No body
|
|
336
|
+
expect(responses[2].status).toBe(400); // Invalid body type
|
|
337
|
+
expect(responses[3].status).toBe(422); // Missing required field
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
Scenario("Route matching performance with complex patterns", ({ Given, When, Then, And }) => {
|
|
342
|
+
responses = [];
|
|
343
|
+
|
|
344
|
+
Given("I create a mock with many route patterns:", (_, docString: string) => {
|
|
345
|
+
// Create mock with many different route patterns for performance testing
|
|
346
|
+
mock = schmock();
|
|
347
|
+
|
|
348
|
+
// Routes that match the test expectations
|
|
349
|
+
mock('GET /api/users', () => ({ type: 'users-list' }));
|
|
350
|
+
mock('GET /api/users/:id', ({ params }) => ({ type: 'user', id: params.id }));
|
|
351
|
+
mock('GET /api/users/:userId/posts', ({ params }) => ({ type: 'user-posts', userId: params.userId }));
|
|
352
|
+
mock('GET /api/users/:userId/posts/:postId', ({ params }) => ({
|
|
353
|
+
type: 'user-post',
|
|
354
|
+
userId: params.userId,
|
|
355
|
+
postId: params.postId
|
|
356
|
+
}));
|
|
357
|
+
mock('GET /api/users/:userId/posts/:postId/comments', ({ params }) => ({
|
|
358
|
+
type: 'post-comments',
|
|
359
|
+
userId: params.userId,
|
|
360
|
+
postId: params.postId
|
|
361
|
+
}));
|
|
362
|
+
mock('GET /api/posts', () => ({ type: 'posts-list' }));
|
|
363
|
+
mock('GET /api/posts/:postId', ({ params }) => ({ type: 'post', postId: params.postId }));
|
|
364
|
+
mock('GET /api/posts/:postId/comments/:commentId', ({ params }) => ({
|
|
365
|
+
type: 'comment',
|
|
366
|
+
postId: params.postId,
|
|
367
|
+
commentId: params.commentId
|
|
368
|
+
}));
|
|
369
|
+
mock('GET /static/:category/:file', ({ params }) => ({
|
|
370
|
+
type: 'static',
|
|
371
|
+
category: params.category,
|
|
372
|
+
file: params.file
|
|
373
|
+
}));
|
|
374
|
+
mock('GET /api/v2/users/:userId', ({ params }) => ({
|
|
375
|
+
type: 'versioned-user',
|
|
376
|
+
userId: params.userId,
|
|
377
|
+
version: 'v2'
|
|
378
|
+
}));
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
When("I send requests to all route patterns simultaneously", async () => {
|
|
382
|
+
const testPaths = [
|
|
383
|
+
"GET /api/users",
|
|
384
|
+
"GET /api/users/123",
|
|
385
|
+
"GET /api/users/123/posts",
|
|
386
|
+
"GET /api/users/123/posts/456",
|
|
387
|
+
"GET /api/users/123/posts/456/comments",
|
|
388
|
+
"GET /api/posts",
|
|
389
|
+
"GET /api/posts/789",
|
|
390
|
+
"GET /api/posts/789/comments/101",
|
|
391
|
+
"GET /static/images/logo.png",
|
|
392
|
+
"GET /api/v2/users/456"
|
|
393
|
+
];
|
|
394
|
+
|
|
395
|
+
const promises = testPaths.map(async (request) => {
|
|
396
|
+
const [method, path] = request.split(" ");
|
|
397
|
+
return await mock.handle(method as any, path);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
responses = await Promise.all(promises);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
Then("each request should match the correct route pattern", () => {
|
|
404
|
+
expect(responses[0].body.type).toBe("users-list");
|
|
405
|
+
expect(responses[1].body.type).toBe("user");
|
|
406
|
+
expect(responses[2].body.type).toBe("user-posts");
|
|
407
|
+
expect(responses[3].body.type).toBe("user-post");
|
|
408
|
+
expect(responses[4].body.type).toBe("post-comments");
|
|
409
|
+
expect(responses[5].body.type).toBe("posts-list");
|
|
410
|
+
expect(responses[6].body.type).toBe("post");
|
|
411
|
+
expect(responses[7].body.type).toBe("comment");
|
|
412
|
+
expect(responses[8].body.type).toBe("static");
|
|
413
|
+
expect(responses[9].body.type).toBe("versioned-user");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
And("parameter extraction should work correctly for all patterns", () => {
|
|
417
|
+
expect(responses[1].body.id).toBe("123");
|
|
418
|
+
expect(responses[2].body.userId).toBe("123");
|
|
419
|
+
expect(responses[3].body.userId).toBe("123");
|
|
420
|
+
expect(responses[3].body.postId).toBe("456");
|
|
421
|
+
expect(responses[8].body.category).toBe("images");
|
|
422
|
+
expect(responses[8].body.file).toBe("logo.png");
|
|
423
|
+
expect(responses[9].body.version).toBe("v2");
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
And("the route matching should be efficient even with many patterns", () => {
|
|
427
|
+
// All requests completed quickly if we got here
|
|
428
|
+
expect(responses).toHaveLength(10);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
When("I send requests to non-matching paths", async () => {
|
|
432
|
+
const invalidPaths = [
|
|
433
|
+
"GET /api/invalid",
|
|
434
|
+
"GET /users", // Missing /api prefix
|
|
435
|
+
"GET /api/users/123/invalid",
|
|
436
|
+
"POST /static/images/test.jpg" // Wrong method
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
responses = [];
|
|
440
|
+
for (const request of invalidPaths) {
|
|
441
|
+
const [method, path] = request.split(" ");
|
|
442
|
+
const response = await mock.handle(method as any, path);
|
|
443
|
+
responses.push(response);
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
Then("they should consistently return {int} responses", (_, expectedStatus: number) => {
|
|
448
|
+
for (const response of responses) {
|
|
449
|
+
expect(response.status).toBe(expectedStatus);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
And("the {int} responses should be fast", (_, statusCode: number) => {
|
|
454
|
+
// If we got here quickly, the 404 responses were fast
|
|
455
|
+
expect(responses.every(r => r.status === statusCode)).toBe(true);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
});
|