@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.
- 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/binary.d.ts +8 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +9 -0
- package/dist/builder.d.ts +31 -10
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +804 -203
- 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 +52 -5
- package/dist/http-helpers.d.ts.map +1 -1
- package/dist/http-helpers.js +153 -37
- package/dist/index.d.ts +381 -60
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/interceptor.d.ts +11 -1
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +354 -156
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +12 -3
- package/dist/plugin-pipeline.d.ts +11 -4
- package/dist/plugin-pipeline.d.ts.map +1 -1
- package/dist/plugin-pipeline.js +117 -39
- 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 +85 -18
- 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 -8
- package/src/audit-core-builder.test.ts +0 -218
- package/src/audit-response-guard.test.ts +0 -38
- package/src/builder.test.ts +0 -289
- package/src/builder.ts +0 -701
- 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.ts +0 -113
- package/src/index.ts +0 -151
- package/src/interceptor.test.ts +0 -291
- package/src/interceptor.ts +0 -320
- package/src/namespace.test.ts +0 -274
- package/src/parser.property.test.ts +0 -594
- package/src/parser.test.ts +0 -148
- package/src/parser.ts +0 -64
- package/src/plugin-pipeline.ts +0 -103
- package/src/plugin-system.test.ts +0 -602
- package/src/response-parser.ts +0 -75
- 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 -165
- package/src/smart-defaults.test.ts +0 -361
- package/src/steps/async-support.steps.ts +0 -400
- package/src/steps/audit-core-builder.steps.ts +0 -188
- package/src/steps/audit-onerror-tuple.steps.ts +0 -99
- 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 -345
- package/src/steps/fluent-api.steps.ts +0 -255
- package/src/steps/http-methods.steps.ts +0 -331
- package/src/steps/interceptor.steps.ts +0 -260
- package/src/steps/lifecycle-events.steps.ts +0 -142
- package/src/steps/performance-reliability.steps.ts +0 -423
- package/src/steps/plugin-integration.steps.ts +0 -280
- package/src/steps/request-history.steps.ts +0 -276
- 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 -233
- package/src/steps/state-concurrency.steps.ts +0 -739
- package/src/steps/stateful-workflows.steps.ts +0 -353
- package/src/types.ts +0 -36
|
@@ -1,280 +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
|
-
|
|
12
|
-
Scenario("Plugin state sharing with pipeline", ({ Given, When, Then, And }) => {
|
|
13
|
-
requestResponses = [];
|
|
14
|
-
|
|
15
|
-
Given("I create a mock with a counter plugin using route state", () => {
|
|
16
|
-
mock = schmock({ state: {} });
|
|
17
|
-
mock("GET /counter", null, { contentType: "application/json" }).pipe({
|
|
18
|
-
name: "counter-plugin",
|
|
19
|
-
process: (ctx, response) => {
|
|
20
|
-
const routeState = ctx.routeState!;
|
|
21
|
-
routeState.request_count =
|
|
22
|
-
((routeState.request_count as number) || 0) + 1;
|
|
23
|
-
|
|
24
|
-
if (!response) {
|
|
25
|
-
return {
|
|
26
|
-
context: ctx,
|
|
27
|
-
response: {
|
|
28
|
-
request_number: routeState.request_count,
|
|
29
|
-
path: ctx.path,
|
|
30
|
-
processed_at: new Date().toISOString(),
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return { context: ctx, response };
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
When("I request {string} three times", async (_, request: string) => {
|
|
41
|
-
const [method, path] = request.split(" ");
|
|
42
|
-
requestResponses = [];
|
|
43
|
-
|
|
44
|
-
for (let i = 0; i < 3; i++) {
|
|
45
|
-
const response = await mock.handle(method as any, path);
|
|
46
|
-
requestResponses.push(response);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
Then(
|
|
51
|
-
"each response should have incrementing {string} values",
|
|
52
|
-
(_, property: string) => {
|
|
53
|
-
expect(requestResponses).toHaveLength(3);
|
|
54
|
-
|
|
55
|
-
for (let i = 0; i < requestResponses.length; i++) {
|
|
56
|
-
expect(requestResponses[i].body[property]).toBe(i + 1);
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
And(
|
|
62
|
-
"each response should have a {string} timestamp",
|
|
63
|
-
(_, property: string) => {
|
|
64
|
-
for (const response of requestResponses) {
|
|
65
|
-
expect(response.body[property]).toBeDefined();
|
|
66
|
-
expect(typeof response.body[property]).toBe("string");
|
|
67
|
-
expect(new Date(response.body[property]).getTime()).toBeGreaterThan(0);
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
And("the route state should persist across requests", () => {
|
|
73
|
-
const requestNumbers = requestResponses.map(
|
|
74
|
-
(r) => r.body.request_number,
|
|
75
|
-
);
|
|
76
|
-
expect(requestNumbers).toEqual([1, 2, 3]);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
Scenario("Multiple plugins in pipeline", ({ Given, When, Then }) => {
|
|
81
|
-
Given("I create a mock with auth and wrapper plugins", () => {
|
|
82
|
-
mock = schmock({});
|
|
83
|
-
mock("GET /users", () => [{ id: 1, name: "John" }], {
|
|
84
|
-
contentType: "application/json",
|
|
85
|
-
})
|
|
86
|
-
.pipe({
|
|
87
|
-
name: "auth-plugin",
|
|
88
|
-
process: (ctx, response) => {
|
|
89
|
-
if (!ctx.headers.authorization) {
|
|
90
|
-
throw new Error("Missing authorization");
|
|
91
|
-
}
|
|
92
|
-
ctx.state.set("user", { id: 1, name: "Admin" });
|
|
93
|
-
return { context: ctx, response };
|
|
94
|
-
},
|
|
95
|
-
})
|
|
96
|
-
.pipe({
|
|
97
|
-
name: "wrapper-plugin",
|
|
98
|
-
process: (ctx, response) => {
|
|
99
|
-
if (response) {
|
|
100
|
-
return {
|
|
101
|
-
context: ctx,
|
|
102
|
-
response: {
|
|
103
|
-
data: response,
|
|
104
|
-
meta: {
|
|
105
|
-
user: ctx.state.get("user"),
|
|
106
|
-
timestamp: "2025-01-31T10:15:30.123Z",
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
return { context: ctx, response };
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
When(
|
|
117
|
-
"I request {string} with headers:",
|
|
118
|
-
async (_, request: string, docString: string) => {
|
|
119
|
-
const [method, path] = request.split(" ");
|
|
120
|
-
const headers = JSON.parse(docString);
|
|
121
|
-
requestResponses = [
|
|
122
|
-
await mock.handle(method as any, path, { headers }),
|
|
123
|
-
];
|
|
124
|
-
},
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
Then("I should receive:", (_, docString: string) => {
|
|
128
|
-
const expected = JSON.parse(docString);
|
|
129
|
-
expect(requestResponses[0].body).toEqual(expected);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
Scenario("Plugin error handling", ({ Given, When, Then, And }) => {
|
|
134
|
-
Given("I create a mock with an auth guard plugin", () => {
|
|
135
|
-
mock = schmock({});
|
|
136
|
-
mock("GET /protected", () => ({ secret: "data" }), {
|
|
137
|
-
contentType: "application/json",
|
|
138
|
-
}).pipe({
|
|
139
|
-
name: "auth-plugin",
|
|
140
|
-
process: (ctx, response) => {
|
|
141
|
-
if (!ctx.headers.authorization) {
|
|
142
|
-
return {
|
|
143
|
-
context: ctx,
|
|
144
|
-
response: [401, { error: "Unauthorized", code: "AUTH_REQUIRED" }],
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
return { context: ctx, response };
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
When(
|
|
153
|
-
"I request {string} without authorization",
|
|
154
|
-
async (_, request: string) => {
|
|
155
|
-
const [method, path] = request.split(" ");
|
|
156
|
-
requestResponses = [await mock.handle(method as any, path)];
|
|
157
|
-
},
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
Then("the status should be {int}", (_, status: number) => {
|
|
161
|
-
expect(requestResponses[0].status).toBe(status);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
And("I should receive:", (_, docString: string) => {
|
|
165
|
-
const expected = JSON.parse(docString);
|
|
166
|
-
expect(requestResponses[0].body).toEqual(expected);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
Scenario(
|
|
171
|
-
"Pipeline order and response transformation",
|
|
172
|
-
({ Given, When, Then }) => {
|
|
173
|
-
Given("I create a mock with three ordered step plugins", () => {
|
|
174
|
-
mock = schmock({});
|
|
175
|
-
mock("GET /data", () => ({ value: 42 }), {
|
|
176
|
-
contentType: "application/json",
|
|
177
|
-
})
|
|
178
|
-
.pipe({
|
|
179
|
-
name: "step-1",
|
|
180
|
-
process: (ctx, response) => {
|
|
181
|
-
ctx.state.set("step1", "processed");
|
|
182
|
-
if (response) {
|
|
183
|
-
return {
|
|
184
|
-
context: ctx,
|
|
185
|
-
response: { ...response, step1: "processed" },
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
return { context: ctx, response };
|
|
189
|
-
},
|
|
190
|
-
})
|
|
191
|
-
.pipe({
|
|
192
|
-
name: "step-2",
|
|
193
|
-
process: (ctx, response) => {
|
|
194
|
-
if (response) {
|
|
195
|
-
return {
|
|
196
|
-
context: ctx,
|
|
197
|
-
response: { ...response, step2: "processed" },
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
return { context: ctx, response };
|
|
201
|
-
},
|
|
202
|
-
})
|
|
203
|
-
.pipe({
|
|
204
|
-
name: "step-3",
|
|
205
|
-
process: (ctx, response) => {
|
|
206
|
-
if (response) {
|
|
207
|
-
return {
|
|
208
|
-
context: ctx,
|
|
209
|
-
response: { ...response, step3: "processed" },
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
return { context: ctx, response };
|
|
213
|
-
},
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
When("I request {string}", async (_, request: string) => {
|
|
218
|
-
const [method, path] = request.split(" ");
|
|
219
|
-
requestResponses = [await mock.handle(method as any, path)];
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
Then("I should receive:", (_, docString: string) => {
|
|
223
|
-
const expected = JSON.parse(docString);
|
|
224
|
-
expect(requestResponses[0].body).toEqual(expected);
|
|
225
|
-
});
|
|
226
|
-
},
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
Scenario("Schema plugin in pipeline", ({ Given, When, Then, And }) => {
|
|
230
|
-
Given("I create a mock with a metadata wrapper plugin", () => {
|
|
231
|
-
mock = schmock({});
|
|
232
|
-
mock(
|
|
233
|
-
"GET /users",
|
|
234
|
-
() => [
|
|
235
|
-
{ id: 1, name: "John Doe", email: "john@example.com" },
|
|
236
|
-
{ id: 2, name: "Jane Smith", email: "jane@example.com" },
|
|
237
|
-
],
|
|
238
|
-
{ contentType: "application/json" },
|
|
239
|
-
).pipe({
|
|
240
|
-
name: "add-metadata",
|
|
241
|
-
process: (ctx, response) => {
|
|
242
|
-
if (response && Array.isArray(response)) {
|
|
243
|
-
return {
|
|
244
|
-
context: ctx,
|
|
245
|
-
response: {
|
|
246
|
-
users: response,
|
|
247
|
-
count: response.length,
|
|
248
|
-
generated_at: new Date().toISOString(),
|
|
249
|
-
},
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
return { context: ctx, response };
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
When("I request {string}", async (_, request: string) => {
|
|
258
|
-
const [method, path] = request.split(" ");
|
|
259
|
-
requestResponses = [await mock.handle(method as any, path)];
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
Then("the response should have a {string} array with {int} items", (_, property: string, count: number) => {
|
|
263
|
-
expect(requestResponses[0].body).toHaveProperty(property);
|
|
264
|
-
expect(Array.isArray(requestResponses[0].body[property])).toBe(true);
|
|
265
|
-
expect(requestResponses[0].body[property]).toHaveLength(count);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
And("the response should have {string} equal to {int}", (_, property: string, value: number) => {
|
|
269
|
-
expect(requestResponses[0].body[property]).toBe(value);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
And(
|
|
273
|
-
"the response should have a {string} timestamp",
|
|
274
|
-
(_, property: string) => {
|
|
275
|
-
expect(requestResponses[0].body).toHaveProperty(property);
|
|
276
|
-
expect(typeof requestResponses[0].body[property]).toBe("string");
|
|
277
|
-
},
|
|
278
|
-
);
|
|
279
|
-
});
|
|
280
|
-
});
|
|
@@ -1,276 +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/request-history.feature");
|
|
7
|
-
|
|
8
|
-
describeFeature(feature, ({ Scenario }) => {
|
|
9
|
-
let mock: CallableMockInstance;
|
|
10
|
-
let response: any;
|
|
11
|
-
|
|
12
|
-
Scenario("Record multiple requests", ({ Given, When, And, Then }) => {
|
|
13
|
-
Given("I create a mock with GET and POST user routes", () => {
|
|
14
|
-
mock = schmock();
|
|
15
|
-
mock("GET /users", [{ id: 1 }]);
|
|
16
|
-
mock("POST /users", ({ body }) => [201, body]);
|
|
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
|
-
And(
|
|
25
|
-
"I request {string} with body:",
|
|
26
|
-
async (_, request: string, docString: string) => {
|
|
27
|
-
const [method, path] = request.split(" ");
|
|
28
|
-
const body = JSON.parse(docString);
|
|
29
|
-
response = await mock.handle(method as any, path, { body });
|
|
30
|
-
},
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
And("I request {string}", async (_, request: string) => {
|
|
34
|
-
const [method, path] = request.split(" ");
|
|
35
|
-
response = await mock.handle(method as any, path);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
Then("the call count should be 3", () => {
|
|
39
|
-
expect(mock.callCount()).toBe(3);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
And(
|
|
43
|
-
"the call count for {string} should be {int}",
|
|
44
|
-
(_, route: string, count: number) => {
|
|
45
|
-
const [method, path] = route.split(" ");
|
|
46
|
-
expect(mock.callCount(method as any, path)).toBe(count);
|
|
47
|
-
},
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
And(
|
|
51
|
-
"the call count for {string} should be {int} request",
|
|
52
|
-
(_, route: string, count: number) => {
|
|
53
|
-
const [method, path] = route.split(" ");
|
|
54
|
-
expect(mock.callCount(method as any, path)).toBe(count);
|
|
55
|
-
},
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
Scenario("Filter history by method and path", ({ Given, When, And, Then }) => {
|
|
60
|
-
Given("I create a mock with users and posts routes", () => {
|
|
61
|
-
mock = schmock();
|
|
62
|
-
mock("GET /users", []);
|
|
63
|
-
mock("POST /users", ({ body }) => [201, body]);
|
|
64
|
-
mock("GET /posts", []);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
When("I request {string}", async (_, request: string) => {
|
|
68
|
-
const [method, path] = request.split(" ");
|
|
69
|
-
response = await mock.handle(method as any, path);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
And(
|
|
73
|
-
"I request {string} with body:",
|
|
74
|
-
async (_, request: string, docString: string) => {
|
|
75
|
-
const [method, path] = request.split(" ");
|
|
76
|
-
const body = JSON.parse(docString);
|
|
77
|
-
response = await mock.handle(method as any, path, { body });
|
|
78
|
-
},
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
And("I request {string}", async (_, request: string) => {
|
|
82
|
-
const [method, path] = request.split(" ");
|
|
83
|
-
response = await mock.handle(method as any, path);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
Then(
|
|
87
|
-
"the history for {string} should have {int} record",
|
|
88
|
-
(_, route: string, count: number) => {
|
|
89
|
-
const [method, path] = route.split(" ");
|
|
90
|
-
expect(mock.history(method as any, path)).toHaveLength(count);
|
|
91
|
-
},
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
And(
|
|
95
|
-
"the history for {string} should have {int} record",
|
|
96
|
-
(_, route: string, count: number) => {
|
|
97
|
-
const [method, path] = route.split(" ");
|
|
98
|
-
expect(mock.history(method as any, path)).toHaveLength(count);
|
|
99
|
-
},
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
And(
|
|
103
|
-
"the history for {string} should have {int} entry",
|
|
104
|
-
(_, route: string, count: number) => {
|
|
105
|
-
const [method, path] = route.split(" ");
|
|
106
|
-
expect(mock.history(method as any, path)).toHaveLength(count);
|
|
107
|
-
},
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
And(
|
|
111
|
-
"the history for {string} should have {int} entries",
|
|
112
|
-
(_, route: string, count: number) => {
|
|
113
|
-
const [method, path] = route.split(" ");
|
|
114
|
-
expect(mock.history(method as any, path)).toHaveLength(count);
|
|
115
|
-
},
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
Scenario("Check if specific route was called", ({ Given, When, Then, And }) => {
|
|
120
|
-
Given("I create a mock with users and posts list routes", () => {
|
|
121
|
-
mock = schmock();
|
|
122
|
-
mock("GET /users", []);
|
|
123
|
-
mock("GET /posts", []);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
When("I request {string}", async (_, request: string) => {
|
|
127
|
-
const [method, path] = request.split(" ");
|
|
128
|
-
response = await mock.handle(method as any, path);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
Then("{string} should have been called", (_, route: string) => {
|
|
132
|
-
const [method, path] = route.split(" ");
|
|
133
|
-
expect(mock.called(method as any, path)).toBe(true);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
And("{string} should not have been called", (_, route: string) => {
|
|
137
|
-
const [method, path] = route.split(" ");
|
|
138
|
-
expect(mock.called(method as any, path)).toBe(false);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
Scenario("Request record captures full details", ({ Given, When, Then, And }) => {
|
|
143
|
-
Given("I create a mock with a parameterized POST route", () => {
|
|
144
|
-
mock = schmock();
|
|
145
|
-
mock("POST /users/:id", ({ params, body }) => [
|
|
146
|
-
200,
|
|
147
|
-
{ ...(body as Record<string, unknown>), id: params.id },
|
|
148
|
-
]);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
When(
|
|
152
|
-
"I request {string} with headers and body:",
|
|
153
|
-
async (_, request: string, docString: string) => {
|
|
154
|
-
const [method, path] = request.split(" ");
|
|
155
|
-
const options = JSON.parse(docString);
|
|
156
|
-
response = await mock.handle(method as any, path, options);
|
|
157
|
-
},
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
Then("the last request should have:", (_, table: any) => {
|
|
161
|
-
const record = mock.lastRequest();
|
|
162
|
-
expect(record).toBeDefined();
|
|
163
|
-
for (const row of table) {
|
|
164
|
-
const field = row.field as keyof typeof record;
|
|
165
|
-
expect(record![field]).toBe(row.value);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
And(
|
|
170
|
-
"the last request params should include {string} = {string}",
|
|
171
|
-
(_, key: string, value: string) => {
|
|
172
|
-
expect(mock.lastRequest()?.params[key]).toBe(value);
|
|
173
|
-
},
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
And(
|
|
177
|
-
"the last request headers should include {string} = {string}",
|
|
178
|
-
(_, key: string, value: string) => {
|
|
179
|
-
expect(mock.lastRequest()?.headers[key]).toBe(value);
|
|
180
|
-
},
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
And(
|
|
184
|
-
"the last request body should have property {string} with value {string}",
|
|
185
|
-
(_, prop: string, value: string) => {
|
|
186
|
-
expect((mock.lastRequest()?.body as any)?.[prop]).toBe(value);
|
|
187
|
-
},
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
And("the last request should have a timestamp", () => {
|
|
191
|
-
const record = mock.lastRequest();
|
|
192
|
-
expect(record?.timestamp).toBeDefined();
|
|
193
|
-
expect(typeof record?.timestamp).toBe("number");
|
|
194
|
-
expect(record!.timestamp).toBeGreaterThan(0);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
And(
|
|
198
|
-
"the last request response status should be {int}",
|
|
199
|
-
(_, status: number) => {
|
|
200
|
-
expect(mock.lastRequest()?.response.status).toBe(status);
|
|
201
|
-
},
|
|
202
|
-
);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
Scenario("Get last request for a specific route", ({ Given, When, And, Then }) => {
|
|
206
|
-
Given("I create a mock echoing POST body at {string}", (_, path: string) => {
|
|
207
|
-
mock = schmock();
|
|
208
|
-
mock(`POST ${path}`, ({ body }) => [201, body]);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
When(
|
|
212
|
-
"I request {string} with body:",
|
|
213
|
-
async (_, request: string, docString: string) => {
|
|
214
|
-
const [method, path] = request.split(" ");
|
|
215
|
-
const body = JSON.parse(docString);
|
|
216
|
-
response = await mock.handle(method as any, path, { body });
|
|
217
|
-
},
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
And(
|
|
221
|
-
"I request {string} with body:",
|
|
222
|
-
async (_, request: string, docString: string) => {
|
|
223
|
-
const [method, path] = request.split(" ");
|
|
224
|
-
const body = JSON.parse(docString);
|
|
225
|
-
response = await mock.handle(method as any, path, { body });
|
|
226
|
-
},
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
Then(
|
|
230
|
-
"the last request for {string} body should have property {string} with value {string}",
|
|
231
|
-
(_, route: string, prop: string, value: string) => {
|
|
232
|
-
const [method, path] = route.split(" ");
|
|
233
|
-
const record = mock.lastRequest(method as any, path);
|
|
234
|
-
expect((record?.body as any)?.[prop]).toBe(value);
|
|
235
|
-
},
|
|
236
|
-
);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
Scenario("404 requests are not recorded in history", ({ Given, When, Then, And }) => {
|
|
240
|
-
Given("I create a mock with only a users route", () => {
|
|
241
|
-
mock = schmock();
|
|
242
|
-
mock("GET /users", []);
|
|
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("the mock should not have been called", () => {
|
|
251
|
-
expect(mock.called()).toBe(false);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
And("the call count should be 0", () => {
|
|
255
|
-
expect(mock.callCount()).toBe(0);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
Scenario("maxHistorySize bounds the history with FIFO eviction", ({ Given, When, Then }) => {
|
|
260
|
-
Given("I create a mock with maxHistorySize {int} and a users route", (_, maxHistorySize: number) => {
|
|
261
|
-
mock = schmock({ maxHistorySize });
|
|
262
|
-
mock("GET /users", []);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
When("I issue {int} requests to {string}", async (_, count: number, request: string) => {
|
|
266
|
-
const [method, path] = request.split(" ");
|
|
267
|
-
for (let i = 0; i < count; i++) {
|
|
268
|
-
await mock.handle(method as any, path);
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
Then("the call count should be {int}", (_, expected: number) => {
|
|
273
|
-
expect(mock.callCount()).toBe(expected);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
});
|
|
@@ -1,88 +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/response-delay.feature");
|
|
6
|
-
|
|
7
|
-
describeFeature(feature, ({ Scenario }) => {
|
|
8
|
-
let mock: Schmock.CallableMockInstance;
|
|
9
|
-
let elapsed: number;
|
|
10
|
-
let elapsed2: number;
|
|
11
|
-
|
|
12
|
-
Scenario("Route delay overrides global delay", ({ Given, And, When, Then }) => {
|
|
13
|
-
Given("a mock with global delay of 200ms", () => {
|
|
14
|
-
mock = schmock({ delay: 200, state: {} });
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
And('a route "GET /fast" with delay of 10ms', () => {
|
|
18
|
-
mock("GET /fast", { ok: true }, { delay: 10 });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
And('a route "GET /default" with no delay override', () => {
|
|
22
|
-
mock("GET /default", { ok: true }, {});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
When('I request "GET /fast"', async () => {
|
|
26
|
-
const start = performance.now();
|
|
27
|
-
await mock.handle("GET", "/fast");
|
|
28
|
-
elapsed = performance.now() - start;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Upper bound is intentionally generous: it only needs to prove the 10ms
|
|
32
|
-
// route delay was used instead of the 200ms global. A tight bound here
|
|
33
|
-
// flakes under CI load (a single timer can be delayed tens of ms).
|
|
34
|
-
Then("the response took less than 150ms", () => {
|
|
35
|
-
expect(elapsed).toBeLessThan(150);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
When('I request "GET /default" with timing', async () => {
|
|
39
|
-
const start = performance.now();
|
|
40
|
-
await mock.handle("GET", "/default");
|
|
41
|
-
elapsed2 = performance.now() - start;
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
Then("that response took at least 150ms", () => {
|
|
45
|
-
expect(elapsed2).toBeGreaterThanOrEqual(150);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
Scenario("Route delay supports random range", ({ Given, And, When, Then }) => {
|
|
50
|
-
Given("a mock with no global delay", () => {
|
|
51
|
-
mock = schmock({ state: {} });
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
And('a route "GET /random" with delay range 10 to 30', () => {
|
|
55
|
-
mock("GET /random", { ok: true }, { delay: [10, 30] });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
When('I request "GET /random"', async () => {
|
|
59
|
-
const start = performance.now();
|
|
60
|
-
await mock.handle("GET", "/random");
|
|
61
|
-
elapsed = performance.now() - start;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
Then("the response took at least 8ms", () => {
|
|
65
|
-
expect(elapsed).toBeGreaterThanOrEqual(8);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
Scenario("No route delay inherits global delay", ({ Given, And, When, Then }) => {
|
|
70
|
-
Given("a mock with global delay of 50ms", () => {
|
|
71
|
-
mock = schmock({ delay: 50, state: {} });
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
And('a route "GET /items" with no delay override', () => {
|
|
75
|
-
mock("GET /items", { ok: true }, {});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
When('I request "GET /items" with timing', async () => {
|
|
79
|
-
const start = performance.now();
|
|
80
|
-
await mock.handle("GET", "/items");
|
|
81
|
-
elapsed = performance.now() - start;
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
Then("that response took at least 40ms", () => {
|
|
85
|
-
expect(elapsed).toBeGreaterThanOrEqual(40);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
});
|