@schmock/core 1.0.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 +62 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +432 -0
- package/dist/errors.d.ts +56 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +92 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/parser.d.ts +19 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +40 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +39 -0
- package/src/builder.d.ts.map +1 -0
- package/src/builder.test.ts +289 -0
- package/src/builder.ts +580 -0
- package/src/debug.test.ts +241 -0
- package/src/delay.test.ts +319 -0
- package/src/errors.d.ts.map +1 -0
- package/src/errors.test.ts +223 -0
- package/src/errors.ts +124 -0
- package/src/factory.test.ts +133 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +80 -0
- package/src/namespace.test.ts +273 -0
- package/src/parser.d.ts.map +1 -0
- package/src/parser.test.ts +131 -0
- package/src/parser.ts +61 -0
- package/src/plugin-system.test.ts +511 -0
- package/src/response-parsing.test.ts +255 -0
- package/src/route-matching.test.ts +351 -0
- package/src/smart-defaults.test.ts +361 -0
- package/src/steps/async-support.steps.ts +427 -0
- package/src/steps/basic-usage.steps.ts +316 -0
- package/src/steps/developer-experience.steps.ts +439 -0
- package/src/steps/error-handling.steps.ts +387 -0
- package/src/steps/fluent-api.steps.ts +252 -0
- package/src/steps/http-methods.steps.ts +397 -0
- package/src/steps/performance-reliability.steps.ts +459 -0
- package/src/steps/plugin-integration.steps.ts +279 -0
- package/src/steps/route-key-format.steps.ts +118 -0
- package/src/steps/state-concurrency.steps.ts +643 -0
- package/src/steps/stateful-workflows.steps.ts +351 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.ts +17 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { schmock } from "./index";
|
|
3
|
+
|
|
4
|
+
describe("smart content-type defaults", () => {
|
|
5
|
+
describe("primitive values", () => {
|
|
6
|
+
it("converts numbers to strings with text/plain", async () => {
|
|
7
|
+
const mock = schmock();
|
|
8
|
+
mock("GET /number", 42);
|
|
9
|
+
|
|
10
|
+
const response = await mock.handle("GET", "/number");
|
|
11
|
+
|
|
12
|
+
expect(response.status).toBe(200);
|
|
13
|
+
expect(response.body).toBe("42");
|
|
14
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("converts booleans to strings with text/plain", async () => {
|
|
18
|
+
const mock = schmock();
|
|
19
|
+
mock("GET /bool", true);
|
|
20
|
+
|
|
21
|
+
const response = await mock.handle("GET", "/bool");
|
|
22
|
+
|
|
23
|
+
expect(response.status).toBe(200);
|
|
24
|
+
expect(response.body).toBe("true");
|
|
25
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("handles strings as text/plain", async () => {
|
|
29
|
+
const mock = schmock();
|
|
30
|
+
mock("GET /string", "Hello World");
|
|
31
|
+
|
|
32
|
+
const response = await mock.handle("GET", "/string");
|
|
33
|
+
|
|
34
|
+
expect(response.status).toBe(200);
|
|
35
|
+
expect(response.body).toBe("Hello World");
|
|
36
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("handles empty string as text/plain", async () => {
|
|
40
|
+
const mock = schmock();
|
|
41
|
+
mock("GET /empty", "");
|
|
42
|
+
|
|
43
|
+
const response = await mock.handle("GET", "/empty");
|
|
44
|
+
|
|
45
|
+
expect(response.status).toBe(200);
|
|
46
|
+
expect(response.body).toBe("");
|
|
47
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("objects and arrays", () => {
|
|
52
|
+
it("handles objects as application/json", async () => {
|
|
53
|
+
const mock = schmock();
|
|
54
|
+
mock("GET /object", { id: 1, name: "John" });
|
|
55
|
+
|
|
56
|
+
const response = await mock.handle("GET", "/object");
|
|
57
|
+
|
|
58
|
+
expect(response.status).toBe(200);
|
|
59
|
+
expect(response.body).toEqual({ id: 1, name: "John" });
|
|
60
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("handles arrays as application/json", async () => {
|
|
64
|
+
const mock = schmock();
|
|
65
|
+
mock("GET /array", ["one", "two", "three"]);
|
|
66
|
+
|
|
67
|
+
const response = await mock.handle("GET", "/array");
|
|
68
|
+
|
|
69
|
+
expect(response.status).toBe(200);
|
|
70
|
+
expect(response.body).toEqual(["one", "two", "three"]);
|
|
71
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("handles empty arrays as application/json", async () => {
|
|
75
|
+
const mock = schmock();
|
|
76
|
+
mock("GET /empty-array", []);
|
|
77
|
+
|
|
78
|
+
const response = await mock.handle("GET", "/empty-array");
|
|
79
|
+
|
|
80
|
+
expect(response.status).toBe(200);
|
|
81
|
+
expect(response.body).toEqual([]);
|
|
82
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("handles empty objects as application/json", async () => {
|
|
86
|
+
const mock = schmock();
|
|
87
|
+
mock("GET /empty-object", {});
|
|
88
|
+
|
|
89
|
+
const response = await mock.handle("GET", "/empty-object");
|
|
90
|
+
|
|
91
|
+
expect(response.status).toBe(200);
|
|
92
|
+
expect(response.body).toEqual({});
|
|
93
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("null and undefined values", () => {
|
|
98
|
+
it("handles null with 204 No Content", async () => {
|
|
99
|
+
const mock = schmock();
|
|
100
|
+
mock("GET /null", null);
|
|
101
|
+
|
|
102
|
+
const response = await mock.handle("GET", "/null");
|
|
103
|
+
|
|
104
|
+
expect(response.status).toBe(204);
|
|
105
|
+
expect(response.body).toBeUndefined();
|
|
106
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("handles undefined with 204 No Content", async () => {
|
|
110
|
+
const mock = schmock();
|
|
111
|
+
mock("GET /undefined", undefined);
|
|
112
|
+
|
|
113
|
+
const response = await mock.handle("GET", "/undefined");
|
|
114
|
+
|
|
115
|
+
expect(response.status).toBe(204);
|
|
116
|
+
expect(response.body).toBeUndefined();
|
|
117
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("Buffer responses", () => {
|
|
122
|
+
it("handles Buffers as application/octet-stream", async () => {
|
|
123
|
+
const mock = schmock();
|
|
124
|
+
const buffer = Buffer.from("binary data");
|
|
125
|
+
mock("GET /binary", buffer);
|
|
126
|
+
|
|
127
|
+
const response = await mock.handle("GET", "/binary");
|
|
128
|
+
|
|
129
|
+
expect(response.status).toBe(200);
|
|
130
|
+
expect(response.body).toBe(buffer);
|
|
131
|
+
expect(response.headers["content-type"]).toBe("application/octet-stream");
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("function generators", () => {
|
|
136
|
+
it("function returning string gets application/json by default", async () => {
|
|
137
|
+
const mock = schmock();
|
|
138
|
+
mock("GET /func-string", () => "Hello");
|
|
139
|
+
|
|
140
|
+
const response = await mock.handle("GET", "/func-string");
|
|
141
|
+
|
|
142
|
+
expect(response.status).toBe(200);
|
|
143
|
+
expect(response.body).toBe("Hello");
|
|
144
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("function returning number gets application/json by default", async () => {
|
|
148
|
+
const mock = schmock();
|
|
149
|
+
mock("GET /func-number", () => 42);
|
|
150
|
+
|
|
151
|
+
const response = await mock.handle("GET", "/func-number");
|
|
152
|
+
|
|
153
|
+
expect(response.status).toBe(200);
|
|
154
|
+
expect(response.body).toBe(42);
|
|
155
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("function returning object gets application/json by default", async () => {
|
|
159
|
+
const mock = schmock();
|
|
160
|
+
mock("GET /func-object", () => ({ id: 1 }));
|
|
161
|
+
|
|
162
|
+
const response = await mock.handle("GET", "/func-object");
|
|
163
|
+
|
|
164
|
+
expect(response.status).toBe(200);
|
|
165
|
+
expect(response.body).toEqual({ id: 1 });
|
|
166
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("function returning null gets 204 status", async () => {
|
|
170
|
+
const mock = schmock();
|
|
171
|
+
mock("GET /func-null", () => null);
|
|
172
|
+
|
|
173
|
+
const response = await mock.handle("GET", "/func-null");
|
|
174
|
+
|
|
175
|
+
expect(response.status).toBe(204);
|
|
176
|
+
expect(response.body).toBeUndefined();
|
|
177
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("explicit contentType overrides", () => {
|
|
182
|
+
it("overrides object to text/plain and stringifies", async () => {
|
|
183
|
+
const mock = schmock();
|
|
184
|
+
mock("GET /override", { foo: "bar" }, { contentType: "text/plain" });
|
|
185
|
+
|
|
186
|
+
const response = await mock.handle("GET", "/override");
|
|
187
|
+
|
|
188
|
+
expect(response.status).toBe(200);
|
|
189
|
+
expect(response.body).toBe('{"foo":"bar"}');
|
|
190
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("overrides number to text/plain and converts to string", async () => {
|
|
194
|
+
const mock = schmock();
|
|
195
|
+
mock("GET /override-number", 123, { contentType: "text/plain" });
|
|
196
|
+
|
|
197
|
+
const response = await mock.handle("GET", "/override-number");
|
|
198
|
+
|
|
199
|
+
expect(response.status).toBe(200);
|
|
200
|
+
expect(response.body).toBe("123");
|
|
201
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("overrides string to application/json", async () => {
|
|
205
|
+
const mock = schmock();
|
|
206
|
+
mock("GET /override-string", "hello", {
|
|
207
|
+
contentType: "application/json",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const response = await mock.handle("GET", "/override-string");
|
|
211
|
+
|
|
212
|
+
expect(response.status).toBe(200);
|
|
213
|
+
expect(response.body).toBe("hello");
|
|
214
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("sets custom content-type", async () => {
|
|
218
|
+
const mock = schmock();
|
|
219
|
+
mock("GET /custom", "<h1>Hello</h1>", { contentType: "text/html" });
|
|
220
|
+
|
|
221
|
+
const response = await mock.handle("GET", "/custom");
|
|
222
|
+
|
|
223
|
+
expect(response.status).toBe(200);
|
|
224
|
+
expect(response.body).toBe("<h1>Hello</h1>");
|
|
225
|
+
expect(response.headers["content-type"]).toBe("text/html");
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("tuple responses preserve explicit control", () => {
|
|
230
|
+
it("tuple responses don't get automatic content-type", async () => {
|
|
231
|
+
const mock = schmock();
|
|
232
|
+
mock("GET /tuple", () => [200, { id: 1 }]);
|
|
233
|
+
|
|
234
|
+
const response = await mock.handle("GET", "/tuple");
|
|
235
|
+
|
|
236
|
+
expect(response.status).toBe(200);
|
|
237
|
+
expect(response.body).toEqual({ id: 1 });
|
|
238
|
+
expect(response.headers).toEqual({});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("tuple with explicit headers keeps them", async () => {
|
|
242
|
+
const mock = schmock();
|
|
243
|
+
mock("GET /tuple-headers", () => [
|
|
244
|
+
201,
|
|
245
|
+
{ created: true },
|
|
246
|
+
{ "X-Custom": "value" },
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
const response = await mock.handle("GET", "/tuple-headers");
|
|
250
|
+
|
|
251
|
+
expect(response.status).toBe(201);
|
|
252
|
+
expect(response.body).toEqual({ created: true });
|
|
253
|
+
expect(response.headers).toEqual({ "X-Custom": "value" });
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("tuple with null body preserves explicit status", async () => {
|
|
257
|
+
const mock = schmock();
|
|
258
|
+
mock("GET /tuple-null", () => [200, null]);
|
|
259
|
+
|
|
260
|
+
const response = await mock.handle("GET", "/tuple-null");
|
|
261
|
+
|
|
262
|
+
expect(response.status).toBe(200); // Tuple format preserves explicit status
|
|
263
|
+
expect(response.body).toBeUndefined();
|
|
264
|
+
expect(response.headers).toEqual({});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("tuple with 204 status and null body", async () => {
|
|
268
|
+
const mock = schmock();
|
|
269
|
+
mock("GET /tuple-204", () => [204, null]);
|
|
270
|
+
|
|
271
|
+
const response = await mock.handle("GET", "/tuple-204");
|
|
272
|
+
|
|
273
|
+
expect(response.status).toBe(204);
|
|
274
|
+
expect(response.body).toBeUndefined();
|
|
275
|
+
expect(response.headers).toEqual({});
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe("edge cases", () => {
|
|
280
|
+
it("handles zero as text/plain", async () => {
|
|
281
|
+
const mock = schmock();
|
|
282
|
+
mock("GET /zero", 0);
|
|
283
|
+
|
|
284
|
+
const response = await mock.handle("GET", "/zero");
|
|
285
|
+
|
|
286
|
+
expect(response.status).toBe(200);
|
|
287
|
+
expect(response.body).toBe("0");
|
|
288
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("handles false as text/plain", async () => {
|
|
292
|
+
const mock = schmock();
|
|
293
|
+
mock("GET /false", false);
|
|
294
|
+
|
|
295
|
+
const response = await mock.handle("GET", "/false");
|
|
296
|
+
|
|
297
|
+
expect(response.status).toBe(200);
|
|
298
|
+
expect(response.body).toBe("false");
|
|
299
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("handles nested objects as application/json", async () => {
|
|
303
|
+
const mock = schmock();
|
|
304
|
+
const nested = {
|
|
305
|
+
user: { id: 1, profile: { name: "John", settings: { theme: "dark" } } },
|
|
306
|
+
};
|
|
307
|
+
mock("GET /nested", nested);
|
|
308
|
+
|
|
309
|
+
const response = await mock.handle("GET", "/nested");
|
|
310
|
+
|
|
311
|
+
expect(response.status).toBe(200);
|
|
312
|
+
expect(response.body).toEqual(nested);
|
|
313
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("handles arrays with mixed types as application/json", async () => {
|
|
317
|
+
const mock = schmock();
|
|
318
|
+
mock("GET /mixed", ["first", 2, { id: 1 }, ["nested", "array"]]);
|
|
319
|
+
|
|
320
|
+
const response = await mock.handle("GET", "/mixed");
|
|
321
|
+
|
|
322
|
+
expect(response.status).toBe(200);
|
|
323
|
+
expect(response.body).toEqual([
|
|
324
|
+
"first",
|
|
325
|
+
2,
|
|
326
|
+
{ id: 1 },
|
|
327
|
+
["nested", "array"],
|
|
328
|
+
]);
|
|
329
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe("consistency with existing auto-detection", () => {
|
|
334
|
+
it("static string gets text/plain", async () => {
|
|
335
|
+
const mock = schmock();
|
|
336
|
+
mock("GET /static-string", "Hello World");
|
|
337
|
+
|
|
338
|
+
const response = await mock.handle("GET", "/static-string");
|
|
339
|
+
|
|
340
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("static object gets application/json", async () => {
|
|
344
|
+
const mock = schmock();
|
|
345
|
+
mock("GET /static-object", { id: 1 });
|
|
346
|
+
|
|
347
|
+
const response = await mock.handle("GET", "/static-object");
|
|
348
|
+
|
|
349
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("function generator gets application/json by default", async () => {
|
|
353
|
+
const mock = schmock();
|
|
354
|
+
mock("GET /func", () => "anything");
|
|
355
|
+
|
|
356
|
+
const response = await mock.handle("GET", "/func");
|
|
357
|
+
|
|
358
|
+
expect(response.headers["content-type"]).toBe("application/json");
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|