@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.
Files changed (53) 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/har/index.d.ts +16 -4
  6. package/dist/har/index.js.map +1 -1
  7. package/dist/parameters/extractors/extractor.d.ts +1 -1
  8. package/dist/parameters/extractors/index.d.ts +2 -1
  9. package/dist/parameters/extractors/index.js +3 -1
  10. package/dist/parameters/extractors/index.js.map +1 -1
  11. package/dist/parameters/extractors/parametrized-extractor.d.ts +4 -2
  12. package/dist/parameters/extractors/regex-extractor.js +2 -13
  13. package/dist/parameters/extractors/regex-extractor.js.map +1 -1
  14. package/dist/parameters/extractors/regex-matcher.d.ts +1 -0
  15. package/dist/parameters/extractors/regex-matcher.js +20 -0
  16. package/dist/parameters/extractors/regex-matcher.js.map +1 -0
  17. package/dist/parameters/extractors/ws-extractor.d.ts +30 -0
  18. package/dist/parameters/extractors/ws-extractor.js +198 -0
  19. package/dist/parameters/extractors/ws-extractor.js.map +1 -0
  20. package/dist/parameters/index.d.ts +6 -2
  21. package/dist/parameters/index.js +42 -3
  22. package/dist/parameters/index.js.map +1 -1
  23. package/dist/parameters/parameter-regex-providers.d.ts +3 -0
  24. package/dist/parameters/parameter-regex-providers.js +7 -1
  25. package/dist/parameters/parameter-regex-providers.js.map +1 -1
  26. package/dist/request/index.d.ts +7 -4
  27. package/dist/request/index.js +21 -9
  28. package/dist/request/index.js.map +1 -1
  29. package/dist/schema/json-schema-generator.d.ts +1 -1
  30. package/dist/schema/json-schema-generator.js +14 -10
  31. package/dist/schema/json-schema-generator.js.map +1 -1
  32. package/package.json +4 -4
  33. package/src/conf/extrema.ts +1 -1
  34. package/src/conf/index.ts +6 -6
  35. package/src/conf/notifications.ts +3 -3
  36. package/src/conf/types.ts +2 -2
  37. package/src/conf/validate.ts +1 -1
  38. package/src/echo/firehose.ts +10 -10
  39. package/src/har/index.ts +15 -5
  40. package/src/parameters/extractors/extractor.ts +1 -1
  41. package/src/parameters/extractors/index.ts +2 -1
  42. package/src/parameters/extractors/json-path-extractor.ts +1 -1
  43. package/src/parameters/extractors/parametrized-extractor.ts +3 -1
  44. package/src/parameters/extractors/regex-extractor.ts +2 -14
  45. package/src/parameters/extractors/regex-matcher.ts +16 -0
  46. package/src/parameters/extractors/ws-extractor.ts +89 -0
  47. package/src/parameters/index.ts +56 -8
  48. package/src/parameters/parameter-functions/textual-parameter-functions.ts +1 -1
  49. package/src/parameters/parameter-regex-providers.ts +8 -0
  50. package/src/request/index.ts +20 -9
  51. package/src/schema/json-schema-generator.ts +14 -12
  52. package/test/parameters/value-utils.spec.js +2 -2
  53. 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;
@@ -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
- searchParameterOccurrences(
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 (!isEmpty(this.searchParameterOccurrences(paramName, stringifyConf))) {
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
+ }
@@ -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',
@@ -45,9 +45,10 @@ export class LoadmillRequest implements RequestLike {
45
45
 
46
46
  assert?: Assertions;
47
47
  extract?: Extractions[];
48
- postScript?: string = '';
49
- timeout: number = DEFAULT_REQUEST_TIMEOUT;
50
- expectedStatus: HttpResponseStatus = 'SUCCESS';
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 != 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
 
@@ -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: HttpResponseStatus;
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
  }