@schmock/core 2.2.3 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -0
  3. package/dist/abort.d.ts +3 -0
  4. package/dist/abort.d.ts.map +1 -0
  5. package/dist/abort.js +30 -0
  6. package/dist/binary.d.ts +8 -0
  7. package/dist/binary.d.ts.map +1 -0
  8. package/dist/binary.js +9 -0
  9. package/dist/builder.d.ts +31 -10
  10. package/dist/builder.d.ts.map +1 -1
  11. package/dist/builder.js +804 -203
  12. package/dist/constants.d.ts +38 -0
  13. package/dist/constants.d.ts.map +1 -1
  14. package/dist/constants.js +123 -2
  15. package/dist/errors.d.ts +14 -3
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +41 -6
  18. package/dist/helpers.d.ts +9 -0
  19. package/dist/helpers.d.ts.map +1 -1
  20. package/dist/helpers.js +18 -2
  21. package/dist/http-helpers.d.ts +52 -5
  22. package/dist/http-helpers.d.ts.map +1 -1
  23. package/dist/http-helpers.js +153 -37
  24. package/dist/index.d.ts +381 -60
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +9 -3
  27. package/dist/interceptor.d.ts +11 -1
  28. package/dist/interceptor.d.ts.map +1 -1
  29. package/dist/interceptor.js +354 -156
  30. package/dist/parser.d.ts.map +1 -1
  31. package/dist/parser.js +12 -3
  32. package/dist/plugin-pipeline.d.ts +11 -4
  33. package/dist/plugin-pipeline.d.ts.map +1 -1
  34. package/dist/plugin-pipeline.js +117 -39
  35. package/dist/response-normalizer.d.ts +16 -0
  36. package/dist/response-normalizer.d.ts.map +1 -0
  37. package/dist/response-normalizer.js +316 -0
  38. package/dist/response-parser.d.ts.map +1 -1
  39. package/dist/response-parser.js +85 -18
  40. package/dist/route-matcher.d.ts +3 -0
  41. package/dist/route-matcher.d.ts.map +1 -1
  42. package/dist/route-matcher.js +12 -6
  43. package/dist/types.d.ts +7 -2
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +18 -8
  46. package/src/audit-core-builder.test.ts +0 -218
  47. package/src/audit-response-guard.test.ts +0 -38
  48. package/src/builder.test.ts +0 -289
  49. package/src/builder.ts +0 -701
  50. package/src/constants.test.ts +0 -99
  51. package/src/constants.ts +0 -73
  52. package/src/debug.test.ts +0 -241
  53. package/src/delay.test.ts +0 -319
  54. package/src/dist-shape.test.ts +0 -35
  55. package/src/errors.test.ts +0 -223
  56. package/src/errors.ts +0 -130
  57. package/src/factory.test.ts +0 -133
  58. package/src/helpers.test.ts +0 -147
  59. package/src/helpers.ts +0 -58
  60. package/src/http-helpers.ts +0 -113
  61. package/src/index.ts +0 -151
  62. package/src/interceptor.test.ts +0 -291
  63. package/src/interceptor.ts +0 -320
  64. package/src/namespace.test.ts +0 -274
  65. package/src/parser.property.test.ts +0 -594
  66. package/src/parser.test.ts +0 -148
  67. package/src/parser.ts +0 -64
  68. package/src/plugin-pipeline.ts +0 -103
  69. package/src/plugin-system.test.ts +0 -602
  70. package/src/response-parser.ts +0 -75
  71. package/src/response-parsing.test.ts +0 -333
  72. package/src/route-matcher.ts +0 -69
  73. package/src/route-matching.test.ts +0 -394
  74. package/src/server.test.ts +0 -165
  75. package/src/smart-defaults.test.ts +0 -361
  76. package/src/steps/async-support.steps.ts +0 -400
  77. package/src/steps/audit-core-builder.steps.ts +0 -188
  78. package/src/steps/audit-onerror-tuple.steps.ts +0 -99
  79. package/src/steps/basic-usage.steps.ts +0 -245
  80. package/src/steps/developer-experience.steps.ts +0 -204
  81. package/src/steps/error-handling.steps.ts +0 -345
  82. package/src/steps/fluent-api.steps.ts +0 -255
  83. package/src/steps/http-methods.steps.ts +0 -331
  84. package/src/steps/interceptor.steps.ts +0 -260
  85. package/src/steps/lifecycle-events.steps.ts +0 -142
  86. package/src/steps/performance-reliability.steps.ts +0 -423
  87. package/src/steps/plugin-integration.steps.ts +0 -280
  88. package/src/steps/request-history.steps.ts +0 -276
  89. package/src/steps/response-delay.steps.ts +0 -88
  90. package/src/steps/route-key-format.steps.ts +0 -99
  91. package/src/steps/standalone-server.steps.ts +0 -233
  92. package/src/steps/state-concurrency.steps.ts +0 -739
  93. package/src/steps/stateful-workflows.steps.ts +0 -353
  94. package/src/types.ts +0 -36
@@ -1,142 +0,0 @@
1
- import { describeFeature, loadFeature } from "@amiceli/vitest-cucumber";
2
- import { expect } from "vitest";
3
- import { schmock } from "../index";
4
-
5
- const feature = await loadFeature("../../features/lifecycle-events.feature");
6
-
7
- describeFeature(feature, ({ Scenario }) => {
8
- let mock: Schmock.CallableMockInstance;
9
- let events: Array<{ type: string; data: unknown }>;
10
- let removedFired: boolean;
11
-
12
- function collectEvent(type: string) {
13
- return (data: unknown) => {
14
- events.push({ type, data });
15
- };
16
- }
17
-
18
- Scenario("Events fire at correct times", ({ Given, And, When, Then }) => {
19
- Given('a mock with a route "GET /items"', () => {
20
- mock = schmock({ state: {} });
21
- mock("GET /items", [{ id: 1 }], {});
22
- });
23
-
24
- And("I register listeners for all events", () => {
25
- events = [];
26
- mock.on("request:start", collectEvent("request:start"));
27
- mock.on("request:match", collectEvent("request:match"));
28
- mock.on("request:notfound", collectEvent("request:notfound"));
29
- mock.on("request:end", collectEvent("request:end"));
30
- });
31
-
32
- When('I request "GET /items"', async () => {
33
- await mock.handle("GET", "/items");
34
- });
35
-
36
- Then('the "request:start" event fired', () => {
37
- expect(events.some((e) => e.type === "request:start")).toBe(true);
38
- });
39
-
40
- And('the "request:match" event fired with routePath "/items"', () => {
41
- const match = events.find((e) => e.type === "request:match");
42
- expect(match).toBeDefined();
43
- const data = match?.data as Record<string, unknown>;
44
- expect(data.routePath).toBe("/items");
45
- });
46
-
47
- And('the "request:end" event fired with status 200', () => {
48
- const end = events.find((e) => e.type === "request:end");
49
- expect(end).toBeDefined();
50
- const data = end?.data as Record<string, unknown>;
51
- expect(data.status).toBe(200);
52
- });
53
- });
54
-
55
- Scenario("Not found event fires for unmatched routes", ({ Given, And, When, Then }) => {
56
- Given('a mock with a route "GET /items"', () => {
57
- mock = schmock({ state: {} });
58
- mock("GET /items", [{ id: 1 }], {});
59
- });
60
-
61
- And("I register listeners for all events", () => {
62
- events = [];
63
- mock.on("request:start", collectEvent("request:start"));
64
- mock.on("request:match", collectEvent("request:match"));
65
- mock.on("request:notfound", collectEvent("request:notfound"));
66
- mock.on("request:end", collectEvent("request:end"));
67
- });
68
-
69
- When('I request "GET /missing"', async () => {
70
- await mock.handle("GET", "/missing");
71
- });
72
-
73
- Then('the "request:start" event fired', () => {
74
- expect(events.some((e) => e.type === "request:start")).toBe(true);
75
- });
76
-
77
- And('the "request:notfound" event fired', () => {
78
- expect(events.some((e) => e.type === "request:notfound")).toBe(true);
79
- });
80
-
81
- And('the "request:end" event fired with status 404', () => {
82
- const end = events.find((e) => e.type === "request:end");
83
- expect(end).toBeDefined();
84
- const data = end?.data as Record<string, unknown>;
85
- expect(data.status).toBe(404);
86
- });
87
- });
88
-
89
- Scenario("Off removes listener", ({ Given, And, When, Then }) => {
90
- Given('a mock with a route "GET /items"', () => {
91
- mock = schmock({ state: {} });
92
- mock("GET /items", [{ id: 1 }], {});
93
- });
94
-
95
- And("I register and remove a listener", () => {
96
- removedFired = false;
97
- const listener = () => {
98
- removedFired = true;
99
- };
100
- mock.on("request:start", listener);
101
- mock.off("request:start", listener);
102
- });
103
-
104
- When('I request "GET /items"', async () => {
105
- await mock.handle("GET", "/items");
106
- });
107
-
108
- Then("the removed listener did not fire", () => {
109
- expect(removedFired).toBe(false);
110
- });
111
- });
112
-
113
- Scenario("Reset clears all listeners", ({ Given, And, When, Then }) => {
114
- Given('a mock with a route "GET /items"', () => {
115
- mock = schmock({ state: {} });
116
- mock("GET /items", [{ id: 1 }], {});
117
- });
118
-
119
- And("I register listeners for all events", () => {
120
- events = [];
121
- mock.on("request:start", collectEvent("request:start"));
122
- mock.on("request:end", collectEvent("request:end"));
123
- });
124
-
125
- When("I reset the mock", () => {
126
- mock.reset();
127
- events = [];
128
- });
129
-
130
- And('I add a route "GET /items" again', () => {
131
- mock("GET /items", [{ id: 1 }], {});
132
- });
133
-
134
- And('I request "GET /items" after reset', async () => {
135
- await mock.handle("GET", "/items");
136
- });
137
-
138
- Then("no events were collected after reset", () => {
139
- expect(events).toHaveLength(0);
140
- });
141
- });
142
- });
@@ -1,423 +0,0 @@
1
- import { describeFeature, loadFeature } from "@amiceli/vitest-cucumber";
2
- import { expect } from "vitest";
3
- import { schmock } from "../index";
4
- import type { CallableMockInstance } from "../types";
5
-
6
- const feature = await loadFeature("../../features/performance-reliability.feature");
7
-
8
- describeFeature(feature, ({ Scenario }) => {
9
- let mock: CallableMockInstance;
10
- let responses: any[] = [];
11
- let responsesTimes: number[] = [];
12
- let expectedResponseCount = 0;
13
-
14
- Scenario("High-volume concurrent requests", ({ Given, When, Then, And }) => {
15
- responses = [];
16
- responsesTimes = [];
17
-
18
- Given("I create a mock for high-volume load testing", () => {
19
- mock = schmock();
20
- mock('GET /api/health', () => ({
21
- status: 'healthy',
22
- timestamp: Date.now()
23
- }));
24
- mock('GET /api/data/:id', ({ params }) => ({
25
- id: params.id,
26
- data: Array.from({ length: 50 }, (_, i) => ({
27
- index: i,
28
- value: Math.random()
29
- })),
30
- generated_at: new Date().toISOString()
31
- }));
32
- mock('POST /api/process', ({ body }) => {
33
- const items = Array.isArray(body) ? body : [body];
34
- return {
35
- processed: items.length,
36
- results: items.map(item => ({ ...item, processed: true })),
37
- batch_id: Math.random().toString(36)
38
- };
39
- });
40
- });
41
-
42
- When("I send {int} concurrent {string} requests", async (_, count: number, request: string) => {
43
- const [method, path] = request.split(" ");
44
- responses = [];
45
- responsesTimes = [];
46
- expectedResponseCount = count;
47
-
48
- const promises = Array.from({ length: count }, async () => {
49
- const startTime = Date.now();
50
- const response = await mock.handle(method as any, path);
51
- const elapsed = Date.now() - startTime;
52
- return { response, elapsed };
53
- });
54
-
55
- const results = await Promise.all(promises);
56
- responses = results.map(r => r.response);
57
- responsesTimes = results.map(r => r.elapsed);
58
- });
59
-
60
- Then("all concurrent requests should complete successfully", () => {
61
- expect(responses).toHaveLength(expectedResponseCount);
62
- for (const response of responses) {
63
- expect(response.status).toBe(200);
64
- }
65
- });
66
-
67
- // Generous ceiling, not a perf target: in-process handlers are sub-millisecond,
68
- // so this only guards against a pathological hang. A tight bound (e.g. 50ms)
69
- // flakes under CI load where 100 concurrent requests contend for the event loop.
70
- And("the average response time should be under {int}ms", (_, maxTime: number) => {
71
- const avgTime = responsesTimes.reduce((a, b) => a + b, 0) / responsesTimes.length;
72
- expect(avgTime).toBeLessThan(maxTime);
73
- });
74
-
75
- And("no requests should timeout", () => {
76
- // All requests completed if we got here, so no timeouts
77
- expect(responses.length).toBeGreaterThan(0);
78
- });
79
-
80
- When("I send {int} concurrent requests to different {string} endpoints", async (_, count: number, pathPattern: string) => {
81
- responses = [];
82
-
83
- const promises = Array.from({ length: count }, async (_, i) => {
84
- const path = pathPattern.replace(":id", `id${i}`);
85
- return await mock.handle("GET", path);
86
- });
87
-
88
- responses = await Promise.all(promises);
89
- });
90
-
91
- Then("all responses should be unique based on ID", () => {
92
- const ids = responses.map(r => r.body.id);
93
- const uniqueIds = new Set(ids);
94
- expect(uniqueIds.size).toBe(responses.length);
95
- });
96
-
97
- And("each response should contain {int} data items", (_, expectedCount: number) => {
98
- for (const response of responses) {
99
- expect(response.body.data).toHaveLength(expectedCount);
100
- }
101
- });
102
-
103
- When("I send {int} concurrent {string} requests with different payloads", async (_, count: number, request: string) => {
104
- const [method, path] = request.split(" ");
105
- responses = [];
106
- expectedResponseCount = count;
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
- expect(responses).toHaveLength(expectedResponseCount);
119
- for (const response of responses) {
120
- expect(response.status).toBe(200);
121
- }
122
- });
123
-
124
- And("each response should have a unique batch_id", () => {
125
- const batchIds = responses.map(r => r.body.batch_id);
126
- const uniqueBatchIds = new Set(batchIds);
127
- expect(uniqueBatchIds.size).toBe(responses.length);
128
- });
129
- });
130
-
131
- Scenario("Memory usage under sustained load", ({ Given, When, Then, And }) => {
132
- Given("I create a mock with large payload handling", () => {
133
- mock = schmock();
134
- mock('POST /api/large-data', ({ body }) => {
135
- const largeArray = Array.from({ length: 1000 }, (_, i) => ({
136
- id: i,
137
- data: 'x'.repeat(100),
138
- timestamp: Date.now(),
139
- payload: body
140
- }));
141
- return {
142
- results: largeArray,
143
- items: largeArray,
144
- total_size: largeArray.length,
145
- size: 'large',
146
- memory_usage: process.memoryUsage ? process.memoryUsage() : null
147
- };
148
- });
149
- mock('GET /api/accumulate/:count', ({ params }) => {
150
- const count = parseInt(params.count);
151
- const items = Array.from({ length: count }, (_, i) => ({
152
- id: i,
153
- value: Math.random(),
154
- timestamp: Date.now()
155
- }));
156
- return {
157
- items: items,
158
- accumulated: items,
159
- total: items.length,
160
- count: items.length,
161
- memory_usage: process.memoryUsage ? process.memoryUsage() : null
162
- };
163
- });
164
- });
165
-
166
- When("I send {int} requests to {string} with {int}KB payloads", async (_, count: number, request: string, payloadSize: number) => {
167
- const [method, path] = request.split(" ");
168
- responses = [];
169
-
170
- const largePayload = { data: 'x'.repeat(payloadSize * 1024) };
171
-
172
- for (let i = 0; i < count; i++) {
173
- const response = await mock.handle(method as any, path, { body: largePayload });
174
- responses.push(response);
175
- }
176
- });
177
-
178
- Then("all requests should complete without memory errors", () => {
179
- expect(responses).toHaveLength(20);
180
- for (const response of responses) {
181
- expect(response.status).toBe(200);
182
- expect(response.body.items).toHaveLength(1000);
183
- }
184
- });
185
-
186
- And("each response should confirm large payload processing", () => {
187
- expect(responses.every(r => r.body.size === 'large')).toBe(true);
188
- });
189
-
190
- When("I request {string} multiple times", async (_, request: string) => {
191
- const [method, path] = request.split(" ");
192
- responses = [];
193
-
194
- for (let i = 0; i < 5; i++) {
195
- const response = await mock.handle(method as any, path);
196
- responses.push(response);
197
- }
198
- });
199
-
200
- Then("each response should contain {int} accumulated items", (_, expectedCount: number) => {
201
- for (const response of responses) {
202
- expect(response.body.accumulated).toHaveLength(expectedCount);
203
- expect(response.body.total).toBe(expectedCount);
204
- }
205
- });
206
-
207
- And("all accumulation requests should complete successfully", () => {
208
- expect(responses).toHaveLength(5);
209
- });
210
- });
211
-
212
- Scenario("Error resilience and recovery", ({ Given, When, Then, And }) => {
213
- responses = [];
214
-
215
- Given("I create a mock with intermittent failure simulation", () => {
216
- mock = schmock();
217
- let requestCount = 0;
218
- mock('POST /api/unreliable', ({ body }) => {
219
- requestCount++;
220
- if (requestCount % 5 === 0) {
221
- return [500, { error: 'Simulated server error', request_id: requestCount }];
222
- }
223
- return [200, { success: true, data: body, request_id: requestCount }];
224
- });
225
- mock('GET /api/flaky', () => {
226
- requestCount++;
227
- if (requestCount % 5 === 0) {
228
- return [500, { error: 'Flaky service error', request_id: requestCount }];
229
- }
230
- return [200, { success: true, request_id: requestCount }];
231
- });
232
- mock('POST /api/validate-strict', ({ body }) => {
233
- if (!body || typeof body !== 'object') {
234
- return [400, { error: 'Request body is required and must be an object', code: 'INVALID_BODY' }];
235
- }
236
- const record = body as Record<string, unknown>;
237
- if (!record.name || typeof record.name !== 'string') {
238
- return [422, { error: 'Name field is required and must be a string', code: 'INVALID_NAME' }];
239
- }
240
- if (!record.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(record.email as string)) {
241
- return [422, { error: 'Valid email address is required', code: 'INVALID_EMAIL' }];
242
- }
243
- return [200, { message: 'Validation successful', data: body }];
244
- });
245
- });
246
-
247
- When("I send {int} requests to {string}", async (_, count: number, request: string) => {
248
- const [method, path] = request.split(" ");
249
- responses = [];
250
-
251
- for (let i = 0; i < count; i++) {
252
- const response = await mock.handle(method as any, path);
253
- responses.push(response);
254
- }
255
- });
256
-
257
- Then("some requests should succeed and some should fail", () => {
258
- const successCount = responses.filter(r => r.status === 200).length;
259
- const errorCount = responses.filter(r => r.status >= 400).length;
260
-
261
- expect(successCount).toBeGreaterThan(0);
262
- expect(errorCount).toBeGreaterThan(0);
263
- expect(successCount + errorCount).toBe(responses.length);
264
- });
265
-
266
- And("the success rate should be between {int}% and {int}%", (_, low: number, high: number) => {
267
- const successCount = responses.filter(r => r.status === 200).length;
268
- const actualRate = (successCount / responses.length) * 100;
269
-
270
- expect(actualRate).toBeGreaterThanOrEqual(low);
271
- expect(actualRate).toBeLessThanOrEqual(high);
272
- });
273
-
274
- And("error responses should have appropriate status codes", () => {
275
- const errorResponses = responses.filter(r => r.status >= 400);
276
- const validErrorCodes = [429, 500, 503];
277
-
278
- for (const response of errorResponses) {
279
- expect(validErrorCodes).toContain(response.status);
280
- }
281
- });
282
-
283
- When("I send requests to {string} with various invalid inputs", async (_, request: string) => {
284
- const [method, path] = request.split(" ");
285
- responses = [];
286
-
287
- // Test various invalid scenarios
288
- const testCases: Schmock.RequestOptions[] = [
289
- { headers: {}, body: null }, // No content-type, no body
290
- { headers: { 'content-type': 'application/json' }, body: null }, // No body
291
- { headers: { 'content-type': 'application/json' }, body: "invalid" }, // Invalid body type
292
- { headers: { 'content-type': 'application/json' }, body: {} }, // Missing required field
293
- ];
294
-
295
- for (const testCase of testCases) {
296
- const response = await mock.handle(method as any, path, testCase);
297
- responses.push(response);
298
- }
299
- });
300
-
301
- Then("each error response should have a non-empty error message", () => {
302
- for (const response of responses) {
303
- expect(response.status).toBeGreaterThanOrEqual(400);
304
- expect(response.body.error).toBeDefined();
305
- expect(typeof response.body.error).toBe('string');
306
- expect(response.body.error.length).toBeGreaterThan(0);
307
- }
308
- });
309
-
310
- And("each error response should have the correct status code", () => {
311
- expect(responses[0].status).toBe(400); // No content-type
312
- expect(responses[1].status).toBe(400); // No body
313
- expect(responses[2].status).toBe(400); // Invalid body type
314
- expect(responses[3].status).toBe(422); // Missing required field
315
- });
316
- });
317
-
318
- Scenario("Route matching performance with complex patterns", ({ Given, When, Then, And }) => {
319
- responses = [];
320
-
321
- Given("I create a mock with many nested route patterns", () => {
322
- mock = schmock();
323
- mock('GET /api/users', () => ({ type: 'users-list' }));
324
- mock('GET /api/users/:id', ({ params }) => ({ type: 'user', id: params.id }));
325
- mock('GET /api/users/:userId/posts', ({ params }) => ({ type: 'user-posts', userId: params.userId }));
326
- mock('GET /api/users/:userId/posts/:postId', ({ params }) => ({
327
- type: 'user-post',
328
- userId: params.userId,
329
- postId: params.postId
330
- }));
331
- mock('GET /api/users/:userId/posts/:postId/comments', ({ params }) => ({
332
- type: 'post-comments',
333
- userId: params.userId,
334
- postId: params.postId
335
- }));
336
- mock('GET /api/posts', () => ({ type: 'posts-list' }));
337
- mock('GET /api/posts/:postId', ({ params }) => ({ type: 'post', postId: params.postId }));
338
- mock('GET /api/posts/:postId/comments/:commentId', ({ params }) => ({
339
- type: 'comment',
340
- postId: params.postId,
341
- commentId: params.commentId
342
- }));
343
- mock('GET /static/:category/:file', ({ params }) => ({
344
- type: 'static',
345
- category: params.category,
346
- file: params.file
347
- }));
348
- mock('GET /api/v2/users/:userId', ({ params }) => ({
349
- type: 'versioned-user',
350
- userId: params.userId,
351
- version: 'v2'
352
- }));
353
- });
354
-
355
- When("I send requests to all route patterns simultaneously", async () => {
356
- const testPaths = [
357
- "GET /api/users",
358
- "GET /api/users/123",
359
- "GET /api/users/123/posts",
360
- "GET /api/users/123/posts/456",
361
- "GET /api/users/123/posts/456/comments",
362
- "GET /api/posts",
363
- "GET /api/posts/789",
364
- "GET /api/posts/789/comments/101",
365
- "GET /static/images/logo.png",
366
- "GET /api/v2/users/456"
367
- ];
368
-
369
- const promises = testPaths.map(async (request) => {
370
- const [method, path] = request.split(" ");
371
- return await mock.handle(method as any, path);
372
- });
373
-
374
- responses = await Promise.all(promises);
375
- });
376
-
377
- Then("each request should match the correct route pattern", () => {
378
- expect(responses[0].body.type).toBe("users-list");
379
- expect(responses[1].body.type).toBe("user");
380
- expect(responses[2].body.type).toBe("user-posts");
381
- expect(responses[3].body.type).toBe("user-post");
382
- expect(responses[4].body.type).toBe("post-comments");
383
- expect(responses[5].body.type).toBe("posts-list");
384
- expect(responses[6].body.type).toBe("post");
385
- expect(responses[7].body.type).toBe("comment");
386
- expect(responses[8].body.type).toBe("static");
387
- expect(responses[9].body.type).toBe("versioned-user");
388
- });
389
-
390
- And("parameter extraction should work correctly for all patterns", () => {
391
- expect(responses[1].body.id).toBe("123");
392
- expect(responses[2].body.userId).toBe("123");
393
- expect(responses[3].body.userId).toBe("123");
394
- expect(responses[3].body.postId).toBe("456");
395
- expect(responses[8].body.category).toBe("images");
396
- expect(responses[8].body.file).toBe("logo.png");
397
- expect(responses[9].body.version).toBe("v2");
398
- });
399
-
400
- When("I send requests to non-matching paths", async () => {
401
- const invalidPaths = [
402
- "GET /api/invalid",
403
- "GET /users", // Missing /api prefix
404
- "GET /api/users/123/invalid",
405
- "POST /static/images/test.jpg" // Wrong method
406
- ];
407
-
408
- responses = [];
409
- for (const request of invalidPaths) {
410
- const [method, path] = request.split(" ");
411
- const response = await mock.handle(method as any, path);
412
- responses.push(response);
413
- }
414
- });
415
-
416
- Then("they should consistently return {int} responses", (_, expectedStatus: number) => {
417
- for (const response of responses) {
418
- expect(response.status).toBe(expectedStatus);
419
- }
420
- });
421
- });
422
-
423
- });