@schmock/core 1.1.0 → 1.2.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.
- package/dist/builder.d.ts +2 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +9 -1
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/package.json +4 -4
- package/src/builder.ts +10 -1
- package/src/constants.ts +16 -0
- package/src/index.ts +3 -0
- package/src/parser.property.test.ts +1 -3
- package/src/plugin-system.test.ts +91 -0
- package/src/response-parsing.test.ts +11 -7
- package/src/steps/developer-experience.steps.ts +1 -1
package/dist/builder.d.ts
CHANGED
|
@@ -10,8 +10,10 @@ export declare class CallableMockInstance {
|
|
|
10
10
|
private plugins;
|
|
11
11
|
private logger;
|
|
12
12
|
private requestHistory;
|
|
13
|
+
private callableRef;
|
|
13
14
|
constructor(globalConfig?: Schmock.GlobalConfig);
|
|
14
15
|
defineRoute(route: Schmock.RouteKey, generator: Schmock.Generator, config: Schmock.RouteConfig): this;
|
|
16
|
+
setCallableRef(ref: Schmock.CallableMockInstance): void;
|
|
15
17
|
pipe(plugin: Schmock.Plugin): this;
|
|
16
18
|
history(method?: Schmock.HttpMethod, path?: string): Schmock.RequestRecord[];
|
|
17
19
|
called(method?: Schmock.HttpMethod, path?: string): boolean;
|
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":"AA0EA;;;;GAIG;AACH,qBAAa,oBAAoB;IAQnB,OAAO,CAAC,YAAY;IAPhC,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;gBAE1C,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;IAqFP,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,oBAAoB,GAAG,IAAI;IAIvD,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI;IAoBlC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE;IAS5E,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;IAYpC,KAAK,IAAI,IAAI;IAab,YAAY,IAAI,IAAI;IAKpB,UAAU,IAAI,IAAI;IASZ,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;IAoL5B;;;;OAIG;YACW,UAAU;IAcxB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAwDrB;;;;;;;;;;OAUG;YACW,iBAAiB;IAqG/B;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS;IA0BjB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;CActB"}
|
package/dist/builder.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isStatusTuple } from "./constants.js";
|
|
1
2
|
import { PluginError, RouteDefinitionError, RouteNotFoundError, SchmockError, } from "./errors.js";
|
|
2
3
|
import { parseRouteKey } from "./parser.js";
|
|
3
4
|
function errorMessage(error) {
|
|
@@ -55,6 +56,7 @@ export class CallableMockInstance {
|
|
|
55
56
|
plugins = [];
|
|
56
57
|
logger;
|
|
57
58
|
requestHistory = [];
|
|
59
|
+
callableRef;
|
|
58
60
|
constructor(globalConfig = {}) {
|
|
59
61
|
this.globalConfig = globalConfig;
|
|
60
62
|
this.logger = new DebugLogger(globalConfig.debug || false);
|
|
@@ -135,6 +137,9 @@ export class CallableMockInstance {
|
|
|
135
137
|
});
|
|
136
138
|
return this;
|
|
137
139
|
}
|
|
140
|
+
setCallableRef(ref) {
|
|
141
|
+
this.callableRef = ref;
|
|
142
|
+
}
|
|
138
143
|
pipe(plugin) {
|
|
139
144
|
this.plugins.push(plugin);
|
|
140
145
|
this.logger.log("plugin", `Registered plugin: ${plugin.name}@${plugin.version || "unknown"}`, {
|
|
@@ -143,6 +148,9 @@ export class CallableMockInstance {
|
|
|
143
148
|
hasProcess: typeof plugin.process === "function",
|
|
144
149
|
hasOnError: typeof plugin.onError === "function",
|
|
145
150
|
});
|
|
151
|
+
if (plugin.install && this.callableRef) {
|
|
152
|
+
plugin.install(this.callableRef);
|
|
153
|
+
}
|
|
146
154
|
return this;
|
|
147
155
|
}
|
|
148
156
|
// ===== Request Spy / History API =====
|
|
@@ -367,7 +375,7 @@ export class CallableMockInstance {
|
|
|
367
375
|
};
|
|
368
376
|
}
|
|
369
377
|
// Handle tuple response format [status, body, headers?]
|
|
370
|
-
if (
|
|
378
|
+
if (isStatusTuple(result)) {
|
|
371
379
|
[status, body, headers = {}] = result;
|
|
372
380
|
tupleFormat = true;
|
|
373
381
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -3,4 +3,9 @@ export declare const ROUTE_NOT_FOUND_CODE: "ROUTE_NOT_FOUND";
|
|
|
3
3
|
export declare const HTTP_METHODS: readonly HttpMethod[];
|
|
4
4
|
export declare function isHttpMethod(method: string): method is HttpMethod;
|
|
5
5
|
export declare function toHttpMethod(method: string): HttpMethod;
|
|
6
|
+
/**
|
|
7
|
+
* Check if a value is a status tuple: [status, body] or [status, body, headers]
|
|
8
|
+
* Guards against misinterpreting numeric arrays like [1, 2, 3] as tuples.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isStatusTuple(value: unknown): value is [number, unknown] | [number, unknown, Record<string, string>];
|
|
6
11
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,oBAAoB,EAAG,iBAA0B,CAAC;AAE/D,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAQpC,CAAC;AAEX,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,UAAU,CAEjE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAMvD"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,oBAAoB,EAAG,iBAA0B,CAAC;AAE/D,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAQpC,CAAC;AAEX,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,UAAU,CAEjE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAMvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAQxE"}
|
package/dist/constants.js
CHANGED
|
@@ -18,3 +18,14 @@ export function toHttpMethod(method) {
|
|
|
18
18
|
}
|
|
19
19
|
return upper;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Check if a value is a status tuple: [status, body] or [status, body, headers]
|
|
23
|
+
* Guards against misinterpreting numeric arrays like [1, 2, 3] as tuples.
|
|
24
|
+
*/
|
|
25
|
+
export function isStatusTuple(value) {
|
|
26
|
+
return (Array.isArray(value) &&
|
|
27
|
+
(value.length === 2 || value.length === 3) &&
|
|
28
|
+
typeof value[0] === "number" &&
|
|
29
|
+
value[0] >= 100 &&
|
|
30
|
+
value[0] <= 599);
|
|
31
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* @returns A callable mock instance
|
|
23
23
|
*/
|
|
24
24
|
export declare function schmock(config?: Schmock.GlobalConfig): Schmock.CallableMockInstance;
|
|
25
|
-
export { HTTP_METHODS, isHttpMethod, ROUTE_NOT_FOUND_CODE, toHttpMethod, } from "./constants.js";
|
|
25
|
+
export { HTTP_METHODS, isHttpMethod, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, } from "./constants.js";
|
|
26
26
|
export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
|
|
27
27
|
export type { CallableMockInstance, Generator, GeneratorFunction, GlobalConfig, HttpMethod, Plugin, PluginContext, PluginResult, RequestContext, RequestOptions, RequestRecord, Response, ResponseBody, ResponseResult, RouteConfig, RouteKey, StaticData, } from "./types.js";
|
|
28
28
|
//# sourceMappingURL=index.d.ts.map
|
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,
|
|
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,CAiC9B;AAGD,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,YAAY,GACb,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,YAAY,EACV,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,MAAM,EACN,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,WAAW,EACX,QAAQ,EACR,UAAU,GACX,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -43,9 +43,10 @@ export function schmock(config) {
|
|
|
43
43
|
resetHistory: instance.resetHistory.bind(instance),
|
|
44
44
|
resetState: instance.resetState.bind(instance),
|
|
45
45
|
});
|
|
46
|
+
instance.setCallableRef(callableInstance);
|
|
46
47
|
return callableInstance;
|
|
47
48
|
}
|
|
48
49
|
// Re-export constants and utilities
|
|
49
|
-
export { HTTP_METHODS, isHttpMethod, ROUTE_NOT_FOUND_CODE, toHttpMethod, } from "./constants.js";
|
|
50
|
+
export { HTTP_METHODS, isHttpMethod, isStatusTuple, ROUTE_NOT_FOUND_CODE, toHttpMethod, } from "./constants.js";
|
|
50
51
|
// Re-export errors
|
|
51
52
|
export { PluginError, ResourceLimitError, ResponseGenerationError, RouteDefinitionError, RouteNotFoundError, RouteParseError, SchemaGenerationError, SchemaValidationError, SchmockError, } from "./errors.js";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schmock/core",
|
|
3
3
|
"description": "Core functionality for Schmock",
|
|
4
|
-
"version": "1.1
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@amiceli/vitest-cucumber": "^6.2.0",
|
|
35
|
-
"@types/node": "^25.1
|
|
36
|
-
"@vitest/ui": "^4.0.
|
|
37
|
-
"vitest": "^4.0.
|
|
35
|
+
"@types/node": "^25.2.1",
|
|
36
|
+
"@vitest/ui": "^4.0.18",
|
|
37
|
+
"vitest": "^4.0.18"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/src/builder.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isStatusTuple } from "./constants.js";
|
|
1
2
|
import {
|
|
2
3
|
PluginError,
|
|
3
4
|
RouteDefinitionError,
|
|
@@ -82,6 +83,7 @@ export class CallableMockInstance {
|
|
|
82
83
|
private plugins: Schmock.Plugin[] = [];
|
|
83
84
|
private logger: DebugLogger;
|
|
84
85
|
private requestHistory: Schmock.RequestRecord[] = [];
|
|
86
|
+
private callableRef: Schmock.CallableMockInstance | undefined;
|
|
85
87
|
|
|
86
88
|
constructor(private globalConfig: Schmock.GlobalConfig = {}) {
|
|
87
89
|
this.logger = new DebugLogger(globalConfig.debug || false);
|
|
@@ -185,6 +187,10 @@ export class CallableMockInstance {
|
|
|
185
187
|
return this;
|
|
186
188
|
}
|
|
187
189
|
|
|
190
|
+
setCallableRef(ref: Schmock.CallableMockInstance): void {
|
|
191
|
+
this.callableRef = ref;
|
|
192
|
+
}
|
|
193
|
+
|
|
188
194
|
pipe(plugin: Schmock.Plugin): this {
|
|
189
195
|
this.plugins.push(plugin);
|
|
190
196
|
this.logger.log(
|
|
@@ -197,6 +203,9 @@ export class CallableMockInstance {
|
|
|
197
203
|
hasOnError: typeof plugin.onError === "function",
|
|
198
204
|
},
|
|
199
205
|
);
|
|
206
|
+
if (plugin.install && this.callableRef) {
|
|
207
|
+
plugin.install(this.callableRef);
|
|
208
|
+
}
|
|
200
209
|
return this;
|
|
201
210
|
}
|
|
202
211
|
|
|
@@ -502,7 +511,7 @@ export class CallableMockInstance {
|
|
|
502
511
|
}
|
|
503
512
|
|
|
504
513
|
// Handle tuple response format [status, body, headers?]
|
|
505
|
-
if (
|
|
514
|
+
if (isStatusTuple(result)) {
|
|
506
515
|
[status, body, headers = {}] = result;
|
|
507
516
|
tupleFormat = true;
|
|
508
517
|
}
|
package/src/constants.ts
CHANGED
|
@@ -23,3 +23,19 @@ export function toHttpMethod(method: string): HttpMethod {
|
|
|
23
23
|
}
|
|
24
24
|
return upper;
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a value is a status tuple: [status, body] or [status, body, headers]
|
|
29
|
+
* Guards against misinterpreting numeric arrays like [1, 2, 3] as tuples.
|
|
30
|
+
*/
|
|
31
|
+
export function isStatusTuple(
|
|
32
|
+
value: unknown,
|
|
33
|
+
): value is [number, unknown] | [number, unknown, Record<string, string>] {
|
|
34
|
+
return (
|
|
35
|
+
Array.isArray(value) &&
|
|
36
|
+
(value.length === 2 || value.length === 3) &&
|
|
37
|
+
typeof value[0] === "number" &&
|
|
38
|
+
value[0] >= 100 &&
|
|
39
|
+
value[0] <= 599
|
|
40
|
+
);
|
|
41
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -55,6 +55,8 @@ export function schmock(
|
|
|
55
55
|
},
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
+
instance.setCallableRef(callableInstance);
|
|
59
|
+
|
|
58
60
|
return callableInstance;
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -62,6 +64,7 @@ export function schmock(
|
|
|
62
64
|
export {
|
|
63
65
|
HTTP_METHODS,
|
|
64
66
|
isHttpMethod,
|
|
67
|
+
isStatusTuple,
|
|
65
68
|
ROUTE_NOT_FOUND_CODE,
|
|
66
69
|
toHttpMethod,
|
|
67
70
|
} from "./constants.js";
|
|
@@ -124,9 +124,7 @@ const namespace = fc.oneof(
|
|
|
124
124
|
safeSeg.map((s) => `/${s}/`), // trailing slash
|
|
125
125
|
fc.constant(""),
|
|
126
126
|
safeSeg.map((s) => `//${s}`), // double leading slash
|
|
127
|
-
fc
|
|
128
|
-
.tuple(safeSeg, safeSeg)
|
|
129
|
-
.map(([a, b]) => `/${a}/${b}`), // nested
|
|
127
|
+
fc.tuple(safeSeg, safeSeg).map(([a, b]) => `/${a}/${b}`), // nested
|
|
130
128
|
);
|
|
131
129
|
|
|
132
130
|
// ============================================================
|
|
@@ -420,6 +420,97 @@ describe("plugin system", () => {
|
|
|
420
420
|
});
|
|
421
421
|
});
|
|
422
422
|
|
|
423
|
+
describe("plugin install hook", () => {
|
|
424
|
+
it("calls install with callable instance when pipe() is invoked", () => {
|
|
425
|
+
const mock = schmock();
|
|
426
|
+
let receivedInstance: unknown;
|
|
427
|
+
|
|
428
|
+
const plugin: Schmock.Plugin = {
|
|
429
|
+
name: "install-test",
|
|
430
|
+
install(instance) {
|
|
431
|
+
receivedInstance = instance;
|
|
432
|
+
},
|
|
433
|
+
process: (ctx: any, res: any) => ({ context: ctx, response: res }),
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
mock.pipe(plugin);
|
|
437
|
+
expect(receivedInstance).toBe(mock);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("works normally when plugin has no install method", async () => {
|
|
441
|
+
const mock = schmock();
|
|
442
|
+
|
|
443
|
+
const plugin: Schmock.Plugin = {
|
|
444
|
+
name: "no-install",
|
|
445
|
+
process: (ctx: any, res: any) => ({ context: ctx, response: res }),
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
mock("GET /test", "hello").pipe(plugin);
|
|
449
|
+
const response = await mock.handle("GET", "/test");
|
|
450
|
+
expect(response.body).toBe("hello");
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("allows install to register routes on the instance", async () => {
|
|
454
|
+
const mock = schmock();
|
|
455
|
+
|
|
456
|
+
const plugin: Schmock.Plugin = {
|
|
457
|
+
name: "route-installer",
|
|
458
|
+
install(instance) {
|
|
459
|
+
instance("GET /installed", { message: "from-install" });
|
|
460
|
+
},
|
|
461
|
+
process: (ctx: any, res: any) => ({ context: ctx, response: res }),
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
mock.pipe(plugin);
|
|
465
|
+
|
|
466
|
+
const response = await mock.handle("GET", "/installed");
|
|
467
|
+
expect(response.status).toBe(200);
|
|
468
|
+
expect(response.body).toEqual({ message: "from-install" });
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("allows install to register multiple routes", async () => {
|
|
472
|
+
const mock = schmock();
|
|
473
|
+
|
|
474
|
+
const plugin: Schmock.Plugin = {
|
|
475
|
+
name: "multi-route-installer",
|
|
476
|
+
install(instance) {
|
|
477
|
+
instance("GET /a", "route-a");
|
|
478
|
+
instance("POST /b", "route-b");
|
|
479
|
+
},
|
|
480
|
+
process: (ctx: any, res: any) => ({ context: ctx, response: res }),
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
mock.pipe(plugin);
|
|
484
|
+
|
|
485
|
+
const responseA = await mock.handle("GET", "/a");
|
|
486
|
+
expect(responseA.body).toBe("route-a");
|
|
487
|
+
|
|
488
|
+
const responseB = await mock.handle("POST", "/b");
|
|
489
|
+
expect(responseB.body).toBe("route-b");
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("routes registered in install work with generator functions", async () => {
|
|
493
|
+
const mock = schmock();
|
|
494
|
+
|
|
495
|
+
const plugin: Schmock.Plugin = {
|
|
496
|
+
name: "generator-installer",
|
|
497
|
+
install(instance) {
|
|
498
|
+
instance("GET /items/:id", (ctx) => ({
|
|
499
|
+
id: ctx.params.id,
|
|
500
|
+
name: "test",
|
|
501
|
+
}));
|
|
502
|
+
},
|
|
503
|
+
process: (ctx: any, res: any) => ({ context: ctx, response: res }),
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
mock.pipe(plugin);
|
|
507
|
+
|
|
508
|
+
const response = await mock.handle("GET", "/items/42");
|
|
509
|
+
expect(response.status).toBe(200);
|
|
510
|
+
expect(response.body).toEqual({ id: "42", name: "test" });
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
423
514
|
describe("debug logging", () => {
|
|
424
515
|
it("logs plugin pipeline execution with debug enabled", async () => {
|
|
425
516
|
const mock = schmock({ debug: true });
|
|
@@ -3,15 +3,14 @@ import { schmock } from "./index";
|
|
|
3
3
|
|
|
4
4
|
describe("response parsing", () => {
|
|
5
5
|
describe("tuple response formats", () => {
|
|
6
|
-
it("
|
|
6
|
+
it("treats single-element array [status] as data, not tuple", async () => {
|
|
7
7
|
const mock = schmock();
|
|
8
8
|
mock("GET /status-only", () => [204] as [number]);
|
|
9
9
|
|
|
10
10
|
const response = await mock.handle("GET", "/status-only");
|
|
11
11
|
|
|
12
|
-
expect(response.status).toBe(
|
|
13
|
-
expect(response.body).
|
|
14
|
-
expect(response.headers).toEqual({});
|
|
12
|
+
expect(response.status).toBe(200);
|
|
13
|
+
expect(response.body).toEqual([204]);
|
|
15
14
|
});
|
|
16
15
|
|
|
17
16
|
it("handles [status, body] tuple", async () => {
|
|
@@ -61,7 +60,7 @@ describe("response parsing", () => {
|
|
|
61
60
|
expect(response.headers).toEqual({});
|
|
62
61
|
});
|
|
63
62
|
|
|
64
|
-
it("
|
|
63
|
+
it("treats arrays with more than 3 elements as data, not tuple", async () => {
|
|
65
64
|
const mock = schmock();
|
|
66
65
|
mock(
|
|
67
66
|
"GET /extra",
|
|
@@ -71,8 +70,13 @@ describe("response parsing", () => {
|
|
|
71
70
|
const response = await mock.handle("GET", "/extra");
|
|
72
71
|
|
|
73
72
|
expect(response.status).toBe(200);
|
|
74
|
-
expect(response.body).
|
|
75
|
-
|
|
73
|
+
expect(response.body).toEqual([
|
|
74
|
+
200,
|
|
75
|
+
"data",
|
|
76
|
+
{},
|
|
77
|
+
"ignored",
|
|
78
|
+
"also-ignored",
|
|
79
|
+
]);
|
|
76
80
|
});
|
|
77
81
|
|
|
78
82
|
it("treats non-numeric first element as body, not status", async () => {
|
|
@@ -342,7 +342,7 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
342
342
|
Scenario("Response tuple format edge cases", ({ Given, When, Then, And }) => {
|
|
343
343
|
Given("I create a mock with three tuple response routes", () => {
|
|
344
344
|
mock = schmock();
|
|
345
|
-
mock("GET /created", [201]);
|
|
345
|
+
mock("GET /created", [201, null]);
|
|
346
346
|
mock("GET /with-headers", [200, { data: true }, { "x-custom": "header" }]);
|
|
347
347
|
mock("GET /empty-with-status", [204, null]);
|
|
348
348
|
});
|