@loadmill/core 0.3.33 → 0.3.37
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/conf/extrema.d.ts +2 -2
- package/dist/conf/extrema.js +2 -0
- package/dist/conf/extrema.js.map +1 -1
- package/dist/conf/types.d.ts +1 -1
- package/dist/har/index.d.ts +16 -4
- package/dist/har/index.js.map +1 -1
- package/dist/parameters/extractors/extractor.d.ts +1 -1
- package/dist/parameters/extractors/index.d.ts +2 -1
- package/dist/parameters/extractors/index.js +3 -1
- package/dist/parameters/extractors/index.js.map +1 -1
- package/dist/parameters/extractors/parametrized-extractor.d.ts +4 -2
- package/dist/parameters/extractors/regex-extractor.js +2 -13
- package/dist/parameters/extractors/regex-extractor.js.map +1 -1
- package/dist/parameters/extractors/regex-matcher.d.ts +1 -0
- package/dist/parameters/extractors/regex-matcher.js +20 -0
- package/dist/parameters/extractors/regex-matcher.js.map +1 -0
- package/dist/parameters/extractors/ws-extractor.d.ts +30 -0
- package/dist/parameters/extractors/ws-extractor.js +198 -0
- package/dist/parameters/extractors/ws-extractor.js.map +1 -0
- package/dist/parameters/index.d.ts +6 -2
- package/dist/parameters/index.js +42 -3
- package/dist/parameters/index.js.map +1 -1
- package/dist/parameters/parameter-regex-providers.d.ts +3 -0
- package/dist/parameters/parameter-regex-providers.js +7 -1
- package/dist/parameters/parameter-regex-providers.js.map +1 -1
- package/dist/request/index.d.ts +7 -4
- package/dist/request/index.js +21 -9
- package/dist/request/index.js.map +1 -1
- package/dist/schema/json-schema-generator.d.ts +1 -1
- package/dist/schema/json-schema-generator.js +14 -10
- package/dist/schema/json-schema-generator.js.map +1 -1
- package/package.json +4 -4
- package/src/conf/extrema.ts +1 -1
- package/src/conf/index.ts +6 -6
- package/src/conf/notifications.ts +3 -3
- package/src/conf/types.ts +2 -2
- package/src/conf/validate.ts +1 -1
- package/src/echo/firehose.ts +10 -10
- package/src/har/index.ts +15 -5
- package/src/parameters/extractors/extractor.ts +1 -1
- package/src/parameters/extractors/index.ts +2 -1
- package/src/parameters/extractors/json-path-extractor.ts +1 -1
- package/src/parameters/extractors/parametrized-extractor.ts +3 -1
- package/src/parameters/extractors/regex-extractor.ts +2 -14
- package/src/parameters/extractors/regex-matcher.ts +16 -0
- package/src/parameters/extractors/ws-extractor.ts +89 -0
- package/src/parameters/index.ts +56 -8
- package/src/parameters/parameter-functions/textual-parameter-functions.ts +1 -1
- package/src/parameters/parameter-regex-providers.ts +8 -0
- package/src/request/index.ts +20 -9
- package/src/schema/json-schema-generator.ts +14 -12
- package/test/parameters/value-utils.spec.js +2 -2
- package/test/schema/json-schema-generator.spec.js +70 -45
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { PresentableError } from '@loadmill/universal/dist/errors';
|
|
2
|
+
import log from '@loadmill/universal/dist/log';
|
|
3
|
+
import { delay } from '@loadmill/universal/dist/promise-utils';
|
|
4
|
+
import { CAPTURE_ALL_REGEX, isCaptureAllRegExp } from '../parameter-regex-providers';
|
|
5
|
+
import { Parameters } from '../type';
|
|
6
|
+
import { ParametrizedExtractor } from './parametrized-extractor';
|
|
7
|
+
import { matchRegex } from './regex-matcher';
|
|
8
|
+
|
|
9
|
+
export class WsExtractor extends ParametrizedExtractor {
|
|
10
|
+
private readonly _messages: string[];
|
|
11
|
+
private readonly _timeLimit: number;
|
|
12
|
+
constructor(
|
|
13
|
+
extractionData: WSExtractionData = { messages: [], timeLimit: 0 },
|
|
14
|
+
parameters: Parameters,
|
|
15
|
+
) {
|
|
16
|
+
super(parameters);
|
|
17
|
+
this._messages = extractionData.messages;
|
|
18
|
+
this._timeLimit = extractionData.timeLimit;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
extractResolved = async (resolvedRegex: string) => {
|
|
22
|
+
log.debug('Resolved WS extraction regex:', resolvedRegex);
|
|
23
|
+
const regexp = new RegExp(resolvedRegex || CAPTURE_ALL_REGEX);
|
|
24
|
+
const wsMessage = await this.queryMessage(regexp);
|
|
25
|
+
log.debug('WS message queried and found:', wsMessage);
|
|
26
|
+
|
|
27
|
+
return matchRegex(regexp, wsMessage);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Querying all received incoming WS messages by regexp.
|
|
32
|
+
* @returns A WS message that satisfies the given regexp. If none found, returns an empty string.
|
|
33
|
+
* @throws RequestFailuresError if the procedure timeouts before any relevant message found.
|
|
34
|
+
*/
|
|
35
|
+
private async queryMessage(
|
|
36
|
+
regexp: RegExp,
|
|
37
|
+
): Promise<string> {
|
|
38
|
+
const res = await this.getWSMessageOrTimeout(regexp);
|
|
39
|
+
if (!res) {
|
|
40
|
+
throw new PresentableError('Websocket waiting timeout expired');
|
|
41
|
+
}
|
|
42
|
+
return res;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private readonly GIVE_WS_MSG_TIME_MS = 250;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get an incoming ws message that satisfies the given regex, out of all already received incoming ws messages.
|
|
49
|
+
*
|
|
50
|
+
* If can't find that message, then actively waiting, until procedure timeouts.
|
|
51
|
+
* @param regex To test the messages with
|
|
52
|
+
* @param timeout Milliseconds for the procedure to find a message
|
|
53
|
+
* @returns ws message as string or undefined if timed out
|
|
54
|
+
*/
|
|
55
|
+
private async getWSMessageOrTimeout(
|
|
56
|
+
regexp: RegExp,
|
|
57
|
+
): Promise<string | undefined> {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
let message = '';
|
|
60
|
+
let elapsedTime = 0;
|
|
61
|
+
|
|
62
|
+
while (!message && elapsedTime < this._timeLimit) {
|
|
63
|
+
message = this.findMessageByRegex(regexp) || '';
|
|
64
|
+
if (message) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await delay(this.GIVE_WS_MSG_TIME_MS);
|
|
69
|
+
elapsedTime = Date.now() - startTime;
|
|
70
|
+
}
|
|
71
|
+
return message;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private findMessageByRegex(regexp: RegExp): string | undefined {
|
|
75
|
+
if (isCaptureAllRegExp(regexp)) {
|
|
76
|
+
return this.getLastMessage();
|
|
77
|
+
}
|
|
78
|
+
return this._messages.find(msg => regexp.test(msg));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private getLastMessage(): string {
|
|
82
|
+
return this._messages[this._messages.length - 1];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type WSExtractionData = {
|
|
87
|
+
messages: string[];
|
|
88
|
+
timeLimit: number;
|
|
89
|
+
}
|
package/src/parameters/index.ts
CHANGED
|
@@ -20,11 +20,14 @@ import {
|
|
|
20
20
|
usageRegExpProvider,
|
|
21
21
|
PARAMETER_USAGE_PATTERN,
|
|
22
22
|
LEGAL_PARAM_CHARS,
|
|
23
|
-
CAPTURE_REGEX
|
|
23
|
+
CAPTURE_REGEX,
|
|
24
|
+
PARAM_USAGE_REGEXP,
|
|
24
25
|
} from './parameter-regex-providers';
|
|
25
26
|
import * as arrayUtils from '@loadmill/universal/dist/array-utils';
|
|
26
27
|
import { PresentableError } from '@loadmill/universal/dist/errors';
|
|
27
28
|
import { applyJSONSchema } from './parameter-functions/json-schema';
|
|
29
|
+
import { Assertions, RequestLike } from '../request';
|
|
30
|
+
import { TestConfLike } from '../conf';
|
|
28
31
|
|
|
29
32
|
function isStackEmpty(s) {
|
|
30
33
|
return s.length === 0;
|
|
@@ -190,6 +193,7 @@ export const parameterUtils = {
|
|
|
190
193
|
throw e;
|
|
191
194
|
}
|
|
192
195
|
},
|
|
196
|
+
|
|
193
197
|
addParamToArray(key, value, arr) {
|
|
194
198
|
arr.push({ [key]: value });
|
|
195
199
|
},
|
|
@@ -236,7 +240,7 @@ export const parameterUtils = {
|
|
|
236
240
|
* ${__encode_url(param1)}
|
|
237
241
|
* ${__if_then_else(param12,'good','bad')}
|
|
238
242
|
*/
|
|
239
|
-
|
|
243
|
+
searchParameterizedExpressionOccurrences(
|
|
240
244
|
parameterName: string,
|
|
241
245
|
data: string
|
|
242
246
|
): Array<string> {
|
|
@@ -258,13 +262,13 @@ export const parameterUtils = {
|
|
|
258
262
|
return extractionParamNames;
|
|
259
263
|
},
|
|
260
264
|
|
|
261
|
-
getUsedConfParams(conf, parameters: Parameters[]): Parameters[] {
|
|
265
|
+
getUsedConfParams(conf: TestConfLike, parameters: Parameters[]): Parameters[] {
|
|
262
266
|
let usedParameters: Parameters[] = [];
|
|
263
267
|
if (parameters) {
|
|
264
268
|
const stringifyConf = JSON.stringify(conf);
|
|
265
269
|
parameters.forEach((p) => {
|
|
266
270
|
const paramName = this.getParameterName(p);
|
|
267
|
-
if (
|
|
271
|
+
if (isParamUsedInConf(paramName, conf, stringifyConf)) {
|
|
268
272
|
const currentUsedParams = this.findUsedParameters(parameters ,p, []);
|
|
269
273
|
usedParameters = usedParameters.concat(currentUsedParams);
|
|
270
274
|
}
|
|
@@ -272,6 +276,11 @@ export const parameterUtils = {
|
|
|
272
276
|
}
|
|
273
277
|
return uniqWith(usedParameters, isEqual);
|
|
274
278
|
},
|
|
279
|
+
|
|
280
|
+
getUsedRequestParams(request, parameters: Parameters[]): Parameters[] {
|
|
281
|
+
return this.getUsedConfParams({ requests: [request] }, parameters);
|
|
282
|
+
},
|
|
283
|
+
|
|
275
284
|
getValueByKeyFromArr(key: string, parameters: Parameters[]) {
|
|
276
285
|
let res;
|
|
277
286
|
const param = getParameterByKey(key, parameters);
|
|
@@ -283,6 +292,15 @@ export const parameterUtils = {
|
|
|
283
292
|
}
|
|
284
293
|
return res;
|
|
285
294
|
},
|
|
295
|
+
|
|
296
|
+
pickFirstValue(p: Parameters): Parameters {
|
|
297
|
+
const name = parameterUtils.getParameterName(p);
|
|
298
|
+
const value = parameterUtils.getParameterValue(p);
|
|
299
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
300
|
+
return { [name]: value[0] };
|
|
301
|
+
}
|
|
302
|
+
return { [name]: value };
|
|
303
|
+
},
|
|
286
304
|
getParameterValue(parameter: Parameters) {
|
|
287
305
|
const [value] = Object.values(parameter);
|
|
288
306
|
return value;
|
|
@@ -322,7 +340,7 @@ export const parameterUtils = {
|
|
|
322
340
|
}
|
|
323
341
|
}
|
|
324
342
|
} else {
|
|
325
|
-
if(!isSingleQuoted(token)) {
|
|
343
|
+
if (!isSingleQuoted(token)) {
|
|
326
344
|
usedParams.push(token);
|
|
327
345
|
}
|
|
328
346
|
}
|
|
@@ -333,7 +351,7 @@ export const parameterUtils = {
|
|
|
333
351
|
},
|
|
334
352
|
deleteAllSrcElementsFromParamArr(paramArr, src) {
|
|
335
353
|
let isSrcInParamArr = this.isParamKeyInArr(src, paramArr);
|
|
336
|
-
while(isSrcInParamArr) {
|
|
354
|
+
while (isSrcInParamArr) {
|
|
337
355
|
this.removeParameterFromArrayByKey(src, paramArr);
|
|
338
356
|
isSrcInParamArr = this.isParamKeyInArr(src, paramArr);
|
|
339
357
|
}
|
|
@@ -372,7 +390,10 @@ export const parameterUtils = {
|
|
|
372
390
|
},
|
|
373
391
|
applyJSONSchema(instance, schema) {
|
|
374
392
|
return applyJSONSchema(instance, schema);
|
|
375
|
-
}
|
|
393
|
+
},
|
|
394
|
+
isUsingParameterizedValue(value: string) {
|
|
395
|
+
return PARAM_USAGE_REGEXP.test(value);
|
|
396
|
+
},
|
|
376
397
|
};
|
|
377
398
|
|
|
378
399
|
function resolvePlainParameter(parameterName: string, parameters) {
|
|
@@ -519,7 +540,7 @@ function resolveOperator(opSymbol: string) {
|
|
|
519
540
|
|
|
520
541
|
function findUsedParametersRecursively(
|
|
521
542
|
suiteParameters: Parameters[], param: Parameters, usedParameters: Parameters[]
|
|
522
|
-
) : Parameters[]{
|
|
543
|
+
) : Parameters[] {
|
|
523
544
|
const paramValueStringified = JSON.stringify(parameterUtils.getParameterValue(param));
|
|
524
545
|
const currentUsedParameterNames = parameterUtils.getUsedRequestParamNames(paramValueStringified);
|
|
525
546
|
|
|
@@ -592,3 +613,30 @@ export const getTokensWithoutOperators = (paramOccurrence: string): string[] =>
|
|
|
592
613
|
const tokens = getTokens(strippedParam);
|
|
593
614
|
return stripOperators(tokens);
|
|
594
615
|
};
|
|
616
|
+
|
|
617
|
+
function isParamUsedInConf(paramName: string, conf: TestConfLike, stringifyConf: string): boolean {
|
|
618
|
+
return !isEmpty(parameterUtils.searchParameterizedExpressionOccurrences(paramName, stringifyConf)) ||
|
|
619
|
+
isParamNameUsedInConf(paramName, conf);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function isParamNameUsedInField(paramName: string, fieldValue?: string): boolean {
|
|
623
|
+
return !!fieldValue && fieldValue === paramName;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function isParamNameUsedInAssertions(paramName: string, assertions: Assertions = []): boolean {
|
|
627
|
+
return assertions.some(assertion => assertion.check === paramName);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function isParamNameUsedInRequest(paramName: string, request: RequestLike): boolean {
|
|
631
|
+
return isParamNameUsedInAssertions(paramName, request.assert) ||
|
|
632
|
+
isParamNameUsedInField(paramName, request.loop?.assert?.check) ||
|
|
633
|
+
isParamNameUsedInField(paramName, request.skipBefore?.condition) ||
|
|
634
|
+
isParamNameUsedInField(paramName, request.stopBefore);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function isParamNameUsedInConf(paramName: string, conf: TestConfLike): boolean {
|
|
638
|
+
if (!isEmpty(conf.requests)) {
|
|
639
|
+
return conf.requests.some(req => isParamNameUsedInRequest(paramName, req));
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
@@ -61,3 +61,11 @@ export const getParameterOperatorsGroupingRegexp = (parameterName) =>
|
|
|
61
61
|
);
|
|
62
62
|
|
|
63
63
|
export const CAPTURE_REGEX = /(([^\\]\(|^\().*[^\\]\))/;
|
|
64
|
+
|
|
65
|
+
export const PARAM_USAGE_REGEXP = /\$\{(.+)\}/;
|
|
66
|
+
|
|
67
|
+
export const CAPTURE_ALL_REGEX = /(.*)/;
|
|
68
|
+
|
|
69
|
+
export function isCaptureAllRegExp(regexp: RegExp): boolean {
|
|
70
|
+
return String(CAPTURE_ALL_REGEX) === String(regexp);
|
|
71
|
+
}
|
package/src/request/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import flatMap = require('lodash/flatMap');
|
|
|
2
2
|
import * as uriUtils from '@loadmill/universal/dist/uri-utils';
|
|
3
3
|
import { Parameters, parameterUtils } from '../parameters';
|
|
4
4
|
|
|
5
|
-
export const DEFAULT_REQUEST_TIMEOUT =
|
|
5
|
+
export const DEFAULT_REQUEST_TIMEOUT = 25000;
|
|
6
6
|
export const supportedMethods = {
|
|
7
7
|
GET: 'GET',
|
|
8
8
|
POST: 'POST',
|
|
@@ -45,9 +45,10 @@ export class LoadmillRequest implements RequestLike {
|
|
|
45
45
|
|
|
46
46
|
assert?: Assertions;
|
|
47
47
|
extract?: Extractions[];
|
|
48
|
-
postScript?: string
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
postScript?: string;
|
|
49
|
+
disabled?: boolean;
|
|
50
|
+
timeout?: number;
|
|
51
|
+
expectedStatus?: HttpResponseStatus;
|
|
51
52
|
|
|
52
53
|
constructor(public url: string) { }
|
|
53
54
|
}
|
|
@@ -82,6 +83,7 @@ export function createRequest(from: RequestLike): LoadmillRequest {
|
|
|
82
83
|
description,
|
|
83
84
|
expectedStatus,
|
|
84
85
|
postScript,
|
|
86
|
+
disabled,
|
|
85
87
|
} = from;
|
|
86
88
|
|
|
87
89
|
const request = new LoadmillRequest(url);
|
|
@@ -104,11 +106,11 @@ export function createRequest(from: RequestLike): LoadmillRequest {
|
|
|
104
106
|
request.postData = { text, mimeType };
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
if (delay
|
|
109
|
+
if (delay !== null && delay !== undefined) {
|
|
108
110
|
request.delay = delay;
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
if (stopBefore) {
|
|
113
|
+
if (stopBefore !== undefined) {
|
|
112
114
|
request.stopBefore = stopBefore;
|
|
113
115
|
}
|
|
114
116
|
|
|
@@ -148,9 +150,12 @@ export function createRequest(from: RequestLike): LoadmillRequest {
|
|
|
148
150
|
request.expectedStatus = expectedStatus;
|
|
149
151
|
}
|
|
150
152
|
|
|
151
|
-
if (postScript) {
|
|
153
|
+
if (postScript !== undefined) {
|
|
152
154
|
request.postScript = postScript;
|
|
153
155
|
}
|
|
156
|
+
if (disabled !== undefined) {
|
|
157
|
+
request.disabled = disabled;
|
|
158
|
+
}
|
|
154
159
|
|
|
155
160
|
return request;
|
|
156
161
|
}
|
|
@@ -192,7 +197,7 @@ function toSingleExtractions(extractions: Extractions): Extractions {
|
|
|
192
197
|
|
|
193
198
|
if (typeof extraction !== 'string') {
|
|
194
199
|
newExtraction = {};
|
|
195
|
-
const { header, jQuery, jsonPath, edn, regex } = extraction;
|
|
200
|
+
const { header, jQuery, jsonPath, edn, regex, ws } = extraction;
|
|
196
201
|
|
|
197
202
|
if (header != null) {
|
|
198
203
|
newExtraction.header = header;
|
|
@@ -216,6 +221,10 @@ function toSingleExtractions(extractions: Extractions): Extractions {
|
|
|
216
221
|
if (regex != null) {
|
|
217
222
|
newExtraction.regex = regex;
|
|
218
223
|
}
|
|
224
|
+
|
|
225
|
+
if (ws != null) {
|
|
226
|
+
newExtraction.ws = ws;
|
|
227
|
+
}
|
|
219
228
|
}
|
|
220
229
|
|
|
221
230
|
result[name] = newExtraction;
|
|
@@ -339,9 +348,10 @@ export interface RequestLike {
|
|
|
339
348
|
postData?: RequestPostData;
|
|
340
349
|
extract?: Extractions | Extractions[];
|
|
341
350
|
headers?: LoadmillHeaders | LoadmillHeaders[];
|
|
342
|
-
expectedStatus
|
|
351
|
+
expectedStatus?: HttpResponseStatus;
|
|
343
352
|
parameters?;
|
|
344
353
|
postScript?: string;
|
|
354
|
+
disabled?: boolean;
|
|
345
355
|
}
|
|
346
356
|
|
|
347
357
|
export type SkipConf = {
|
|
@@ -396,6 +406,7 @@ export type ExtractionObj = {
|
|
|
396
406
|
query: string;
|
|
397
407
|
attr?: string;
|
|
398
408
|
};
|
|
409
|
+
ws?: string;
|
|
399
410
|
};
|
|
400
411
|
|
|
401
412
|
export type Assertions = Assertion[];
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import find = require('lodash/find');
|
|
1
2
|
import log from '@loadmill/universal/dist/log';
|
|
3
|
+
import isEmpty = require('lodash/isEmpty');
|
|
2
4
|
|
|
3
|
-
export const generateJSONSchema = (jsObj: any, ignoreKeys: string[] = []): PrimitiveSchema | Schema => {
|
|
5
|
+
export const generateJSONSchema = (jsObj: any, ignoreKeys: string[] = [], shouldAddNullable: boolean = false): PrimitiveSchema | Schema => {
|
|
4
6
|
try {
|
|
5
|
-
const schema = _getSchema(jsObj, ignoreKeys);
|
|
7
|
+
const schema = _getSchema(jsObj, ignoreKeys, shouldAddNullable);
|
|
6
8
|
return schema;
|
|
7
9
|
} catch (err) {
|
|
8
10
|
_handleError('Error generating JSON Schema', err, jsObj);
|
|
@@ -11,27 +13,27 @@ export const generateJSONSchema = (jsObj: any, ignoreKeys: string[] = []): Primi
|
|
|
11
13
|
return {};
|
|
12
14
|
};
|
|
13
15
|
|
|
14
|
-
const _getSchema = (obj, ignoreKeys: string[] = []) => {
|
|
16
|
+
const _getSchema = (obj, ignoreKeys: string[] = [], shouldAddNullable: boolean = false) => {
|
|
15
17
|
if (obj == null) {
|
|
16
|
-
return {};
|
|
18
|
+
return shouldAddNullable ? { type: 'object', nullable: true } :{};
|
|
17
19
|
}
|
|
18
20
|
else if (_isPrimitive(obj)) {
|
|
19
21
|
return {
|
|
20
|
-
'type': typeof obj
|
|
21
|
-
'const': obj
|
|
22
|
+
'type': typeof obj
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
else if (Array.isArray(obj)) {
|
|
25
26
|
const items: any[] = [];
|
|
26
|
-
for (const [,value] of Object.entries(obj)) {
|
|
27
|
-
|
|
27
|
+
for (const [, value] of Object.entries(obj)) {
|
|
28
|
+
const elementSchema = _getSchema(value, ignoreKeys);
|
|
29
|
+
if (!find(items, elementSchema)) {
|
|
30
|
+
items.push(elementSchema);
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
// The use of 'oneOf' creates a schema that validates the existence of the items but not their order in the array
|
|
30
|
-
return {
|
|
34
|
+
return {
|
|
31
35
|
'type': 'array',
|
|
32
|
-
'items': {
|
|
33
|
-
'anyOf': items
|
|
34
|
-
}
|
|
36
|
+
'items': isEmpty(items) ? {} : { 'anyOf': items }
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
else {
|
|
@@ -36,7 +36,7 @@ suite('test valueUtils module', () => {
|
|
|
36
36
|
expect(valueUtils.isTruthyParameterValue('undefined')).equals(false);
|
|
37
37
|
});
|
|
38
38
|
it('"NaN" => false', () => {
|
|
39
|
-
expect(valueUtils.isTruthyParameterValue('NaN')).equals(false);
|
|
39
|
+
expect(valueUtils.isTruthyParameterValue('NaN')).equals(false);
|
|
40
40
|
});
|
|
41
41
|
it('"" => false', () => {
|
|
42
42
|
expect(valueUtils.isTruthyParameterValue('')).equals(false);
|
|
@@ -66,7 +66,7 @@ suite('test valueUtils module', () => {
|
|
|
66
66
|
expect(valueUtils.isTruthyParameterValue([])).equals(true);
|
|
67
67
|
});
|
|
68
68
|
it('Infinity => true', () => {
|
|
69
|
-
expect(valueUtils.isTruthyParameterValue(Infinity)).equals(true);
|
|
69
|
+
expect(valueUtils.isTruthyParameterValue(Infinity)).equals(true);
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { suite } = require('mocha');
|
|
1
|
+
const { suite, describe, it } = require('mocha');
|
|
2
2
|
const { expect } = require('chai');
|
|
3
3
|
const { Validator } = require('jsonschema');
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ suite('JSON Schema generator', () => {
|
|
|
10
10
|
describe('Primitive cases', () => {
|
|
11
11
|
it('Just 1 string', () => {
|
|
12
12
|
const string = 'arnon';
|
|
13
|
-
const expected = { type: 'string'
|
|
13
|
+
const expected = { type: 'string' };
|
|
14
14
|
const res = generateJSONSchema(string);
|
|
15
15
|
expect(res).eql(expected);
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ suite('JSON Schema generator', () => {
|
|
|
19
19
|
});
|
|
20
20
|
it('Just 1 number', () => {
|
|
21
21
|
const number = 123;
|
|
22
|
-
const expected = { 'type': 'number'
|
|
22
|
+
const expected = { 'type': 'number' };
|
|
23
23
|
const res = generateJSONSchema(number);
|
|
24
24
|
expect(res).eql(expected);
|
|
25
25
|
|
|
@@ -28,7 +28,7 @@ suite('JSON Schema generator', () => {
|
|
|
28
28
|
});
|
|
29
29
|
it('Just 1 boolean', () => {
|
|
30
30
|
const boolie = false;
|
|
31
|
-
const expected = { 'type': 'boolean'
|
|
31
|
+
const expected = { 'type': 'boolean' };
|
|
32
32
|
const res = generateJSONSchema(boolie);
|
|
33
33
|
expect(res).eql(expected);
|
|
34
34
|
|
|
@@ -37,6 +37,18 @@ suite('JSON Schema generator', () => {
|
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
39
|
describe('Complex cases', () => {
|
|
40
|
+
it('Empty Array', () => {
|
|
41
|
+
const array = [];
|
|
42
|
+
const expected = {
|
|
43
|
+
'type': 'array',
|
|
44
|
+
'items': {}
|
|
45
|
+
};
|
|
46
|
+
const res = generateJSONSchema(array);
|
|
47
|
+
expect(res).eql(expected);
|
|
48
|
+
|
|
49
|
+
const schemaRes = v.validate(array, res);
|
|
50
|
+
expect(schemaRes.errors).eql([]);
|
|
51
|
+
});
|
|
40
52
|
it('Array of strings', () => {
|
|
41
53
|
const array = ['Arnon','Yochai'];
|
|
42
54
|
const expected = {
|
|
@@ -45,12 +57,7 @@ suite('JSON Schema generator', () => {
|
|
|
45
57
|
'anyOf':
|
|
46
58
|
[
|
|
47
59
|
{
|
|
48
|
-
'type': 'string'
|
|
49
|
-
'const': 'Arnon'
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
'type': 'string',
|
|
53
|
-
'const': 'Yochai'
|
|
60
|
+
'type': 'string'
|
|
54
61
|
}
|
|
55
62
|
]
|
|
56
63
|
}
|
|
@@ -60,6 +67,8 @@ suite('JSON Schema generator', () => {
|
|
|
60
67
|
|
|
61
68
|
const schemaRes = v.validate(array, res);
|
|
62
69
|
expect(schemaRes.errors).eql([]);
|
|
70
|
+
const schemaResRev = v.validate(array.reverse(), res);
|
|
71
|
+
expect(schemaResRev.errors).eql([]);
|
|
63
72
|
});
|
|
64
73
|
it('Object of primitives', () => {
|
|
65
74
|
const obj = { 'name':'arnon', 'age':48, 'isAntar':true };
|
|
@@ -67,16 +76,13 @@ suite('JSON Schema generator', () => {
|
|
|
67
76
|
'type': 'object',
|
|
68
77
|
'properties': {
|
|
69
78
|
'name': {
|
|
70
|
-
'type': 'string'
|
|
71
|
-
'const': 'arnon'
|
|
79
|
+
'type': 'string'
|
|
72
80
|
},
|
|
73
81
|
'age': {
|
|
74
|
-
'type': 'number'
|
|
75
|
-
'const': 48
|
|
82
|
+
'type': 'number'
|
|
76
83
|
},
|
|
77
84
|
'isAntar': {
|
|
78
|
-
'type': 'boolean'
|
|
79
|
-
'const': true
|
|
85
|
+
'type': 'boolean'
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
};
|
|
@@ -86,7 +92,7 @@ suite('JSON Schema generator', () => {
|
|
|
86
92
|
const schemaRes = v.validate(obj, res);
|
|
87
93
|
expect(schemaRes.errors).eql([]);
|
|
88
94
|
});
|
|
89
|
-
it('Array of objects', () => {
|
|
95
|
+
it('Array of similar objects', () => {
|
|
90
96
|
const array = [{ 'street': 'Arnon', 'building': 42 }, { 'street': 'Yochai', 'building': 77 }];
|
|
91
97
|
const expected = {
|
|
92
98
|
'type': 'array',
|
|
@@ -96,25 +102,52 @@ suite('JSON Schema generator', () => {
|
|
|
96
102
|
'type': 'object',
|
|
97
103
|
'properties': {
|
|
98
104
|
'street': {
|
|
99
|
-
'type': 'string'
|
|
100
|
-
'const': 'Arnon'
|
|
105
|
+
'type': 'string'
|
|
101
106
|
},
|
|
102
107
|
'building': {
|
|
103
|
-
'type': 'number'
|
|
104
|
-
|
|
108
|
+
'type': 'number'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const res = generateJSONSchema(array);
|
|
116
|
+
expect(res).eql(expected);
|
|
117
|
+
|
|
118
|
+
const schemaRes = v.validate(array, res);
|
|
119
|
+
expect(schemaRes.errors).eql([]);
|
|
120
|
+
const schemaResRev = v.validate(array.reverse(), res);
|
|
121
|
+
expect(schemaResRev.errors).eql([]);
|
|
122
|
+
});
|
|
123
|
+
it('Array of different types of objects', () => {
|
|
124
|
+
const array = [
|
|
125
|
+
{ a: 1 },
|
|
126
|
+
{ a: 2 },
|
|
127
|
+
{ a: 3 },
|
|
128
|
+
{ a: 4 },
|
|
129
|
+
{ b: 5 },
|
|
130
|
+
{ b: 6 },
|
|
131
|
+
{ b: 7 },
|
|
132
|
+
{ b: 8 },
|
|
133
|
+
];
|
|
134
|
+
const expected = {
|
|
135
|
+
'type': 'array',
|
|
136
|
+
'items': {
|
|
137
|
+
'anyOf': [
|
|
138
|
+
{
|
|
139
|
+
'type': 'object',
|
|
140
|
+
'properties': {
|
|
141
|
+
'a': {
|
|
142
|
+
'type': 'number'
|
|
105
143
|
}
|
|
106
144
|
}
|
|
107
145
|
},
|
|
108
146
|
{
|
|
109
147
|
'type': 'object',
|
|
110
148
|
'properties': {
|
|
111
|
-
'
|
|
112
|
-
'type': '
|
|
113
|
-
'const': 'Yochai'
|
|
114
|
-
},
|
|
115
|
-
'building': {
|
|
116
|
-
'type': 'number',
|
|
117
|
-
'const': 77
|
|
149
|
+
'b': {
|
|
150
|
+
'type': 'number'
|
|
118
151
|
}
|
|
119
152
|
}
|
|
120
153
|
}
|
|
@@ -126,6 +159,8 @@ suite('JSON Schema generator', () => {
|
|
|
126
159
|
|
|
127
160
|
const schemaRes = v.validate(array, res);
|
|
128
161
|
expect(schemaRes.errors).eql([]);
|
|
162
|
+
const schemaResRev = v.validate(array.reverse(), res);
|
|
163
|
+
expect(schemaResRev.errors).eql([]);
|
|
129
164
|
});
|
|
130
165
|
it('Mixed object', () => {
|
|
131
166
|
const obj = {
|
|
@@ -142,24 +177,20 @@ suite('JSON Schema generator', () => {
|
|
|
142
177
|
'type': 'object',
|
|
143
178
|
'properties': {
|
|
144
179
|
'foo': {
|
|
145
|
-
'type': 'number'
|
|
146
|
-
'const': 123
|
|
180
|
+
'type': 'number'
|
|
147
181
|
},
|
|
148
182
|
'bar': {
|
|
149
183
|
'type': 'object',
|
|
150
184
|
'properties': {
|
|
151
185
|
'x': {
|
|
152
|
-
'type': 'number'
|
|
153
|
-
'const': 1
|
|
186
|
+
'type': 'number'
|
|
154
187
|
},
|
|
155
188
|
'y': {
|
|
156
|
-
'type': 'string'
|
|
157
|
-
'const': 'one'
|
|
189
|
+
'type': 'string'
|
|
158
190
|
},
|
|
159
191
|
'null': {},
|
|
160
192
|
'yes': {
|
|
161
|
-
'type': 'boolean'
|
|
162
|
-
'const': true
|
|
193
|
+
'type': 'boolean'
|
|
163
194
|
}
|
|
164
195
|
}
|
|
165
196
|
},
|
|
@@ -168,21 +199,15 @@ suite('JSON Schema generator', () => {
|
|
|
168
199
|
'items': {
|
|
169
200
|
'anyOf': [
|
|
170
201
|
{
|
|
171
|
-
'type': 'number'
|
|
172
|
-
'const': 1
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
'type': 'number',
|
|
176
|
-
'const': 2
|
|
202
|
+
'type': 'number'
|
|
177
203
|
},
|
|
178
204
|
{
|
|
179
205
|
'type': 'array',
|
|
180
206
|
'items': {
|
|
181
|
-
'anyOf':
|
|
207
|
+
'anyOf':
|
|
182
208
|
[
|
|
183
209
|
{
|
|
184
|
-
'type': 'number'
|
|
185
|
-
'const': 3
|
|
210
|
+
'type': 'number'
|
|
186
211
|
}
|
|
187
212
|
]
|
|
188
213
|
}
|