@schmock/core 2.3.0 → 2.4.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/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/abort.d.ts +3 -0
- package/dist/abort.d.ts.map +1 -0
- package/dist/abort.js +30 -0
- package/dist/builder.d.ts +31 -10
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +760 -190
- package/dist/constants.d.ts +38 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +123 -2
- package/dist/errors.d.ts +14 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +41 -6
- package/dist/helpers.d.ts +9 -0
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +18 -2
- package/dist/http-helpers.d.ts +39 -4
- package/dist/http-helpers.d.ts.map +1 -1
- package/dist/http-helpers.js +147 -49
- package/dist/index.d.ts +292 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/interceptor.d.ts +11 -1
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +316 -169
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +12 -3
- package/dist/plugin-pipeline.d.ts +3 -3
- package/dist/plugin-pipeline.d.ts.map +1 -1
- package/dist/plugin-pipeline.js +29 -14
- package/dist/response-normalizer.d.ts +16 -0
- package/dist/response-normalizer.d.ts.map +1 -0
- package/dist/response-normalizer.js +316 -0
- package/dist/response-parser.d.ts.map +1 -1
- package/dist/response-parser.js +39 -2
- package/dist/route-matcher.d.ts +3 -0
- package/dist/route-matcher.d.ts.map +1 -1
- package/dist/route-matcher.js +12 -6
- package/dist/types.d.ts +7 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +18 -6
- package/src/audit-core-builder.test.ts +0 -218
- package/src/audit-response-guard.test.ts +0 -38
- package/src/binary.test.ts +0 -126
- package/src/binary.ts +0 -11
- package/src/builder.test.ts +0 -289
- package/src/builder.ts +0 -748
- package/src/constants.test.ts +0 -99
- package/src/constants.ts +0 -73
- package/src/debug.test.ts +0 -241
- package/src/delay.test.ts +0 -319
- package/src/dist-shape.test.ts +0 -35
- package/src/errors.test.ts +0 -223
- package/src/errors.ts +0 -130
- package/src/factory.test.ts +0 -133
- package/src/helpers.test.ts +0 -147
- package/src/helpers.ts +0 -58
- package/src/http-helpers.test.ts +0 -39
- package/src/http-helpers.ts +0 -149
- package/src/index.ts +0 -152
- package/src/interceptor.test.ts +0 -488
- package/src/interceptor.ts +0 -407
- package/src/namespace.test.ts +0 -274
- package/src/parser.property.test.ts +0 -630
- package/src/parser.test.ts +0 -148
- package/src/parser.ts +0 -64
- package/src/plugin-pipeline.ts +0 -208
- package/src/plugin-system.test.ts +0 -697
- package/src/response-parser.ts +0 -114
- package/src/response-parsing.test.ts +0 -333
- package/src/route-matcher.ts +0 -69
- package/src/route-matching.test.ts +0 -394
- package/src/server.test.ts +0 -234
- package/src/smart-defaults.test.ts +0 -361
- package/src/steps/async-support.steps.ts +0 -421
- package/src/steps/audit-core-builder.steps.ts +0 -188
- package/src/steps/audit-onerror-tuple.steps.ts +0 -83
- package/src/steps/basic-usage.steps.ts +0 -245
- package/src/steps/developer-experience.steps.ts +0 -204
- package/src/steps/error-handling.steps.ts +0 -555
- package/src/steps/fetch-interceptor.steps.ts +0 -457
- package/src/steps/fluent-api.steps.ts +0 -255
- package/src/steps/http-methods.steps.ts +0 -342
- package/src/steps/lifecycle-events.steps.ts +0 -149
- package/src/steps/performance-reliability.steps.ts +0 -90
- package/src/steps/plugin-integration.steps.ts +0 -334
- package/src/steps/request-history.steps.ts +0 -458
- package/src/steps/response-delay.steps.ts +0 -88
- package/src/steps/route-key-format.steps.ts +0 -99
- package/src/steps/standalone-server.steps.ts +0 -227
- package/src/steps/state-concurrency.steps.ts +0 -167
- package/src/types.ts +0 -36
|
@@ -1,457 +0,0 @@
|
|
|
1
|
-
/// <reference path="../../schmock.d.ts" />
|
|
2
|
-
|
|
3
|
-
import { describeFeature, loadFeature } from "@amiceli/vitest-cucumber";
|
|
4
|
-
import { expect, type Mock, vi } from "vitest";
|
|
5
|
-
import { schmock } from "../index.js";
|
|
6
|
-
|
|
7
|
-
const feature = await loadFeature("../../features/fetch-interceptor.feature");
|
|
8
|
-
|
|
9
|
-
describeFeature(feature, ({ Scenario, AfterEachScenario }) => {
|
|
10
|
-
let mock: Schmock.CallableMockInstance;
|
|
11
|
-
let handle: Schmock.InterceptHandle | undefined;
|
|
12
|
-
let originalFetch: typeof globalThis.fetch;
|
|
13
|
-
let savedFetch: Mock<typeof globalThis.fetch>;
|
|
14
|
-
let fetchResponse: Response | undefined;
|
|
15
|
-
let additionalHandles: Schmock.InterceptHandle[] = [];
|
|
16
|
-
|
|
17
|
-
function setup() {
|
|
18
|
-
originalFetch = globalThis.fetch;
|
|
19
|
-
savedFetch = vi
|
|
20
|
-
.fn<typeof globalThis.fetch>()
|
|
21
|
-
.mockResolvedValue(new Response("real backend"));
|
|
22
|
-
globalThis.fetch = savedFetch;
|
|
23
|
-
mock = schmock();
|
|
24
|
-
fetchResponse = undefined;
|
|
25
|
-
additionalHandles = [];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
AfterEachScenario(() => {
|
|
29
|
-
handle?.restore();
|
|
30
|
-
for (const additionalHandle of additionalHandles) {
|
|
31
|
-
additionalHandle.restore();
|
|
32
|
-
}
|
|
33
|
-
handle = undefined;
|
|
34
|
-
additionalHandles = [];
|
|
35
|
-
globalThis.fetch = originalFetch;
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
Scenario(
|
|
39
|
-
"Intercept a matched fetch request",
|
|
40
|
-
({ Given, When, Then, And }) => {
|
|
41
|
-
Given(
|
|
42
|
-
'a Schmock instance with route "GET /api/users" returning users',
|
|
43
|
-
() => {
|
|
44
|
-
setup();
|
|
45
|
-
mock("GET /api/users", [{ id: 1, name: "Alice" }]);
|
|
46
|
-
},
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
And("fetch is intercepted", () => {
|
|
50
|
-
handle = mock.intercept();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
When('I fetch "/api/users"', async () => {
|
|
54
|
-
fetchResponse = await fetch("http://localhost/api/users");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
Then("the fetch response status should be 200", () => {
|
|
58
|
-
expect(fetchResponse?.status).toBe(200);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
And("the fetch response body should be the mocked users", async () => {
|
|
62
|
-
const body = await fetchResponse?.json();
|
|
63
|
-
expect(body).toEqual([{ id: 1, name: "Alice" }]);
|
|
64
|
-
});
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
Scenario("Passthrough for unmatched routes", ({ Given, When, Then, And }) => {
|
|
69
|
-
Given(
|
|
70
|
-
'a Schmock instance with route "GET /api/users" returning users',
|
|
71
|
-
() => {
|
|
72
|
-
setup();
|
|
73
|
-
mock("GET /api/users", [{ id: 1 }]);
|
|
74
|
-
},
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
And("fetch is intercepted with passthrough enabled", () => {
|
|
78
|
-
handle = mock.intercept({ passthrough: true });
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
When('I fetch "/api/other"', async () => {
|
|
82
|
-
fetchResponse = await fetch("http://localhost/api/other");
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
Then("the original fetch should have been called", () => {
|
|
86
|
-
expect(savedFetch).toHaveBeenCalled();
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
Scenario("Passthrough disabled returns 404", ({ Given, When, Then, And }) => {
|
|
91
|
-
Given(
|
|
92
|
-
'a Schmock instance with route "GET /api/users" returning users',
|
|
93
|
-
() => {
|
|
94
|
-
setup();
|
|
95
|
-
mock("GET /api/users", [{ id: 1 }]);
|
|
96
|
-
},
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
And("fetch is intercepted with passthrough disabled", () => {
|
|
100
|
-
handle = mock.intercept({ passthrough: false });
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
When('I fetch "/api/other"', async () => {
|
|
104
|
-
fetchResponse = await fetch("http://localhost/api/other");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
Then("the fetch response status should be 404", () => {
|
|
108
|
-
expect(fetchResponse?.status).toBe(404);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
Scenario("Restore puts original fetch back", ({ Given, When, Then, And }) => {
|
|
113
|
-
let savedRef: typeof globalThis.fetch;
|
|
114
|
-
|
|
115
|
-
Given(
|
|
116
|
-
'a Schmock instance with route "GET /api/users" returning users',
|
|
117
|
-
() => {
|
|
118
|
-
setup();
|
|
119
|
-
mock("GET /api/users", [{ id: 1 }]);
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
And("fetch is intercepted", () => {
|
|
124
|
-
savedRef = globalThis.fetch;
|
|
125
|
-
handle = mock.intercept();
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
When("I restore the interceptor", () => {
|
|
129
|
-
handle?.restore();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
Then("globalThis.fetch should be the original function", () => {
|
|
133
|
-
expect(globalThis.fetch).toBe(savedRef);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
Scenario(
|
|
138
|
-
"BaseUrl filters which requests are intercepted",
|
|
139
|
-
({ Given, When, Then, And }) => {
|
|
140
|
-
Given(
|
|
141
|
-
'a Schmock instance with route "GET /api/users" returning users',
|
|
142
|
-
() => {
|
|
143
|
-
setup();
|
|
144
|
-
mock("GET /api/users", [{ id: 1 }]);
|
|
145
|
-
},
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
And('fetch is intercepted with baseUrl "/api"', () => {
|
|
149
|
-
handle = mock.intercept({ baseUrl: "/api" });
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
When('I fetch "/other/endpoint"', async () => {
|
|
153
|
-
await fetch("http://localhost/other/endpoint");
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
Then("the original fetch should have been called", () => {
|
|
157
|
-
expect(savedFetch).toHaveBeenCalled();
|
|
158
|
-
});
|
|
159
|
-
},
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
Scenario(
|
|
163
|
-
"Origin-form baseUrl intercepts matching origin only",
|
|
164
|
-
({ Given, When, Then, And }) => {
|
|
165
|
-
Given(
|
|
166
|
-
'a Schmock instance with route "GET /api/users" returning users',
|
|
167
|
-
() => {
|
|
168
|
-
setup();
|
|
169
|
-
mock("GET /api/users", [{ id: 1 }]);
|
|
170
|
-
},
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
And('fetch is intercepted with baseUrl "https://api.example.com"', () => {
|
|
174
|
-
handle = mock.intercept({ baseUrl: "https://api.example.com" });
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
When('I fetch "https://api.example.com/api/users"', async () => {
|
|
178
|
-
fetchResponse = await fetch("https://api.example.com/api/users");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
Then("the response status should be {int}", (_, status: number) => {
|
|
182
|
-
expect(fetchResponse?.status).toBe(status);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
And("the fetch response body should be the mocked users", async () => {
|
|
186
|
-
expect(await fetchResponse?.json()).toEqual([{ id: 1 }]);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
And("the original fetch should not have been called", () => {
|
|
190
|
-
expect(savedFetch).not.toHaveBeenCalled();
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
When('I fetch "https://other.example.com/api/users"', async () => {
|
|
194
|
-
await fetch("https://other.example.com/api/users");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
Then(
|
|
198
|
-
"the original fetch should have been called exactly once for {string}",
|
|
199
|
-
(_, url: string) => {
|
|
200
|
-
expect(savedFetch.mock.calls).toEqual([[url, undefined]]);
|
|
201
|
-
},
|
|
202
|
-
);
|
|
203
|
-
},
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
Scenario(
|
|
207
|
-
"Origin-form baseUrl with a path prefix requires both to match",
|
|
208
|
-
({ Given, When, Then, And }) => {
|
|
209
|
-
Given(
|
|
210
|
-
'a Schmock instance with route "GET /v1/users" returning users',
|
|
211
|
-
() => {
|
|
212
|
-
setup();
|
|
213
|
-
mock("GET /v1/users", [{ id: 1 }]);
|
|
214
|
-
},
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
And(
|
|
218
|
-
'fetch is intercepted with baseUrl "https://api.example.com/v1"',
|
|
219
|
-
() => {
|
|
220
|
-
handle = mock.intercept({ baseUrl: "https://api.example.com/v1" });
|
|
221
|
-
},
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
When('I fetch "https://api.example.com/v1/users"', async () => {
|
|
225
|
-
fetchResponse = await fetch("https://api.example.com/v1/users");
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
Then("the response status should be {int}", (_, status: number) => {
|
|
229
|
-
expect(fetchResponse?.status).toBe(status);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
And("the fetch response body should be the mocked users", async () => {
|
|
233
|
-
expect(await fetchResponse?.json()).toEqual([{ id: 1 }]);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
And("the original fetch should not have been called", () => {
|
|
237
|
-
expect(savedFetch).not.toHaveBeenCalled();
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
When('I fetch "https://api.example.com/users"', async () => {
|
|
241
|
-
await fetch("https://api.example.com/users");
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
Then(
|
|
245
|
-
"the original fetch should have been called exactly once for {string}",
|
|
246
|
-
(_, url: string) => {
|
|
247
|
-
expect(savedFetch.mock.calls).toEqual([[url, undefined]]);
|
|
248
|
-
},
|
|
249
|
-
);
|
|
250
|
-
},
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
Scenario(
|
|
254
|
-
"beforeRequest hook modifies the request",
|
|
255
|
-
({ Given, When, Then, And }) => {
|
|
256
|
-
Given(
|
|
257
|
-
'a Schmock instance with route "GET /api/users" that reads headers',
|
|
258
|
-
() => {
|
|
259
|
-
setup();
|
|
260
|
-
mock("GET /api/users", ({ headers }) => [
|
|
261
|
-
200,
|
|
262
|
-
{ token: headers["x-token"] },
|
|
263
|
-
]);
|
|
264
|
-
},
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
And(
|
|
268
|
-
"fetch is intercepted with a beforeRequest hook that adds a header",
|
|
269
|
-
() => {
|
|
270
|
-
handle = mock.intercept({
|
|
271
|
-
beforeRequest: (req) => ({
|
|
272
|
-
...req,
|
|
273
|
-
headers: { ...req.headers, "x-token": "injected" },
|
|
274
|
-
}),
|
|
275
|
-
});
|
|
276
|
-
},
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
When('I fetch "/api/users"', async () => {
|
|
280
|
-
fetchResponse = await fetch("http://localhost/api/users");
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
Then(
|
|
284
|
-
"the response should contain the injected header value",
|
|
285
|
-
async () => {
|
|
286
|
-
const body = await fetchResponse?.json();
|
|
287
|
-
expect(body).toEqual({ token: "injected" });
|
|
288
|
-
},
|
|
289
|
-
);
|
|
290
|
-
},
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
Scenario(
|
|
294
|
-
"beforeResponse hook modifies the response",
|
|
295
|
-
({ Given, When, Then, And }) => {
|
|
296
|
-
Given(
|
|
297
|
-
'a Schmock instance with route "GET /api/users" returning users',
|
|
298
|
-
() => {
|
|
299
|
-
setup();
|
|
300
|
-
mock("GET /api/users", [{ id: 1 }]);
|
|
301
|
-
},
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
And(
|
|
305
|
-
"fetch is intercepted with a beforeResponse hook that adds a header",
|
|
306
|
-
() => {
|
|
307
|
-
handle = mock.intercept({
|
|
308
|
-
beforeResponse: (res) => ({
|
|
309
|
-
...res,
|
|
310
|
-
headers: { ...res.headers, "x-mock": "true" },
|
|
311
|
-
}),
|
|
312
|
-
});
|
|
313
|
-
},
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
When('I fetch "/api/users"', async () => {
|
|
317
|
-
fetchResponse = await fetch("http://localhost/api/users");
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
Then("the fetch response should have the injected header", () => {
|
|
321
|
-
expect(fetchResponse?.headers.get("x-mock")).toBe("true");
|
|
322
|
-
});
|
|
323
|
-
},
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
Scenario("Double intercept throws an error", ({ Given, When, Then, And }) => {
|
|
327
|
-
let error: Error | undefined;
|
|
328
|
-
|
|
329
|
-
Given(
|
|
330
|
-
'a Schmock instance with route "GET /api/users" returning users',
|
|
331
|
-
() => {
|
|
332
|
-
setup();
|
|
333
|
-
mock("GET /api/users", [{ id: 1 }]);
|
|
334
|
-
},
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
And("fetch is intercepted", () => {
|
|
338
|
-
handle = mock.intercept();
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
When("I try to intercept again", () => {
|
|
342
|
-
try {
|
|
343
|
-
mock.intercept();
|
|
344
|
-
} catch (caught) {
|
|
345
|
-
error = caught instanceof Error ? caught : new Error(String(caught));
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
Then("it should throw an error about already intercepting", () => {
|
|
350
|
-
expect(error?.message).toMatch(/already intercepting/i);
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
Scenario(
|
|
355
|
-
"Multiple mocks compose from newest to oldest",
|
|
356
|
-
({ Given, When, Then, And }) => {
|
|
357
|
-
let newerMock: Schmock.CallableMockInstance;
|
|
358
|
-
let newerHandle: Schmock.InterceptHandle | undefined;
|
|
359
|
-
let sharedResponse: Response | undefined;
|
|
360
|
-
let olderResponse: Response | undefined;
|
|
361
|
-
|
|
362
|
-
Given(
|
|
363
|
-
'an older mock for both routes and a newer mock for "GET /api/shared"',
|
|
364
|
-
() => {
|
|
365
|
-
setup();
|
|
366
|
-
mock("GET /api/older", { source: "older" });
|
|
367
|
-
mock("GET /api/shared", { source: "older" });
|
|
368
|
-
newerMock = schmock();
|
|
369
|
-
newerMock("GET /api/shared", { source: "newer" });
|
|
370
|
-
},
|
|
371
|
-
);
|
|
372
|
-
|
|
373
|
-
And("both mocks intercept fetch in that order", () => {
|
|
374
|
-
handle = mock.intercept();
|
|
375
|
-
newerHandle = newerMock.intercept();
|
|
376
|
-
additionalHandles.push(newerHandle);
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
When("I fetch the shared and older routes", async () => {
|
|
380
|
-
sharedResponse = await fetch("http://localhost/api/shared");
|
|
381
|
-
olderResponse = await fetch("http://localhost/api/older");
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
Then("the shared route should use the newer mock", async () => {
|
|
385
|
-
expect(await sharedResponse?.json()).toEqual({ source: "newer" });
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
And("the older route should fall through to the older mock", async () => {
|
|
389
|
-
expect(await olderResponse?.json()).toEqual({ source: "older" });
|
|
390
|
-
});
|
|
391
|
-
},
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
Scenario(
|
|
395
|
-
"Interceptors can be restored out of order",
|
|
396
|
-
({ Given, When, Then }) => {
|
|
397
|
-
let newerMock: Schmock.CallableMockInstance;
|
|
398
|
-
let newerHandle: Schmock.InterceptHandle | undefined;
|
|
399
|
-
let newerResponse: Response | undefined;
|
|
400
|
-
|
|
401
|
-
Given("an older mock and a newer mock both intercepting fetch", () => {
|
|
402
|
-
setup();
|
|
403
|
-
mock("GET /api/older", { source: "older" });
|
|
404
|
-
newerMock = schmock();
|
|
405
|
-
newerMock("GET /api/newer", { source: "newer" });
|
|
406
|
-
handle = mock.intercept();
|
|
407
|
-
newerHandle = newerMock.intercept();
|
|
408
|
-
additionalHandles.push(newerHandle);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
When("I restore the older interceptor first", () => {
|
|
412
|
-
handle?.restore();
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
Then("the newer interceptor should remain active", async () => {
|
|
416
|
-
newerResponse = await fetch("http://localhost/api/newer");
|
|
417
|
-
expect(newerHandle?.active).toBe(true);
|
|
418
|
-
expect(await newerResponse.json()).toEqual({ source: "newer" });
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
When("I restore the newer interceptor", () => {
|
|
422
|
-
newerHandle?.restore();
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
Then("globalThis.fetch should be the original function", () => {
|
|
426
|
-
expect(globalThis.fetch).toBe(savedFetch);
|
|
427
|
-
});
|
|
428
|
-
},
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
Scenario(
|
|
432
|
-
"Restore preserves a third-party fetch replacement",
|
|
433
|
-
({ Given, When, Then, And }) => {
|
|
434
|
-
let replacementFetch: Mock<typeof globalThis.fetch>;
|
|
435
|
-
|
|
436
|
-
Given("fetch is intercepted", () => {
|
|
437
|
-
setup();
|
|
438
|
-
handle = mock.intercept();
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
When("another library replaces globalThis.fetch", () => {
|
|
442
|
-
replacementFetch = vi
|
|
443
|
-
.fn<typeof globalThis.fetch>()
|
|
444
|
-
.mockResolvedValue(new Response("third-party backend"));
|
|
445
|
-
globalThis.fetch = replacementFetch;
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
And("I restore the interceptor", () => {
|
|
449
|
-
handle?.restore();
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
Then("the third-party fetch replacement should remain installed", () => {
|
|
453
|
-
expect(globalThis.fetch).toBe(replacementFetch);
|
|
454
|
-
});
|
|
455
|
-
},
|
|
456
|
-
);
|
|
457
|
-
});
|
|
@@ -1,255 +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/fluent-api.feature");
|
|
7
|
-
|
|
8
|
-
describeFeature(feature, ({ Scenario }) => {
|
|
9
|
-
let mock: CallableMockInstance;
|
|
10
|
-
let response: any;
|
|
11
|
-
let responses: any[] = [];
|
|
12
|
-
|
|
13
|
-
Scenario("Simple route with generator function", ({ Given, When, Then }) => {
|
|
14
|
-
Given("I create a mock with a JSON generator at {string}", (_, route: string) => {
|
|
15
|
-
mock = schmock({});
|
|
16
|
-
mock(route as Schmock.RouteKey, () => [{ id: 1, name: "John" }], {
|
|
17
|
-
contentType: "application/json",
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
When("I request {string}", async (_, request: string) => {
|
|
22
|
-
const [method, path] = request.split(" ");
|
|
23
|
-
response = await mock.handle(method as any, path);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
Then("I should receive:", (_, docString: string) => {
|
|
27
|
-
const expected = JSON.parse(docString);
|
|
28
|
-
expect(response.body).toEqual(expected);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
Scenario(
|
|
33
|
-
"Route with dynamic response and global state",
|
|
34
|
-
({ Given, When, Then, And }) => {
|
|
35
|
-
responses = [];
|
|
36
|
-
|
|
37
|
-
Given("I create a mock with a stateful counter", () => {
|
|
38
|
-
mock = schmock({ state: { count: 0 } });
|
|
39
|
-
mock(
|
|
40
|
-
"GET /counter",
|
|
41
|
-
({ state }) => {
|
|
42
|
-
const s = state as { count: number };
|
|
43
|
-
s.count++;
|
|
44
|
-
return { value: s.count };
|
|
45
|
-
},
|
|
46
|
-
{ contentType: "application/json" },
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
When("I request {string} twice", async (_, request: string) => {
|
|
51
|
-
const [method, path] = request.split(" ");
|
|
52
|
-
responses = [];
|
|
53
|
-
responses.push(await mock.handle(method as any, path));
|
|
54
|
-
responses.push(await mock.handle(method as any, path));
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
Then("the first response should have value {int}", (_, value: number) => {
|
|
58
|
-
expect(responses[0].body).toEqual({ value });
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
And("the second response should have value {int}", (_, value: number) => {
|
|
62
|
-
expect(responses[1].body).toEqual({ value });
|
|
63
|
-
});
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
Scenario("Route with parameters", ({ Given, When, Then }) => {
|
|
68
|
-
Given("I create a mock with a parameterized route {string}", (_, route: string) => {
|
|
69
|
-
mock = schmock({});
|
|
70
|
-
mock(route as Schmock.RouteKey, ({ params }) => ({ userId: params.id }), {
|
|
71
|
-
contentType: "application/json",
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
When("I request {string}", async (_, request: string) => {
|
|
76
|
-
const [method, path] = request.split(" ");
|
|
77
|
-
response = await mock.handle(method as any, path);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
Then("I should receive:", (_, docString: string) => {
|
|
81
|
-
const expected = JSON.parse(docString);
|
|
82
|
-
expect(response.body).toEqual(expected);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
Scenario("Response with custom status code", ({ Given, When, Then, And }) => {
|
|
87
|
-
Given("I create a mock returning status {int} for {string}", (_, _status: number, route: string) => {
|
|
88
|
-
mock = schmock({});
|
|
89
|
-
mock(route as Schmock.RouteKey, ({ body }) => {
|
|
90
|
-
const b = body as Record<string, unknown>;
|
|
91
|
-
return [201, { id: 1, ...b }];
|
|
92
|
-
}, {
|
|
93
|
-
contentType: "application/json",
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
When(
|
|
98
|
-
"I request {string} with body:",
|
|
99
|
-
async (_, request: string, docString: string) => {
|
|
100
|
-
const [method, path] = request.split(" ");
|
|
101
|
-
const body = JSON.parse(docString);
|
|
102
|
-
response = await mock.handle(method as any, path, { body });
|
|
103
|
-
},
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
Then("the status should be {int}", (_, status: number) => {
|
|
107
|
-
expect(response.status).toBe(status);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
And("I should receive:", (_, docString: string) => {
|
|
111
|
-
const expected = JSON.parse(docString);
|
|
112
|
-
expect(response.body).toEqual(expected);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
Scenario("Static data response", ({ Given, When, Then }) => {
|
|
117
|
-
Given("I create a mock with static config data at {string}", (_, route: string) => {
|
|
118
|
-
mock = schmock({});
|
|
119
|
-
mock(route as Schmock.RouteKey, { version: "1.0.0", features: ["auth"] }, {
|
|
120
|
-
contentType: "application/json",
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
When("I request {string}", async (_, request: string) => {
|
|
125
|
-
const [method, path] = request.split(" ");
|
|
126
|
-
response = await mock.handle(method as any, path);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
Then("I should receive:", (_, docString: string) => {
|
|
130
|
-
const expected = JSON.parse(docString);
|
|
131
|
-
expect(response.body).toEqual(expected);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
Scenario("404 for undefined routes", ({ Given, When, Then }) => {
|
|
136
|
-
Given("I create a mock with only a {string} route", (_, route: string) => {
|
|
137
|
-
mock = schmock({});
|
|
138
|
-
mock(route as Schmock.RouteKey, () => [], { contentType: "application/json" });
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
When("I request {string}", async (_, request: string) => {
|
|
142
|
-
const [method, path] = request.split(" ");
|
|
143
|
-
response = await mock.handle(method as any, path);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
Then("the status should be {int}", (_, status: number) => {
|
|
147
|
-
expect(response.status).toBe(status);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
Scenario("Query parameters", ({ Given, When, Then }) => {
|
|
152
|
-
Given("I create a mock that reads query parameters at {string}", (_, route: string) => {
|
|
153
|
-
mock = schmock({});
|
|
154
|
-
mock(
|
|
155
|
-
route as Schmock.RouteKey,
|
|
156
|
-
({ query }) => ({
|
|
157
|
-
results: [],
|
|
158
|
-
query: query.q,
|
|
159
|
-
}),
|
|
160
|
-
{ contentType: "application/json" },
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
When("I request {string}", async (_, request: string) => {
|
|
165
|
-
const [method, fullPath] = request.split(" ");
|
|
166
|
-
const [path, queryString] = fullPath.split("?");
|
|
167
|
-
|
|
168
|
-
const query: Record<string, string> = {};
|
|
169
|
-
if (queryString) {
|
|
170
|
-
queryString.split("&").forEach((param) => {
|
|
171
|
-
const [key, value] = param.split("=");
|
|
172
|
-
query[key] = value;
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
response = await mock.handle(method as any, path, { query });
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
Then("I should receive:", (_, docString: string) => {
|
|
180
|
-
const expected = JSON.parse(docString);
|
|
181
|
-
expect(response.body).toEqual(expected);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
Scenario("Request headers access", ({ Given, When, Then }) => {
|
|
186
|
-
Given("I create a mock that reads headers at {string}", (_, route: string) => {
|
|
187
|
-
mock = schmock({});
|
|
188
|
-
mock(
|
|
189
|
-
route as Schmock.RouteKey,
|
|
190
|
-
({ headers }) => ({
|
|
191
|
-
authenticated: headers.authorization === "Bearer token123",
|
|
192
|
-
}),
|
|
193
|
-
{ contentType: "application/json" },
|
|
194
|
-
);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
When(
|
|
198
|
-
"I request {string} with headers:",
|
|
199
|
-
async (_, request: string, docString: string) => {
|
|
200
|
-
const [method, path] = request.split(" ");
|
|
201
|
-
const headers = JSON.parse(docString);
|
|
202
|
-
response = await mock.handle(method as any, path, { headers });
|
|
203
|
-
},
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
Then("I should receive:", (_, docString: string) => {
|
|
207
|
-
const expected = JSON.parse(docString);
|
|
208
|
-
expect(response.body).toEqual(expected);
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
Scenario("Global configuration with namespace", ({ Given, When, Then }) => {
|
|
213
|
-
Given("I create a mock with namespace {string}", (_, namespace: string) => {
|
|
214
|
-
mock = schmock({ namespace });
|
|
215
|
-
mock("GET /users", () => [], { contentType: "application/json" });
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
When("I request {string}", async (_, request: string) => {
|
|
219
|
-
const [method, path] = request.split(" ");
|
|
220
|
-
response = await mock.handle(method as any, path);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
Then("I should receive:", (_, docString: string) => {
|
|
224
|
-
const expected = JSON.parse(docString);
|
|
225
|
-
expect(response.body).toEqual(expected);
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
Scenario("Plugin pipeline with pipe chaining", ({ Given, When, Then }) => {
|
|
230
|
-
Given("I create a mock with two passthrough plugins", () => {
|
|
231
|
-
mock = schmock({});
|
|
232
|
-
mock("GET /users", () => [{ id: 1, name: "John" }], {
|
|
233
|
-
contentType: "application/json",
|
|
234
|
-
})
|
|
235
|
-
.pipe({
|
|
236
|
-
name: "logging",
|
|
237
|
-
process: (ctx, response) => ({ context: ctx, response }),
|
|
238
|
-
})
|
|
239
|
-
.pipe({
|
|
240
|
-
name: "cors",
|
|
241
|
-
process: (ctx, response) => ({ context: ctx, response }),
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
When("I request {string}", async (_, request: string) => {
|
|
246
|
-
const [method, path] = request.split(" ");
|
|
247
|
-
response = await mock.handle(method as any, path);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
Then("I should receive:", (_, docString: string) => {
|
|
251
|
-
const expected = JSON.parse(docString);
|
|
252
|
-
expect(response.body).toEqual(expected);
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
});
|