@schmock/core 2.3.0 → 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.
- 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 +14 -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,334 +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/plugin-integration.feature");
|
|
7
|
-
|
|
8
|
-
describeFeature(feature, ({ Scenario }) => {
|
|
9
|
-
let mock: CallableMockInstance;
|
|
10
|
-
let requestResponses: any[] = [];
|
|
11
|
-
let guardedGeneratorExecutions = 0;
|
|
12
|
-
|
|
13
|
-
Scenario("Plugin state sharing with pipeline", ({ Given, When, Then, And }) => {
|
|
14
|
-
requestResponses = [];
|
|
15
|
-
|
|
16
|
-
Given("I create a mock with a counter plugin using route state", () => {
|
|
17
|
-
mock = schmock({ state: {} });
|
|
18
|
-
mock("GET /counter", null, { contentType: "application/json" }).pipe({
|
|
19
|
-
name: "counter-plugin",
|
|
20
|
-
process: (ctx, response) => {
|
|
21
|
-
const routeState = ctx.routeState!;
|
|
22
|
-
routeState.request_count =
|
|
23
|
-
((routeState.request_count as number) || 0) + 1;
|
|
24
|
-
|
|
25
|
-
if (!response) {
|
|
26
|
-
return {
|
|
27
|
-
context: ctx,
|
|
28
|
-
response: {
|
|
29
|
-
request_number: routeState.request_count,
|
|
30
|
-
path: ctx.path,
|
|
31
|
-
processed_at: new Date().toISOString(),
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { context: ctx, response };
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
When("I request {string} three times", async (_, request: string) => {
|
|
42
|
-
const [method, path] = request.split(" ");
|
|
43
|
-
requestResponses = [];
|
|
44
|
-
|
|
45
|
-
for (let i = 0; i < 3; i++) {
|
|
46
|
-
const response = await mock.handle(method as any, path);
|
|
47
|
-
requestResponses.push(response);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
Then(
|
|
52
|
-
"each response should have incrementing {string} values",
|
|
53
|
-
(_, property: string) => {
|
|
54
|
-
expect(requestResponses).toHaveLength(3);
|
|
55
|
-
|
|
56
|
-
for (let i = 0; i < requestResponses.length; i++) {
|
|
57
|
-
expect(requestResponses[i].body[property]).toBe(i + 1);
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
And(
|
|
63
|
-
"each response should have a {string} timestamp",
|
|
64
|
-
(_, property: string) => {
|
|
65
|
-
for (const response of requestResponses) {
|
|
66
|
-
expect(response.body[property]).toBeDefined();
|
|
67
|
-
expect(typeof response.body[property]).toBe("string");
|
|
68
|
-
expect(new Date(response.body[property]).getTime()).toBeGreaterThan(0);
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
And("the route state should persist across requests", () => {
|
|
74
|
-
const requestNumbers = requestResponses.map(
|
|
75
|
-
(r) => r.body.request_number,
|
|
76
|
-
);
|
|
77
|
-
expect(requestNumbers).toEqual([1, 2, 3]);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
Scenario("Multiple plugins in pipeline", ({ Given, When, Then }) => {
|
|
82
|
-
Given("I create a mock with auth and wrapper plugins", () => {
|
|
83
|
-
mock = schmock({});
|
|
84
|
-
mock("GET /users", () => [{ id: 1, name: "John" }], {
|
|
85
|
-
contentType: "application/json",
|
|
86
|
-
})
|
|
87
|
-
.pipe({
|
|
88
|
-
name: "auth-plugin",
|
|
89
|
-
process: (ctx, response) => {
|
|
90
|
-
if (!ctx.headers.authorization) {
|
|
91
|
-
throw new Error("Missing authorization");
|
|
92
|
-
}
|
|
93
|
-
ctx.state.set("user", { id: 1, name: "Admin" });
|
|
94
|
-
return { context: ctx, response };
|
|
95
|
-
},
|
|
96
|
-
})
|
|
97
|
-
.pipe({
|
|
98
|
-
name: "wrapper-plugin",
|
|
99
|
-
process: (ctx, response) => {
|
|
100
|
-
if (response) {
|
|
101
|
-
return {
|
|
102
|
-
context: ctx,
|
|
103
|
-
response: {
|
|
104
|
-
data: response,
|
|
105
|
-
meta: {
|
|
106
|
-
user: ctx.state.get("user"),
|
|
107
|
-
timestamp: "2025-01-31T10:15:30.123Z",
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
return { context: ctx, response };
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
When(
|
|
118
|
-
"I request {string} with headers:",
|
|
119
|
-
async (_, request: string, docString: string) => {
|
|
120
|
-
const [method, path] = request.split(" ");
|
|
121
|
-
const headers = JSON.parse(docString);
|
|
122
|
-
requestResponses = [
|
|
123
|
-
await mock.handle(method as any, path, { headers }),
|
|
124
|
-
];
|
|
125
|
-
},
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
Then("I should receive:", (_, docString: string) => {
|
|
129
|
-
const expected = JSON.parse(docString);
|
|
130
|
-
expect(requestResponses[0].body).toEqual(expected);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
Scenario("Plugin error handling", ({ Given, When, Then, And }) => {
|
|
135
|
-
Given("I create a mock with an auth guard plugin", () => {
|
|
136
|
-
mock = schmock({});
|
|
137
|
-
mock("GET /protected", () => ({ secret: "data" }), {
|
|
138
|
-
contentType: "application/json",
|
|
139
|
-
}).pipe({
|
|
140
|
-
name: "auth-plugin",
|
|
141
|
-
process: (ctx, response) => {
|
|
142
|
-
if (!ctx.headers.authorization) {
|
|
143
|
-
return {
|
|
144
|
-
context: ctx,
|
|
145
|
-
response: [401, { error: "Unauthorized", code: "AUTH_REQUIRED" }],
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
return { context: ctx, response };
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
When(
|
|
154
|
-
"I request {string} without authorization",
|
|
155
|
-
async (_, request: string) => {
|
|
156
|
-
const [method, path] = request.split(" ");
|
|
157
|
-
requestResponses = [await mock.handle(method as any, path)];
|
|
158
|
-
},
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
Then("the status should be {int}", (_, status: number) => {
|
|
162
|
-
expect(requestResponses[0].status).toBe(status);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
And("I should receive:", (_, docString: string) => {
|
|
166
|
-
const expected = JSON.parse(docString);
|
|
167
|
-
expect(requestResponses[0].body).toEqual(expected);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
Scenario(
|
|
172
|
-
"Request guard prevents route side effects",
|
|
173
|
-
({ Given, When, Then, And }) => {
|
|
174
|
-
Given(
|
|
175
|
-
"I create a mock whose guarded generator records each execution",
|
|
176
|
-
() => {
|
|
177
|
-
guardedGeneratorExecutions = 0;
|
|
178
|
-
mock = schmock();
|
|
179
|
-
mock("POST /guarded", () => {
|
|
180
|
-
guardedGeneratorExecutions += 1;
|
|
181
|
-
return [201, { created: true }];
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const guardPlugin = {
|
|
185
|
-
name: "pre-request-auth",
|
|
186
|
-
beforeRequest(context: Schmock.PluginContext) {
|
|
187
|
-
if (!context.headers.authorization) {
|
|
188
|
-
return {
|
|
189
|
-
context,
|
|
190
|
-
response: [
|
|
191
|
-
401,
|
|
192
|
-
{ error: "Unauthorized", code: "AUTH_REQUIRED" },
|
|
193
|
-
],
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
return { context };
|
|
197
|
-
},
|
|
198
|
-
process(context: Schmock.PluginContext, response?: unknown) {
|
|
199
|
-
return { context, response };
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
mock.pipe(guardPlugin);
|
|
204
|
-
},
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
When(
|
|
208
|
-
"I request the guarded route without authorization",
|
|
209
|
-
async () => {
|
|
210
|
-
requestResponses = [await mock.handle("POST", "/guarded")];
|
|
211
|
-
},
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
Then("the guarded response status should be {int}", (_, status: number) => {
|
|
215
|
-
expect(requestResponses[0].status).toBe(status);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
And("the guarded generator should not have executed", () => {
|
|
219
|
-
expect(guardedGeneratorExecutions).toBe(0);
|
|
220
|
-
});
|
|
221
|
-
},
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
Scenario(
|
|
225
|
-
"Pipeline order and response transformation",
|
|
226
|
-
({ Given, When, Then }) => {
|
|
227
|
-
Given("I create a mock with three ordered step plugins", () => {
|
|
228
|
-
mock = schmock({});
|
|
229
|
-
mock("GET /data", () => ({ value: 42 }), {
|
|
230
|
-
contentType: "application/json",
|
|
231
|
-
})
|
|
232
|
-
.pipe({
|
|
233
|
-
name: "step-1",
|
|
234
|
-
process: (ctx, response) => {
|
|
235
|
-
ctx.state.set("step1", "processed");
|
|
236
|
-
if (response) {
|
|
237
|
-
return {
|
|
238
|
-
context: ctx,
|
|
239
|
-
response: { ...response, step1: "processed" },
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
return { context: ctx, response };
|
|
243
|
-
},
|
|
244
|
-
})
|
|
245
|
-
.pipe({
|
|
246
|
-
name: "step-2",
|
|
247
|
-
process: (ctx, response) => {
|
|
248
|
-
if (response) {
|
|
249
|
-
return {
|
|
250
|
-
context: ctx,
|
|
251
|
-
response: { ...response, step2: "processed" },
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
return { context: ctx, response };
|
|
255
|
-
},
|
|
256
|
-
})
|
|
257
|
-
.pipe({
|
|
258
|
-
name: "step-3",
|
|
259
|
-
process: (ctx, response) => {
|
|
260
|
-
if (response) {
|
|
261
|
-
return {
|
|
262
|
-
context: ctx,
|
|
263
|
-
response: { ...response, step3: "processed" },
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
return { context: ctx, response };
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
When("I request {string}", async (_, request: string) => {
|
|
272
|
-
const [method, path] = request.split(" ");
|
|
273
|
-
requestResponses = [await mock.handle(method as any, path)];
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
Then("I should receive:", (_, docString: string) => {
|
|
277
|
-
const expected = JSON.parse(docString);
|
|
278
|
-
expect(requestResponses[0].body).toEqual(expected);
|
|
279
|
-
});
|
|
280
|
-
},
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
Scenario("Schema plugin in pipeline", ({ Given, When, Then, And }) => {
|
|
284
|
-
Given("I create a mock with a metadata wrapper plugin", () => {
|
|
285
|
-
mock = schmock({});
|
|
286
|
-
mock(
|
|
287
|
-
"GET /users",
|
|
288
|
-
() => [
|
|
289
|
-
{ id: 1, name: "John Doe", email: "john@example.com" },
|
|
290
|
-
{ id: 2, name: "Jane Smith", email: "jane@example.com" },
|
|
291
|
-
],
|
|
292
|
-
{ contentType: "application/json" },
|
|
293
|
-
).pipe({
|
|
294
|
-
name: "add-metadata",
|
|
295
|
-
process: (ctx, response) => {
|
|
296
|
-
if (response && Array.isArray(response)) {
|
|
297
|
-
return {
|
|
298
|
-
context: ctx,
|
|
299
|
-
response: {
|
|
300
|
-
users: response,
|
|
301
|
-
count: response.length,
|
|
302
|
-
generated_at: new Date().toISOString(),
|
|
303
|
-
},
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
return { context: ctx, response };
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
When("I request {string}", async (_, request: string) => {
|
|
312
|
-
const [method, path] = request.split(" ");
|
|
313
|
-
requestResponses = [await mock.handle(method as any, path)];
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
Then("the response should have a {string} array with {int} items", (_, property: string, count: number) => {
|
|
317
|
-
expect(requestResponses[0].body).toHaveProperty(property);
|
|
318
|
-
expect(Array.isArray(requestResponses[0].body[property])).toBe(true);
|
|
319
|
-
expect(requestResponses[0].body[property]).toHaveLength(count);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
And("the response should have {string} equal to {int}", (_, property: string, value: number) => {
|
|
323
|
-
expect(requestResponses[0].body[property]).toBe(value);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
And(
|
|
327
|
-
"the response should have a {string} timestamp",
|
|
328
|
-
(_, property: string) => {
|
|
329
|
-
expect(requestResponses[0].body).toHaveProperty(property);
|
|
330
|
-
expect(typeof requestResponses[0].body[property]).toBe("string");
|
|
331
|
-
},
|
|
332
|
-
);
|
|
333
|
-
});
|
|
334
|
-
});
|