@schmock/core 1.13.0 → 2.0.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 (40) hide show
  1. package/dist/builder.d.ts +2 -0
  2. package/dist/builder.d.ts.map +1 -1
  3. package/dist/builder.js +13 -0
  4. package/dist/constants.d.ts +8 -0
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/constants.js +12 -0
  7. package/dist/helpers.d.ts +9 -0
  8. package/dist/helpers.d.ts.map +1 -0
  9. package/dist/helpers.js +37 -0
  10. package/dist/index.d.ts +4 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +7 -1
  13. package/dist/interceptor.d.ts +5 -0
  14. package/dist/interceptor.d.ts.map +1 -0
  15. package/dist/interceptor.js +213 -0
  16. package/dist/plugin-pipeline.js +1 -1
  17. package/dist/types.d.ts +5 -0
  18. package/dist/types.d.ts.map +1 -1
  19. package/package.json +1 -1
  20. package/src/builder.ts +23 -0
  21. package/src/constants.test.ts +40 -0
  22. package/src/constants.ts +18 -0
  23. package/src/helpers.test.ts +147 -0
  24. package/src/helpers.ts +58 -0
  25. package/src/index.ts +21 -0
  26. package/src/interceptor.test.ts +291 -0
  27. package/src/interceptor.ts +272 -0
  28. package/src/parser.property.test.ts +101 -0
  29. package/src/plugin-pipeline.ts +1 -1
  30. package/src/response-parsing.test.ts +74 -0
  31. package/src/server.test.ts +49 -0
  32. package/src/steps/async-support.steps.ts +0 -35
  33. package/src/steps/basic-usage.steps.ts +0 -84
  34. package/src/steps/developer-experience.steps.ts +0 -269
  35. package/src/steps/error-handling.steps.ts +0 -66
  36. package/src/steps/http-methods.steps.ts +0 -66
  37. package/src/steps/interceptor.steps.ts +206 -0
  38. package/src/steps/request-history.steps.ts +0 -75
  39. package/src/steps/route-key-format.steps.ts +0 -19
  40. package/src/types.ts +5 -0
@@ -0,0 +1,272 @@
1
+ /// <reference path="../schmock.d.ts" />
2
+
3
+ import { isRouteNotFound, toHttpMethod } from "./constants.js";
4
+
5
+ /**
6
+ * Extract pathname from a URL string, handling both absolute and relative URLs.
7
+ */
8
+ function extractPathname(url: string): string {
9
+ const queryStart = url.indexOf("?");
10
+ const urlWithoutQuery = queryStart === -1 ? url : url.slice(0, queryStart);
11
+
12
+ if (urlWithoutQuery.includes("://")) {
13
+ try {
14
+ return new URL(urlWithoutQuery).pathname;
15
+ } catch {
16
+ // Fall through to simple extraction
17
+ }
18
+ }
19
+
20
+ if (!urlWithoutQuery.startsWith("/")) {
21
+ return `/${urlWithoutQuery}`;
22
+ }
23
+
24
+ return urlWithoutQuery;
25
+ }
26
+
27
+ /**
28
+ * Extract query parameters from a URL string.
29
+ */
30
+ function extractQuery(url: string): Record<string, string> {
31
+ const queryStart = url.indexOf("?");
32
+ if (queryStart === -1) return {};
33
+
34
+ const params = new URLSearchParams(url.slice(queryStart + 1));
35
+ const result: Record<string, string> = {};
36
+ params.forEach((value, key) => {
37
+ result[key] = value;
38
+ });
39
+ return result;
40
+ }
41
+
42
+ /**
43
+ * Extract headers from fetch init or Request object.
44
+ */
45
+ function extractHeaders(
46
+ input: RequestInfo | URL,
47
+ init?: RequestInit,
48
+ ): Record<string, string> {
49
+ const headers: Record<string, string> = {};
50
+
51
+ const raw =
52
+ init?.headers ?? (input instanceof Request ? input.headers : undefined);
53
+ if (!raw) return headers;
54
+
55
+ if (raw instanceof Headers) {
56
+ raw.forEach((value, key) => {
57
+ headers[key] = value;
58
+ });
59
+ } else if (Array.isArray(raw)) {
60
+ for (const [key, value] of raw) {
61
+ headers[key.toLowerCase()] = value;
62
+ }
63
+ } else {
64
+ for (const key of Object.keys(raw)) {
65
+ headers[key.toLowerCase()] = (raw as Record<string, string>)[key];
66
+ }
67
+ }
68
+
69
+ return headers;
70
+ }
71
+
72
+ /**
73
+ * Extract body from fetch init, parsing JSON when possible.
74
+ */
75
+ async function extractBody(
76
+ input: RequestInfo | URL,
77
+ init?: RequestInit,
78
+ ): Promise<unknown> {
79
+ // Per Fetch spec, init.body overrides Request.body when both are present
80
+ const bodyInit = init?.body ?? (input instanceof Request ? input.body : null);
81
+ if (bodyInit === null || bodyInit === undefined) return undefined;
82
+
83
+ // String body — try to parse as JSON
84
+ if (typeof bodyInit === "string") {
85
+ try {
86
+ return JSON.parse(bodyInit);
87
+ } catch {
88
+ return bodyInit;
89
+ }
90
+ }
91
+
92
+ // URLSearchParams — convert to key/value object
93
+ if (bodyInit instanceof URLSearchParams) {
94
+ const result: Record<string, string> = {};
95
+ bodyInit.forEach((value, key) => {
96
+ result[key] = value;
97
+ });
98
+ return result;
99
+ }
100
+
101
+ // Request with body — clone and read
102
+ if (input instanceof Request && !init?.body && input.body) {
103
+ try {
104
+ return await input.clone().json();
105
+ } catch {
106
+ try {
107
+ return await input.clone().text();
108
+ } catch {
109
+ return undefined;
110
+ }
111
+ }
112
+ }
113
+
114
+ return undefined;
115
+ }
116
+
117
+ /**
118
+ * Create a fetch interceptor that routes requests through mock.handle().
119
+ */
120
+ export function createFetchInterceptor(
121
+ handle: (
122
+ method: Schmock.HttpMethod,
123
+ path: string,
124
+ requestOptions?: Schmock.RequestOptions,
125
+ ) => Promise<Schmock.Response>,
126
+ options: Schmock.InterceptOptions = {},
127
+ ): Schmock.InterceptHandle {
128
+ const {
129
+ baseUrl,
130
+ passthrough = true,
131
+ beforeRequest,
132
+ beforeResponse,
133
+ errorFormatter,
134
+ } = options;
135
+
136
+ const originalFetch = globalThis.fetch;
137
+ let active = true;
138
+
139
+ globalThis.fetch = async (
140
+ input: RequestInfo | URL,
141
+ init?: RequestInit,
142
+ ): Promise<Response> => {
143
+ // Resolve the URL string
144
+ const urlString =
145
+ input instanceof Request
146
+ ? input.url
147
+ : input instanceof URL
148
+ ? input.href
149
+ : input;
150
+
151
+ const path = extractPathname(urlString);
152
+
153
+ // BaseUrl filter — non-matching requests go straight to real fetch
154
+ // Enforce segment boundary: /api must not match /apiv2
155
+ if (baseUrl) {
156
+ const normalizedBase = baseUrl.endsWith("/")
157
+ ? baseUrl.slice(0, -1)
158
+ : baseUrl;
159
+ const isMatch =
160
+ path === normalizedBase || path.startsWith(`${normalizedBase}/`);
161
+ if (!isMatch) {
162
+ return originalFetch(input, init);
163
+ }
164
+ }
165
+
166
+ // Build adapter request
167
+ const method =
168
+ input instanceof Request ? input.method : (init?.method ?? "GET");
169
+ const headers = extractHeaders(input, init);
170
+ const query = extractQuery(urlString);
171
+ const body = await extractBody(input, init);
172
+
173
+ let adapterRequest: Schmock.AdapterRequest = {
174
+ method,
175
+ path,
176
+ headers,
177
+ body,
178
+ query,
179
+ };
180
+
181
+ try {
182
+ // Apply beforeRequest hook
183
+ if (beforeRequest) {
184
+ const modified = await beforeRequest(adapterRequest);
185
+ if (modified) {
186
+ adapterRequest = modified;
187
+ }
188
+ }
189
+
190
+ const schmockResponse = await handle(
191
+ toHttpMethod(adapterRequest.method),
192
+ adapterRequest.path,
193
+ {
194
+ headers: adapterRequest.headers,
195
+ body: adapterRequest.body,
196
+ query: adapterRequest.query,
197
+ },
198
+ );
199
+
200
+ // Route not found — passthrough or 404
201
+ if (isRouteNotFound(schmockResponse)) {
202
+ if (passthrough) {
203
+ return originalFetch(input, init);
204
+ }
205
+ return new Response(
206
+ JSON.stringify({
207
+ error: "No matching mock route found",
208
+ code: "ROUTE_NOT_FOUND",
209
+ }),
210
+ {
211
+ status: 404,
212
+ headers: { "content-type": "application/json" },
213
+ },
214
+ );
215
+ }
216
+
217
+ // Apply beforeResponse hook
218
+ let response: Schmock.AdapterResponse = schmockResponse;
219
+ if (beforeResponse) {
220
+ const modified = await beforeResponse(response, adapterRequest);
221
+ if (modified) {
222
+ response = modified;
223
+ }
224
+ }
225
+
226
+ // Build fetch Response
227
+ const responseHeaders = new Headers(response.headers);
228
+ if (
229
+ !responseHeaders.has("content-type") &&
230
+ response.body !== null &&
231
+ response.body !== undefined
232
+ ) {
233
+ responseHeaders.set("content-type", "application/json");
234
+ }
235
+
236
+ const responseBody =
237
+ response.body === null || response.body === undefined
238
+ ? null
239
+ : typeof response.body === "string"
240
+ ? response.body
241
+ : JSON.stringify(response.body);
242
+
243
+ return new Response(responseBody, {
244
+ status: response.status,
245
+ headers: responseHeaders,
246
+ });
247
+ } catch (error) {
248
+ if (errorFormatter) {
249
+ const formatted = errorFormatter(
250
+ error instanceof Error ? error : new Error(String(error)),
251
+ );
252
+ return new Response(JSON.stringify(formatted), {
253
+ status: 500,
254
+ headers: { "content-type": "application/json" },
255
+ });
256
+ }
257
+ throw error;
258
+ }
259
+ };
260
+
261
+ return {
262
+ restore() {
263
+ if (active) {
264
+ globalThis.fetch = originalFetch;
265
+ active = false;
266
+ }
267
+ },
268
+ get active() {
269
+ return active;
270
+ },
271
+ };
272
+ }
@@ -491,3 +491,104 @@ describe("handle() — fuzz", () => {
491
491
  );
492
492
  });
493
493
  });
494
+
495
+ // ============================================================
496
+ // Route matching round-trip properties
497
+ // ============================================================
498
+
499
+ describe("route matching round-trip — property", () => {
500
+ it("parameterized route: arbitrary param values are extracted correctly", () => {
501
+ fc.assert(
502
+ fc.asyncProperty(
503
+ safeSeg,
504
+ safeSeg,
505
+ safeSeg,
506
+ async (resource, paramA, paramB) => {
507
+ const mock = schmock();
508
+ let capturedParams: Record<string, string> = {};
509
+ mock(`GET /${resource}/:a/:b` as any, (ctx) => {
510
+ capturedParams = ctx.params;
511
+ return { ok: true };
512
+ });
513
+
514
+ const response = await mock.handle(
515
+ "GET",
516
+ `/${resource}/${paramA}/${paramB}`,
517
+ );
518
+ expect(response.status).toBe(200);
519
+ expect(capturedParams.a).toBe(paramA);
520
+ expect(capturedParams.b).toBe(paramB);
521
+ },
522
+ ),
523
+ { numRuns: 300 },
524
+ );
525
+ });
526
+
527
+ it("any valid response format produces a valid Response with status/body/headers", () => {
528
+ const responseGen = fc.oneof(
529
+ // Object body
530
+ fc.record({ key: safeSeg }),
531
+ // Tuple [status, body]
532
+ fc.tuple(fc.integer({ min: 100, max: 599 }), fc.record({ v: safeSeg })),
533
+ // Tuple [status, body, headers]
534
+ fc.tuple(
535
+ fc.integer({ min: 100, max: 599 }),
536
+ fc.record({ v: safeSeg }),
537
+ fc.constant({}),
538
+ ),
539
+ // null/undefined
540
+ fc.constant(null),
541
+ fc.constant(undefined),
542
+ );
543
+
544
+ fc.assert(
545
+ fc.asyncProperty(responseGen, async (genValue) => {
546
+ const mock = schmock();
547
+ mock("GET /test", () => genValue);
548
+
549
+ const response = await mock.handle("GET", "/test");
550
+ expect(typeof response.status).toBe("number");
551
+ expect(response.status).toBeGreaterThanOrEqual(100);
552
+ expect(response.status).toBeLessThanOrEqual(599);
553
+ expect(response).toHaveProperty("headers");
554
+ expect(typeof response.headers).toBe("object");
555
+ }),
556
+ { numRuns: 300 },
557
+ );
558
+ });
559
+
560
+ it("header keys are always lowercased after extractHeaders in interceptor", () => {
561
+ const headerKey = fc
562
+ .stringMatching(/^[A-Za-z][A-Za-z0-9-]{0,20}$/)
563
+ .filter((k) => k.length > 0);
564
+ const headerVal = safeSeg;
565
+
566
+ fc.assert(
567
+ fc.asyncProperty(headerKey, headerVal, async (key, val) => {
568
+ const mock = schmock();
569
+ let capturedHeaders: Record<string, string> = {};
570
+ mock("GET /hdr", (ctx) => {
571
+ capturedHeaders = ctx.headers;
572
+ return { ok: true };
573
+ });
574
+ const handle = mock.intercept();
575
+
576
+ try {
577
+ await fetch("http://localhost/hdr", {
578
+ headers: { [key]: val },
579
+ });
580
+
581
+ // All header keys should be lowercase
582
+ for (const hdrKey of Object.keys(capturedHeaders)) {
583
+ expect(hdrKey).toBe(hdrKey.toLowerCase());
584
+ }
585
+ // The value should be preserved
586
+ expect(capturedHeaders[key.toLowerCase()]).toBe(val);
587
+ } finally {
588
+ handle.restore();
589
+ }
590
+ }),
591
+ { numRuns: 200 },
592
+ );
593
+ });
594
+ });
@@ -30,7 +30,7 @@ export async function runPluginPipeline(
30
30
  try {
31
31
  const result = await plugin.process(currentContext, response);
32
32
 
33
- if (!result || !result.context) {
33
+ if (!result?.context) {
34
34
  throw new Error(`Plugin ${plugin.name} didn't return valid result`);
35
35
  }
36
36
 
@@ -225,6 +225,80 @@ describe("response parsing", () => {
225
225
  });
226
226
  });
227
227
 
228
+ describe("status code boundaries", () => {
229
+ it("status code 100 (lower boundary) is accepted as tuple", async () => {
230
+ const mock = schmock();
231
+ mock("GET /continue", () => [100, "Continue"]);
232
+
233
+ const response = await mock.handle("GET", "/continue");
234
+ expect(response.status).toBe(100);
235
+ expect(response.body).toBe("Continue");
236
+ });
237
+
238
+ it("status code 599 (upper boundary) is accepted as tuple", async () => {
239
+ const mock = schmock();
240
+ mock("GET /custom", () => [599, { message: "custom status" }]);
241
+
242
+ const response = await mock.handle("GET", "/custom");
243
+ expect(response.status).toBe(599);
244
+ expect(response.body).toEqual({ message: "custom status" });
245
+ });
246
+
247
+ it("float status like 200.5 is accepted as tuple (number check)", async () => {
248
+ const mock = schmock();
249
+ mock("GET /float", () => [200.5, { ok: true }]);
250
+
251
+ const response = await mock.handle("GET", "/float");
252
+ // isStatusTuple checks typeof === 'number' and 100 <= n <= 599
253
+ // 200.5 passes both checks, so it's treated as a tuple
254
+ expect(response.status).toBe(200.5);
255
+ expect(response.body).toEqual({ ok: true });
256
+ });
257
+
258
+ it("array [201, null, undefined] — tuple with undefined headers defaults to empty", async () => {
259
+ const mock = schmock();
260
+ mock("GET /null-headers", () => [201, null, undefined] as any);
261
+
262
+ const response = await mock.handle("GET", "/null-headers");
263
+ // isStatusTuple: length 3, first element 201 (number, 100-599) -> true
264
+ // destructure: [status=201, body=null, headers=undefined]
265
+ // headers defaults to {} via `headers = {}`
266
+ expect(response.status).toBe(201);
267
+ expect(response.body).toBeUndefined(); // null body becomes undefined
268
+ expect(response.headers).toEqual({});
269
+ });
270
+
271
+ it("non-tuple array [1, 2, 3, 4] with length > 3 treated as body", async () => {
272
+ const mock = schmock();
273
+ mock("GET /long-array", () => [1, 2, 3, 4] as any);
274
+
275
+ const response = await mock.handle("GET", "/long-array");
276
+ // isStatusTuple requires length 2 or 3, so length 4 => not a tuple => body
277
+ expect(response.status).toBe(200);
278
+ expect(response.body).toEqual([1, 2, 3, 4]);
279
+ });
280
+
281
+ it("status 99 (below 100) is NOT treated as a tuple", async () => {
282
+ const mock = schmock();
283
+ mock("GET /below-range", () => [99, { data: true }]);
284
+
285
+ const response = await mock.handle("GET", "/below-range");
286
+ // isStatusTuple requires value[0] >= 100, so 99 fails => treated as body
287
+ expect(response.status).toBe(200);
288
+ expect(response.body).toEqual([99, { data: true }]);
289
+ });
290
+
291
+ it("status 600 (above 599) is NOT treated as a tuple", async () => {
292
+ const mock = schmock();
293
+ mock("GET /above-range", () => [600, { data: true }]);
294
+
295
+ const response = await mock.handle("GET", "/above-range");
296
+ // isStatusTuple requires value[0] <= 599, so 600 fails => treated as body
297
+ expect(response.status).toBe(200);
298
+ expect(response.body).toEqual([600, { data: true }]);
299
+ });
300
+ });
301
+
228
302
  describe("edge cases", () => {
229
303
  it("handles response with circular references gracefully", async () => {
230
304
  const mock = schmock();
@@ -113,4 +113,53 @@ describe("Standalone Server", () => {
113
113
  mock.close();
114
114
  expect(() => mock.close()).not.toThrow();
115
115
  });
116
+
117
+ it("generator that throws synchronously returns 500", async () => {
118
+ mock = schmock();
119
+ mock("GET /boom", () => {
120
+ throw new Error("sync kaboom");
121
+ });
122
+ const info = await mock.listen(0);
123
+
124
+ const res = await fetch(`http://127.0.0.1:${info.port}/boom`);
125
+ expect(res.status).toBe(500);
126
+ const body = await res.json();
127
+ expect(body.error).toBe("sync kaboom");
128
+ });
129
+
130
+ it("generator that returns rejected Promise returns 500", async () => {
131
+ mock = schmock();
132
+ mock("GET /reject", async () => {
133
+ throw new Error("async rejection");
134
+ });
135
+ const info = await mock.listen(0);
136
+
137
+ const res = await fetch(`http://127.0.0.1:${info.port}/reject`);
138
+ expect(res.status).toBe(500);
139
+ const body = await res.json();
140
+ expect(body.error).toBe("async rejection");
141
+ });
142
+
143
+ it("close terminates keep-alive connections immediately", async () => {
144
+ mock = schmock();
145
+ mock("GET /test", { ok: true });
146
+ const info = await mock.listen(0);
147
+
148
+ // Make a keep-alive request to establish a persistent connection
149
+ const res = await fetch(`http://127.0.0.1:${info.port}/test`, {
150
+ headers: { connection: "keep-alive" },
151
+ });
152
+ expect(res.status).toBe(200);
153
+
154
+ // Close the server — should terminate all connections
155
+ mock.close();
156
+
157
+ // Subsequent request should fail immediately (not hang on keep-alive)
158
+ try {
159
+ await fetch(`http://127.0.0.1:${info.port}/test`);
160
+ expect.unreachable("Should have thrown");
161
+ } catch {
162
+ // Expected: connection refused
163
+ }
164
+ });
116
165
  });
@@ -64,41 +64,6 @@ describeFeature(feature, ({ Scenario }) => {
64
64
  });
65
65
  });
66
66
 
67
- Scenario("Multiple async generators in different routes", ({ Given, When, Then, And }) => {
68
- Given("I create a mock with async routes for posts and comments", () => {
69
- mock = schmock();
70
- mock("GET /async-posts", async () => {
71
- await new Promise(resolve => setTimeout(resolve, 5));
72
- return [{ id: 1, title: "First Post" }];
73
- });
74
- mock("GET /async-comments", async () => {
75
- await new Promise(resolve => setTimeout(resolve, 8));
76
- return [{ id: 1, comment: "Great post!" }];
77
- });
78
- });
79
-
80
- When("I make concurrent requests to {string} and {string}", async (_, path1: string, path2: string) => {
81
- const [posts, comments] = await Promise.all([
82
- mock.handle("GET", path1),
83
- mock.handle("GET", path2),
84
- ]);
85
- responses = [posts, comments];
86
- });
87
-
88
- Then("both responses should be returned successfully", () => {
89
- expect(responses[0].status).toBe(200);
90
- expect(responses[1].status).toBe(200);
91
- });
92
-
93
- And("the posts response should contain {string}", (_, text: string) => {
94
- expect(JSON.stringify(responses[0].body)).toContain(text);
95
- });
96
-
97
- And("the comments response should contain {string}", (_, text: string) => {
98
- expect(JSON.stringify(responses[1].body)).toContain(text);
99
- });
100
- });
101
-
102
67
  Scenario("Async plugin processing", ({ Given, When, Then, And }) => {
103
68
  Given("I create a mock with an async processing plugin at {string}", (_, route: string) => {
104
69
  const [method, path] = route.split(" ");
@@ -110,90 +110,6 @@ describeFeature(feature, ({ Scenario }) => {
110
110
  });
111
111
  });
112
112
 
113
- Scenario("Undefined response", ({ Given, When, Then, And }) => {
114
- Given("I create a mock returning undefined at {string}", (_, route: string) => {
115
- const [method, path] = route.split(" ");
116
- mock = schmock();
117
- mock(`${method} ${path}` as Schmock.RouteKey, undefined);
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 empty response", () => {
126
- expect(response.body).toBeUndefined();
127
- });
128
-
129
- And("the status should be {int}", (_, status: number) => {
130
- expect(response.status).toBe(status);
131
- });
132
- });
133
-
134
- Scenario("Empty string response", ({ Given, When, Then, And }) => {
135
- Given("I create a mock returning empty string at {string}", (_, route: string) => {
136
- const [method, path] = route.split(" ");
137
- mock = schmock();
138
- mock(`${method} ${path}` as Schmock.RouteKey, "");
139
- });
140
-
141
- When("I request {string}", async (_, request: string) => {
142
- const [method, path] = request.split(" ");
143
- response = await mock.handle(method as any, path);
144
- });
145
-
146
- Then("I should receive text {string}", (_, expectedText: string) => {
147
- expect(response.body).toBe(expectedText);
148
- });
149
-
150
- And("the content-type should be {string}", (_, contentType: string) => {
151
- expect(response.headers?.["content-type"]).toBe(contentType);
152
- });
153
- });
154
-
155
- Scenario("Number response", ({ Given, When, Then, And }) => {
156
- Given("I create a mock returning number {int} at {string}", (_, num: number, route: string) => {
157
- const [method, path] = route.split(" ");
158
- mock = schmock();
159
- mock(`${method} ${path}` as Schmock.RouteKey, num);
160
- });
161
-
162
- When("I request {string}", async (_, request: string) => {
163
- const [method, path] = request.split(" ");
164
- response = await mock.handle(method as any, path);
165
- });
166
-
167
- Then("I should receive text {string}", (_, expectedText: string) => {
168
- expect(response.body).toBe(expectedText);
169
- });
170
-
171
- And("the content-type should be {string}", (_, contentType: string) => {
172
- expect(response.headers?.["content-type"]).toBe(contentType);
173
- });
174
- });
175
-
176
- Scenario("Boolean response", ({ Given, When, Then, And }) => {
177
- Given("I create a mock returning boolean true at {string}", (_, route: string) => {
178
- const [method, path] = route.split(" ");
179
- mock = schmock();
180
- mock(`${method} ${path}` as Schmock.RouteKey, true);
181
- });
182
-
183
- When("I request {string}", async (_, request: string) => {
184
- const [method, path] = request.split(" ");
185
- response = await mock.handle(method as any, path);
186
- });
187
-
188
- Then("I should receive text {string}", (_, expectedText: string) => {
189
- expect(response.body).toBe(expectedText);
190
- });
191
-
192
- And("the content-type should be {string}", (_, contentType: string) => {
193
- expect(response.headers?.["content-type"]).toBe(contentType);
194
- });
195
- });
196
-
197
113
  Scenario("Function returning string", ({ Given, When, Then }) => {
198
114
  Given("I create a mock with a string generator at {string}", (_, route: string) => {
199
115
  const [method, path] = route.split(" ");