@schmock/core 1.0.3 → 1.1.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/dist/builder.d.ts +13 -5
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +147 -60
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +20 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -11
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +2 -17
- package/dist/types.d.ts +17 -210
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/package.json +4 -4
- package/src/builder.test.ts +2 -2
- package/src/builder.ts +232 -108
- package/src/constants.test.ts +59 -0
- package/src/constants.ts +25 -0
- package/src/errors.ts +3 -1
- package/src/index.ts +41 -29
- package/src/namespace.test.ts +3 -2
- package/src/parser.property.test.ts +495 -0
- package/src/parser.ts +2 -20
- package/src/route-matching.test.ts +1 -1
- package/src/steps/async-support.steps.ts +101 -91
- package/src/steps/basic-usage.steps.ts +49 -36
- package/src/steps/developer-experience.steps.ts +110 -94
- package/src/steps/error-handling.steps.ts +90 -66
- package/src/steps/fluent-api.steps.ts +75 -72
- package/src/steps/http-methods.steps.ts +33 -33
- package/src/steps/performance-reliability.steps.ts +52 -88
- package/src/steps/plugin-integration.steps.ts +176 -176
- package/src/steps/request-history.steps.ts +333 -0
- package/src/steps/state-concurrency.steps.ts +418 -316
- package/src/steps/stateful-workflows.steps.ts +138 -136
- package/src/types.ts +20 -259
|
@@ -0,0 +1,333 @@
|
|
|
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("No requests recorded initially", ({ Given, Then, And }) => {
|
|
13
|
+
Given("I create a mock with a single GET route for history", () => {
|
|
14
|
+
mock = schmock();
|
|
15
|
+
mock("GET /users", [{ id: 1 }]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
Then("the mock should not have been called", () => {
|
|
19
|
+
expect(mock.called()).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
And("the call count should be 0", () => {
|
|
23
|
+
expect(mock.callCount()).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
And("the history should be empty", () => {
|
|
27
|
+
expect(mock.history()).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
Scenario("Record a single GET request", ({ Given, When, Then, And }) => {
|
|
32
|
+
Given("I create a mock returning users at {string}", (_, route: string) => {
|
|
33
|
+
mock = schmock();
|
|
34
|
+
mock(route as Schmock.RouteKey, [{ id: 1, name: "John" }]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
When("I request {string}", async (_, request: string) => {
|
|
38
|
+
const [method, path] = request.split(" ");
|
|
39
|
+
response = await mock.handle(method as any, path);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
Then("the mock should have been called", () => {
|
|
43
|
+
expect(mock.called()).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
And("the call count should be 1", () => {
|
|
47
|
+
expect(mock.callCount()).toBe(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
And("the history should have 1 entry", () => {
|
|
51
|
+
expect(mock.history()).toHaveLength(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
And("the last request method should be {string}", (_, method: string) => {
|
|
55
|
+
expect(mock.lastRequest()?.method).toBe(method);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
And("the last request path should be {string}", (_, path: string) => {
|
|
59
|
+
expect(mock.lastRequest()?.path).toBe(path);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
Scenario("Record multiple requests", ({ Given, When, And, Then }) => {
|
|
64
|
+
Given("I create a mock with GET and POST user routes", () => {
|
|
65
|
+
mock = schmock();
|
|
66
|
+
mock("GET /users", [{ id: 1 }]);
|
|
67
|
+
mock("POST /users", ({ body }) => [201, body]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
When("I request {string}", async (_, request: string) => {
|
|
71
|
+
const [method, path] = request.split(" ");
|
|
72
|
+
response = await mock.handle(method as any, path);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
And(
|
|
76
|
+
"I request {string} with body:",
|
|
77
|
+
async (_, request: string, docString: string) => {
|
|
78
|
+
const [method, path] = request.split(" ");
|
|
79
|
+
const body = JSON.parse(docString);
|
|
80
|
+
response = await mock.handle(method as any, path, { body });
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
And("I request {string}", async (_, request: string) => {
|
|
85
|
+
const [method, path] = request.split(" ");
|
|
86
|
+
response = await mock.handle(method as any, path);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
Then("the call count should be 3", () => {
|
|
90
|
+
expect(mock.callCount()).toBe(3);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
And(
|
|
94
|
+
"the call count for {string} should be {int}",
|
|
95
|
+
(_, route: string, count: number) => {
|
|
96
|
+
const [method, path] = route.split(" ");
|
|
97
|
+
expect(mock.callCount(method as any, path)).toBe(count);
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
And(
|
|
102
|
+
"the call count for {string} should be {int} request",
|
|
103
|
+
(_, route: string, count: number) => {
|
|
104
|
+
const [method, path] = route.split(" ");
|
|
105
|
+
expect(mock.callCount(method as any, path)).toBe(count);
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
Scenario("Filter history by method and path", ({ Given, When, And, Then }) => {
|
|
111
|
+
Given("I create a mock with users and posts routes", () => {
|
|
112
|
+
mock = schmock();
|
|
113
|
+
mock("GET /users", []);
|
|
114
|
+
mock("POST /users", ({ body }) => [201, body]);
|
|
115
|
+
mock("GET /posts", []);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
When("I request {string}", async (_, request: string) => {
|
|
119
|
+
const [method, path] = request.split(" ");
|
|
120
|
+
response = await mock.handle(method as any, path);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
And(
|
|
124
|
+
"I request {string} with body:",
|
|
125
|
+
async (_, request: string, docString: string) => {
|
|
126
|
+
const [method, path] = request.split(" ");
|
|
127
|
+
const body = JSON.parse(docString);
|
|
128
|
+
response = await mock.handle(method as any, path, { body });
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
And("I request {string}", async (_, request: string) => {
|
|
133
|
+
const [method, path] = request.split(" ");
|
|
134
|
+
response = await mock.handle(method as any, path);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
Then(
|
|
138
|
+
"the history for {string} should have {int} record",
|
|
139
|
+
(_, route: string, count: number) => {
|
|
140
|
+
const [method, path] = route.split(" ");
|
|
141
|
+
expect(mock.history(method as any, path)).toHaveLength(count);
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
And(
|
|
146
|
+
"the history for {string} should have {int} record",
|
|
147
|
+
(_, route: string, count: number) => {
|
|
148
|
+
const [method, path] = route.split(" ");
|
|
149
|
+
expect(mock.history(method as any, path)).toHaveLength(count);
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
And(
|
|
154
|
+
"the history for {string} should have {int} entry",
|
|
155
|
+
(_, route: string, count: number) => {
|
|
156
|
+
const [method, path] = route.split(" ");
|
|
157
|
+
expect(mock.history(method as any, path)).toHaveLength(count);
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
And(
|
|
162
|
+
"the history for {string} should have {int} entries",
|
|
163
|
+
(_, route: string, count: number) => {
|
|
164
|
+
const [method, path] = route.split(" ");
|
|
165
|
+
expect(mock.history(method as any, path)).toHaveLength(count);
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
Scenario("Check if specific route was called", ({ Given, When, Then, And }) => {
|
|
171
|
+
Given("I create a mock with users and posts list routes", () => {
|
|
172
|
+
mock = schmock();
|
|
173
|
+
mock("GET /users", []);
|
|
174
|
+
mock("GET /posts", []);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
When("I request {string}", async (_, request: string) => {
|
|
178
|
+
const [method, path] = request.split(" ");
|
|
179
|
+
response = await mock.handle(method as any, path);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
Then("{string} should have been called", (_, route: string) => {
|
|
183
|
+
const [method, path] = route.split(" ");
|
|
184
|
+
expect(mock.called(method as any, path)).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
And("{string} should not have been called", (_, route: string) => {
|
|
188
|
+
const [method, path] = route.split(" ");
|
|
189
|
+
expect(mock.called(method as any, path)).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
Scenario("Request record captures full details", ({ Given, When, Then, And }) => {
|
|
194
|
+
Given("I create a mock with a parameterized POST route", () => {
|
|
195
|
+
mock = schmock();
|
|
196
|
+
mock("POST /users/:id", ({ params, body }) => [
|
|
197
|
+
200,
|
|
198
|
+
{ ...(body as Record<string, unknown>), id: params.id },
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
When(
|
|
203
|
+
"I request {string} with headers and body:",
|
|
204
|
+
async (_, request: string, docString: string) => {
|
|
205
|
+
const [method, path] = request.split(" ");
|
|
206
|
+
const options = JSON.parse(docString);
|
|
207
|
+
response = await mock.handle(method as any, path, options);
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
Then("the last request should have:", (_, table: any) => {
|
|
212
|
+
const record = mock.lastRequest();
|
|
213
|
+
expect(record).toBeDefined();
|
|
214
|
+
for (const row of table) {
|
|
215
|
+
const field = row.field as keyof typeof record;
|
|
216
|
+
expect(record![field]).toBe(row.value);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
And(
|
|
221
|
+
"the last request params should include {string} = {string}",
|
|
222
|
+
(_, key: string, value: string) => {
|
|
223
|
+
expect(mock.lastRequest()?.params[key]).toBe(value);
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
And(
|
|
228
|
+
"the last request headers should include {string} = {string}",
|
|
229
|
+
(_, key: string, value: string) => {
|
|
230
|
+
expect(mock.lastRequest()?.headers[key]).toBe(value);
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
And(
|
|
235
|
+
"the last request body should have property {string} with value {string}",
|
|
236
|
+
(_, prop: string, value: string) => {
|
|
237
|
+
expect((mock.lastRequest()?.body as any)?.[prop]).toBe(value);
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
And("the last request should have a timestamp", () => {
|
|
242
|
+
const record = mock.lastRequest();
|
|
243
|
+
expect(record?.timestamp).toBeDefined();
|
|
244
|
+
expect(typeof record?.timestamp).toBe("number");
|
|
245
|
+
expect(record!.timestamp).toBeGreaterThan(0);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
And(
|
|
249
|
+
"the last request response status should be {int}",
|
|
250
|
+
(_, status: number) => {
|
|
251
|
+
expect(mock.lastRequest()?.response.status).toBe(status);
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
Scenario("Get last request for a specific route", ({ Given, When, And, Then }) => {
|
|
257
|
+
Given("I create a mock echoing POST body at {string}", (_, path: string) => {
|
|
258
|
+
mock = schmock();
|
|
259
|
+
mock(`POST ${path}`, ({ body }) => [201, body]);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
When(
|
|
263
|
+
"I request {string} with body:",
|
|
264
|
+
async (_, request: string, docString: string) => {
|
|
265
|
+
const [method, path] = request.split(" ");
|
|
266
|
+
const body = JSON.parse(docString);
|
|
267
|
+
response = await mock.handle(method as any, path, { body });
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
And(
|
|
272
|
+
"I request {string} with body:",
|
|
273
|
+
async (_, request: string, docString: string) => {
|
|
274
|
+
const [method, path] = request.split(" ");
|
|
275
|
+
const body = JSON.parse(docString);
|
|
276
|
+
response = await mock.handle(method as any, path, { body });
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
Then(
|
|
281
|
+
"the last request for {string} body should have property {string} with value {string}",
|
|
282
|
+
(_, route: string, prop: string, value: string) => {
|
|
283
|
+
const [method, path] = route.split(" ");
|
|
284
|
+
const record = mock.lastRequest(method as any, path);
|
|
285
|
+
expect((record?.body as any)?.[prop]).toBe(value);
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
Scenario("History works with namespaced mocks", ({ Given, When, Then, And }) => {
|
|
291
|
+
Given("I create a namespaced mock under {string}", (_, namespace: string) => {
|
|
292
|
+
mock = schmock({ namespace });
|
|
293
|
+
mock("GET /users", [{ id: 1 }]);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
When("I request {string}", async (_, request: string) => {
|
|
297
|
+
const [method, path] = request.split(" ");
|
|
298
|
+
response = await mock.handle(method as any, path);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
Then("the mock should have been called", () => {
|
|
302
|
+
expect(mock.called()).toBe(true);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
And("the call count should be 1", () => {
|
|
306
|
+
expect(mock.callCount()).toBe(1);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
And("the last request path should be {string}", (_, path: string) => {
|
|
310
|
+
expect(mock.lastRequest()?.path).toBe(path);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
Scenario("404 requests are not recorded in history", ({ Given, When, Then, And }) => {
|
|
315
|
+
Given("I create a mock with only a users route", () => {
|
|
316
|
+
mock = schmock();
|
|
317
|
+
mock("GET /users", []);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
When("I request {string}", async (_, request: string) => {
|
|
321
|
+
const [method, path] = request.split(" ");
|
|
322
|
+
response = await mock.handle(method as any, path);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
Then("the mock should not have been called", () => {
|
|
326
|
+
expect(mock.called()).toBe(false);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
And("the call count should be 0", () => {
|
|
330
|
+
expect(mock.callCount()).toBe(0);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|