@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,245 +0,0 @@
1
- import { describeFeature, loadFeature } from "@amiceli/vitest-cucumber";
2
- import { expect } from "vitest";
3
- import type { CallableMockInstance } from "../types";
4
- import { schmock } from "../index";
5
-
6
- const feature = await loadFeature("../../features/basic-usage.feature");
7
-
8
- describeFeature(feature, ({ Scenario }) => {
9
- let mock: CallableMockInstance;
10
- let response: any;
11
-
12
- Scenario("Simplest possible mock - plain text", ({ Given, When, Then, And }) => {
13
- Given("I create a plain text mock returning {string} at {string}", (_, text: string, route: string) => {
14
- const [method, path] = route.split(" ");
15
- mock = schmock();
16
- mock(`${method} ${path}` as Schmock.RouteKey, text);
17
- });
18
-
19
- When("I request {string}", async (_, request: string) => {
20
- const [method, path] = request.split(" ");
21
- response = await mock.handle(method as any, path);
22
- });
23
-
24
- Then("I should receive text {string}", (_, expectedText: string) => {
25
- expect(response.body).toBe(expectedText);
26
- });
27
-
28
- And("the content-type should be {string}", (_, contentType: string) => {
29
- expect(response.headers?.["content-type"]).toBe(contentType);
30
- });
31
- });
32
-
33
- Scenario("Return JSON without specifying contentType", ({ Given, When, Then, And }) => {
34
- Given("I create a mock returning a JSON array at {string}", (_, route: string) => {
35
- const [method, path] = route.split(" ");
36
- mock = schmock();
37
- mock(`${method} ${path}` as Schmock.RouteKey, [{ id: 1, name: "John" }]);
38
- });
39
-
40
- When("I request {string}", async (_, request: string) => {
41
- const [method, path] = request.split(" ");
42
- response = await mock.handle(method as any, path);
43
- });
44
-
45
- Then("I should receive:", (_, docString: string) => {
46
- const expected = JSON.parse(docString);
47
- expect(response.body).toEqual(expected);
48
- });
49
-
50
- And("the content-type should be {string}", (_, contentType: string) => {
51
- expect(response.headers?.["content-type"]).toBe(contentType);
52
- });
53
- });
54
-
55
- Scenario("Return object without contentType", ({ Given, When, Then, And }) => {
56
- Given("I create a mock returning a JSON object at {string}", (_, route: string) => {
57
- const [method, path] = route.split(" ");
58
- mock = schmock();
59
- mock(`${method} ${path}` as Schmock.RouteKey, { id: 1, name: "John" });
60
- });
61
-
62
- When("I request {string}", async (_, request: string) => {
63
- const [method, path] = request.split(" ");
64
- response = await mock.handle(method as any, path);
65
- });
66
-
67
- Then("I should receive:", (_, docString: string) => {
68
- const expected = JSON.parse(docString);
69
- expect(response.body).toEqual(expected);
70
- });
71
-
72
- And("the content-type should be {string}", (_, contentType: string) => {
73
- expect(response.headers?.["content-type"]).toBe(contentType);
74
- });
75
- });
76
-
77
- Scenario("Empty mock instance", ({ Given, When, Then }) => {
78
- Given("I create an empty mock with no routes", () => {
79
- mock = schmock();
80
- });
81
-
82
- When("I request {string}", async (_, request: string) => {
83
- const [method, path] = request.split(" ");
84
- response = await mock.handle(method as any, path);
85
- });
86
-
87
- Then("the status should be {int}", (_, status: number) => {
88
- expect(response.status).toBe(status);
89
- });
90
- });
91
-
92
- Scenario("Null response", ({ Given, When, Then, And }) => {
93
- Given("I create a mock returning null at {string}", (_, route: string) => {
94
- const [method, path] = route.split(" ");
95
- mock = schmock();
96
- mock(`${method} ${path}` as Schmock.RouteKey, null);
97
- });
98
-
99
- When("I request {string}", async (_, request: string) => {
100
- const [method, path] = request.split(" ");
101
- response = await mock.handle(method as any, path);
102
- });
103
-
104
- Then("I should receive empty response", () => {
105
- expect(response.body).toBeUndefined();
106
- });
107
-
108
- And("the status should be {int}", (_, status: number) => {
109
- expect(response.status).toBe(status);
110
- });
111
- });
112
-
113
- Scenario("Function returning string", ({ Given, When, Then }) => {
114
- Given("I create a mock with a string generator at {string}", (_, route: string) => {
115
- const [method, path] = route.split(" ");
116
- mock = schmock();
117
- mock(`${method} ${path}` as Schmock.RouteKey, () => "Dynamic response");
118
- });
119
-
120
- When("I request {string}", async (_, request: string) => {
121
- const [method, path] = request.split(" ");
122
- response = await mock.handle(method as any, path);
123
- });
124
-
125
- Then("I should receive text {string}", (_, expectedText: string) => {
126
- expect(response.body).toBe(expectedText);
127
- });
128
- });
129
-
130
- Scenario("Function returning object", ({ Given, When, Then, And }) => {
131
- Given("I create a mock with an object generator at {string}", (_, route: string) => {
132
- const [method, path] = route.split(" ");
133
- mock = schmock();
134
- mock(`${method} ${path}` as Schmock.RouteKey, () => ({ timestamp: Date.now() }));
135
- });
136
-
137
- When("I request {string}", async (_, request: string) => {
138
- const [method, path] = request.split(" ");
139
- response = await mock.handle(method as any, path);
140
- });
141
-
142
- Then("the response should be valid JSON", () => {
143
- expect(() => JSON.parse(JSON.stringify(response.body))).not.toThrow();
144
- });
145
-
146
- And("the response should have property {string}", (_, property: string) => {
147
- expect(response.body).toHaveProperty(property);
148
- });
149
- });
150
-
151
- Scenario("Multiple routes", ({ Given, When, Then, And }) => {
152
- let homeResponse: any;
153
- let aboutResponse: any;
154
- let contactResponse: any;
155
-
156
- Given("I create a mock with three routes", () => {
157
- mock = schmock();
158
- mock("GET /", "Home");
159
- mock("GET /about", "About Us");
160
- mock("GET /contact", { email: "test@example.com" });
161
- });
162
-
163
- When("I make three requests to different routes", async () => {
164
- homeResponse = await mock.handle("GET", "/");
165
- aboutResponse = await mock.handle("GET", "/about");
166
- contactResponse = await mock.handle("GET", "/contact");
167
- });
168
-
169
- Then("the home route should return {string}", (_, expectedText: string) => {
170
- expect(homeResponse.body).toBe(expectedText);
171
- });
172
-
173
- And("the about route should return {string}", (_, expectedText: string) => {
174
- expect(aboutResponse.body).toBe(expectedText);
175
- });
176
-
177
- And("the contact route should return:", (_, docString: string) => {
178
- const expected = JSON.parse(docString);
179
- expect(contactResponse.body).toEqual(expected);
180
- });
181
- });
182
-
183
- Scenario("Override contentType detection", ({ Given, When, Then, And }) => {
184
- Given("I create a mock with explicit text/plain contentType at {string}", (_, route: string) => {
185
- const [method, path] = route.split(" ");
186
- mock = schmock();
187
- mock(`${method} ${path}` as Schmock.RouteKey, { foo: "bar" }, { contentType: "text/plain" });
188
- });
189
-
190
- When("I request {string}", async (_, request: string) => {
191
- const [method, path] = request.split(" ");
192
- response = await mock.handle(method as any, path);
193
- });
194
-
195
- Then("I should receive text {string}", (_, expectedText: string) => {
196
- expect(response.body).toBe(expectedText);
197
- });
198
-
199
- And("the content-type should be {string}", (_, contentType: string) => {
200
- expect(response.headers?.["content-type"]).toBe(contentType);
201
- });
202
- });
203
-
204
- Scenario("HTML response", ({ Given, When, Then, And }) => {
205
- Given("I create a mock returning HTML at {string}", (_, route: string) => {
206
- const [method, path] = route.split(" ");
207
- mock = schmock();
208
- mock(`${method} ${path}` as Schmock.RouteKey, "<h1>Hello</h1>", { contentType: "text/html" });
209
- });
210
-
211
- When("I request {string}", async (_, request: string) => {
212
- const [method, path] = request.split(" ");
213
- response = await mock.handle(method as any, path);
214
- });
215
-
216
- Then("I should receive text {string}", (_, expectedText: string) => {
217
- expect(response.body).toBe(expectedText);
218
- });
219
-
220
- And("the content-type should be {string}", (_, contentType: string) => {
221
- expect(response.headers?.["content-type"]).toBe(contentType);
222
- });
223
- });
224
-
225
- Scenario("Binary/buffer response detection", ({ Given, When, Then, And }) => {
226
- Given("I create a mock returning a Buffer at {string}", (_, route: string) => {
227
- const [method, path] = route.split(" ");
228
- mock = schmock();
229
- mock(`${method} ${path}` as Schmock.RouteKey, Buffer.from("binary data") as unknown as Schmock.Generator);
230
- });
231
-
232
- When("I request {string}", async (_, request: string) => {
233
- const [method, path] = request.split(" ");
234
- response = await mock.handle(method as any, path);
235
- });
236
-
237
- Then("I should receive buffer data", () => {
238
- expect(Buffer.isBuffer(response.body)).toBe(true);
239
- });
240
-
241
- And("the content-type should be {string}", (_, contentType: string) => {
242
- expect(response.headers?.["content-type"]).toBe(contentType);
243
- });
244
- });
245
- });
@@ -1,204 +0,0 @@
1
- import { describeFeature, loadFeature } from "@amiceli/vitest-cucumber";
2
- import { expect } from "vitest";
3
- import { schmock } from "../index";
4
- import type { CallableMockInstance, Plugin, PluginContext } from "../types";
5
-
6
- const feature = await loadFeature("../../features/developer-experience.feature");
7
-
8
- describeFeature(feature, ({ Scenario }) => {
9
- let mock: CallableMockInstance;
10
- let response: any;
11
- let responses: any[] = [];
12
-
13
- Scenario("State confusion between global and local state", ({ Given, When, Then, And }) => {
14
- Given("I create a mock with global state and a local state counter", () => {
15
- mock = schmock({ state: { global: 1 } });
16
- mock("GET /counter", ({ state }) => {
17
- const current = (state.local as number | undefined) || 0;
18
- state.local = current + 1;
19
- return { global: state.global, local: state.local };
20
- });
21
- });
22
-
23
- When("I request {string} twice", async (_, request: string) => {
24
- const [method, path] = request.split(" ");
25
- responses = [];
26
- responses.push(await mock.handle(method as any, path));
27
- responses.push(await mock.handle(method as any, path));
28
- });
29
-
30
- Then("the first response should have:", (_, docString: string) => {
31
- const expected = JSON.parse(docString);
32
- expect(responses[0].body).toEqual(expected);
33
- });
34
-
35
- And("the second response should have:", (_, docString: string) => {
36
- const expected = JSON.parse(docString);
37
- expect(responses[1].body).toEqual(expected);
38
- });
39
- });
40
-
41
- Scenario("Route precedence with similar paths", ({ Given, When, Then, And }) => {
42
- Given("I create a mock with an exact route and a parameterized route on {string}", (_, basePath: string) => {
43
- mock = schmock();
44
- mock(`GET ${basePath}/profile`, { type: "profile" });
45
- mock(`GET ${basePath}/:id`, ({ params }) => ({ type: "user", id: params.id }));
46
- });
47
-
48
- When("I test both route precedence scenarios", async () => {
49
- responses = [];
50
- responses.push(await mock.handle("GET", "/users/profile"));
51
- responses.push(await mock.handle("GET", "/users/123"));
52
- });
53
-
54
- Then("the profile route should return exact match:", (_, docString: string) => {
55
- const expected = JSON.parse(docString);
56
- expect(responses[0].body).toEqual(expected);
57
- });
58
-
59
- And("the parameterized route should match with param:", (_, docString: string) => {
60
- const expected = JSON.parse(docString);
61
- expect(responses[1].body).toEqual(expected);
62
- });
63
- });
64
-
65
- Scenario("Plugin order affecting results", ({ Given, When, Then }) => {
66
- Given("I create a mock with two plugins that each set a step number", () => {
67
- mock = schmock();
68
- const plugin1: Plugin = {
69
- name: "first",
70
- process: (ctx: PluginContext, response: unknown) => ({
71
- context: ctx,
72
- response: { ...(response as Record<string, unknown>), step: 1 },
73
- }),
74
- };
75
- const plugin2: Plugin = {
76
- name: "second",
77
- process: (ctx: PluginContext, response: unknown) => ({
78
- context: ctx,
79
- response: { ...(response as Record<string, unknown>), step: 2 },
80
- }),
81
- };
82
- mock("GET /order", { original: true }).pipe(plugin1).pipe(plugin2);
83
- });
84
-
85
- When("I request {string}", async (_, request: string) => {
86
- const [method, path] = request.split(" ");
87
- response = await mock.handle(method as any, path);
88
- });
89
-
90
- Then("I should receive:", (_, docString: string) => {
91
- const expected = JSON.parse(docString);
92
- expect(response.body).toEqual(expected);
93
- });
94
- });
95
-
96
- Scenario("Plugin expecting different context structure", ({ Given, When, Then }) => {
97
- Given("I create a mock with a plugin that reads context properties", () => {
98
- mock = schmock();
99
- const plugin: Plugin = {
100
- name: "context-reader",
101
- process: (ctx: PluginContext, _response: unknown) => ({
102
- context: ctx,
103
- response: {
104
- method: ctx.method,
105
- path: ctx.path,
106
- hasBody: !!ctx.body,
107
- hasQuery: !!ctx.query && Object.keys(ctx.query).length > 0,
108
- },
109
- }),
110
- };
111
- mock("POST /analyze", null).pipe(plugin);
112
- });
113
-
114
- When("I request {string} with body:", async (_, request: string, docString: string) => {
115
- const [method, fullPath] = request.split(" ");
116
- const [path, queryString] = fullPath.split("?");
117
-
118
- const query: Record<string, string> = {};
119
- if (queryString) {
120
- queryString.split("&").forEach((param) => {
121
- const [key, value] = param.split("=");
122
- query[key] = value || "";
123
- });
124
- }
125
-
126
- const body = JSON.parse(docString);
127
- response = await mock.handle(method as any, path, { body, query });
128
- });
129
-
130
- Then("I should receive:", (_, docString: string) => {
131
- const expected = JSON.parse(docString);
132
- expect(response.body).toEqual(expected);
133
- });
134
- });
135
-
136
- Scenario("Response tuple format edge cases", ({ Given, When, Then, And }) => {
137
- Given("I create a mock with three tuple response routes", () => {
138
- mock = schmock();
139
- mock("GET /created", [201, null]);
140
- mock("GET /with-headers", [200, { data: true }, { "x-custom": "header" }]);
141
- mock("GET /empty-with-status", [204, null]);
142
- });
143
-
144
- When("I test all tuple response formats", async () => {
145
- responses = [];
146
- responses.push(await mock.handle("GET", "/created"));
147
- responses.push(await mock.handle("GET", "/with-headers"));
148
- responses.push(await mock.handle("GET", "/empty-with-status"));
149
- });
150
-
151
- Then("the created endpoint should return status 201 with empty body", () => {
152
- expect(responses[0].status).toBe(201);
153
- expect(responses[0].body).toBeUndefined();
154
- });
155
-
156
- And("the headers endpoint should return status 200 with data and custom header", () => {
157
- expect(responses[1].status).toBe(200);
158
- expect(responses[1].body).toEqual({ data: true });
159
- expect(responses[1].headers?.["x-custom"]).toBe("header");
160
- });
161
-
162
- And("the empty endpoint should return status 204 with null body", () => {
163
- expect(responses[2].status).toBe(204);
164
- expect(responses[2].body).toBeUndefined();
165
- });
166
- });
167
-
168
- Scenario("Registering duplicate routes first route wins", ({ Given, When, Then }) => {
169
- Given("I create a mock with two routes on {string} with different data", (_, route: string) => {
170
- mock = schmock();
171
- mock(route as Schmock.RouteKey, [{ id: 1 }]);
172
- mock(route as Schmock.RouteKey, [{ id: 2 }]);
173
- });
174
-
175
- When("I request {string}", async (_, request: string) => {
176
- const [method, path] = request.split(" ");
177
- response = await mock.handle(method as any, path);
178
- });
179
-
180
- Then("the first route response should win:", (_, docString: string) => {
181
- const expected = JSON.parse(docString);
182
- expect(response.body).toEqual(expected);
183
- });
184
- });
185
-
186
- Scenario("Duplicate routes are not duplicated in getRoutes()", ({ Given, When, Then }) => {
187
- let routesList: ReturnType<typeof mock.getRoutes>;
188
-
189
- Given("I create a mock with two routes on {string} with different data", (_, route: string) => {
190
- mock = schmock();
191
- mock(route as Schmock.RouteKey, [{ id: 1 }]);
192
- mock(route as Schmock.RouteKey, [{ id: 2 }]);
193
- });
194
-
195
- When("I call getRoutes", () => {
196
- routesList = mock.getRoutes();
197
- });
198
-
199
- Then("only one route entry is returned", () => {
200
- expect(routesList).toHaveLength(1);
201
- });
202
- });
203
-
204
- });