@schmock/core 2.3.0 → 2.4.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/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/builder.d.ts +31 -10
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +760 -190
- 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 +39 -4
- package/dist/http-helpers.d.ts.map +1 -1
- package/dist/http-helpers.js +147 -49
- package/dist/index.d.ts +292 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/interceptor.d.ts +11 -1
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +316 -169
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +12 -3
- package/dist/plugin-pipeline.d.ts +3 -3
- package/dist/plugin-pipeline.d.ts.map +1 -1
- package/dist/plugin-pipeline.js +29 -14
- 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 +39 -2
- 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 -6
- package/src/audit-core-builder.test.ts +0 -218
- package/src/audit-response-guard.test.ts +0 -38
- package/src/binary.test.ts +0 -126
- package/src/binary.ts +0 -11
- package/src/builder.test.ts +0 -289
- package/src/builder.ts +0 -748
- 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.test.ts +0 -39
- package/src/http-helpers.ts +0 -149
- package/src/index.ts +0 -152
- package/src/interceptor.test.ts +0 -488
- package/src/interceptor.ts +0 -407
- package/src/namespace.test.ts +0 -274
- package/src/parser.property.test.ts +0 -630
- package/src/parser.test.ts +0 -148
- package/src/parser.ts +0 -64
- package/src/plugin-pipeline.ts +0 -208
- package/src/plugin-system.test.ts +0 -697
- package/src/response-parser.ts +0 -114
- 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 -234
- package/src/smart-defaults.test.ts +0 -361
- package/src/steps/async-support.steps.ts +0 -421
- package/src/steps/audit-core-builder.steps.ts +0 -188
- package/src/steps/audit-onerror-tuple.steps.ts +0 -83
- 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 -555
- package/src/steps/fetch-interceptor.steps.ts +0 -457
- package/src/steps/fluent-api.steps.ts +0 -255
- package/src/steps/http-methods.steps.ts +0 -342
- package/src/steps/lifecycle-events.steps.ts +0 -149
- package/src/steps/performance-reliability.steps.ts +0 -90
- package/src/steps/plugin-integration.steps.ts +0 -334
- package/src/steps/request-history.steps.ts +0 -458
- 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 -227
- package/src/steps/state-concurrency.steps.ts +0 -167
- package/src/types.ts +0 -36
package/src/delay.test.ts
DELETED
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { schmock } from "./index";
|
|
3
|
-
|
|
4
|
-
describe("response delay functionality", () => {
|
|
5
|
-
describe("fixed delay", () => {
|
|
6
|
-
it("applies fixed delay to responses", async () => {
|
|
7
|
-
const mock = schmock({ delay: 100 });
|
|
8
|
-
mock("GET /test", "response");
|
|
9
|
-
|
|
10
|
-
const start = Date.now();
|
|
11
|
-
const response = await mock.handle("GET", "/test");
|
|
12
|
-
const duration = Date.now() - start;
|
|
13
|
-
|
|
14
|
-
expect(response.body).toBe("response");
|
|
15
|
-
expect(duration).toBeGreaterThanOrEqual(95); // Allow some tolerance
|
|
16
|
-
expect(duration).toBeLessThan(150);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("applies delay to all routes", async () => {
|
|
20
|
-
const mock = schmock({ delay: 50 });
|
|
21
|
-
mock("GET /route1", "response1");
|
|
22
|
-
mock("POST /route2", "response2");
|
|
23
|
-
|
|
24
|
-
const start1 = Date.now();
|
|
25
|
-
const response1 = await mock.handle("GET", "/route1");
|
|
26
|
-
const duration1 = Date.now() - start1;
|
|
27
|
-
|
|
28
|
-
const start2 = Date.now();
|
|
29
|
-
const response2 = await mock.handle("POST", "/route2");
|
|
30
|
-
const duration2 = Date.now() - start2;
|
|
31
|
-
|
|
32
|
-
expect(response1.body).toBe("response1");
|
|
33
|
-
expect(response2.body).toBe("response2");
|
|
34
|
-
expect(duration1).toBeGreaterThanOrEqual(45);
|
|
35
|
-
expect(duration2).toBeGreaterThanOrEqual(45);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("works with zero delay", async () => {
|
|
39
|
-
const mock = schmock({ delay: 0 });
|
|
40
|
-
mock("GET /test", "response");
|
|
41
|
-
|
|
42
|
-
const start = Date.now();
|
|
43
|
-
const response = await mock.handle("GET", "/test");
|
|
44
|
-
const duration = Date.now() - start;
|
|
45
|
-
|
|
46
|
-
expect(response.body).toBe("response");
|
|
47
|
-
expect(duration).toBeLessThan(50); // Should be very fast
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe("random delay range", () => {
|
|
52
|
-
it("applies random delay within specified range", async () => {
|
|
53
|
-
const mock = schmock({ delay: [100, 200] });
|
|
54
|
-
mock("GET /test", "response");
|
|
55
|
-
|
|
56
|
-
const start = Date.now();
|
|
57
|
-
const response = await mock.handle("GET", "/test");
|
|
58
|
-
const duration = Date.now() - start;
|
|
59
|
-
|
|
60
|
-
expect(response.body).toBe("response");
|
|
61
|
-
expect(duration).toBeGreaterThanOrEqual(95); // Allow some tolerance for timer precision
|
|
62
|
-
expect(duration).toBeLessThan(250);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("generates different delays for multiple requests", async () => {
|
|
66
|
-
const mock = schmock({ delay: [50, 150] });
|
|
67
|
-
mock("GET /test", "response");
|
|
68
|
-
|
|
69
|
-
const durations: number[] = [];
|
|
70
|
-
|
|
71
|
-
for (let i = 0; i < 5; i++) {
|
|
72
|
-
const start = Date.now();
|
|
73
|
-
await mock.handle("GET", "/test");
|
|
74
|
-
durations.push(Date.now() - start);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Check that we got some variation in delays
|
|
78
|
-
const minDuration = Math.min(...durations);
|
|
79
|
-
const maxDuration = Math.max(...durations);
|
|
80
|
-
|
|
81
|
-
expect(minDuration).toBeGreaterThanOrEqual(45);
|
|
82
|
-
expect(maxDuration).toBeLessThan(200);
|
|
83
|
-
// There should be some variation (not all identical)
|
|
84
|
-
expect(maxDuration - minDuration).toBeGreaterThan(10);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("handles reversed range [max, min]", async () => {
|
|
88
|
-
const mock = schmock({ delay: [200, 100] });
|
|
89
|
-
mock("GET /test", "response");
|
|
90
|
-
|
|
91
|
-
const start = Date.now();
|
|
92
|
-
const response = await mock.handle("GET", "/test");
|
|
93
|
-
const duration = Date.now() - start;
|
|
94
|
-
|
|
95
|
-
expect(response.body).toBe("response");
|
|
96
|
-
// Should still work, treating it as [100, 200]
|
|
97
|
-
expect(duration).toBeGreaterThanOrEqual(0);
|
|
98
|
-
expect(duration).toBeLessThan(300);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("handles equal min and max values", async () => {
|
|
102
|
-
const mock = schmock({ delay: [100, 100] });
|
|
103
|
-
mock("GET /test", "response");
|
|
104
|
-
|
|
105
|
-
const start = Date.now();
|
|
106
|
-
const response = await mock.handle("GET", "/test");
|
|
107
|
-
const duration = Date.now() - start;
|
|
108
|
-
|
|
109
|
-
expect(response.body).toBe("response");
|
|
110
|
-
expect(duration).toBeGreaterThanOrEqual(95);
|
|
111
|
-
expect(duration).toBeLessThan(150);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe("delay with different response types", () => {
|
|
116
|
-
it("applies delay to function generators", async () => {
|
|
117
|
-
const mock = schmock({ delay: 50 });
|
|
118
|
-
mock("GET /dynamic", () => ({ timestamp: Date.now() }));
|
|
119
|
-
|
|
120
|
-
const start = Date.now();
|
|
121
|
-
const response = await mock.handle("GET", "/dynamic");
|
|
122
|
-
const duration = Date.now() - start;
|
|
123
|
-
|
|
124
|
-
expect(response.body).toHaveProperty("timestamp");
|
|
125
|
-
expect(duration).toBeGreaterThanOrEqual(45);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("applies delay to static responses", async () => {
|
|
129
|
-
const mock = schmock({ delay: 50 });
|
|
130
|
-
mock("GET /static", { data: "static" });
|
|
131
|
-
|
|
132
|
-
const start = Date.now();
|
|
133
|
-
const response = await mock.handle("GET", "/static");
|
|
134
|
-
const duration = Date.now() - start;
|
|
135
|
-
|
|
136
|
-
expect(response.body).toEqual({ data: "static" });
|
|
137
|
-
expect(duration).toBeGreaterThanOrEqual(45);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("applies delay to tuple responses", async () => {
|
|
141
|
-
const mock = schmock({ delay: 50 });
|
|
142
|
-
mock("GET /tuple", () => [201, { created: true }]);
|
|
143
|
-
|
|
144
|
-
const start = Date.now();
|
|
145
|
-
const response = await mock.handle("GET", "/tuple");
|
|
146
|
-
const duration = Date.now() - start;
|
|
147
|
-
|
|
148
|
-
expect(response.status).toBe(201);
|
|
149
|
-
expect(response.body).toEqual({ created: true });
|
|
150
|
-
expect(duration).toBeGreaterThanOrEqual(45);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("applies delay to error responses", async () => {
|
|
154
|
-
const mock = schmock({ delay: 50 });
|
|
155
|
-
mock("GET /error", () => {
|
|
156
|
-
throw new Error("Test error");
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const start = Date.now();
|
|
160
|
-
const response = await mock.handle("GET", "/error");
|
|
161
|
-
const duration = Date.now() - start;
|
|
162
|
-
|
|
163
|
-
expect(response.status).toBe(500);
|
|
164
|
-
expect(duration).toBeGreaterThanOrEqual(45);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe("delay with plugins", () => {
|
|
169
|
-
it("applies delay after plugin processing", async () => {
|
|
170
|
-
const mock = schmock({ delay: 50 });
|
|
171
|
-
|
|
172
|
-
const plugin = {
|
|
173
|
-
name: "slow-plugin",
|
|
174
|
-
process: async (ctx: any, res: any) => {
|
|
175
|
-
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
176
|
-
return { context: ctx, response: `processed-${res}` };
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
mock("GET /test", "original").pipe(plugin);
|
|
181
|
-
|
|
182
|
-
const start = Date.now();
|
|
183
|
-
const response = await mock.handle("GET", "/test");
|
|
184
|
-
const duration = Date.now() - start;
|
|
185
|
-
|
|
186
|
-
expect(response.body).toBe("processed-original");
|
|
187
|
-
expect(duration).toBeGreaterThanOrEqual(75); // Plugin delay + response delay
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it("applies delay even when plugin generates response", async () => {
|
|
191
|
-
const mock = schmock({ delay: 50 });
|
|
192
|
-
|
|
193
|
-
const plugin = {
|
|
194
|
-
name: "generator-plugin",
|
|
195
|
-
process: (ctx: any, _res: any) => {
|
|
196
|
-
return { context: ctx, response: "plugin-generated" };
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
mock("GET /test", null).pipe(plugin);
|
|
201
|
-
|
|
202
|
-
const start = Date.now();
|
|
203
|
-
const response = await mock.handle("GET", "/test");
|
|
204
|
-
const duration = Date.now() - start;
|
|
205
|
-
|
|
206
|
-
expect(response.body).toBe("plugin-generated");
|
|
207
|
-
expect(duration).toBeGreaterThanOrEqual(45);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
describe("no delay configuration", () => {
|
|
212
|
-
it("doesn't apply delay when not configured", async () => {
|
|
213
|
-
const mock = schmock();
|
|
214
|
-
mock("GET /test", "response");
|
|
215
|
-
|
|
216
|
-
const start = Date.now();
|
|
217
|
-
const response = await mock.handle("GET", "/test");
|
|
218
|
-
const duration = Date.now() - start;
|
|
219
|
-
|
|
220
|
-
expect(response.body).toBe("response");
|
|
221
|
-
expect(duration).toBeLessThan(30); // Should be very fast without delay
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it("doesn't apply delay when delay is undefined", async () => {
|
|
225
|
-
const mock = schmock({ delay: undefined });
|
|
226
|
-
mock("GET /test", "response");
|
|
227
|
-
|
|
228
|
-
const start = Date.now();
|
|
229
|
-
const response = await mock.handle("GET", "/test");
|
|
230
|
-
const duration = Date.now() - start;
|
|
231
|
-
|
|
232
|
-
expect(response.body).toBe("response");
|
|
233
|
-
expect(duration).toBeLessThan(30);
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
describe("delay edge cases", () => {
|
|
238
|
-
it("handles very small delays", async () => {
|
|
239
|
-
const mock = schmock({ delay: 1 });
|
|
240
|
-
mock("GET /test", "response");
|
|
241
|
-
|
|
242
|
-
const start = Date.now();
|
|
243
|
-
const response = await mock.handle("GET", "/test");
|
|
244
|
-
const duration = Date.now() - start;
|
|
245
|
-
|
|
246
|
-
expect(response.body).toBe("response");
|
|
247
|
-
expect(duration).toBeGreaterThanOrEqual(0);
|
|
248
|
-
expect(duration).toBeLessThan(50);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it("handles large delays", async () => {
|
|
252
|
-
const mock = schmock({ delay: 500 });
|
|
253
|
-
mock("GET /test", "response");
|
|
254
|
-
|
|
255
|
-
const start = Date.now();
|
|
256
|
-
const response = await mock.handle("GET", "/test");
|
|
257
|
-
const duration = Date.now() - start;
|
|
258
|
-
|
|
259
|
-
expect(response.body).toBe("response");
|
|
260
|
-
expect(duration).toBeGreaterThanOrEqual(490);
|
|
261
|
-
expect(duration).toBeLessThan(600);
|
|
262
|
-
}, 1000); // Increase test timeout
|
|
263
|
-
|
|
264
|
-
it("handles negative delays gracefully", async () => {
|
|
265
|
-
const mock = schmock({ delay: -50 });
|
|
266
|
-
mock("GET /test", "response");
|
|
267
|
-
|
|
268
|
-
const start = Date.now();
|
|
269
|
-
const response = await mock.handle("GET", "/test");
|
|
270
|
-
const duration = Date.now() - start;
|
|
271
|
-
|
|
272
|
-
expect(response.body).toBe("response");
|
|
273
|
-
// Should not crash, and should be fast
|
|
274
|
-
expect(duration).toBeLessThan(50);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it("handles delay range with negative values", async () => {
|
|
278
|
-
const mock = schmock({ delay: [-10, 50] });
|
|
279
|
-
mock("GET /test", "response");
|
|
280
|
-
|
|
281
|
-
const start = Date.now();
|
|
282
|
-
const response = await mock.handle("GET", "/test");
|
|
283
|
-
const duration = Date.now() - start;
|
|
284
|
-
|
|
285
|
-
expect(response.body).toBe("response");
|
|
286
|
-
// Should handle gracefully
|
|
287
|
-
expect(duration).toBeGreaterThanOrEqual(0);
|
|
288
|
-
expect(duration).toBeLessThan(100);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
describe("concurrent requests with delay", () => {
|
|
293
|
-
it("applies delay to concurrent requests independently", async () => {
|
|
294
|
-
const mock = schmock({ delay: 100 });
|
|
295
|
-
mock("GET /test", "response");
|
|
296
|
-
|
|
297
|
-
const start = Date.now();
|
|
298
|
-
|
|
299
|
-
// Start multiple requests concurrently
|
|
300
|
-
const promises = [
|
|
301
|
-
mock.handle("GET", "/test"),
|
|
302
|
-
mock.handle("GET", "/test"),
|
|
303
|
-
mock.handle("GET", "/test"),
|
|
304
|
-
];
|
|
305
|
-
|
|
306
|
-
const responses = await Promise.all(promises);
|
|
307
|
-
const duration = Date.now() - start;
|
|
308
|
-
|
|
309
|
-
// All should succeed
|
|
310
|
-
responses.forEach((response) => {
|
|
311
|
-
expect(response.body).toBe("response");
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
// Should take about 100ms, not 300ms (concurrent, not sequential)
|
|
315
|
-
expect(duration).toBeGreaterThanOrEqual(95);
|
|
316
|
-
expect(duration).toBeLessThan(200);
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
});
|
package/src/dist-shape.test.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Regression coverage for issue #395 — a top-level `import { createServer }
|
|
7
|
-
* from "node:http"` in `dist/builder.js` was preserved by browser bundlers
|
|
8
|
-
* (esbuild / Angular / Vite) even when `.listen()` was never called, causing
|
|
9
|
-
* `node:http` resolution failures in browsers.
|
|
10
|
-
*
|
|
11
|
-
* Inspect published artifacts: top-level `node:http` imports must not exist.
|
|
12
|
-
* `node:http` may only appear inside dynamic imports (`await import(...)`).
|
|
13
|
-
*/
|
|
14
|
-
const distRoot = resolve(__dirname, "..", "dist");
|
|
15
|
-
|
|
16
|
-
// Static ESM imports from node: modules — `from "node:..."`. Dynamic imports
|
|
17
|
-
// (`await import("node:...")`) have no `from` clause and are not matched.
|
|
18
|
-
function staticNodeImports(src: string): string[] {
|
|
19
|
-
return src.match(/from\s*["']node:[^"']+["']/g) ?? [];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe.skipIf(!existsSync(distRoot))(
|
|
23
|
-
"dist shape — browser compatibility (#395)",
|
|
24
|
-
() => {
|
|
25
|
-
it("dist/builder.js has no static node:* imports", () => {
|
|
26
|
-
const src = readFileSync(resolve(distRoot, "builder.js"), "utf8");
|
|
27
|
-
expect(staticNodeImports(src)).toEqual([]);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("dist/index.js has no static node:* imports", () => {
|
|
31
|
-
const src = readFileSync(resolve(distRoot, "index.js"), "utf8");
|
|
32
|
-
expect(staticNodeImports(src)).toEqual([]);
|
|
33
|
-
});
|
|
34
|
-
},
|
|
35
|
-
);
|
package/src/errors.test.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
PluginError,
|
|
4
|
-
ResourceLimitError,
|
|
5
|
-
ResponseGenerationError,
|
|
6
|
-
RouteDefinitionError,
|
|
7
|
-
RouteNotFoundError,
|
|
8
|
-
RouteParseError,
|
|
9
|
-
SchemaGenerationError,
|
|
10
|
-
SchemaValidationError,
|
|
11
|
-
SchmockError,
|
|
12
|
-
} from "./errors";
|
|
13
|
-
|
|
14
|
-
describe("error classes", () => {
|
|
15
|
-
describe("SchmockError", () => {
|
|
16
|
-
it("creates base error with code and context", () => {
|
|
17
|
-
const error = new SchmockError("test message", "TEST_CODE", {
|
|
18
|
-
data: "test",
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
expect(error.message).toBe("test message");
|
|
22
|
-
expect(error.code).toBe("TEST_CODE");
|
|
23
|
-
expect(error.context).toEqual({ data: "test" });
|
|
24
|
-
expect(error.name).toBe("SchmockError");
|
|
25
|
-
expect(error).toBeInstanceOf(Error);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("creates error without context", () => {
|
|
29
|
-
const error = new SchmockError("test message", "TEST_CODE");
|
|
30
|
-
|
|
31
|
-
expect(error.context).toBeUndefined();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("captures stack trace", () => {
|
|
35
|
-
const error = new SchmockError("test", "TEST");
|
|
36
|
-
|
|
37
|
-
expect(error.stack).toBeDefined();
|
|
38
|
-
expect(error.stack).toContain("SchmockError");
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe("RouteNotFoundError", () => {
|
|
43
|
-
it("formats message with method and path", () => {
|
|
44
|
-
const error = new RouteNotFoundError("GET", "/users/123");
|
|
45
|
-
|
|
46
|
-
expect(error.message).toBe("Route not found: GET /users/123");
|
|
47
|
-
expect(error.code).toBe("ROUTE_NOT_FOUND");
|
|
48
|
-
expect(error.context).toEqual({ method: "GET", path: "/users/123" });
|
|
49
|
-
expect(error.name).toBe("RouteNotFoundError");
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe("RouteParseError", () => {
|
|
54
|
-
it("includes route key and reason in message", () => {
|
|
55
|
-
const error = new RouteParseError("INVALID /test", "Missing method");
|
|
56
|
-
|
|
57
|
-
expect(error.message).toBe(
|
|
58
|
-
'Invalid route key format: "INVALID /test". Missing method',
|
|
59
|
-
);
|
|
60
|
-
expect(error.code).toBe("ROUTE_PARSE_ERROR");
|
|
61
|
-
expect(error.context).toEqual({
|
|
62
|
-
routeKey: "INVALID /test",
|
|
63
|
-
reason: "Missing method",
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe("ResponseGenerationError", () => {
|
|
69
|
-
it("wraps original error", () => {
|
|
70
|
-
const originalError = new Error("Original failure");
|
|
71
|
-
const error = new ResponseGenerationError("GET /users", originalError);
|
|
72
|
-
|
|
73
|
-
expect(error.message).toBe(
|
|
74
|
-
"Failed to generate response for route GET /users: Original failure",
|
|
75
|
-
);
|
|
76
|
-
expect(error.code).toBe("RESPONSE_GENERATION_ERROR");
|
|
77
|
-
expect(error.context).toEqual({
|
|
78
|
-
route: "GET /users",
|
|
79
|
-
originalError,
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe("PluginError", () => {
|
|
85
|
-
it("includes plugin name and wraps error", () => {
|
|
86
|
-
const originalError = new Error("Plugin failed");
|
|
87
|
-
const error = new PluginError("test-plugin", originalError);
|
|
88
|
-
|
|
89
|
-
expect(error.message).toBe('Plugin "test-plugin" failed: Plugin failed');
|
|
90
|
-
expect(error.code).toBe("PLUGIN_ERROR");
|
|
91
|
-
expect(error.context).toEqual({
|
|
92
|
-
pluginName: "test-plugin",
|
|
93
|
-
originalError,
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe("RouteDefinitionError", () => {
|
|
99
|
-
it("includes route key and reason", () => {
|
|
100
|
-
const error = new RouteDefinitionError(
|
|
101
|
-
"GET /test",
|
|
102
|
-
"Missing response function",
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
expect(error.message).toBe(
|
|
106
|
-
'Invalid route definition for "GET /test": Missing response function',
|
|
107
|
-
);
|
|
108
|
-
expect(error.code).toBe("ROUTE_DEFINITION_ERROR");
|
|
109
|
-
expect(error.context).toEqual({
|
|
110
|
-
routeKey: "GET /test",
|
|
111
|
-
reason: "Missing response function",
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe("SchemaValidationError", () => {
|
|
117
|
-
it("includes path and issue", () => {
|
|
118
|
-
const error = new SchemaValidationError(
|
|
119
|
-
"users.name",
|
|
120
|
-
"Required field missing",
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
expect(error.message).toBe(
|
|
124
|
-
"Schema validation failed at users.name: Required field missing",
|
|
125
|
-
);
|
|
126
|
-
expect(error.code).toBe("SCHEMA_VALIDATION_ERROR");
|
|
127
|
-
expect(error.context).toEqual({
|
|
128
|
-
schemaPath: "users.name",
|
|
129
|
-
issue: "Required field missing",
|
|
130
|
-
suggestion: undefined,
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("includes suggestion when provided", () => {
|
|
135
|
-
const error = new SchemaValidationError(
|
|
136
|
-
"users.age",
|
|
137
|
-
"Invalid type",
|
|
138
|
-
"Use number instead of string",
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
expect(error.message).toBe(
|
|
142
|
-
"Schema validation failed at users.age: Invalid type. Use number instead of string",
|
|
143
|
-
);
|
|
144
|
-
expect(error.context?.suggestion).toBe("Use number instead of string");
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe("SchemaGenerationError", () => {
|
|
149
|
-
it("wraps schema generation failure", () => {
|
|
150
|
-
const originalError = new Error("Invalid schema");
|
|
151
|
-
const schema = { type: "object" };
|
|
152
|
-
const error = new SchemaGenerationError(
|
|
153
|
-
"GET /users",
|
|
154
|
-
originalError,
|
|
155
|
-
schema,
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
expect(error.message).toBe(
|
|
159
|
-
"Schema generation failed for route GET /users: Invalid schema",
|
|
160
|
-
);
|
|
161
|
-
expect(error.code).toBe("SCHEMA_GENERATION_ERROR");
|
|
162
|
-
expect(error.context).toEqual({
|
|
163
|
-
route: "GET /users",
|
|
164
|
-
originalError,
|
|
165
|
-
schema,
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("works without schema context", () => {
|
|
170
|
-
const originalError = new Error("Invalid schema");
|
|
171
|
-
const error = new SchemaGenerationError("GET /users", originalError);
|
|
172
|
-
|
|
173
|
-
expect(error.context?.schema).toBeUndefined();
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe("ResourceLimitError", () => {
|
|
178
|
-
it("includes resource and limit", () => {
|
|
179
|
-
const error = new ResourceLimitError("memory", 1024);
|
|
180
|
-
|
|
181
|
-
expect(error.message).toBe(
|
|
182
|
-
"Resource limit exceeded for memory: limit=1024",
|
|
183
|
-
);
|
|
184
|
-
expect(error.code).toBe("RESOURCE_LIMIT_ERROR");
|
|
185
|
-
expect(error.context).toEqual({
|
|
186
|
-
resource: "memory",
|
|
187
|
-
limit: 1024,
|
|
188
|
-
actual: undefined,
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("includes actual value when provided", () => {
|
|
193
|
-
const error = new ResourceLimitError("connections", 100, 150);
|
|
194
|
-
|
|
195
|
-
expect(error.message).toBe(
|
|
196
|
-
"Resource limit exceeded for connections: limit=100, actual=150",
|
|
197
|
-
);
|
|
198
|
-
expect(error.context?.actual).toBe(150);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe("error inheritance", () => {
|
|
203
|
-
it("all custom errors inherit from SchmockError", () => {
|
|
204
|
-
const errors = [
|
|
205
|
-
new RouteNotFoundError("GET", "/test"),
|
|
206
|
-
new RouteParseError("invalid", "reason"),
|
|
207
|
-
new ResponseGenerationError("route", new Error("test")),
|
|
208
|
-
new PluginError("plugin", new Error("test")),
|
|
209
|
-
new RouteDefinitionError("route", "reason"),
|
|
210
|
-
new SchemaValidationError("path", "issue"),
|
|
211
|
-
new SchemaGenerationError("route", new Error("test")),
|
|
212
|
-
new ResourceLimitError("resource", 100),
|
|
213
|
-
];
|
|
214
|
-
|
|
215
|
-
for (const error of errors) {
|
|
216
|
-
expect(error).toBeInstanceOf(SchmockError);
|
|
217
|
-
expect(error).toBeInstanceOf(Error);
|
|
218
|
-
expect(error.code).toBeDefined();
|
|
219
|
-
expect(error.name).toBeDefined();
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
});
|
package/src/errors.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
export function errorMessage(error: unknown): string {
|
|
2
|
-
return error instanceof Error ? error.message : "Unknown error";
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Base error class for all Schmock errors
|
|
7
|
-
*/
|
|
8
|
-
export class SchmockError extends Error {
|
|
9
|
-
constructor(
|
|
10
|
-
message: string,
|
|
11
|
-
public readonly code: string,
|
|
12
|
-
public readonly context?: unknown,
|
|
13
|
-
) {
|
|
14
|
-
super(message);
|
|
15
|
-
this.name = "SchmockError";
|
|
16
|
-
if (typeof Error.captureStackTrace === "function") {
|
|
17
|
-
Error.captureStackTrace(this, this.constructor);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Error thrown when a route is not found
|
|
24
|
-
*/
|
|
25
|
-
export class RouteNotFoundError extends SchmockError {
|
|
26
|
-
constructor(method: string, path: string) {
|
|
27
|
-
super(`Route not found: ${method} ${path}`, "ROUTE_NOT_FOUND", {
|
|
28
|
-
method,
|
|
29
|
-
path,
|
|
30
|
-
});
|
|
31
|
-
this.name = "RouteNotFoundError";
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Error thrown when route parsing fails
|
|
37
|
-
*/
|
|
38
|
-
export class RouteParseError extends SchmockError {
|
|
39
|
-
constructor(routeKey: string, reason: string) {
|
|
40
|
-
super(
|
|
41
|
-
`Invalid route key format: "${routeKey}". ${reason}`,
|
|
42
|
-
"ROUTE_PARSE_ERROR",
|
|
43
|
-
{ routeKey, reason },
|
|
44
|
-
);
|
|
45
|
-
this.name = "RouteParseError";
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Error thrown when response generation fails
|
|
51
|
-
*/
|
|
52
|
-
export class ResponseGenerationError extends SchmockError {
|
|
53
|
-
constructor(route: string, error: Error) {
|
|
54
|
-
super(
|
|
55
|
-
`Failed to generate response for route ${route}: ${error.message}`,
|
|
56
|
-
"RESPONSE_GENERATION_ERROR",
|
|
57
|
-
{ route, originalError: error },
|
|
58
|
-
);
|
|
59
|
-
this.name = "ResponseGenerationError";
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Error thrown when a plugin fails
|
|
65
|
-
*/
|
|
66
|
-
export class PluginError extends SchmockError {
|
|
67
|
-
constructor(pluginName: string, error: Error) {
|
|
68
|
-
super(`Plugin "${pluginName}" failed: ${error.message}`, "PLUGIN_ERROR", {
|
|
69
|
-
pluginName,
|
|
70
|
-
originalError: error,
|
|
71
|
-
});
|
|
72
|
-
this.name = "PluginError";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Error thrown when route definition is invalid
|
|
78
|
-
*/
|
|
79
|
-
export class RouteDefinitionError extends SchmockError {
|
|
80
|
-
constructor(routeKey: string, reason: string) {
|
|
81
|
-
super(
|
|
82
|
-
`Invalid route definition for "${routeKey}": ${reason}`,
|
|
83
|
-
"ROUTE_DEFINITION_ERROR",
|
|
84
|
-
{ routeKey, reason },
|
|
85
|
-
);
|
|
86
|
-
this.name = "RouteDefinitionError";
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Error thrown when schema validation fails
|
|
92
|
-
*/
|
|
93
|
-
export class SchemaValidationError extends SchmockError {
|
|
94
|
-
constructor(schemaPath: string, issue: string, suggestion?: string) {
|
|
95
|
-
super(
|
|
96
|
-
`Schema validation failed at ${schemaPath}: ${issue}${suggestion ? `. ${suggestion}` : ""}`,
|
|
97
|
-
"SCHEMA_VALIDATION_ERROR",
|
|
98
|
-
{ schemaPath, issue, suggestion },
|
|
99
|
-
);
|
|
100
|
-
this.name = "SchemaValidationError";
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Error thrown when schema generation fails
|
|
106
|
-
*/
|
|
107
|
-
export class SchemaGenerationError extends SchmockError {
|
|
108
|
-
constructor(route: string, error: Error, schema?: unknown) {
|
|
109
|
-
super(
|
|
110
|
-
`Schema generation failed for route ${route}: ${error.message}`,
|
|
111
|
-
"SCHEMA_GENERATION_ERROR",
|
|
112
|
-
{ route, originalError: error, schema },
|
|
113
|
-
);
|
|
114
|
-
this.name = "SchemaGenerationError";
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Error thrown when resource limits are exceeded
|
|
120
|
-
*/
|
|
121
|
-
export class ResourceLimitError extends SchmockError {
|
|
122
|
-
constructor(resource: string, limit: number, actual?: number) {
|
|
123
|
-
super(
|
|
124
|
-
`Resource limit exceeded for ${resource}: limit=${limit}${actual ? `, actual=${actual}` : ""}`,
|
|
125
|
-
"RESOURCE_LIMIT_ERROR",
|
|
126
|
-
{ resource, limit, actual },
|
|
127
|
-
);
|
|
128
|
-
this.name = "ResourceLimitError";
|
|
129
|
-
}
|
|
130
|
-
}
|