@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.
Files changed (93) 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/builder.d.ts +31 -10
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +760 -190
  9. package/dist/constants.d.ts +38 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/constants.js +123 -2
  12. package/dist/errors.d.ts +14 -3
  13. package/dist/errors.d.ts.map +1 -1
  14. package/dist/errors.js +41 -6
  15. package/dist/helpers.d.ts +9 -0
  16. package/dist/helpers.d.ts.map +1 -1
  17. package/dist/helpers.js +18 -2
  18. package/dist/http-helpers.d.ts +39 -4
  19. package/dist/http-helpers.d.ts.map +1 -1
  20. package/dist/http-helpers.js +147 -49
  21. package/dist/index.d.ts +292 -34
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +8 -3
  24. package/dist/interceptor.d.ts +11 -1
  25. package/dist/interceptor.d.ts.map +1 -1
  26. package/dist/interceptor.js +316 -169
  27. package/dist/parser.d.ts.map +1 -1
  28. package/dist/parser.js +12 -3
  29. package/dist/plugin-pipeline.d.ts +3 -3
  30. package/dist/plugin-pipeline.d.ts.map +1 -1
  31. package/dist/plugin-pipeline.js +29 -14
  32. package/dist/response-normalizer.d.ts +16 -0
  33. package/dist/response-normalizer.d.ts.map +1 -0
  34. package/dist/response-normalizer.js +316 -0
  35. package/dist/response-parser.d.ts.map +1 -1
  36. package/dist/response-parser.js +39 -2
  37. package/dist/route-matcher.d.ts +3 -0
  38. package/dist/route-matcher.d.ts.map +1 -1
  39. package/dist/route-matcher.js +12 -6
  40. package/dist/types.d.ts +7 -2
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +14 -6
  43. package/src/audit-core-builder.test.ts +0 -218
  44. package/src/audit-response-guard.test.ts +0 -38
  45. package/src/binary.test.ts +0 -126
  46. package/src/binary.ts +0 -11
  47. package/src/builder.test.ts +0 -289
  48. package/src/builder.ts +0 -748
  49. package/src/constants.test.ts +0 -99
  50. package/src/constants.ts +0 -73
  51. package/src/debug.test.ts +0 -241
  52. package/src/delay.test.ts +0 -319
  53. package/src/dist-shape.test.ts +0 -35
  54. package/src/errors.test.ts +0 -223
  55. package/src/errors.ts +0 -130
  56. package/src/factory.test.ts +0 -133
  57. package/src/helpers.test.ts +0 -147
  58. package/src/helpers.ts +0 -58
  59. package/src/http-helpers.test.ts +0 -39
  60. package/src/http-helpers.ts +0 -149
  61. package/src/index.ts +0 -152
  62. package/src/interceptor.test.ts +0 -488
  63. package/src/interceptor.ts +0 -407
  64. package/src/namespace.test.ts +0 -274
  65. package/src/parser.property.test.ts +0 -630
  66. package/src/parser.test.ts +0 -148
  67. package/src/parser.ts +0 -64
  68. package/src/plugin-pipeline.ts +0 -208
  69. package/src/plugin-system.test.ts +0 -697
  70. package/src/response-parser.ts +0 -114
  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 -234
  75. package/src/smart-defaults.test.ts +0 -361
  76. package/src/steps/async-support.steps.ts +0 -421
  77. package/src/steps/audit-core-builder.steps.ts +0 -188
  78. package/src/steps/audit-onerror-tuple.steps.ts +0 -83
  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 -555
  82. package/src/steps/fetch-interceptor.steps.ts +0 -457
  83. package/src/steps/fluent-api.steps.ts +0 -255
  84. package/src/steps/http-methods.steps.ts +0 -342
  85. package/src/steps/lifecycle-events.steps.ts +0 -149
  86. package/src/steps/performance-reliability.steps.ts +0 -90
  87. package/src/steps/plugin-integration.steps.ts +0 -334
  88. package/src/steps/request-history.steps.ts +0 -458
  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 -227
  92. package/src/steps/state-concurrency.steps.ts +0 -167
  93. 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
- });