@schmock/core 2.2.2 → 2.3.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/binary.d.ts +8 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +9 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +60 -29
- package/dist/http-helpers.d.ts +15 -3
- package/dist/http-helpers.d.ts.map +1 -1
- package/dist/http-helpers.js +24 -6
- package/dist/index.d.ts +90 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +71 -20
- package/dist/plugin-pipeline.d.ts +11 -4
- package/dist/plugin-pipeline.d.ts.map +1 -1
- package/dist/plugin-pipeline.js +98 -35
- package/dist/response-parser.d.ts.map +1 -1
- package/dist/response-parser.js +47 -17
- package/package.json +5 -3
- package/src/binary.test.ts +126 -0
- package/src/binary.ts +11 -0
- package/src/builder.ts +82 -35
- package/src/http-helpers.test.ts +39 -0
- package/src/http-helpers.ts +46 -10
- package/src/index.ts +1 -0
- package/src/interceptor.test.ts +197 -0
- package/src/interceptor.ts +215 -128
- package/src/parser.property.test.ts +91 -55
- package/src/plugin-pipeline.ts +150 -45
- package/src/plugin-system.test.ts +95 -0
- package/src/response-parser.ts +58 -19
- package/src/server.test.ts +81 -12
- package/src/steps/async-support.steps.ts +29 -8
- package/src/steps/audit-onerror-tuple.steps.ts +14 -30
- package/src/steps/error-handling.steps.ts +463 -253
- package/src/steps/fetch-interceptor.steps.ts +457 -0
- package/src/steps/http-methods.steps.ts +19 -8
- package/src/steps/lifecycle-events.steps.ts +50 -43
- package/src/steps/performance-reliability.steps.ts +78 -411
- package/src/steps/plugin-integration.steps.ts +54 -0
- package/src/steps/request-history.steps.ts +412 -230
- package/src/steps/standalone-server.steps.ts +7 -13
- package/src/steps/state-concurrency.steps.ts +142 -714
- package/src/steps/interceptor.steps.ts +0 -260
- package/src/steps/stateful-workflows.steps.ts +0 -353
package/dist/binary.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe binary body detection.
|
|
3
|
+
*
|
|
4
|
+
* Every ArrayBuffer view is accepted, including DataView, typed arrays backed
|
|
5
|
+
* by SharedArrayBuffer, and Node.js Buffer without referencing its global.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isBinaryBody(value: unknown): value is ArrayBuffer | ArrayBufferView;
|
|
8
|
+
//# sourceMappingURL=binary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binary.d.ts","sourceRoot":"","sources":["../src/binary.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,WAAW,GAAG,eAAe,CAExC"}
|
package/dist/binary.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe binary body detection.
|
|
3
|
+
*
|
|
4
|
+
* Every ArrayBuffer view is accepted, including DataView, typed arrays backed
|
|
5
|
+
* by SharedArrayBuffer, and Node.js Buffer without referencing its global.
|
|
6
|
+
*/
|
|
7
|
+
export function isBinaryBody(value) {
|
|
8
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
9
|
+
}
|
package/dist/builder.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AA4DA;;;;GAIG;AACH,qBAAa,oBAAoB;;IAanB,OAAO,CAAC,YAAY;IAZhC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,WAAW,CAA2C;IAC9D,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,eAAe,CAAwC;IAE/D,OAAO,CAAC,SAAS,CAAoC;gBAEjC,YAAY,GAAE,OAAO,CAAC,YAAiB;IAa3D,WAAW,CACT,KAAK,EAAE,OAAO,CAAC,QAAQ,EACvB,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,MAAM,EAAE,OAAO,CAAC,WAAW,GAC1B,IAAI;IAsFP,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,oBAAoB,GAAG,IAAI;IAIvD,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI;IAoBlC;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE;IAW5E,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO;IAS3D,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM;IAS7D,WAAW,CACT,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAC3B,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,GAAG,SAAS;IAgBpC,SAAS,IAAI,OAAO,CAAC,SAAS,EAAE;IAQhC,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAMnC,EAAE,CAAC,CAAC,SAAS,OAAO,CAAC,YAAY,EAC/B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GACnD,IAAI;IAUP,GAAG,CAAC,CAAC,SAAS,OAAO,CAAC,YAAY,EAChC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GACnD,IAAI;IAKP,OAAO,CAAC,IAAI;IAWZ,KAAK,IAAI,IAAI;IAiBb,YAAY,IAAI,IAAI;IAKpB,UAAU,IAAI,IAAI;IAWlB,MAAM,CAAC,IAAI,SAAI,EAAE,QAAQ,SAAc,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC;IAwErE,KAAK,IAAI,IAAI;IAab,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,gBAAgB,GAAG,OAAO,CAAC,eAAe;IAgBhE,MAAM,CACV,MAAM,EAAE,OAAO,CAAC,UAAU,EAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,GAC/B,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;IAgR5B;;;;OAIG;YACW,UAAU;CAgBzB"}
|
package/dist/builder.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { isBinaryBody } from "./binary.js";
|
|
1
2
|
import { normalizePath, toHttpMethod } from "./constants.js";
|
|
2
3
|
import { errorMessage, RouteDefinitionError, RouteNotFoundError, SchmockError, } from "./errors.js";
|
|
3
4
|
import { collectBody, parseNodeHeaders, parseNodeQuery, writeSchmockResponse, } from "./http-helpers.js";
|
|
4
5
|
import { createFetchInterceptor } from "./interceptor.js";
|
|
5
6
|
import { parseRouteKey } from "./parser.js";
|
|
6
|
-
import { runPluginPipeline } from "./plugin-pipeline.js";
|
|
7
|
+
import { recoverGeneratorError, runPluginBeforeRequest, runPluginPipeline, } from "./plugin-pipeline.js";
|
|
7
8
|
import { parseResponse } from "./response-parser.js";
|
|
8
9
|
import { extractParams, findRoute, isGeneratorFunction, } from "./route-matcher.js";
|
|
9
10
|
/**
|
|
@@ -83,8 +84,8 @@ export class CallableMockInstance {
|
|
|
83
84
|
// Default to plain text for primitives
|
|
84
85
|
routeConfig.contentType = "text/plain";
|
|
85
86
|
}
|
|
86
|
-
else if (
|
|
87
|
-
// Default to octet-stream for
|
|
87
|
+
else if (isBinaryBody(generator)) {
|
|
88
|
+
// Default to octet-stream for browser and Node binary values
|
|
88
89
|
routeConfig.contentType = "application/octet-stream";
|
|
89
90
|
}
|
|
90
91
|
else {
|
|
@@ -298,8 +299,16 @@ export class CallableMockInstance {
|
|
|
298
299
|
});
|
|
299
300
|
this.server = httpServer;
|
|
300
301
|
return new Promise((resolve, reject) => {
|
|
301
|
-
|
|
302
|
+
const handleStartupError = (error) => {
|
|
303
|
+
if (this.server === httpServer) {
|
|
304
|
+
this.server = undefined;
|
|
305
|
+
this.serverInfo = undefined;
|
|
306
|
+
}
|
|
307
|
+
reject(error);
|
|
308
|
+
};
|
|
309
|
+
httpServer.once("error", handleStartupError);
|
|
302
310
|
httpServer.listen(port, hostname, () => {
|
|
311
|
+
httpServer.off("error", handleStartupError);
|
|
303
312
|
const addr = httpServer.address();
|
|
304
313
|
const actualPort = addr !== null && typeof addr === "object" ? addr.port : port;
|
|
305
314
|
this.serverInfo = { port: actualPort, hostname };
|
|
@@ -361,7 +370,7 @@ export class CallableMockInstance {
|
|
|
361
370
|
const response = {
|
|
362
371
|
status: 404,
|
|
363
372
|
body: { error: error.message, code: error.code },
|
|
364
|
-
headers: {},
|
|
373
|
+
headers: { "content-type": "application/json" },
|
|
365
374
|
};
|
|
366
375
|
this.emit("request:end", {
|
|
367
376
|
method,
|
|
@@ -385,7 +394,7 @@ export class CallableMockInstance {
|
|
|
385
394
|
const response = {
|
|
386
395
|
status: 404,
|
|
387
396
|
body: { error: error.message, code: error.code },
|
|
388
|
-
headers: {},
|
|
397
|
+
headers: { "content-type": "application/json" },
|
|
389
398
|
};
|
|
390
399
|
this.emit("request:end", {
|
|
391
400
|
method,
|
|
@@ -405,24 +414,8 @@ export class CallableMockInstance {
|
|
|
405
414
|
routePath: matchedRoute.path,
|
|
406
415
|
params,
|
|
407
416
|
});
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
method,
|
|
411
|
-
path: requestPath,
|
|
412
|
-
params,
|
|
413
|
-
query: reqQuery,
|
|
414
|
-
headers: reqHeaders,
|
|
415
|
-
body: options?.body,
|
|
416
|
-
state: this.globalConfig.state || {},
|
|
417
|
-
};
|
|
418
|
-
let result;
|
|
419
|
-
if (isGeneratorFunction(matchedRoute.generator)) {
|
|
420
|
-
result = await matchedRoute.generator(context);
|
|
421
|
-
}
|
|
422
|
-
else {
|
|
423
|
-
result = matchedRoute.generator;
|
|
424
|
-
}
|
|
425
|
-
// Build plugin context
|
|
417
|
+
// Build plugin context before route code so request guards can reject
|
|
418
|
+
// invalid or unauthorized requests without triggering side effects.
|
|
426
419
|
let pluginContext = {
|
|
427
420
|
path: requestPath,
|
|
428
421
|
route: matchedRoute.config,
|
|
@@ -434,11 +427,48 @@ export class CallableMockInstance {
|
|
|
434
427
|
state: new Map(),
|
|
435
428
|
routeState: this.globalConfig.state || {},
|
|
436
429
|
};
|
|
430
|
+
const preflightResult = await runPluginBeforeRequest(this.plugins, pluginContext, this.logger);
|
|
431
|
+
pluginContext = preflightResult.context;
|
|
432
|
+
if (preflightResult.requestShortCircuited === true) {
|
|
433
|
+
pluginContext = { ...pluginContext, requestShortCircuited: true };
|
|
434
|
+
}
|
|
435
|
+
let result = preflightResult.response;
|
|
436
|
+
let skipPostProcessing = preflightResult.recoveredFromError === true;
|
|
437
|
+
if (result === undefined) {
|
|
438
|
+
const context = {
|
|
439
|
+
method: pluginContext.method,
|
|
440
|
+
path: pluginContext.path,
|
|
441
|
+
params: pluginContext.params,
|
|
442
|
+
query: pluginContext.query,
|
|
443
|
+
headers: pluginContext.headers,
|
|
444
|
+
body: pluginContext.body,
|
|
445
|
+
state: pluginContext.routeState ?? this.globalConfig.state ?? {},
|
|
446
|
+
};
|
|
447
|
+
try {
|
|
448
|
+
if (isGeneratorFunction(matchedRoute.generator)) {
|
|
449
|
+
result = await matchedRoute.generator(context);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
result = matchedRoute.generator;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
const recovery = await recoverGeneratorError(this.plugins, pluginContext, error, this.logger);
|
|
457
|
+
pluginContext = recovery.context;
|
|
458
|
+
result = recovery.response;
|
|
459
|
+
skipPostProcessing = recovery.recoveredFromError === true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
437
462
|
// Run plugin pipeline to transform the response
|
|
438
463
|
try {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
464
|
+
if (skipPostProcessing) {
|
|
465
|
+
this.logger.log("pipeline", "Skipping response processors after error recovery");
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
const pipelineResult = await runPluginPipeline(this.plugins, pluginContext, result, this.logger);
|
|
469
|
+
pluginContext = pipelineResult.context;
|
|
470
|
+
result = pipelineResult.response;
|
|
471
|
+
}
|
|
442
472
|
}
|
|
443
473
|
catch (error) {
|
|
444
474
|
this.logger.log("error", `[${requestId}] Plugin pipeline error: ${errorMessage(error)}`);
|
|
@@ -489,7 +519,7 @@ export class CallableMockInstance {
|
|
|
489
519
|
error: errorMessage(error),
|
|
490
520
|
code: error instanceof SchmockError ? error.code : "INTERNAL_ERROR",
|
|
491
521
|
},
|
|
492
|
-
headers: {},
|
|
522
|
+
headers: { "content-type": "application/json" },
|
|
493
523
|
};
|
|
494
524
|
// Apply delay even for error responses
|
|
495
525
|
await this.applyDelay();
|
|
@@ -514,10 +544,11 @@ export class CallableMockInstance {
|
|
|
514
544
|
if (!effectiveDelay) {
|
|
515
545
|
return;
|
|
516
546
|
}
|
|
517
|
-
const
|
|
547
|
+
const configuredMs = Array.isArray(effectiveDelay)
|
|
518
548
|
? Math.random() * (effectiveDelay[1] - effectiveDelay[0]) +
|
|
519
549
|
effectiveDelay[0]
|
|
520
550
|
: effectiveDelay;
|
|
551
|
+
const ms = Math.max(0, configuredMs);
|
|
521
552
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
522
553
|
}
|
|
523
554
|
}
|
package/dist/http-helpers.d.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ServerResponse } from "node:http";
|
|
2
|
+
interface RequestWithHeaders {
|
|
3
|
+
readonly headers: {
|
|
4
|
+
readonly [header: string]: string | string[] | undefined;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
interface BodyReadable {
|
|
8
|
+
on(event: "error", listener: (error: Error) => void): this;
|
|
9
|
+
on(event: "data", listener: (chunk: Buffer) => void): this;
|
|
10
|
+
on(event: "end", listener: () => void): this;
|
|
11
|
+
destroy(error?: Error): this;
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* Convert Node.js IncomingMessage headers to a flat Record<string, string>.
|
|
4
15
|
* Drops array-valued headers (keeps only string values).
|
|
5
16
|
*/
|
|
6
|
-
export declare function parseNodeHeaders(req:
|
|
17
|
+
export declare function parseNodeHeaders(req: RequestWithHeaders): Record<string, string>;
|
|
7
18
|
/**
|
|
8
19
|
* Extract query parameters from a URL as a flat Record<string, string>.
|
|
9
20
|
*/
|
|
@@ -16,10 +27,11 @@ export declare function parseNodeQuery(url: URL): Record<string, string>;
|
|
|
16
27
|
* @param headers - Parsed request headers
|
|
17
28
|
* @param maxBodySize - Maximum body size in bytes (default: 10 MB)
|
|
18
29
|
*/
|
|
19
|
-
export declare function collectBody(req:
|
|
30
|
+
export declare function collectBody(req: BodyReadable, headers: Record<string, string>, maxBodySize?: number): Promise<unknown>;
|
|
20
31
|
/**
|
|
21
32
|
* Write a Schmock Response to a Node.js ServerResponse.
|
|
22
33
|
* Serializes non-string bodies as JSON and sets content-type when missing.
|
|
23
34
|
*/
|
|
24
35
|
export declare function writeSchmockResponse(res: ServerResponse, response: Schmock.Response, extraHeaders?: Record<string, string>): void;
|
|
36
|
+
export {};
|
|
25
37
|
//# sourceMappingURL=http-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-helpers.d.ts","sourceRoot":"","sources":["../src/http-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"http-helpers.d.ts","sourceRoot":"","sources":["../src/http-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;KAC1D,CAAC;CACH;AAED,UAAU,YAAY;IACpB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;IAC3D,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC3D,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC7C,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,kBAAkB,GACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM/D;AAKD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,WAAW,SAAwB,GAClC,OAAO,CAAC,OAAO,CAAC,CAqClB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,IAAI,CA2CN"}
|
package/dist/http-helpers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBinaryBody } from "./binary.js";
|
|
1
2
|
/**
|
|
2
3
|
* Convert Node.js IncomingMessage headers to a flat Record<string, string>.
|
|
3
4
|
* Drops array-valued headers (keeps only string values).
|
|
@@ -75,16 +76,33 @@ export function writeSchmockResponse(res, response, extraHeaders) {
|
|
|
75
76
|
...response.headers,
|
|
76
77
|
...extraHeaders,
|
|
77
78
|
};
|
|
78
|
-
|
|
79
|
+
const hasContentType = Object.keys(responseHeaders).some((header) => header.toLowerCase() === "content-type");
|
|
80
|
+
if (!hasContentType &&
|
|
81
|
+
response.body !== undefined &&
|
|
82
|
+
isBinaryBody(response.body)) {
|
|
83
|
+
responseHeaders["content-type"] = "application/octet-stream";
|
|
84
|
+
}
|
|
85
|
+
else if (!hasContentType &&
|
|
79
86
|
response.body !== undefined &&
|
|
80
87
|
typeof response.body !== "string") {
|
|
81
88
|
responseHeaders["content-type"] = "application/json";
|
|
82
89
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
let responseBody;
|
|
91
|
+
if (response.body === undefined) {
|
|
92
|
+
responseBody = undefined;
|
|
93
|
+
}
|
|
94
|
+
else if (typeof response.body === "string") {
|
|
95
|
+
responseBody = response.body;
|
|
96
|
+
}
|
|
97
|
+
else if (response.body instanceof ArrayBuffer) {
|
|
98
|
+
responseBody = new Uint8Array(response.body);
|
|
99
|
+
}
|
|
100
|
+
else if (ArrayBuffer.isView(response.body)) {
|
|
101
|
+
responseBody = new Uint8Array(response.body.buffer, response.body.byteOffset, response.body.byteLength);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
responseBody = JSON.stringify(response.body);
|
|
105
|
+
}
|
|
88
106
|
res.writeHead(response.status, responseHeaders);
|
|
89
107
|
res.end(responseBody);
|
|
90
108
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ namespace Schmock {
|
|
|
28
28
|
* 'DELETE /api/posts/:postId/comments/:commentId'
|
|
29
29
|
*/
|
|
30
30
|
type RouteKey = `${HttpMethod} ${string}`;
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
/**
|
|
33
33
|
* Plugin interface for extending Schmock functionality
|
|
34
34
|
*/
|
|
@@ -45,6 +45,15 @@ namespace Schmock {
|
|
|
45
45
|
*/
|
|
46
46
|
install?(instance: CallableMockInstance): void;
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Inspect or transform a request before its route generator executes.
|
|
50
|
+
* Returning a response short-circuits the generator, while returning only
|
|
51
|
+
* a context allows request changes to flow into the generator.
|
|
52
|
+
*/
|
|
53
|
+
beforeRequest?(
|
|
54
|
+
context: PluginContext,
|
|
55
|
+
): PluginResult | void | Promise<PluginResult | void>;
|
|
56
|
+
|
|
48
57
|
/**
|
|
49
58
|
* Process the request through this plugin
|
|
50
59
|
* First plugin to set response becomes the generator, others transform
|
|
@@ -52,16 +61,22 @@ namespace Schmock {
|
|
|
52
61
|
* @param response - Response from previous plugin (if any)
|
|
53
62
|
* @returns Updated context and response
|
|
54
63
|
*/
|
|
55
|
-
process(
|
|
64
|
+
process(
|
|
65
|
+
context: PluginContext,
|
|
66
|
+
response?: unknown,
|
|
67
|
+
): PluginResult | Promise<PluginResult>;
|
|
56
68
|
|
|
57
69
|
/**
|
|
58
|
-
* Called when an
|
|
59
|
-
*
|
|
70
|
+
* Called when this plugin or an earlier pipeline stage fails. If this hook
|
|
71
|
+
* does not recover, downstream handlers are tried in registration order.
|
|
60
72
|
* @param error - The error that occurred
|
|
61
73
|
* @param context - Plugin context
|
|
62
74
|
* @returns Modified error, response data, or void to continue error propagation
|
|
63
75
|
*/
|
|
64
|
-
onError?(
|
|
76
|
+
onError?(
|
|
77
|
+
error: Error,
|
|
78
|
+
context: PluginContext,
|
|
79
|
+
): Error | ResponseResult | void | Promise<Error | ResponseResult | void>;
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
/**
|
|
@@ -99,6 +114,8 @@ namespace Schmock {
|
|
|
99
114
|
body?: unknown;
|
|
100
115
|
/** Shared state between plugins for this request */
|
|
101
116
|
state: Map<string, unknown>;
|
|
117
|
+
/** True when a beforeRequest hook supplied the response instead of the route generator. */
|
|
118
|
+
requestShortCircuited?: boolean;
|
|
102
119
|
/** Route-specific state */
|
|
103
120
|
routeState?: Record<string, unknown>;
|
|
104
121
|
}
|
|
@@ -148,20 +165,28 @@ namespace Schmock {
|
|
|
148
165
|
/**
|
|
149
166
|
* Generator types that can be passed to route definitions
|
|
150
167
|
*/
|
|
151
|
-
type Generator =
|
|
152
|
-
| GeneratorFunction
|
|
153
|
-
| StaticData
|
|
154
|
-
| JSONSchema7;
|
|
168
|
+
type Generator = GeneratorFunction | StaticData | JSONSchema7;
|
|
155
169
|
|
|
156
170
|
/**
|
|
157
171
|
* Function that generates responses
|
|
158
172
|
*/
|
|
159
|
-
type GeneratorFunction = (
|
|
173
|
+
type GeneratorFunction = (
|
|
174
|
+
context: RequestContext,
|
|
175
|
+
) => ResponseResult | Promise<ResponseResult>;
|
|
160
176
|
|
|
161
177
|
/**
|
|
162
178
|
* Static data (non-function) that gets returned as-is
|
|
163
179
|
*/
|
|
164
|
-
type StaticData =
|
|
180
|
+
type StaticData =
|
|
181
|
+
| string
|
|
182
|
+
| number
|
|
183
|
+
| boolean
|
|
184
|
+
| null
|
|
185
|
+
| undefined
|
|
186
|
+
| Record<string, unknown>
|
|
187
|
+
| unknown[]
|
|
188
|
+
| ArrayBuffer
|
|
189
|
+
| ArrayBufferView;
|
|
165
190
|
|
|
166
191
|
/**
|
|
167
192
|
* Context passed to generator functions
|
|
@@ -240,12 +265,12 @@ namespace Schmock {
|
|
|
240
265
|
interface CallableMockInstance {
|
|
241
266
|
/**
|
|
242
267
|
* Define a route by calling the instance directly
|
|
243
|
-
*
|
|
268
|
+
*
|
|
244
269
|
* @param route - Route pattern in format 'METHOD /path'
|
|
245
270
|
* @param generator - Response generator (function, static data, or schema)
|
|
246
271
|
* @param config - Route-specific configuration
|
|
247
272
|
* @returns The same instance for method chaining
|
|
248
|
-
*
|
|
273
|
+
*
|
|
249
274
|
* @example
|
|
250
275
|
* ```typescript
|
|
251
276
|
* const mock = schmock()
|
|
@@ -253,19 +278,22 @@ namespace Schmock {
|
|
|
253
278
|
* mock('POST /users', userData, { contentType: 'application/json' })
|
|
254
279
|
* ```
|
|
255
280
|
*/
|
|
256
|
-
(
|
|
281
|
+
(
|
|
282
|
+
route: RouteKey,
|
|
283
|
+
generator: Generator,
|
|
284
|
+
config?: RouteConfig,
|
|
285
|
+
): CallableMockInstance;
|
|
257
286
|
|
|
258
287
|
/**
|
|
259
288
|
* Add a plugin to the pipeline
|
|
260
|
-
*
|
|
289
|
+
*
|
|
261
290
|
* @param plugin - Plugin to add to the pipeline
|
|
262
291
|
* @returns The same instance for method chaining
|
|
263
|
-
*
|
|
292
|
+
*
|
|
264
293
|
* @example
|
|
265
294
|
* ```typescript
|
|
295
|
+
* mock.pipe(authPlugin()).pipe(corsPlugin())
|
|
266
296
|
* mock('GET /users', generator, config)
|
|
267
|
-
* .pipe(authPlugin())
|
|
268
|
-
* .pipe(corsPlugin())
|
|
269
297
|
* ```
|
|
270
298
|
*/
|
|
271
299
|
pipe(plugin: Plugin): CallableMockInstance;
|
|
@@ -285,7 +313,11 @@ namespace Schmock {
|
|
|
285
313
|
* })
|
|
286
314
|
* ```
|
|
287
315
|
*/
|
|
288
|
-
handle(
|
|
316
|
+
handle(
|
|
317
|
+
method: HttpMethod,
|
|
318
|
+
path: string,
|
|
319
|
+
options?: RequestOptions,
|
|
320
|
+
): Promise<Response>;
|
|
289
321
|
|
|
290
322
|
// ===== Request Spy / History API =====
|
|
291
323
|
|
|
@@ -347,12 +379,18 @@ namespace Schmock {
|
|
|
347
379
|
/**
|
|
348
380
|
* Register an event listener
|
|
349
381
|
*/
|
|
350
|
-
on<E extends SchmockEvent>(
|
|
382
|
+
on<E extends SchmockEvent>(
|
|
383
|
+
event: E,
|
|
384
|
+
listener: (data: SchmockEventMap[E]) => void,
|
|
385
|
+
): CallableMockInstance;
|
|
351
386
|
|
|
352
387
|
/**
|
|
353
388
|
* Remove an event listener
|
|
354
389
|
*/
|
|
355
|
-
off<E extends SchmockEvent>(
|
|
390
|
+
off<E extends SchmockEvent>(
|
|
391
|
+
event: E,
|
|
392
|
+
listener: (data: SchmockEventMap[E]) => void,
|
|
393
|
+
): CallableMockInstance;
|
|
356
394
|
|
|
357
395
|
// ===== Introspection =====
|
|
358
396
|
|
|
@@ -555,6 +593,8 @@ namespace Schmock {
|
|
|
555
593
|
interface CrudOperationMeta {
|
|
556
594
|
/** Full success response schema (wrapper + items) */
|
|
557
595
|
responseSchema?: JSONSchema7;
|
|
596
|
+
/** Concrete success status selected from the operation contract. */
|
|
597
|
+
responseStatus?: number;
|
|
558
598
|
/** Response headers from spec */
|
|
559
599
|
responseHeaders?: Record<string, ResponseHeaderDef>;
|
|
560
600
|
/** Error response schemas keyed by status code */
|
|
@@ -627,14 +667,24 @@ namespace Schmock {
|
|
|
627
667
|
passthrough?: boolean;
|
|
628
668
|
errorFormatter?: (error: Error, request: unknown) => unknown;
|
|
629
669
|
transformRequest?: (request: unknown) => AdapterRequestOverride;
|
|
630
|
-
transformResponse?: (
|
|
631
|
-
response: Response,
|
|
632
|
-
request: unknown,
|
|
633
|
-
) => Response;
|
|
670
|
+
transformResponse?: (response: Response, request: unknown) => Response;
|
|
634
671
|
}
|
|
635
672
|
|
|
636
673
|
// ===== OpenAPI Plugin Options =====
|
|
637
674
|
|
|
675
|
+
/** A callback request resolved from an OpenAPI callback expression. */
|
|
676
|
+
interface OpenApiCallbackRequest {
|
|
677
|
+
url: string;
|
|
678
|
+
method: HttpMethod;
|
|
679
|
+
headers: Record<string, string>;
|
|
680
|
+
body?: unknown;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** Explicit application-owned delivery for OpenAPI callbacks. */
|
|
684
|
+
interface OpenApiCallbackOptions {
|
|
685
|
+
dispatch(request: OpenApiCallbackRequest): void | Promise<void>;
|
|
686
|
+
}
|
|
687
|
+
|
|
638
688
|
/**
|
|
639
689
|
* Options for the OpenAPI plugin
|
|
640
690
|
*/
|
|
@@ -643,6 +693,7 @@ namespace Schmock {
|
|
|
643
693
|
seed?: SeedConfig;
|
|
644
694
|
validateRequests?: boolean;
|
|
645
695
|
validateResponses?: boolean;
|
|
696
|
+
/** @deprecated Unsupported. Supplying this option throws OPENAPI_UNSUPPORTED_OPTION. */
|
|
646
697
|
queryFeatures?: {
|
|
647
698
|
pagination?: boolean;
|
|
648
699
|
sorting?: boolean;
|
|
@@ -652,12 +703,24 @@ namespace Schmock {
|
|
|
652
703
|
debug?: boolean;
|
|
653
704
|
fakerSeed?: number;
|
|
654
705
|
security?: boolean;
|
|
706
|
+
/**
|
|
707
|
+
* Enable callback delivery through an application-supplied dispatcher.
|
|
708
|
+
* Callbacks are disabled when this option is omitted; Schmock never
|
|
709
|
+
* performs callback network requests implicitly.
|
|
710
|
+
*/
|
|
711
|
+
callbacks?: OpenApiCallbackOptions;
|
|
655
712
|
/** Replace response schemas for specific routes. Key format: "METHOD /path" or "METHOD /path STATUS" */
|
|
656
713
|
schemas?: Record<string, import("json-schema").JSONSchema7>;
|
|
657
714
|
/** Called before generating a response body. Return a schema to replace the original, or void to keep it. */
|
|
658
715
|
onSchema?: (
|
|
659
716
|
schema: import("json-schema").JSONSchema7,
|
|
660
|
-
context: {
|
|
717
|
+
context: {
|
|
718
|
+
method: string;
|
|
719
|
+
path: string;
|
|
720
|
+
params: Record<string, string>;
|
|
721
|
+
query: Record<string, string>;
|
|
722
|
+
headers: Record<string, string>;
|
|
723
|
+
},
|
|
661
724
|
) => import("json-schema").JSONSchema7 | undefined;
|
|
662
725
|
}
|
|
663
726
|
|
|
@@ -698,7 +761,6 @@ namespace Schmock {
|
|
|
698
761
|
hostname: string;
|
|
699
762
|
close(): void;
|
|
700
763
|
}
|
|
701
|
-
|
|
702
764
|
}
|
|
703
765
|
}
|
|
704
766
|
// >>> schmock ambient namespace <<<
|
|
@@ -726,6 +788,7 @@ namespace Schmock {
|
|
|
726
788
|
* @returns A callable mock instance
|
|
727
789
|
*/
|
|
728
790
|
export declare function schmock(config?: Schmock.GlobalConfig): Schmock.CallableMockInstance;
|
|
791
|
+
export { isBinaryBody } from "./binary.js";
|
|
729
792
|
export { HTTP_METHODS, isHttpMethod, isRouteNotFound, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, toRouteKey, } from "./constants.js";
|
|
730
793
|
export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
|
|
731
794
|
export { badRequest, created, forbidden, noContent, notFound, paginate, serverError, unauthorized, } from "./helpers.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CACrB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,GAC5B,OAAO,CAAC,oBAAoB,CAqD9B;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CACrB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,GAC5B,OAAO,CAAC,oBAAoB,CAqD9B;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,UAAU,GACX,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,YAAY,EACV,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,MAAM,EACN,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,QAAQ,EACR,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ export function schmock(config) {
|
|
|
59
59
|
instance.setCallableRef(callableInstance);
|
|
60
60
|
return callableInstance;
|
|
61
61
|
}
|
|
62
|
+
export { isBinaryBody } from "./binary.js";
|
|
62
63
|
// Re-export constants and utilities
|
|
63
64
|
export { HTTP_METHODS, isHttpMethod, isRouteNotFound, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, toRouteKey, } from "./constants.js";
|
|
64
65
|
// Re-export errors
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAwPA;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,CACN,MAAM,EAAE,OAAO,CAAC,UAAU,EAC1B,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,KACpC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAC9B,OAAO,GAAE,OAAO,CAAC,gBAAqB,GACrC,OAAO,CAAC,eAAe,CAoJzB"}
|