@loadmill/core 0.3.34 → 0.3.38

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.
Files changed (55) hide show
  1. package/dist/conf/extrema.d.ts +2 -2
  2. package/dist/conf/extrema.js +2 -0
  3. package/dist/conf/extrema.js.map +1 -1
  4. package/dist/conf/types.d.ts +1 -1
  5. package/dist/conf/validate.js +4 -1
  6. package/dist/conf/validate.js.map +1 -1
  7. package/dist/har/index.d.ts +16 -4
  8. package/dist/har/index.js.map +1 -1
  9. package/dist/parameters/extractors/extractor.d.ts +1 -1
  10. package/dist/parameters/extractors/index.d.ts +2 -1
  11. package/dist/parameters/extractors/index.js +3 -1
  12. package/dist/parameters/extractors/index.js.map +1 -1
  13. package/dist/parameters/extractors/parametrized-extractor.d.ts +4 -2
  14. package/dist/parameters/extractors/regex-extractor.js +2 -13
  15. package/dist/parameters/extractors/regex-extractor.js.map +1 -1
  16. package/dist/parameters/extractors/regex-matcher.d.ts +1 -0
  17. package/dist/parameters/extractors/regex-matcher.js +20 -0
  18. package/dist/parameters/extractors/regex-matcher.js.map +1 -0
  19. package/dist/parameters/extractors/ws-extractor.d.ts +30 -0
  20. package/dist/parameters/extractors/ws-extractor.js +198 -0
  21. package/dist/parameters/extractors/ws-extractor.js.map +1 -0
  22. package/dist/parameters/index.d.ts +5 -2
  23. package/dist/parameters/index.js +33 -3
  24. package/dist/parameters/index.js.map +1 -1
  25. package/dist/parameters/parameter-regex-providers.d.ts +3 -0
  26. package/dist/parameters/parameter-regex-providers.js +7 -1
  27. package/dist/parameters/parameter-regex-providers.js.map +1 -1
  28. package/dist/request/index.d.ts +4 -1
  29. package/dist/request/index.js +17 -5
  30. package/dist/request/index.js.map +1 -1
  31. package/dist/schema/json-schema-generator.d.ts +1 -1
  32. package/dist/schema/json-schema-generator.js +14 -10
  33. package/dist/schema/json-schema-generator.js.map +1 -1
  34. package/package.json +4 -4
  35. package/src/conf/extrema.ts +1 -1
  36. package/src/conf/index.ts +6 -6
  37. package/src/conf/notifications.ts +3 -3
  38. package/src/conf/types.ts +2 -2
  39. package/src/conf/validate.ts +5 -2
  40. package/src/echo/firehose.ts +10 -10
  41. package/src/har/index.ts +15 -5
  42. package/src/parameters/extractors/extractor.ts +1 -1
  43. package/src/parameters/extractors/index.ts +2 -1
  44. package/src/parameters/extractors/json-path-extractor.ts +1 -1
  45. package/src/parameters/extractors/parametrized-extractor.ts +3 -1
  46. package/src/parameters/extractors/regex-extractor.ts +2 -14
  47. package/src/parameters/extractors/regex-matcher.ts +16 -0
  48. package/src/parameters/extractors/ws-extractor.ts +89 -0
  49. package/src/parameters/index.ts +45 -8
  50. package/src/parameters/parameter-functions/textual-parameter-functions.ts +1 -1
  51. package/src/parameters/parameter-regex-providers.ts +8 -0
  52. package/src/request/index.ts +15 -4
  53. package/src/schema/json-schema-generator.ts +14 -12
  54. package/test/parameters/value-utils.spec.js +2 -2
  55. 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
+ }
@@ -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;
@@ -237,7 +240,7 @@ export const parameterUtils = {
237
240
  * ${__encode_url(param1)}
238
241
  * ${__if_then_else(param12,'good','bad')}
239
242
  */
240
- searchParameterOccurrences(
243
+ searchParameterizedExpressionOccurrences(
241
244
  parameterName: string,
242
245
  data: string
243
246
  ): Array<string> {
@@ -259,13 +262,13 @@ export const parameterUtils = {
259
262
  return extractionParamNames;
260
263
  },
261
264
 
262
- getUsedConfParams(conf, parameters: Parameters[]): Parameters[] {
265
+ getUsedConfParams(conf: TestConfLike, parameters: Parameters[]): Parameters[] {
263
266
  let usedParameters: Parameters[] = [];
264
267
  if (parameters) {
265
268
  const stringifyConf = JSON.stringify(conf);
266
269
  parameters.forEach((p) => {
267
270
  const paramName = this.getParameterName(p);
268
- if (!isEmpty(this.searchParameterOccurrences(paramName, stringifyConf))) {
271
+ if (isParamUsedInConf(paramName, conf, stringifyConf)) {
269
272
  const currentUsedParams = this.findUsedParameters(parameters ,p, []);
270
273
  usedParameters = usedParameters.concat(currentUsedParams);
271
274
  }
@@ -274,6 +277,10 @@ export const parameterUtils = {
274
277
  return uniqWith(usedParameters, isEqual);
275
278
  },
276
279
 
280
+ getUsedRequestParams(request, parameters: Parameters[]): Parameters[] {
281
+ return this.getUsedConfParams({ requests: [request] }, parameters);
282
+ },
283
+
277
284
  getValueByKeyFromArr(key: string, parameters: Parameters[]) {
278
285
  let res;
279
286
  const param = getParameterByKey(key, parameters);
@@ -333,7 +340,7 @@ export const parameterUtils = {
333
340
  }
334
341
  }
335
342
  } else {
336
- if(!isSingleQuoted(token)) {
343
+ if (!isSingleQuoted(token)) {
337
344
  usedParams.push(token);
338
345
  }
339
346
  }
@@ -344,7 +351,7 @@ export const parameterUtils = {
344
351
  },
345
352
  deleteAllSrcElementsFromParamArr(paramArr, src) {
346
353
  let isSrcInParamArr = this.isParamKeyInArr(src, paramArr);
347
- while(isSrcInParamArr) {
354
+ while (isSrcInParamArr) {
348
355
  this.removeParameterFromArrayByKey(src, paramArr);
349
356
  isSrcInParamArr = this.isParamKeyInArr(src, paramArr);
350
357
  }
@@ -383,7 +390,10 @@ export const parameterUtils = {
383
390
  },
384
391
  applyJSONSchema(instance, schema) {
385
392
  return applyJSONSchema(instance, schema);
386
- }
393
+ },
394
+ isUsingParameterizedValue(value: string) {
395
+ return PARAM_USAGE_REGEXP.test(value);
396
+ },
387
397
  };
388
398
 
389
399
  function resolvePlainParameter(parameterName: string, parameters) {
@@ -530,7 +540,7 @@ function resolveOperator(opSymbol: string) {
530
540
 
531
541
  function findUsedParametersRecursively(
532
542
  suiteParameters: Parameters[], param: Parameters, usedParameters: Parameters[]
533
- ) : Parameters[]{
543
+ ) : Parameters[] {
534
544
  const paramValueStringified = JSON.stringify(parameterUtils.getParameterValue(param));
535
545
  const currentUsedParameterNames = parameterUtils.getUsedRequestParamNames(paramValueStringified);
536
546
 
@@ -603,3 +613,30 @@ export const getTokensWithoutOperators = (paramOccurrence: string): string[] =>
603
613
  const tokens = getTokens(strippedParam);
604
614
  return stripOperators(tokens);
605
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
+ }
@@ -77,7 +77,7 @@ export const textualParameterFunctions = {
77
77
  },
78
78
  2
79
79
  ),
80
-
80
+
81
81
  __array_in_range: new BasicParameterFunction(
82
82
  (arrayAsStr, start, end) => {
83
83
  const arr: Array<string> = toArray(arrayAsStr);
@@ -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
+ }
@@ -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 = 60000;
5
+ export const DEFAULT_REQUEST_TIMEOUT = 25000;
6
6
  export const supportedMethods = {
7
7
  GET: 'GET',
8
8
  POST: 'POST',
@@ -46,6 +46,7 @@ export class LoadmillRequest implements RequestLike {
46
46
  assert?: Assertions;
47
47
  extract?: Extractions[];
48
48
  postScript?: string;
49
+ disabled?: boolean;
49
50
  timeout?: number;
50
51
  expectedStatus?: HttpResponseStatus;
51
52
 
@@ -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 != null && 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
 
@@ -151,6 +153,9 @@ export function createRequest(from: RequestLike): LoadmillRequest {
151
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;
@@ -342,6 +351,7 @@ export interface RequestLike {
342
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
- items.push(_getSchema(value, ignoreKeys));
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', const: 'arnon' };
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', 'const': 123 };
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', 'const': false };
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
- 'const': 42
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
- 'street': {
112
- 'type': 'string',
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
  }