@loopback/testlab 4.0.0-alpha.7 → 4.0.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/LICENSE +25 -0
- package/README.md +411 -20
- package/dist/client.d.ts +25 -0
- package/dist/client.js +40 -0
- package/dist/client.js.map +1 -0
- package/dist/expect.d.ts +2 -0
- package/{lib/testlab.js → dist/expect.js} +4 -11
- package/dist/expect.js.map +1 -0
- package/dist/http-error-logger.d.ts +9 -0
- package/dist/http-error-logger.js +23 -0
- package/dist/http-error-logger.js.map +1 -0
- package/dist/http-server-config.d.ts +26 -0
- package/dist/http-server-config.js +76 -0
- package/dist/http-server-config.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/request.d.ts +13 -0
- package/dist/request.js +40 -0
- package/dist/request.js.map +1 -0
- package/dist/shot.d.ts +24 -0
- package/dist/shot.js +108 -0
- package/dist/shot.js.map +1 -0
- package/dist/sinon.d.ts +25 -0
- package/dist/sinon.js +35 -0
- package/dist/sinon.js.map +1 -0
- package/dist/skip.d.ts +49 -0
- package/dist/skip.js +65 -0
- package/dist/skip.js.map +1 -0
- package/dist/test-sandbox.d.ts +89 -0
- package/dist/test-sandbox.js +139 -0
- package/dist/test-sandbox.js.map +1 -0
- package/dist/to-json.d.ts +23 -0
- package/dist/to-json.js +12 -0
- package/dist/to-json.js.map +1 -0
- package/dist/validate-api-spec.d.ts +1 -0
- package/dist/validate-api-spec.js +16 -0
- package/dist/validate-api-spec.js.map +1 -0
- package/fixtures/README.md +9 -0
- package/fixtures/cert.pem +21 -0
- package/fixtures/copy-me.txt +1 -0
- package/fixtures/key.pem +28 -0
- package/fixtures/pfx.pfx +0 -0
- package/package.json +46 -32
- package/should-as-function.d.ts +12 -3
- package/src/client.ts +57 -0
- package/src/expect.ts +15 -0
- package/src/http-error-logger.ts +35 -0
- package/src/http-server-config.ts +97 -0
- package/src/index.ts +37 -0
- package/src/request.ts +45 -0
- package/src/shot.ts +191 -0
- package/src/sinon.ts +38 -0
- package/src/skip.ts +76 -0
- package/src/test-sandbox.ts +181 -0
- package/src/to-json.ts +54 -0
- package/src/validate-api-spec.ts +14 -0
- package/index.d.ts +0 -6
- package/index.js +0 -9
- package/lib/client.d.ts +0 -23
- package/lib/client.js +0 -34
- package/lib/client.js.map +0 -1
- package/lib/shot.d.ts +0 -17
- package/lib/shot.js +0 -36
- package/lib/shot.js.map +0 -1
- package/lib/testlab.d.ts +0 -9
- package/lib/testlab.js.map +0 -1
- package/lib/validate-api-spec.d.ts +0 -2
- package/lib/validate-api-spec.js +0 -29
- package/lib/validate-api-spec.js.map +0 -1
- package/lib6/client.d.ts +0 -23
- package/lib6/client.js +0 -44
- package/lib6/client.js.map +0 -1
- package/lib6/shot.d.ts +0 -17
- package/lib6/shot.js +0 -36
- package/lib6/shot.js.map +0 -1
- package/lib6/testlab.d.ts +0 -9
- package/lib6/testlab.js +0 -22
- package/lib6/testlab.js.map +0 -1
- package/lib6/validate-api-spec.d.ts +0 -2
- package/lib6/validate-api-spec.js +0 -39
- package/lib6/validate-api-spec.js.map +0 -1
package/src/request.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/testlab
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import http, {IncomingMessage} from 'http';
|
|
7
|
+
import https from 'https';
|
|
8
|
+
import url from 'url';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Async wrapper for making HTTP GET requests
|
|
12
|
+
* @param urlString
|
|
13
|
+
*/
|
|
14
|
+
export function httpGetAsync(
|
|
15
|
+
urlString: string,
|
|
16
|
+
agent?: http.Agent,
|
|
17
|
+
): Promise<IncomingMessage> {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const urlOptions = url.parse(urlString);
|
|
20
|
+
const options = {agent, ...urlOptions};
|
|
21
|
+
http.get(options, resolve).on('error', reject);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Async wrapper for making HTTPS GET requests
|
|
27
|
+
* @param urlString
|
|
28
|
+
*/
|
|
29
|
+
export function httpsGetAsync(
|
|
30
|
+
urlString: string,
|
|
31
|
+
agent?: https.Agent,
|
|
32
|
+
): Promise<IncomingMessage> {
|
|
33
|
+
agent =
|
|
34
|
+
agent ??
|
|
35
|
+
new https.Agent({
|
|
36
|
+
rejectUnauthorized: false,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const urlOptions = url.parse(urlString);
|
|
40
|
+
const options = {agent, ...urlOptions};
|
|
41
|
+
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
https.get(options, resolve).on('error', reject);
|
|
44
|
+
});
|
|
45
|
+
}
|
package/src/shot.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2017,2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/testlab
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* HTTP Request/Response mocks
|
|
8
|
+
* https://github.com/hapijs/shot
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
12
|
+
|
|
13
|
+
import express from 'express';
|
|
14
|
+
import {IncomingMessage, ServerResponse} from 'http';
|
|
15
|
+
import {
|
|
16
|
+
Listener as ShotListener,
|
|
17
|
+
RequestOptions as ShotRequestOptions,
|
|
18
|
+
ResponseObject,
|
|
19
|
+
} from 'shot'; // <-- workaround for missing type-defs for @hapi/shot
|
|
20
|
+
import util from 'util';
|
|
21
|
+
|
|
22
|
+
const inject: (
|
|
23
|
+
dispatchFunc: ShotListener,
|
|
24
|
+
options: ShotRequestOptions,
|
|
25
|
+
) => Promise<ResponseObject> = require('@hapi/shot');
|
|
26
|
+
// ^^ workaround for missing type-defs for @hapi/shot
|
|
27
|
+
|
|
28
|
+
export {inject, ShotRequestOptions};
|
|
29
|
+
|
|
30
|
+
const ShotRequest: ShotRequestCtor = require('@hapi/shot/lib/request');
|
|
31
|
+
type ShotRequestCtor = new (options: ShotRequestOptions) => IncomingMessage;
|
|
32
|
+
|
|
33
|
+
export function stubServerRequest(
|
|
34
|
+
options: ShotRequestOptions,
|
|
35
|
+
): IncomingMessage {
|
|
36
|
+
const stub = new ShotRequest(options);
|
|
37
|
+
// Hacky workaround for Express, see
|
|
38
|
+
// https://github.com/expressjs/express/blob/4.16.3/lib/middleware/init.js
|
|
39
|
+
// https://github.com/hapijs/shot/issues/82#issuecomment-247943773
|
|
40
|
+
// https://github.com/jfhbrook/pickleback
|
|
41
|
+
Object.assign(stub, ShotRequest.prototype);
|
|
42
|
+
return stub;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ShotResponse: ShotResponseCtor = require('@hapi/shot/lib/response');
|
|
46
|
+
export type ShotCallback = (response: ResponseObject) => void;
|
|
47
|
+
|
|
48
|
+
export type ShotResponseCtor = new (
|
|
49
|
+
request: IncomingMessage,
|
|
50
|
+
onEnd: ShotCallback,
|
|
51
|
+
) => ServerResponse;
|
|
52
|
+
|
|
53
|
+
export function stubServerResponse(
|
|
54
|
+
request: IncomingMessage,
|
|
55
|
+
onEnd: ShotCallback,
|
|
56
|
+
): ServerResponse {
|
|
57
|
+
const stub = new ShotResponse(request, onEnd);
|
|
58
|
+
// Hacky workaround for Express, see
|
|
59
|
+
// https://github.com/expressjs/express/blob/4.16.3/lib/middleware/init.js
|
|
60
|
+
// https://github.com/hapijs/shot/issues/82#issuecomment-247943773
|
|
61
|
+
// https://github.com/jfhbrook/pickleback
|
|
62
|
+
Object.assign(stub, ShotResponse.prototype);
|
|
63
|
+
return stub;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type ObservedResponse = ResponseObject;
|
|
67
|
+
|
|
68
|
+
export interface HandlerContextStub {
|
|
69
|
+
request: IncomingMessage;
|
|
70
|
+
response: ServerResponse;
|
|
71
|
+
result: Promise<ObservedResponse>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function stubHandlerContext(
|
|
75
|
+
requestOptions: ShotRequestOptions = {url: '/'},
|
|
76
|
+
): HandlerContextStub {
|
|
77
|
+
const request = stubServerRequest(requestOptions);
|
|
78
|
+
let response: ServerResponse | undefined;
|
|
79
|
+
const result = new Promise<ObservedResponse>(resolve => {
|
|
80
|
+
response = new ShotResponse(request, resolve);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const context = {request, response: response!, result};
|
|
84
|
+
defineCustomContextInspect(context, requestOptions);
|
|
85
|
+
return context;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ExpressContextStub extends HandlerContextStub {
|
|
89
|
+
app: express.Application;
|
|
90
|
+
request: express.Request;
|
|
91
|
+
response: express.Response;
|
|
92
|
+
result: Promise<ObservedResponse>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function stubExpressContext(
|
|
96
|
+
requestOptions: ShotRequestOptions = {url: '/'},
|
|
97
|
+
): ExpressContextStub {
|
|
98
|
+
const app = express();
|
|
99
|
+
|
|
100
|
+
const request = new ShotRequest(requestOptions) as express.Request;
|
|
101
|
+
// mix in Express Request API
|
|
102
|
+
const RequestApi = (express as any).request;
|
|
103
|
+
for (const key of Object.getOwnPropertyNames(RequestApi)) {
|
|
104
|
+
Object.defineProperty(
|
|
105
|
+
request,
|
|
106
|
+
key,
|
|
107
|
+
Object.getOwnPropertyDescriptor(RequestApi, key)!,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
request.app = app;
|
|
111
|
+
request.originalUrl = request.url;
|
|
112
|
+
parseQuery(request);
|
|
113
|
+
|
|
114
|
+
let response: express.Response | undefined;
|
|
115
|
+
const result = new Promise<ObservedResponse>(resolve => {
|
|
116
|
+
response = new ShotResponse(request, resolve) as express.Response;
|
|
117
|
+
// mix in Express Response API
|
|
118
|
+
Object.assign(response, (express as any).response);
|
|
119
|
+
const ResponseApi = (express as any).response;
|
|
120
|
+
for (const key of Object.getOwnPropertyNames(ResponseApi)) {
|
|
121
|
+
Object.defineProperty(
|
|
122
|
+
response,
|
|
123
|
+
key,
|
|
124
|
+
Object.getOwnPropertyDescriptor(ResponseApi, key)!,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
response.app = app;
|
|
128
|
+
(response as any).req = request;
|
|
129
|
+
(request as any).res = response;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const context = {app, request, response: response!, result};
|
|
133
|
+
defineCustomContextInspect(context, requestOptions);
|
|
134
|
+
return context;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Use `express.query` to parse the query string into `request.query` object
|
|
139
|
+
* @param request - Express http request object
|
|
140
|
+
*/
|
|
141
|
+
function parseQuery(request: express.Request) {
|
|
142
|
+
// Use `express.query` to parse the query string
|
|
143
|
+
// See https://github.com/expressjs/express/blob/master/lib/express.js#L79
|
|
144
|
+
// See https://github.com/expressjs/express/blob/master/lib/middleware/query.js
|
|
145
|
+
(express as any).query()(request, {}, () => {});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function defineCustomContextInspect(
|
|
149
|
+
context: HandlerContextStub,
|
|
150
|
+
requestOptions: ShotRequestOptions,
|
|
151
|
+
) {
|
|
152
|
+
// Setup custom inspect functions to make test error messages easier to read
|
|
153
|
+
const inspectOpts = (depth: number, opts: any) =>
|
|
154
|
+
util.inspect(requestOptions, opts);
|
|
155
|
+
|
|
156
|
+
defineCustomInspect(
|
|
157
|
+
context.request,
|
|
158
|
+
(depth, opts) => `[RequestStub with options ${inspectOpts(depth, opts)}]`,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
defineCustomInspect(
|
|
162
|
+
context.response,
|
|
163
|
+
(depth, opts) =>
|
|
164
|
+
`[ResponseStub for request with options ${inspectOpts(depth, opts)}]`,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
context.result = context.result.then(r => {
|
|
168
|
+
defineCustomInspect(
|
|
169
|
+
r,
|
|
170
|
+
(depth, opts) =>
|
|
171
|
+
`[ObservedResponse for request with options ${inspectOpts(
|
|
172
|
+
depth,
|
|
173
|
+
opts,
|
|
174
|
+
)}]`,
|
|
175
|
+
);
|
|
176
|
+
return r;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// @types/node@v10.17.29 seems to miss the type definition of `util.inspect.custom`
|
|
181
|
+
// error TS2339: Property 'custom' does not exist on type 'typeof inspect'.
|
|
182
|
+
// Use a workaround for now to access the `custom` symbol for now.
|
|
183
|
+
// https://nodejs.org/api/util.html#util_util_inspect_custom
|
|
184
|
+
const custom = Symbol.for('nodejs.util.inspect.custom');
|
|
185
|
+
|
|
186
|
+
function defineCustomInspect(
|
|
187
|
+
obj: any,
|
|
188
|
+
inspectFn: (depth: number, opts: any) => {},
|
|
189
|
+
) {
|
|
190
|
+
obj[custom] = inspectFn;
|
|
191
|
+
}
|
package/src/sinon.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/testlab
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import sinon, {SinonSpy} from 'sinon';
|
|
7
|
+
|
|
8
|
+
export {sinon, SinonSpy};
|
|
9
|
+
|
|
10
|
+
export type StubbedInstanceWithSinonAccessor<T> = T & {
|
|
11
|
+
stubs: sinon.SinonStubbedInstance<T>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new object with the given functions as the prototype and stubs all
|
|
16
|
+
* implemented functions.
|
|
17
|
+
*
|
|
18
|
+
* Note: The given constructor function is not invoked. See also the stub API.
|
|
19
|
+
*
|
|
20
|
+
* This is a helper method replacing `sinon.createStubInstance` and working
|
|
21
|
+
* around the limitations of TypeScript and Sinon, where Sinon is not able to
|
|
22
|
+
* list private/protected members in the type definition of the stub instance
|
|
23
|
+
* and therefore the stub instance cannot be assigned to places expecting TType.
|
|
24
|
+
* See also
|
|
25
|
+
* - https://github.com/Microsoft/TypeScript/issues/13543
|
|
26
|
+
* - https://github.com/DefinitelyTyped/DefinitelyTyped/issues/14811
|
|
27
|
+
*
|
|
28
|
+
* @typeParam TType - Type being stubbed.
|
|
29
|
+
* @param constructor - Object or class to stub.
|
|
30
|
+
* @returns A stubbed version of the constructor, with an extra property `stubs`
|
|
31
|
+
* providing access to stub API for individual methods.
|
|
32
|
+
*/
|
|
33
|
+
export function createStubInstance<TType>(
|
|
34
|
+
constructor: sinon.StubbableType<TType>,
|
|
35
|
+
): StubbedInstanceWithSinonAccessor<TType> {
|
|
36
|
+
const stub = sinon.createStubInstance(constructor);
|
|
37
|
+
return Object.assign(stub as TType, {stubs: stub});
|
|
38
|
+
}
|
package/src/skip.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/testlab
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A function defining a new test case or a test suite, e.g. `it` or `describe`.
|
|
8
|
+
*/
|
|
9
|
+
export type TestDefinition<ARGS extends unknown[], RETVAL> = (
|
|
10
|
+
name: string,
|
|
11
|
+
...args: ARGS
|
|
12
|
+
) => RETVAL;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Helper function for skipping tests when a certain condition is met.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* skipIf(
|
|
20
|
+
* !features.freeFormProperties,
|
|
21
|
+
* describe,
|
|
22
|
+
* 'free-form properties (strict: false)',
|
|
23
|
+
* () => {
|
|
24
|
+
* // the tests
|
|
25
|
+
* }
|
|
26
|
+
* );
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @param skip - Should the test case/suite be skipped?
|
|
30
|
+
* @param verb - The function to invoke to define the test case or the test
|
|
31
|
+
* suite, e.g. `it` or `describe`.
|
|
32
|
+
* @param name - The test name (the first argument of `verb` function).
|
|
33
|
+
* @param args - Additional arguments (framework specific), typically a function
|
|
34
|
+
* implementing the test.
|
|
35
|
+
*/
|
|
36
|
+
export function skipIf<ARGS extends unknown[], RETVAL>(
|
|
37
|
+
skip: boolean,
|
|
38
|
+
verb: TestDefinition<ARGS, RETVAL> & {skip: TestDefinition<ARGS, RETVAL>},
|
|
39
|
+
name: string,
|
|
40
|
+
...args: ARGS
|
|
41
|
+
): RETVAL {
|
|
42
|
+
if (skip) {
|
|
43
|
+
return verb.skip(`[SKIPPED] ${name}`, ...args);
|
|
44
|
+
} else {
|
|
45
|
+
return verb(name, ...args);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Helper function for skipping tests on Travis CI.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* skipOnTravis(it, 'does something when some condition', async () => {
|
|
56
|
+
* // the test
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @param verb - The function to invoke to define the test case or the test
|
|
61
|
+
* suite, e.g. `it` or `describe`.
|
|
62
|
+
* @param name - The test name (the first argument of `verb` function).
|
|
63
|
+
* @param args - Additional arguments (framework specific), typically a function
|
|
64
|
+
* implementing the test.
|
|
65
|
+
*/
|
|
66
|
+
export function skipOnTravis<ARGS extends unknown[], RETVAL>(
|
|
67
|
+
verb: TestDefinition<ARGS, RETVAL> & {skip: TestDefinition<ARGS, RETVAL>},
|
|
68
|
+
name: string,
|
|
69
|
+
...args: ARGS
|
|
70
|
+
): RETVAL {
|
|
71
|
+
if (process.env.TRAVIS) {
|
|
72
|
+
return verb.skip(`[SKIPPED ON TRAVIS] ${name}`, ...args);
|
|
73
|
+
} else {
|
|
74
|
+
return verb(name, ...args);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/testlab
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
appendFile,
|
|
8
|
+
copy,
|
|
9
|
+
emptyDir,
|
|
10
|
+
ensureDir,
|
|
11
|
+
ensureDirSync,
|
|
12
|
+
mkdtempSync,
|
|
13
|
+
outputFile,
|
|
14
|
+
outputJson,
|
|
15
|
+
pathExists,
|
|
16
|
+
readFile,
|
|
17
|
+
remove,
|
|
18
|
+
} from 'fs-extra';
|
|
19
|
+
import {join, parse, resolve} from 'path';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options for a test sandbox
|
|
23
|
+
*/
|
|
24
|
+
export interface TestSandboxOptions {
|
|
25
|
+
/**
|
|
26
|
+
* The `subdir` controls if/how the sandbox creates a subdirectory under the
|
|
27
|
+
* root path. It has one of the following values:
|
|
28
|
+
*
|
|
29
|
+
* - `true`: Creates a unique subdirectory. This will be the default behavior.
|
|
30
|
+
* - `false`: Uses the root path as the target directory without creating a
|
|
31
|
+
* subdirectory.
|
|
32
|
+
* - a string such as `sub-dir-1`: creates a subdirectory with the given value.
|
|
33
|
+
*/
|
|
34
|
+
subdir: boolean | string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* TestSandbox class provides a convenient way to get a reference to a
|
|
39
|
+
* sandbox folder in which you can perform operations for testing purposes.
|
|
40
|
+
*/
|
|
41
|
+
export class TestSandbox {
|
|
42
|
+
// Path of the TestSandbox
|
|
43
|
+
private _path?: string;
|
|
44
|
+
|
|
45
|
+
public get path(): string {
|
|
46
|
+
if (!this._path) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`TestSandbox instance was deleted. Create a new instance.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return this._path;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Will create a directory if it doesn't already exist. If it exists, you
|
|
56
|
+
* still get an instance of the TestSandbox.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* // Create a sandbox as a unique temporary subdirectory under the rootPath
|
|
61
|
+
* const sandbox = new TestSandbox(rootPath);
|
|
62
|
+
* const sandbox = new TestSandbox(rootPath, {subdir: true});
|
|
63
|
+
*
|
|
64
|
+
* // Create a sandbox in the root path directly
|
|
65
|
+
* // This is same as the old behavior
|
|
66
|
+
* const sandbox = new TestSandbox(rootPath, {subdir: false});
|
|
67
|
+
*
|
|
68
|
+
* // Create a sandbox in the `test1` subdirectory of the root path
|
|
69
|
+
* const sandbox = new TestSandbox(rootPath, {subdir: 'test1'});
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @param rootPath - Root path of the TestSandbox. If relative it will be
|
|
73
|
+
* resolved against the current directory.
|
|
74
|
+
* @param options - Options to control if/how the sandbox creates a
|
|
75
|
+
* subdirectory for the sandbox. If not provided, the sandbox
|
|
76
|
+
* will automatically creates a unique temporary subdirectory. This allows
|
|
77
|
+
* sandboxes with the same root path can be used in parallel during testing.
|
|
78
|
+
*/
|
|
79
|
+
constructor(rootPath: string, options?: TestSandboxOptions) {
|
|
80
|
+
rootPath = resolve(rootPath);
|
|
81
|
+
ensureDirSync(rootPath);
|
|
82
|
+
options = {subdir: true, ...options};
|
|
83
|
+
const subdir = typeof options.subdir === 'string' ? options.subdir : '.';
|
|
84
|
+
if (options.subdir !== true) {
|
|
85
|
+
this._path = resolve(rootPath, subdir);
|
|
86
|
+
} else {
|
|
87
|
+
// Create a unique temporary directory under the root path
|
|
88
|
+
// See https://nodejs.org/api/fs.html#fs_fs_mkdtempsync_prefix_options
|
|
89
|
+
this._path = mkdtempSync(join(rootPath, `/${process.pid}`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resets the TestSandbox. (Remove all files in it).
|
|
95
|
+
*/
|
|
96
|
+
async reset(): Promise<void> {
|
|
97
|
+
// Decache files from require's cache so future tests aren't affected incase
|
|
98
|
+
// a file is recreated in sandbox with the same file name but different
|
|
99
|
+
// contents after resetting the sandbox.
|
|
100
|
+
for (const key in require.cache) {
|
|
101
|
+
if (key.startsWith(this.path)) {
|
|
102
|
+
delete require.cache[key];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await emptyDir(this.path);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Deletes the TestSandbox.
|
|
111
|
+
*/
|
|
112
|
+
async delete(): Promise<void> {
|
|
113
|
+
await remove(this.path);
|
|
114
|
+
delete this._path;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Makes a directory in the TestSandbox
|
|
119
|
+
*
|
|
120
|
+
* @param dir - Name of directory to create (relative to TestSandbox path)
|
|
121
|
+
*/
|
|
122
|
+
async mkdir(dir: string): Promise<void> {
|
|
123
|
+
await ensureDir(resolve(this.path, dir));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Copies a file from src to the TestSandbox. If copying a `.js` file which
|
|
128
|
+
* has an accompanying `.js.map` file in the src file location, the dest file
|
|
129
|
+
* will have its sourceMappingURL updated to point to the original file as
|
|
130
|
+
* an absolute path so you don't need to copy the map file.
|
|
131
|
+
*
|
|
132
|
+
* @param src - Absolute path of file to be copied to the TestSandbox
|
|
133
|
+
* @param dest - Optional. Destination filename of the copy operation
|
|
134
|
+
* (relative to TestSandbox). Original filename used if not specified.
|
|
135
|
+
* @param transform - Optional. A function to transform the file content.
|
|
136
|
+
*/
|
|
137
|
+
async copyFile(
|
|
138
|
+
src: string,
|
|
139
|
+
dest?: string,
|
|
140
|
+
transform?: (content: string) => string,
|
|
141
|
+
): Promise<void> {
|
|
142
|
+
dest = dest
|
|
143
|
+
? resolve(this.path, dest)
|
|
144
|
+
: resolve(this.path, parse(src).base);
|
|
145
|
+
|
|
146
|
+
if (transform == null) {
|
|
147
|
+
await copy(src, dest);
|
|
148
|
+
} else {
|
|
149
|
+
let content = await readFile(src, 'utf-8');
|
|
150
|
+
content = transform(content);
|
|
151
|
+
await outputFile(dest, content, {encoding: 'utf-8'});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (parse(src).ext === '.js' && (await pathExists(src + '.map'))) {
|
|
155
|
+
const srcMap = src + '.map';
|
|
156
|
+
await appendFile(dest, `\n//# sourceMappingURL=${srcMap}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Creates a new file and writes the given data serialized as JSON.
|
|
162
|
+
*
|
|
163
|
+
* @param dest - Destination filename, optionally including a relative path.
|
|
164
|
+
* @param data - The data to write.
|
|
165
|
+
*/
|
|
166
|
+
async writeJsonFile(dest: string, data: unknown): Promise<void> {
|
|
167
|
+
dest = resolve(this.path, dest);
|
|
168
|
+
return outputJson(dest, data, {spaces: 2});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Creates a new file and writes the given data as a UTF-8-encoded text.
|
|
173
|
+
*
|
|
174
|
+
* @param dest - Destination filename, optionally including a relative path.
|
|
175
|
+
* @param data - The text to write.
|
|
176
|
+
*/
|
|
177
|
+
async writeTextFile(dest: string, data: string): Promise<void> {
|
|
178
|
+
dest = resolve(this.path, dest);
|
|
179
|
+
return outputFile(dest, data, 'utf-8');
|
|
180
|
+
}
|
|
181
|
+
}
|
package/src/to-json.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/testlab
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
/* eslint-disable @typescript-eslint/unified-signatures */
|
|
7
|
+
|
|
8
|
+
// Important! Date.prototype.toJSON() returns a string.
|
|
9
|
+
export function toJSON(value: Date): string;
|
|
10
|
+
|
|
11
|
+
// Important! Functions cannot be encoded in JSON.
|
|
12
|
+
export function toJSON(value: Function): undefined;
|
|
13
|
+
|
|
14
|
+
// Distinguish arrays from objects (an array is an object too)
|
|
15
|
+
export function toJSON(value: unknown[]): unknown[];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* JSON encoding does not preserve properties that are undefined
|
|
19
|
+
* As a result, deepEqual checks fail because the expected model
|
|
20
|
+
* value contains these undefined property values, while the actual
|
|
21
|
+
* result returned by REST API does not.
|
|
22
|
+
* Use this function to convert a model instance into a data object
|
|
23
|
+
* as returned by REST API
|
|
24
|
+
*/
|
|
25
|
+
export function toJSON(value: object): object;
|
|
26
|
+
|
|
27
|
+
// The following overloads are provided for convenience.
|
|
28
|
+
// In practice, they should not be necessary, as they simply return the input
|
|
29
|
+
// value without any modifications.
|
|
30
|
+
|
|
31
|
+
export function toJSON(value: undefined): undefined;
|
|
32
|
+
export function toJSON(value: null): null;
|
|
33
|
+
export function toJSON(value: number): number;
|
|
34
|
+
export function toJSON(value: boolean): boolean;
|
|
35
|
+
export function toJSON(value: string): string;
|
|
36
|
+
|
|
37
|
+
// The following overloads are required to allow TypesScript handle
|
|
38
|
+
// commonly used union types.
|
|
39
|
+
|
|
40
|
+
export function toJSON(value: unknown[] | null): unknown[] | null;
|
|
41
|
+
export function toJSON(value: unknown[] | undefined): unknown[] | undefined;
|
|
42
|
+
export function toJSON(
|
|
43
|
+
value: unknown[] | null | undefined,
|
|
44
|
+
): unknown[] | null | undefined;
|
|
45
|
+
|
|
46
|
+
export function toJSON(value: object | null): object | null;
|
|
47
|
+
export function toJSON(value: object | undefined): object | undefined;
|
|
48
|
+
export function toJSON(
|
|
49
|
+
value: object | null | undefined,
|
|
50
|
+
): object | null | undefined;
|
|
51
|
+
|
|
52
|
+
export function toJSON<T>(value: T) {
|
|
53
|
+
return JSON.parse(JSON.stringify({value})).value;
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2017,2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/testlab
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
const validator = require('oas-validator');
|
|
7
|
+
import {promisify} from 'util';
|
|
8
|
+
|
|
9
|
+
const validateAsync = promisify(validator.validate);
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
export async function validateApiSpec(spec: any): Promise<void> {
|
|
13
|
+
await validateAsync(spec, {});
|
|
14
|
+
}
|
package/index.d.ts
DELETED
package/index.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Copyright IBM Corp. 2013,2017. All Rights Reserved.
|
|
2
|
-
// Node module: @loopback/testlab
|
|
3
|
-
// This file is licensed under the MIT License.
|
|
4
|
-
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
-
|
|
6
|
-
const nodeMajorVersion = +process.versions.node.split('.')[0];
|
|
7
|
-
module.exports = nodeMajorVersion >= 7 ?
|
|
8
|
-
require('./lib/testlab') :
|
|
9
|
-
require('./lib6/testlab');
|
package/lib/client.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/// <reference types="supertest" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
|
-
import * as http from 'http';
|
|
4
|
-
import supertest = require('supertest');
|
|
5
|
-
export { supertest };
|
|
6
|
-
export declare type Client = supertest.SuperTest<supertest.Test>;
|
|
7
|
-
/**
|
|
8
|
-
* Create a SuperTest client connected to an HTTP server listening
|
|
9
|
-
* on an ephemeral port and calling `handler` to handle incoming requests.
|
|
10
|
-
* @param handler
|
|
11
|
-
*/
|
|
12
|
-
export declare function createClientForHandler(handler: (req: http.ServerRequest, res: http.ServerResponse) => void): Client;
|
|
13
|
-
export interface Server {
|
|
14
|
-
config: {
|
|
15
|
-
port: number;
|
|
16
|
-
};
|
|
17
|
-
start(): Promise<void>;
|
|
18
|
-
}
|
|
19
|
-
export declare function createClientForServer(server: Server): Promise<Client>;
|
|
20
|
-
export interface Application {
|
|
21
|
-
handleHttp(req: http.ServerRequest, res: http.ServerResponse): void;
|
|
22
|
-
}
|
|
23
|
-
export declare function createClientForApp(app: Application): Client;
|
package/lib/client.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Copyright IBM Corp. 2013,2017. All Rights Reserved.
|
|
3
|
-
// Node module: @loopback/testlab
|
|
4
|
-
// This file is licensed under the MIT License.
|
|
5
|
-
// License text available at https://opensource.org/licenses/MIT
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
/*
|
|
8
|
-
* HTTP client utilities
|
|
9
|
-
*/
|
|
10
|
-
const http = require("http");
|
|
11
|
-
const supertest = require("supertest");
|
|
12
|
-
exports.supertest = supertest;
|
|
13
|
-
/**
|
|
14
|
-
* Create a SuperTest client connected to an HTTP server listening
|
|
15
|
-
* on an ephemeral port and calling `handler` to handle incoming requests.
|
|
16
|
-
* @param handler
|
|
17
|
-
*/
|
|
18
|
-
function createClientForHandler(handler) {
|
|
19
|
-
const server = http.createServer(handler);
|
|
20
|
-
return supertest(server);
|
|
21
|
-
}
|
|
22
|
-
exports.createClientForHandler = createClientForHandler;
|
|
23
|
-
async function createClientForServer(server) {
|
|
24
|
-
await server.start();
|
|
25
|
-
const url = `http://127.0.0.1:${server.config.port}`;
|
|
26
|
-
// TODO(bajtos) Find a way how to stop the server after all tests are done
|
|
27
|
-
return supertest(url);
|
|
28
|
-
}
|
|
29
|
-
exports.createClientForServer = createClientForServer;
|
|
30
|
-
function createClientForApp(app) {
|
|
31
|
-
return createClientForHandler(app.handleHttp);
|
|
32
|
-
}
|
|
33
|
-
exports.createClientForApp = createClientForApp;
|
|
34
|
-
//# sourceMappingURL=client.js.map
|
package/lib/client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;AAEhE;;GAEG;AAEH,6BAA6B;AAC7B,uCAAwC;AAEhC,8BAAS;AAIjB;;;;GAIG;AACH,gCACE,OAAoE;IAEpE,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC;AALD,wDAKC;AAOM,KAAK,gCAAgC,MAAc;IACxD,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACrB,MAAM,GAAG,GAAG,oBAAoB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACrD,0EAA0E;IAC1E,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AALD,sDAKC;AAMD,4BAAmC,GAAgB;IACjD,MAAM,CAAC,sBAAsB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAChD,CAAC;AAFD,gDAEC"}
|