@typespec/spec-api 0.1.0-alpha.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/CHANGELOG.md +2 -0
- package/LICENSE +21 -0
- package/dist/expectation.d.ts +63 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +87 -0
- package/dist/expectation.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mock-request.d.ts +22 -0
- package/dist/mock-request.d.ts.map +1 -0
- package/dist/mock-request.js +23 -0
- package/dist/mock-request.js.map +1 -0
- package/dist/request-validations.d.ts +24 -0
- package/dist/request-validations.d.ts.map +1 -0
- package/dist/request-validations.js +164 -0
- package/dist/request-validations.js.map +1 -0
- package/dist/response-utils.d.ts +15 -0
- package/dist/response-utils.d.ts.map +1 -0
- package/dist/response-utils.js +24 -0
- package/dist/response-utils.js.map +1 -0
- package/dist/routes.d.ts +65 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +79 -0
- package/dist/routes.js.map +1 -0
- package/dist/scenarios.d.ts +26 -0
- package/dist/scenarios.d.ts.map +1 -0
- package/dist/scenarios.js +50 -0
- package/dist/scenarios.js.map +1 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation-error.d.ts +14 -0
- package/dist/validation-error.d.ts.map +1 -0
- package/dist/validation-error.js +20 -0
- package/dist/validation-error.js.map +1 -0
- package/package.json +54 -0
- package/src/expectation.ts +106 -0
- package/src/index.ts +48 -0
- package/src/mock-request.ts +31 -0
- package/src/request-validations.ts +216 -0
- package/src/response-utils.ts +26 -0
- package/src/routes.ts +92 -0
- package/src/scenarios.ts +76 -0
- package/src/types.ts +125 -0
- package/src/validation-error.ts +21 -0
- package/temp/.tsbuildinfo +1 -0
- package/test/expectation.test.ts +53 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +11 -0
package/dist/routes.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register a GET request for the provided uri.
|
|
3
|
+
* @param uri URI to match.
|
|
4
|
+
* @param func Request handler.
|
|
5
|
+
*/
|
|
6
|
+
function get(uri, func) {
|
|
7
|
+
return request("get", uri, func);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Register a POST request for the provided uri.
|
|
11
|
+
* @param uri URI to match.
|
|
12
|
+
* @param func Request handler.
|
|
13
|
+
*/
|
|
14
|
+
function post(uri, func) {
|
|
15
|
+
return request("post", uri, func);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register a PUT request for the provided uri.
|
|
19
|
+
* @param uri URI to match.
|
|
20
|
+
* @param func Request handler.
|
|
21
|
+
*/
|
|
22
|
+
function put(uri, func) {
|
|
23
|
+
return request("put", uri, func);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register a PATCH request for the provided uri.
|
|
27
|
+
* @param uri URI to match.
|
|
28
|
+
* @param func Request handler.
|
|
29
|
+
*/
|
|
30
|
+
function patch(uri, func) {
|
|
31
|
+
return request("patch", uri, func);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Register a DELETE request for the provided uri.
|
|
35
|
+
* @param uri URI to match.
|
|
36
|
+
* @param func Request handler.
|
|
37
|
+
*/
|
|
38
|
+
function deleteReq(uri, func) {
|
|
39
|
+
return request("delete", uri, func);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Register a Options request for the provided uri.
|
|
43
|
+
* @param uri URI to match.
|
|
44
|
+
* @param func Request handler.
|
|
45
|
+
*/
|
|
46
|
+
function options(uri, func) {
|
|
47
|
+
return request("options", uri, func);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Register a HEAD request for the provided uri.
|
|
51
|
+
* @param uri URI to match.
|
|
52
|
+
* @param name Name of the scenario(For coverage).
|
|
53
|
+
* @param func Request handler.
|
|
54
|
+
*/
|
|
55
|
+
function head(uri, func) {
|
|
56
|
+
return request("head", uri, func);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Register a request for the provided uri.
|
|
60
|
+
* @param method Method to use.
|
|
61
|
+
* @param uri URI to match.
|
|
62
|
+
* @param func Request handler.
|
|
63
|
+
*
|
|
64
|
+
* @note prefer to use the corresponding method method directly instead of `request()`(i.e `get(), post()`)
|
|
65
|
+
*/
|
|
66
|
+
function request(method, uri, handler) {
|
|
67
|
+
return { method, uri, handler };
|
|
68
|
+
}
|
|
69
|
+
export const mockapi = {
|
|
70
|
+
get,
|
|
71
|
+
post,
|
|
72
|
+
put,
|
|
73
|
+
patch,
|
|
74
|
+
delete: deleteReq,
|
|
75
|
+
options,
|
|
76
|
+
head,
|
|
77
|
+
request,
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,SAAS,GAAG,CAAqC,GAAW,EAAE,IAAO;IACnE,OAAO,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,SAAS,IAAI,CAA+B,GAAW,EAAE,IAAO;IAC9D,OAAO,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,SAAS,GAAG,CAA+B,GAAW,EAAE,IAAO;IAC7D,OAAO,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,SAAS,KAAK,CAA+B,GAAW,EAAE,IAAO;IAC/D,OAAO,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAA+B,GAAW,EAAE,IAAO;IACnE,OAAO,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,SAAS,OAAO,CAA+B,GAAW,EAAE,IAAO;IACjE,OAAO,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,SAAS,IAAI,CAA+B,GAAW,EAAE,IAAO;IAC9D,OAAO,OAAO,CAAI,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,OAAO,CACd,MAAkB,EAClB,GAAW,EACX,OAAU;IAEV,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAS,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,GAAG;IACH,IAAI;IACJ,GAAG;IACH,KAAK;IACL,MAAM,EAAE,SAAS;IACjB,OAAO;IACP,IAAI;IACJ,OAAO;CACR,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { KeyedMockApi, KeyedMockApiDefinition, MockApi, MockApiDefinition, PassByKeyScenario, PassByServiceKeyScenario, PassOnCodeScenario, PassOnSuccessScenario } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Specify that the scenario should be a `pass` if all the endpoints are called and the API response with 2xx exit code.
|
|
4
|
+
* @param apis Endpoint or List of endpoints for this scenario
|
|
5
|
+
*/
|
|
6
|
+
export declare function passOnSuccess(apis: MockApi | readonly MockApi[] | MockApiDefinition | readonly MockApiDefinition[]): PassOnSuccessScenario;
|
|
7
|
+
/**
|
|
8
|
+
* Specify that the scenario should be a `pass` if all the endpoints are called and the API response with the given exit code.
|
|
9
|
+
* @param code Status code all endpoint should return
|
|
10
|
+
* @param apis Endpoint or List of endpoints for this scenario
|
|
11
|
+
*/
|
|
12
|
+
export declare function passOnCode(code: number, apis: MockApi | readonly MockApi[] | MockApiDefinition): PassOnCodeScenario;
|
|
13
|
+
export interface WithKeysScenarioExpect<K extends string> {
|
|
14
|
+
pass(api: KeyedMockApi<K>): PassByKeyScenario<K>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Specify a list of keys that must be hit to this scenario to pass
|
|
18
|
+
* @param keys List of keys
|
|
19
|
+
* @param api Mock api that in the MockResponse can return a pass key.
|
|
20
|
+
*/
|
|
21
|
+
export declare function withKeys<const K extends string>(keys: K[]): WithKeysScenarioExpect<K>;
|
|
22
|
+
export interface WithServiceKeysScenarioExpect<K extends string> {
|
|
23
|
+
pass(api: KeyedMockApiDefinition<K> | KeyedMockApiDefinition<K>[]): PassByServiceKeyScenario<K>;
|
|
24
|
+
}
|
|
25
|
+
export declare function withServiceKeys<const K extends string>(keys: K[]): WithServiceKeysScenarioExpect<K>;
|
|
26
|
+
//# sourceMappingURL=scenarios.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../src/scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,GAAG,SAAS,OAAO,EAAE,GAAG,iBAAiB,GAAG,SAAS,iBAAiB,EAAE,GACpF,qBAAqB,CAKvB;AACD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GAAG,SAAS,OAAO,EAAE,GAAG,iBAAiB,GACrD,kBAAkB,CAMpB;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,MAAM;IACtD,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;CAClD;AACD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAUrF;AAED,MAAM,WAAW,6BAA6B,CAAC,CAAC,SAAS,MAAM;IAC7D,IAAI,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,EAAE,GAAG,wBAAwB,CAAC,CAAC,CAAC,CAAC;CACjG;AAED,wBAAgB,eAAe,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,EACpD,IAAI,EAAE,CAAC,EAAE,GACR,6BAA6B,CAAC,CAAC,CAAC,CAUlC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Specify that the scenario should be a `pass` if all the endpoints are called and the API response with 2xx exit code.
|
|
3
|
+
* @param apis Endpoint or List of endpoints for this scenario
|
|
4
|
+
*/
|
|
5
|
+
export function passOnSuccess(apis) {
|
|
6
|
+
return {
|
|
7
|
+
passCondition: "response-success",
|
|
8
|
+
apis: Array.isArray(apis) ? apis : [apis],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Specify that the scenario should be a `pass` if all the endpoints are called and the API response with the given exit code.
|
|
13
|
+
* @param code Status code all endpoint should return
|
|
14
|
+
* @param apis Endpoint or List of endpoints for this scenario
|
|
15
|
+
*/
|
|
16
|
+
export function passOnCode(code, apis) {
|
|
17
|
+
return {
|
|
18
|
+
passCondition: "status-code",
|
|
19
|
+
code,
|
|
20
|
+
apis: Array.isArray(apis) ? apis : [apis],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Specify a list of keys that must be hit to this scenario to pass
|
|
25
|
+
* @param keys List of keys
|
|
26
|
+
* @param api Mock api that in the MockResponse can return a pass key.
|
|
27
|
+
*/
|
|
28
|
+
export function withKeys(keys) {
|
|
29
|
+
return {
|
|
30
|
+
pass: (api) => {
|
|
31
|
+
return {
|
|
32
|
+
passCondition: "by-key",
|
|
33
|
+
keys,
|
|
34
|
+
apis: [api],
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function withServiceKeys(keys) {
|
|
40
|
+
return {
|
|
41
|
+
pass: (api) => {
|
|
42
|
+
return {
|
|
43
|
+
passCondition: "by-key",
|
|
44
|
+
keys,
|
|
45
|
+
apis: Array.isArray(api) ? api : [api],
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=scenarios.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenarios.js","sourceRoot":"","sources":["../src/scenarios.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAqF;IAErF,OAAO;QACL,aAAa,EAAE,kBAAkB;QACjC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;KAC1C,CAAC;AACJ,CAAC;AACD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,IAAsD;IAEtD,OAAO;QACL,aAAa,EAAE,aAAa;QAC5B,IAAI;QACJ,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;KAC1C,CAAC;AACJ,CAAC;AAKD;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAyB,IAAS;IACxD,OAAO;QACL,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;YACZ,OAAO;gBACL,aAAa,EAAE,QAAQ;gBACvB,IAAI;gBACJ,IAAI,EAAE,CAAC,GAAG,CAAC;aACZ,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,eAAe,CAC7B,IAAS;IAET,OAAO;QACL,IAAI,EAAE,CAAC,GAA4D,EAAE,EAAE;YACrE,OAAO;gBACL,aAAa,EAAE,QAAQ;gBACvB,IAAI;gBACJ,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;aACvC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Request } from "express";
|
|
2
|
+
import { MockRequest } from "./mock-request.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extension of the express.js request which include a rawBody.
|
|
5
|
+
*/
|
|
6
|
+
export interface RequestExt extends Request {
|
|
7
|
+
rawBody?: string | Buffer;
|
|
8
|
+
files?: {
|
|
9
|
+
[fieldname: string]: Express.Multer.File[];
|
|
10
|
+
} | Express.Multer.File[] | undefined;
|
|
11
|
+
}
|
|
12
|
+
export type ScenarioPassCondition = "response-success" | "status-code";
|
|
13
|
+
export interface PassOnSuccessScenario {
|
|
14
|
+
passCondition: "response-success";
|
|
15
|
+
apis: MockApi[] | MockApiDefinition[];
|
|
16
|
+
}
|
|
17
|
+
export interface PassOnCodeScenario {
|
|
18
|
+
passCondition: "status-code";
|
|
19
|
+
code: number;
|
|
20
|
+
apis: MockApi[] | MockApiDefinition[];
|
|
21
|
+
}
|
|
22
|
+
export interface PassByKeyScenario<K extends string = string> {
|
|
23
|
+
passCondition: "by-key";
|
|
24
|
+
keys: K[];
|
|
25
|
+
apis: KeyedMockApi<K>[];
|
|
26
|
+
}
|
|
27
|
+
export interface PassByServiceKeyScenario<K extends string = string> {
|
|
28
|
+
passCondition: "by-key";
|
|
29
|
+
keys: K[];
|
|
30
|
+
apis: KeyedMockApiDefinition<K>[];
|
|
31
|
+
}
|
|
32
|
+
export type ScenarioMockApi = PassOnSuccessScenario | PassOnCodeScenario | PassByKeyScenario | PassByServiceKeyScenario;
|
|
33
|
+
export type MockRequestHandler = SimpleMockRequestHandler | KeyedMockRequestHandler;
|
|
34
|
+
export type SimpleMockRequestHandler = (req: MockRequest) => MockResponse | Promise<MockResponse>;
|
|
35
|
+
export type KeyedMockRequestHandler<T extends string = string> = (req: MockRequest) => KeyedMockResponse<T> | Promise<KeyedMockResponse<T>>;
|
|
36
|
+
export type KeyedServiceRequestHandler<T extends string = string> = (req: ServiceRequest) => KeyedMockResponse<T> | Promise<KeyedMockResponse<T>>;
|
|
37
|
+
export type HttpMethod = "get" | "post" | "put" | "patch" | "delete" | "head" | "options";
|
|
38
|
+
export type MockApiForHandler<Handler extends MockRequestHandler> = Handler extends KeyedMockRequestHandler<infer K> ? KeyedMockApi<K> : MockApi;
|
|
39
|
+
export interface MockApi {
|
|
40
|
+
method: HttpMethod;
|
|
41
|
+
uri: string;
|
|
42
|
+
handler: MockRequestHandler;
|
|
43
|
+
kind: "MockApi";
|
|
44
|
+
}
|
|
45
|
+
export interface MockApiDefinition {
|
|
46
|
+
uri: string;
|
|
47
|
+
method: HttpMethod;
|
|
48
|
+
request: ServiceRequest;
|
|
49
|
+
response: MockResponse;
|
|
50
|
+
handler?: MockRequestHandler;
|
|
51
|
+
kind: "MockApiDefinition";
|
|
52
|
+
}
|
|
53
|
+
export interface ServiceRequestFile {
|
|
54
|
+
fieldname: string;
|
|
55
|
+
originalname: string;
|
|
56
|
+
buffer: Buffer;
|
|
57
|
+
mimetype: string;
|
|
58
|
+
}
|
|
59
|
+
export interface ServiceRequest {
|
|
60
|
+
body?: any;
|
|
61
|
+
status?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Query parameters to match to the request.
|
|
64
|
+
*/
|
|
65
|
+
params?: Record<string, unknown>;
|
|
66
|
+
headers?: Record<string, unknown>;
|
|
67
|
+
files?: ServiceRequestFile[];
|
|
68
|
+
}
|
|
69
|
+
export declare const Fail: unique symbol;
|
|
70
|
+
export interface KeyedMockApi<K extends string> extends MockApi {
|
|
71
|
+
handler: KeyedMockRequestHandler<K>;
|
|
72
|
+
}
|
|
73
|
+
export interface KeyedMockApiDefinition<K extends string> extends MockApiDefinition {
|
|
74
|
+
handler: KeyedMockRequestHandler<K>;
|
|
75
|
+
}
|
|
76
|
+
export interface MockResponse {
|
|
77
|
+
status: number;
|
|
78
|
+
headers?: {
|
|
79
|
+
[key: string]: string | null;
|
|
80
|
+
};
|
|
81
|
+
body?: MockResponseBody;
|
|
82
|
+
/**
|
|
83
|
+
* Let the mock API know that this request was successful to counting coverage regardless of the status code.
|
|
84
|
+
* By default only 2xx status code will count toward success.
|
|
85
|
+
*/
|
|
86
|
+
testSuccessful?: boolean;
|
|
87
|
+
}
|
|
88
|
+
export interface KeyedMockResponse<K extends string = string> extends MockResponse {
|
|
89
|
+
pass: K | typeof Fail;
|
|
90
|
+
}
|
|
91
|
+
export interface MockResponseBody {
|
|
92
|
+
contentType: string;
|
|
93
|
+
rawContent: string | Buffer | undefined;
|
|
94
|
+
}
|
|
95
|
+
export type CollectionFormat = "multi" | "csv" | "ssv" | "tsv" | "pipes";
|
|
96
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA4FA,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class ValidationError extends Error {
|
|
2
|
+
expected: unknown | undefined;
|
|
3
|
+
actual: unknown | undefined;
|
|
4
|
+
status: number;
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown there there is a validation issue.
|
|
7
|
+
* @param message Message describing the error.
|
|
8
|
+
* @param expected expected value.
|
|
9
|
+
* @param actual actual value.
|
|
10
|
+
*/
|
|
11
|
+
constructor(message: string, expected: unknown | undefined, actual: unknown | undefined);
|
|
12
|
+
toJSON(): string;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=validation-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation-error.d.ts","sourceRoot":"","sources":["../src/validation-error.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAgB,SAAQ,KAAK;IAW/B,QAAQ,EAAE,OAAO,GAAG,SAAS;IAC7B,MAAM,EAAE,OAAO,GAAG,SAAS;IAX7B,MAAM,SAAO;IAEpB;;;;;OAKG;gBAED,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,OAAO,GAAG,SAAS,EAC7B,MAAM,EAAE,OAAO,GAAG,SAAS;IAK7B,MAAM,IAAI,MAAM;CAGxB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class ValidationError extends Error {
|
|
2
|
+
expected;
|
|
3
|
+
actual;
|
|
4
|
+
status = 400;
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown there there is a validation issue.
|
|
7
|
+
* @param message Message describing the error.
|
|
8
|
+
* @param expected expected value.
|
|
9
|
+
* @param actual actual value.
|
|
10
|
+
*/
|
|
11
|
+
constructor(message, expected, actual) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.expected = expected;
|
|
14
|
+
this.actual = actual;
|
|
15
|
+
}
|
|
16
|
+
toJSON() {
|
|
17
|
+
return JSON.stringify({ message: this.message, expected: this.expected, actual: this.actual });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=validation-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation-error.js","sourceRoot":"","sources":["../src/validation-error.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAW/B;IACA;IAXF,MAAM,GAAG,GAAG,CAAC;IAEpB;;;;;OAKG;IACH,YACE,OAAe,EACR,QAA6B,EAC7B,MAA2B;QAElC,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,aAAQ,GAAR,QAAQ,CAAqB;QAC7B,WAAM,GAAN,MAAM,CAAqB;IAGpC,CAAC;IAEM,MAAM;QACX,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjG,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@typespec/spec-api",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "Spec api to implement mock api",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.0.0"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/microsoft/typespec.git"
|
|
13
|
+
},
|
|
14
|
+
"author": "Microsoft",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/microsoft/typespec/issues"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/microsoft/typespec#readme",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"body-parser": "^1.20.3",
|
|
22
|
+
"deep-equal": "^2.2.0",
|
|
23
|
+
"express": "^4.21.1",
|
|
24
|
+
"express-promise-router": "^4.1.1",
|
|
25
|
+
"morgan": "^1.10.0",
|
|
26
|
+
"multer": "^1.4.5-lts.1",
|
|
27
|
+
"picocolors": "~1.1.0",
|
|
28
|
+
"prettier": "~3.3.3",
|
|
29
|
+
"winston": "^3.15.0",
|
|
30
|
+
"xml2js": "^0.6.2",
|
|
31
|
+
"yargs": "~17.7.2"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/body-parser": "^1.19.2",
|
|
35
|
+
"@types/deep-equal": "^1.0.1",
|
|
36
|
+
"@types/express": "^5.0.0",
|
|
37
|
+
"@types/morgan": "^1.9.4",
|
|
38
|
+
"@types/multer": "^1.4.10",
|
|
39
|
+
"@types/node": "~22.7.5",
|
|
40
|
+
"@types/xml2js": "^0.4.11",
|
|
41
|
+
"@types/yargs": "~17.0.33",
|
|
42
|
+
"@vitest/coverage-v8": "^2.1.2",
|
|
43
|
+
"@vitest/ui": "^2.1.2",
|
|
44
|
+
"rimraf": "~6.0.1",
|
|
45
|
+
"typescript": "~5.6.3",
|
|
46
|
+
"vitest": "^2.1.2"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"watch": "tsc -p ./tsconfig.build.json --watch",
|
|
50
|
+
"build": "tsc -p ./tsconfig.build.json",
|
|
51
|
+
"clean": "rimraf dist/ temp/",
|
|
52
|
+
"test": "vitest run"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import deepEqual from "deep-equal";
|
|
2
|
+
import {
|
|
3
|
+
validateBodyEmpty,
|
|
4
|
+
validateBodyEquals,
|
|
5
|
+
validateBodyNotEmpty,
|
|
6
|
+
validateCoercedDateBodyEquals,
|
|
7
|
+
validateHeader,
|
|
8
|
+
validateQueryParam,
|
|
9
|
+
validateRawBodyEquals,
|
|
10
|
+
validateXmlBodyEquals,
|
|
11
|
+
} from "./request-validations.js";
|
|
12
|
+
import { CollectionFormat, RequestExt } from "./types.js";
|
|
13
|
+
import { ValidationError } from "./validation-error.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Class containing all the expectations that can be run on the request.
|
|
17
|
+
*/
|
|
18
|
+
export class RequestExpectation {
|
|
19
|
+
public constructor(private originalRequest: RequestExt) {}
|
|
20
|
+
/**
|
|
21
|
+
* Expect the raw body of the request to match the given string.
|
|
22
|
+
* @param rawBody Raw request body.
|
|
23
|
+
* @throws {ValidationError} if there is an error.
|
|
24
|
+
*/
|
|
25
|
+
public rawBodyEquals(expectedRawBody: string | Buffer | undefined): void {
|
|
26
|
+
validateRawBodyEquals(this.originalRequest, expectedRawBody);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Expect the body of the request to match the given object.
|
|
31
|
+
* @param rawBody Raw request body.
|
|
32
|
+
* @throws {ValidationError} if there is an error.
|
|
33
|
+
*/
|
|
34
|
+
public bodyEquals(expectedRawBody: unknown | undefined): void {
|
|
35
|
+
validateBodyEquals(this.originalRequest, expectedRawBody);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Expect the coerced body of the request to match the given object.
|
|
40
|
+
* @param rawBody Raw request body.
|
|
41
|
+
* @throws {ValidationError} if there is an error.
|
|
42
|
+
*/
|
|
43
|
+
public coercedBodyEquals(expectedRawBody: unknown | undefined): void {
|
|
44
|
+
validateCoercedDateBodyEquals(this.originalRequest, expectedRawBody);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Expect the body of the request to be empty.
|
|
49
|
+
* @throws {ValidationError} if there is an error.
|
|
50
|
+
*/
|
|
51
|
+
public bodyEmpty(): void {
|
|
52
|
+
validateBodyEmpty(this.originalRequest);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Expect the body of the request to be not empty.
|
|
57
|
+
* @throws {ValidationError} if there is an error.
|
|
58
|
+
*/
|
|
59
|
+
public bodyNotEmpty(): void {
|
|
60
|
+
validateBodyNotEmpty(this.originalRequest);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Expect the header of the request contains the expected key/value pair
|
|
65
|
+
* @param headerName Key expected in header
|
|
66
|
+
* @param expectedValue Values expected in header
|
|
67
|
+
* @throws {ValidationError} if there is an error.
|
|
68
|
+
*/
|
|
69
|
+
public containsHeader(headerName: string, expectedValue: string): void {
|
|
70
|
+
validateHeader(this.originalRequest, headerName, expectedValue);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Expect the query string of the request contains the expected name/value pair.
|
|
75
|
+
* @param paramName Name of the query parameter.
|
|
76
|
+
* @param expectedValue Value expected of the query parameter.
|
|
77
|
+
*/
|
|
78
|
+
public containsQueryParam(
|
|
79
|
+
paramName: string,
|
|
80
|
+
expectedValue: string | string[],
|
|
81
|
+
collectionFormat?: CollectionFormat,
|
|
82
|
+
): void {
|
|
83
|
+
validateQueryParam(this.originalRequest, paramName, expectedValue, collectionFormat);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if two requests are equal
|
|
88
|
+
* @param actual Actual value
|
|
89
|
+
* @param expected Expected value
|
|
90
|
+
*/
|
|
91
|
+
public deepEqual(actual: unknown, expected: unknown, message = "Values not deep equal"): void {
|
|
92
|
+
if (!deepEqual(actual, expected, { strict: true })) {
|
|
93
|
+
throw new ValidationError(message, expected, actual);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Expect the body of the request to be semantically equivalent to the provided XML string.
|
|
99
|
+
* The XML declaration prefix will automatically be added to expectedBody.
|
|
100
|
+
* @param expectedBody expected value of request body.
|
|
101
|
+
* @throws {ValidationError} if there is an error.
|
|
102
|
+
*/
|
|
103
|
+
public xmlBodyEquals(expectedBody: string): void {
|
|
104
|
+
validateXmlBodyEquals(this.originalRequest, expectedBody);
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export { MockRequest } from "./mock-request.js";
|
|
2
|
+
export {
|
|
3
|
+
BODY_EMPTY_ERROR_MESSAGE,
|
|
4
|
+
BODY_NOT_EMPTY_ERROR_MESSAGE,
|
|
5
|
+
BODY_NOT_EQUAL_ERROR_MESSAGE,
|
|
6
|
+
validateBodyEmpty,
|
|
7
|
+
validateBodyEquals,
|
|
8
|
+
validateBodyNotEmpty,
|
|
9
|
+
validateCoercedDateBodyEquals,
|
|
10
|
+
validateHeader,
|
|
11
|
+
validateQueryParam,
|
|
12
|
+
validateRawBodyEquals,
|
|
13
|
+
validateValueFormat,
|
|
14
|
+
validateXmlBodyEquals,
|
|
15
|
+
} from "./request-validations.js";
|
|
16
|
+
export { json, xml } from "./response-utils.js";
|
|
17
|
+
export { mockapi } from "./routes.js";
|
|
18
|
+
export {
|
|
19
|
+
WithKeysScenarioExpect,
|
|
20
|
+
passOnCode,
|
|
21
|
+
passOnSuccess,
|
|
22
|
+
withKeys,
|
|
23
|
+
withServiceKeys,
|
|
24
|
+
} from "./scenarios.js";
|
|
25
|
+
export {
|
|
26
|
+
CollectionFormat,
|
|
27
|
+
Fail,
|
|
28
|
+
HttpMethod,
|
|
29
|
+
KeyedMockApi,
|
|
30
|
+
KeyedMockRequestHandler,
|
|
31
|
+
KeyedMockResponse,
|
|
32
|
+
MockApi,
|
|
33
|
+
MockApiDefinition,
|
|
34
|
+
MockApiForHandler,
|
|
35
|
+
MockRequestHandler,
|
|
36
|
+
MockResponse,
|
|
37
|
+
MockResponseBody,
|
|
38
|
+
PassByKeyScenario,
|
|
39
|
+
PassByServiceKeyScenario,
|
|
40
|
+
PassOnCodeScenario,
|
|
41
|
+
PassOnSuccessScenario,
|
|
42
|
+
RequestExt,
|
|
43
|
+
ScenarioMockApi,
|
|
44
|
+
ScenarioPassCondition,
|
|
45
|
+
ServiceRequestFile,
|
|
46
|
+
SimpleMockRequestHandler,
|
|
47
|
+
} from "./types.js";
|
|
48
|
+
export { ValidationError } from "./validation-error.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RequestExpectation } from "./expectation.js";
|
|
2
|
+
import { RequestExt } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export class MockRequest {
|
|
5
|
+
public readonly expect: RequestExpectation;
|
|
6
|
+
|
|
7
|
+
public readonly baseUrl: string;
|
|
8
|
+
public readonly headers: { [key: string]: string };
|
|
9
|
+
public readonly query: { [key: string]: string | string[] };
|
|
10
|
+
public readonly params: { [key: string]: string };
|
|
11
|
+
public readonly body: any;
|
|
12
|
+
public readonly files?:
|
|
13
|
+
| {
|
|
14
|
+
[fieldname: string]: Express.Multer.File[];
|
|
15
|
+
}
|
|
16
|
+
| Express.Multer.File[]
|
|
17
|
+
| undefined;
|
|
18
|
+
|
|
19
|
+
public constructor(public originalRequest: RequestExt) {
|
|
20
|
+
this.baseUrl = getRequestBaseUrl(originalRequest);
|
|
21
|
+
this.expect = new RequestExpectation(originalRequest);
|
|
22
|
+
this.headers = originalRequest.headers as { [key: string]: string };
|
|
23
|
+
this.query = originalRequest.query as { [key: string]: string };
|
|
24
|
+
this.params = originalRequest.params as { [key: string]: string };
|
|
25
|
+
this.body = originalRequest.body;
|
|
26
|
+
this.files = originalRequest.files;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const getRequestBaseUrl = (request: RequestExt): string =>
|
|
31
|
+
`${request.protocol}://${request.get("host")}`;
|