diff --git a/.gitignore b/.gitignore index 6b7e9586d4a9017cdce94a43976f072762c1e5db..4ac0229c425999f972a8480e63396c1daa92d87b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *~ *.log *.tgz +*.todo .nyc_output .DS_Store /lib diff --git a/README.md b/README.md index 823f38f9a2a55c84795f1846331f63d23798d14e..ea51bf7f16907c107b041121ce8fe9747b5d0f03 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ [](http://inch-ci.org/github/json-schema-faker/json-schema-faker) [](http://json-schema-faker.github.io/json-schema-faker/) +[](https://snyk.io/test/github/json-schema-faker/json-schema-faker) > Use [JSON Schema](http://json-schema.org/draft-04/json-schema-core.html) along with fake generators to provide consistent and meaningful fake data for your system. diff --git a/dist/json-schema-faker.bundle.min.js b/dist/bundle.umd.min.js similarity index 53% rename from dist/json-schema-faker.bundle.min.js rename to dist/bundle.umd.min.js index 26ed29064b812440883c968d55f618e62f78b0a3..9009fe48a4acb3224e84e09a2a3ea0176d75373e 100644 Binary files a/dist/json-schema-faker.bundle.min.js and b/dist/bundle.umd.min.js differ diff --git a/dist/json-schema-faker.cjs.js b/dist/index.js similarity index 89% rename from dist/json-schema-faker.cjs.js rename to dist/index.js index 0e936f75e8f03237aa51eb5f38b57c6207c917db..917e600af5e1b36ef78486fb718200a8e6dd8787 100644 Binary files a/dist/json-schema-faker.cjs.js and b/dist/index.js differ diff --git a/dist/json-schema-faker.es.js b/dist/index.mjs similarity index 89% rename from dist/json-schema-faker.es.js rename to dist/index.mjs index c65055f725938a7b8e0acf60720a09acea00a0ff..459fbd7449b8953900b9e6b2f99d79140d8a5fd9 100644 Binary files a/dist/json-schema-faker.es.js and b/dist/index.mjs differ diff --git a/dist/index.umd.js b/dist/index.umd.js new file mode 100644 index 0000000000000000000000000000000000000000..4c678eaedbdc8865a94837d75b390d62eabb752b Binary files /dev/null and b/dist/index.umd.js differ diff --git a/dist/index.umd.min.js b/dist/index.umd.min.js new file mode 100644 index 0000000000000000000000000000000000000000..6bd7b6adb6f91280f0c9350addca69ccd86873b1 Binary files /dev/null and b/dist/index.umd.min.js differ diff --git a/dist/index.umd.min.js.map b/dist/index.umd.min.js.map new file mode 100644 index 0000000000000000000000000000000000000000..0df08a47a0281e36ae78826bccb2d1293fc4261d Binary files /dev/null and b/dist/index.umd.min.js.map differ diff --git a/dist/json-schema-faker.js b/dist/json-schema-faker.js deleted file mode 100644 index 81e67a5ff46f45a1326d8ef3371787b614423fba..0000000000000000000000000000000000000000 Binary files a/dist/json-schema-faker.js and /dev/null differ diff --git a/dist/json-schema-faker.min.js b/dist/json-schema-faker.min.js deleted file mode 100644 index c5b7659846e1dc1688f864f2f17f81d4968d108b..0000000000000000000000000000000000000000 Binary files a/dist/json-schema-faker.min.js and /dev/null differ diff --git a/dist/json-schema-faker.min.js.map b/dist/json-schema-faker.min.js.map deleted file mode 100644 index 0741cacbbb6cb2df9f522957d0c4386c22303a61..0000000000000000000000000000000000000000 Binary files a/dist/json-schema-faker.min.js.map and /dev/null differ diff --git a/docs/README.md b/docs/README.md index f966c8aed1711a803bd288b76cab9ab384cfe3e8..903ae34d5f0e9127ebad30af2a6425eb9abd8b5b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -109,7 +109,7 @@ jsf.locate('faker'); - `ignoreMissingRefs` — If enabled, it will resolve to `{}` for unknown references (default: `false`) - `failOnInvalidTypes` — If enabled, it will throw an `Error` for unknown types (default: `true`) - `failOnInvalidFormat` — If enabled, it will throw an `Error` for unknown formats (default: `true`) -- `alwaysFakeOptionals` — When enabled, it will set `optionalsProbability` as `1.0` (default: `false`) +- `alwaysFakeOptionals` — When enabled, it will set `optionalsProbability: 1.0` and ` fixedProbabilities: true` (default: `false`) - `optionalsProbability` — A value from `0.0` to `1.0` to generate values in a consistent way, e.g. `0.5` will generate from `0%` to `50%` of values. Using arrays it means items, on objects they're properties, etc. (default: `false`) - `fixedProbabilities` — If enabled, then `optionalsProbability: 0.5` will always generate the half of values (default: `false`) - `useExamplesValue` — If enabled, it will return a random value from `examples` if they're present (default: `false`) diff --git a/package-lock.json b/package-lock.json index e736b4f30e651aeab77b77e74215c2f531a4c5ac..4ff3ccd92ed2923b27aea6d850a4d309b7852fc0 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 010f8b4157efe31c783e95d7debecee841c786ae..15fb8b928be7308311ae3bf5b73f6a77c875dafe 100644 --- a/package.json +++ b/package.json @@ -3,21 +3,21 @@ "version": "0.5.0-rc16", "description": "JSON-Schema + fake data generators", "homepage": "http://json-schema-faker.js.org", - "main": "dist/json-schema-faker.cjs.js", - "module": "dist/json-schema-faker.es.js", - "browser": "dist/json-schema-faker.min.js", + "main": "dist/index.js", + "module": "dist/index.mjs", + "browser": "dist/index.umd.min.js", "scripts": { - "build:concat:dist": "concat -o dist/json-schema-faker.bundle.min.js node_modules/json-schema-ref-parser/dist/ref-parser.min.js node_modules/jsonpath/jsonpath.js dist/json-schema-faker.min.js", - "build:browser": "bili --banner --target browser --format umd,umd-min --moduleName JSONSchemaFaker --name json-schema-faker --js buble && npm run build:concat:dist", - "build:node": "bili src/index.js --target node --js buble --format es,cjs", + "build:concat:dist": "concat -o dist/bundle.umd.min.js node_modules/json-schema-ref-parser/dist/ref-parser.min.js node_modules/jsonpath/jsonpath.js dist/index.umd.min.js", + "build:browser": "bili --banner --format umd --format umd-min --module-name JSONSchemaFaker --minimal && npm run build:concat:dist", + "build:node": "bili src/index.js --minimal --format es --format cjs", "build": "npm run build:browser && npm run build:node", "dev": "npm test -- -w", "test": "npm run test:unit --", "test:ci": "npm run coverage:all && npm run report -- -r lcov", "test:all": "npm run test:run tests && npm run report -- -r html", "test:run": "NODE_ENV=test _mocha --exit --recursive --watch-extensions js,json -r esm -bR spec", - "test:unit": "npm run test:run tests/unit", - "test:schema": "npm run test:run tests/schema", + "test:unit": "npm run test:run tests/unit --", + "test:schema": "npm run test:run tests/schema --", "coverage": "nyc -x '**/tests/**' -x '**/*.spec.js'", "coverage:all": "npm run coverage -- npm run test:all", "coverage:unit": "npm run coverage -- npm run test:unit", @@ -53,7 +53,7 @@ }, "devDependencies": { "ajv": "^6.5.3", - "bili": "^3.1.2", + "bili": "^4.2.5", "chai": "^4.1.2", "chance": "^1.0.9", "clone": "^2.1.2", @@ -64,19 +64,19 @@ "fs-extra": "^7.0.0", "glob": "^7.1.2", "is-my-json-valid": "^2.19.0", - "mocha": "^5.2.0", - "nyc": "^13.0.1", - "rollup": "^0.66.2", + "mocha": "^6.0.0", + "nyc": "^13.3.0", + "rollup": "^1.2.2", "rollup-plugin-commonjs": "^9.1.0", - "rollup-plugin-node-resolve": "^3.0.0", - "seedrandom": "^2.4.3", + "rollup-plugin-node-resolve": "^4.0.0", + "seedrandom": "^3.0.1", "semver": "^5.3.0", "tv4": "^1.3.0", "z-schema": "^3.18.4" }, "dependencies": { - "json-schema-ref-parser": "^5.0.0", - "jsonpath": "^1.0.0", + "json-schema-ref-parser": "^6.0.2", + "jsonpath": "^1.0.1", "randexp": "^0.5.3" } } diff --git a/src/api/format.js b/src/api/format.js index 8d752c0a1ed95530da211a0a73ae311a012c6e89..cedfeecb699dccebdbac23f8fc91793b3f3ff6a8 100644 --- a/src/api/format.js +++ b/src/api/format.js @@ -14,7 +14,9 @@ const registry = new Registry(); function formatAPI(nameOrFormatMap, callback) { if (typeof nameOrFormatMap === 'undefined') { return registry.list(); - } else if (typeof nameOrFormatMap === 'string') { + } + + if (typeof nameOrFormatMap === 'string') { if (typeof callback === 'function') { registry.register(nameOrFormatMap, callback); } else if (callback === null || callback === false) { diff --git a/src/api/option.js b/src/api/option.js index e014d608c0d361b87188796f956e02c29f6550c2..97bb5920332f24ceedbbc9f8b235232b18df6cf8 100644 --- a/src/api/option.js +++ b/src/api/option.js @@ -9,8 +9,12 @@ const registry = new OptionRegistry(); * @param nameOrOptionMap * @returns {any} */ -function optionAPI(nameOrOptionMap) { +function optionAPI(nameOrOptionMap, optionalValue) { if (typeof nameOrOptionMap === 'string') { + if (typeof optionalValue !== 'undefined') { + return registry.register(nameOrOptionMap, optionalValue); + } + return registry.get(nameOrOptionMap); } diff --git a/src/core/random.js b/src/core/random.js index 0ea6faffdafdc36e28b277314ed56454e6aadbbe..add5d1f0f9bec6ed9c97df89242da8ec4e055cdb 100644 --- a/src/core/random.js +++ b/src/core/random.js @@ -1,7 +1,7 @@ import RandExp from 'randexp'; import optionAPI from '../api/option'; -import env from '../core/constants'; +import env from './constants'; function getRandomInteger(min, max) { min = typeof min === 'undefined' ? env.MIN_INTEGER : min; @@ -15,8 +15,7 @@ function _randexp(value) { RandExp.prototype.max = optionAPI('defaultRandExpMax'); // same implementation as the original except using our random - RandExp.prototype.randInt = (a, b) => - a + Math.floor(optionAPI('random')() * (1 + (b - a))); + RandExp.prototype.randInt = (a, b) => a + Math.floor(optionAPI('random')() * (1 + (b - a))); const re = new RandExp(value); diff --git a/src/core/run.js b/src/core/run.js index 07979b36d5d22629973dc055d783dc0e46b6bbca..d53f73b0f70d90d68ee05e2944eb21baf32b1cf9 100644 --- a/src/core/run.js +++ b/src/core/run.js @@ -83,7 +83,7 @@ function resolve(obj, data, values, property) { // TODO provide types function run(refs, schema, container) { try { - const result = traverse(utils.merge({}, schema), [], function reduce(sub, maxReduceDepth, parentSchemaPath) { + const result = traverse(utils.clone(schema), [], function reduce(sub, maxReduceDepth, parentSchemaPath) { if (typeof maxReduceDepth === 'undefined') { maxReduceDepth = random.number(1, 3); } @@ -163,8 +163,18 @@ function run(refs, schema, container) { return { thunk() { const copy = utils.omitProps(sub, ['anyOf', 'oneOf']); - - utils.merge(copy, random.pick(mix)); + const fixed = random.pick(mix); + utils.merge(copy, fixed); + + if (sub.oneOf) { + mix.forEach(omit => { + if (omit !== fixed && omit.required) { + omit.required.forEach(key => { + delete copy.properties[key]; + }); + } + }); + } return copy; }, diff --git a/src/core/traverse.js b/src/core/traverse.js index 43c100d7b205066e318804024d61197211dec951..fd3488653cad9416b2d02ee42adb1c11cb726996 100644 --- a/src/core/traverse.js +++ b/src/core/traverse.js @@ -80,7 +80,7 @@ function traverse(schema, path, resolve, rootSchema) { return types[type](schema, path, resolve, traverse); } catch (e) { if (typeof e.path === 'undefined') { - throw new ParseError(e.message, path); + throw new ParseError(e.stack, path); } throw e; } diff --git a/src/core/utils.js b/src/core/utils.js index d6d56043a795ff695edde4df01090af1c7df6bbb..c0146b132045e183cb53fce60f9207a49ebf20c8 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -1,5 +1,5 @@ import optionAPI from '../api/option'; -import env from '../core/constants'; +import env from './constants'; import random from './random'; function getSubAttribute(obj, dotSeparatedKey) { @@ -133,7 +133,11 @@ function typecast(type, schema, callback) { const max = Math.min(params.maxLength || Infinity, Infinity); while (value.length < min) { - value += ` ${value}`; + if (!schema.pattern) { + value += `${random.pick([' ', '/', '_', '-', '+', '=', '@', '^'])}${value}`; + } else { + value += random.randexp(schema.pattern); + } } if (value.length > max) { @@ -143,7 +147,7 @@ function typecast(type, schema, callback) { switch (schema.format) { case 'date-time': case 'datetime': - value = new Date(value).toISOString(); + value = new Date(value).toISOString().replace(/([0-9])0+Z$/, '$1Z'); break; case 'date': @@ -151,7 +155,7 @@ function typecast(type, schema, callback) { break; case 'time': - value = new Date(value).toISOString().substr(11); + value = new Date(`1969-01-01 ${value}`).toISOString().substr(11); break; default: @@ -188,6 +192,21 @@ function merge(a, b) { return a; } +function clone(obj) { + if (!obj || typeof obj !== 'object') { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(x => clone(x)); + } + + return Object.keys(obj).reduce((prev, cur) => { + prev[cur] = clone(obj[cur]); + return prev; + }, {}); +} + function short(schema) { const s = JSON.stringify(schema); const l = JSON.stringify(schema, null, 2); @@ -207,6 +226,7 @@ function anyValue() { undefined, [], {}, + // FIXME: use built-in random? Math.random(), Math.random().toString(36).substr(2), ]); @@ -294,7 +314,7 @@ function omitProps(obj, props) { if (Array.isArray(obj[k])) { copy[k] = obj[k].slice(); } else { - copy[k] = typeof obj[k] === 'object' + copy[k] = obj[k] instanceof Object ? merge({}, obj[k]) : obj[k]; } @@ -322,6 +342,7 @@ export default { omitProps, typecast, merge, + clone, short, notValue, anyValue, diff --git a/src/generators/thunk.js b/src/generators/thunk.js index 99523c8b88a93ba478301d2880defe48d595487a..ab50a6aa55071b04494b9f38804cc80977ae4fd0 100644 --- a/src/generators/thunk.js +++ b/src/generators/thunk.js @@ -1,4 +1,4 @@ -import words from '../generators/words'; +import words from './words'; import random from '../core/random'; /** diff --git a/src/types/array.js b/src/types/array.js index cd5f6a3a3c8571b0ca5f11e43f52667ce01883cd..c1b63e3ac9196e8c1a0b9edfe38a5518094a9bb8 100644 --- a/src/types/array.js +++ b/src/types/array.js @@ -14,7 +14,11 @@ function unique(path, items, value, sample, resolve, traverseCallback) { if (seen.indexOf(json) === -1) { seen.push(json); tmp.push(obj); + + return true; } + + return false; } items.forEach(walk); @@ -23,10 +27,11 @@ function unique(path, items, value, sample, resolve, traverseCallback) { let limit = 100; while (tmp.length !== items.length) { - walk(traverseCallback(value.items || sample, path, resolve)); + if (!walk(traverseCallback(value.items || sample, path, resolve))) { + limit -= 1; + } if (!limit) { - limit -= 1; break; } } @@ -76,14 +81,14 @@ function arrayType(value, path, resolve, traverseCallback) { } const optionalsProbability = optionAPI('alwaysFakeOptionals') === true ? 1.0 : optionAPI('optionalsProbability'); - const fixedProbabilities = optionAPI('fixedProbabilities') || false; + const fixedProbabilities = optionAPI('alwaysFakeOptionals') || optionAPI('fixedProbabilities') || false; let length = random.number(minItems, maxItems, 1, 5); if (optionalsProbability !== false) { - length = fixedProbabilities + length = Math.max(fixedProbabilities ? Math.round((maxItems || length) * optionalsProbability) - : Math.abs(random.number(minItems, maxItems) * optionalsProbability); + : Math.abs(random.number(minItems, maxItems) * optionalsProbability), minItems || 0); } // TODO below looks bad. Should additionalItems be copied as-is? diff --git a/src/types/null.js b/src/types/null.js index 9362f837fb5b88674dae34bbf792ec2a24c72c16..9e9bbb20f6b8bee7396f42942b4caa62c1d61c8e 100644 --- a/src/types/null.js +++ b/src/types/null.js @@ -3,4 +3,3 @@ import nullGenerator from '../generators/null'; const nullType = nullGenerator; export default nullType; - diff --git a/src/types/number.js b/src/types/number.js index ea5e31550878142e6d68e4f56fcba85ceb2aedb8..7eeafffe65bfe21d3c095db4511411d2bd12755c 100644 --- a/src/types/number.js +++ b/src/types/number.js @@ -45,7 +45,6 @@ function numberType(value) { fix = (num / multipleOf) % 1; } while (fix !== 0); - // FIXME: https://github.com/json-schema-faker/json-schema-faker/issues/379 return num; diff --git a/src/types/object.js b/src/types/object.js index c71faf5ee7e6402ecc3abef703bfe545083d8651..c5add7359c812d84aff1185fe4db39eec0a5fb6e 100644 --- a/src/types/object.js +++ b/src/types/object.js @@ -2,7 +2,6 @@ import random from '../core/random'; import words from '../generators/words'; import utils from '../core/utils'; import optionAPI from '../api/option'; -import ParseError from '../core/error'; // fallback generator const anyType = { type: ['string', 'number', 'integer', 'boolean'] }; @@ -13,7 +12,7 @@ function objectType(value, path, resolve, traverseCallback) { const properties = value.properties || {}; const patternProperties = value.patternProperties || {}; - const requiredProperties = (value.required || []).slice(); + const requiredProperties = typeof value.required === 'boolean' ? [] : (value.required || []).slice(); const allowsAdditional = value.additionalProperties !== false; const propertyKeys = Object.keys(properties); @@ -26,12 +25,12 @@ function objectType(value, path, resolve, traverseCallback) { const additionalProperties = allowsAdditional // eslint-disable-line ? (value.additionalProperties === true ? anyType : value.additionalProperties) - : null; + : value.additionalProperties; - if (!allowsAdditional && - propertyKeys.length === 0 && - patternPropertyKeys.length === 0 && - utils.hasProperties(value, 'minProperties', 'maxProperties', 'dependencies', 'required') + if (!allowsAdditional + && propertyKeys.length === 0 + && patternPropertyKeys.length === 0 + && utils.hasProperties(value, 'minProperties', 'maxProperties', 'dependencies', 'required') ) { // just nothing return {}; @@ -48,23 +47,23 @@ function objectType(value, path, resolve, traverseCallback) { } const optionalsProbability = optionAPI('alwaysFakeOptionals') === true ? 1.0 : optionAPI('optionalsProbability'); - const fixedProbabilities = optionAPI('fixedProbabilities') || false; + const fixedProbabilities = optionAPI('alwaysFakeOptionals') || optionAPI('fixedProbabilities') || false; const ignoreProperties = optionAPI('ignoreProperties') || []; const min = Math.max(value.minProperties || 0, requiredProperties.length); - const max = Math.min(value.maxProperties || allProperties.length, allProperties.length); + const max = value.maxProperties || (allProperties.length + random.number(1, 5)); let neededExtras = Math.max(0, min - requiredProperties.length); if (allProperties.length === 1 && !requiredProperties.length) { - neededExtras = random.number(neededExtras, allProperties.length + (max - min)); + neededExtras = random.number(neededExtras, allProperties.length + (allProperties.length - min)); } if (optionalsProbability !== false) { if (fixedProbabilities === true) { - neededExtras = Math.round((min - requiredProperties.length) + (optionalsProbability * (max - min))); + neededExtras = Math.round((min - requiredProperties.length) + (optionalsProbability * (allProperties.length - min))); } else { - neededExtras = random.number(min - requiredProperties.length, optionalsProbability * (max - min)); + neededExtras = random.number(min - requiredProperties.length, optionalsProbability * (allProperties.length - min)); } } @@ -75,6 +74,35 @@ function objectType(value, path, resolve, traverseCallback) { // properties are read from right-to-left const _props = requiredProperties.concat(extraProperties).slice(0, max); + const _defns = []; + + if (value.dependencies) { + Object.keys(value.dependencies).forEach(prop => { + const _required = value.dependencies[prop]; + + if (_props.indexOf(prop) !== -1) { + if (Array.isArray(_required)) { + // property-dependencies + _required.forEach(sub => { + if (_props.indexOf(sub) === -1) { + _props.push(sub); + } + }); + } else { + _defns.push(_required); + } + } + }); + + // schema-dependencies + if (_defns.length) { + delete value.dependencies; + + return traverseCallback({ + allOf: _defns.concat(value), + }, path.concat(['properties']), resolve); + } + } const skipped = []; const missing = []; @@ -89,32 +117,40 @@ function objectType(value, path, resolve, traverseCallback) { } } - // first ones are the required properies - if (properties[key]) { + if (additionalProperties === false) { + if (requiredProperties.indexOf(key) !== -1) { + props[key] = properties[key]; + } + } else if (properties[key]) { props[key] = properties[key]; - } else { - let found; + } + + let found; - // then try patternProperties - patternPropertyKeys.forEach(_key => { - if (key.match(new RegExp(_key))) { - found = true; + // then try patternProperties + patternPropertyKeys.forEach(_key => { + if (key.match(new RegExp(_key))) { + found = true; + + if (props[key]) { + utils.merge(props[key], patternProperties[_key]); + } else { props[random.randexp(key)] = patternProperties[_key]; } - }); + } + }); - if (!found) { - // try patternProperties again, - const subschema = patternProperties[key] || additionalProperties; + if (!found) { + // try patternProperties again, + const subschema = patternProperties[key] || additionalProperties; - // FIXME: allow anyType as fallback when no subschema is given? + // FIXME: allow anyType as fallback when no subschema is given? - if (subschema) { - // otherwise we can use additionalProperties? - props[patternProperties[key] ? random.randexp(key) : key] = subschema; - } else { - missing.push(key); - } + if (subschema && additionalProperties !== false) { + // otherwise we can use additionalProperties? + props[patternProperties[key] ? random.randexp(key) : key] = properties[key] || subschema; + } else { + missing.push(key); } } }); @@ -125,6 +161,9 @@ function objectType(value, path, resolve, traverseCallback) { // discard already ignored props if they're not required to be filled... let current = Object.keys(props).length + (fillProps ? 0 : skipped.length); + // generate dynamic suffix for additional props... + const hash = suffix => random.randexp(`_?[_a-f\\d]{1,3}${suffix ? '\\$?' : ''}`); + function get() { let one; @@ -173,7 +212,7 @@ function objectType(value, path, resolve, traverseCallback) { current += 1; } } else { - const word = get() || (words(1) + random.randexp('[a-f\\d]{1,3}')); + const word = get() || (words(1) + hash()); if (!props[word]) { props[word] = additionalProperties || anyType; @@ -186,6 +225,7 @@ function objectType(value, path, resolve, traverseCallback) { const _key = patternPropertyKeys[i]; const word = random.randexp(_key); + if (!props[word]) { props[word] = patternProperties[_key]; current += 1; @@ -193,18 +233,16 @@ function objectType(value, path, resolve, traverseCallback) { } } - if (!allowsAdditional && current < min) { - if (missing.length) { - throw new ParseError(`properties '${ - missing.join(', ') - }' were not found while additionalProperties is false:\n${ - utils.short(value) - }`, path); - } + // fill up-to this value and no more! + const maximum = random.number(min, max); + + for (; current < maximum && additionalProperties;) { + const word = words(1) + hash(true); - throw new ParseError(`properties constraints were too strong to successfully generate a valid object for:\n${ - utils.short(value) - }`, path); + if (!props[word]) { + props[word] = additionalProperties; + current += 1; + } } return traverseCallback(props, path.concat(['properties']), resolve); diff --git a/tests/schema/core/dependencies.json b/tests/schema/core/dependencies.json new file mode 100644 index 0000000000000000000000000000000000000000..dc0086923fc7e65ad311bed894f3d864a5093894 --- /dev/null +++ b/tests/schema/core/dependencies.json @@ -0,0 +1,69 @@ +[ + { + "description": "dependencies support", + "tests": [ + { + "description": "should handle property-dependencies", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "credit_card": { + "type": "number" + }, + "billing_address": { + "type": "string" + } + }, + "required": [ + "name" + ], + "dependencies": { + "credit_card": [ + "billing_address" + ] + } + }, + "valid": true, + "set": { + "alwaysFakeOptionals": true + } + }, + { + "description": "should handle schema-dependencies", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "credit_card": { + "type": "number" + } + }, + "required": [ + "name" + ], + "dependencies": { + "credit_card": { + "properties": { + "billing_address": { + "type": "string" + } + }, + "required": [ + "billing_address" + ] + } + } + }, + "valid": true, + "set": { + "alwaysFakeOptionals": true + } + } + ] + } +] diff --git a/tests/schema/core/issues/issue-379.json b/tests/schema/core/issues/issue-379.json index cd3051d9ccbdbcca7d55f446171286efef638707..6740930e5cf0ef4f09f87079759d5b77de12843e 100644 --- a/tests/schema/core/issues/issue-379.json +++ b/tests/schema/core/issues/issue-379.json @@ -18,9 +18,8 @@ "multipleOf": 0.01 }, "seed": 0.06559633646612273, - "equal": 13119267.29, - "skip": true, - "valid": true + "equal": 13119267.290000001, + "valid": false } ] } diff --git a/tests/schema/core/issues/issue-386.json b/tests/schema/core/issues/issue-386.json index 65acbe24bff58f1428ad797c5da2d8a923666f4d..ae499a8b0d24384f28d2bd270c9a672cb3700436 100644 --- a/tests/schema/core/issues/issue-386.json +++ b/tests/schema/core/issues/issue-386.json @@ -60,9 +60,8 @@ "additionalProperties": false }, "set": { - "optionalsProbability": 1 + "alwaysFakeOptionals": true }, - "skip": true, "valid": true } ] diff --git a/tests/schema/core/issues/issue-486.json b/tests/schema/core/issues/issue-486.json new file mode 100644 index 0000000000000000000000000000000000000000..9ea4aae523160fab612891347dea4bdfae4f9a0d --- /dev/null +++ b/tests/schema/core/issues/issue-486.json @@ -0,0 +1,26 @@ +[ + { + "description": "pattern constraints", + "tests": [ + { + "description": "should fullfil minLength/maxLength constraints", + "schema": { + "type": "string", + "minLength": 8, + "maxLength": 20, + "pattern": "^[a-zA-Z0-9_]+$" + }, + "valid": true + }, + { + "description": "would fail on complex patterns", + "schema": { + "type": "string", + "pattern": "^A[CD]{1,6}B$", + "minLength": 6 + }, + "throws": "String does not match pattern" + } + ] + } +] diff --git a/tests/schema/core/issues/issue-489.json b/tests/schema/core/issues/issue-489.json new file mode 100644 index 0000000000000000000000000000000000000000..8f7578526db397db87ca4bbb38d7d443e153a68a --- /dev/null +++ b/tests/schema/core/issues/issue-489.json @@ -0,0 +1,55 @@ +[ + { + "description": "support consider patternProperties", + "tests": [ + { + "description": "ensure first schema passes", + "schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "patternProperties": { + "test": { + "type": "null" + } + }, + "properties": { + "test": { + "title": "short description" + } + }, + "required": ["test"] + }, + "valid": true + }, + { + "description": "merge both properties definitions", + "schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "patternProperties": { + ".*_int$": { + "type": "integer" + } + }, + "properties": { + "big_int": { + "title": "short description", + "minimum": 100 + }, + "normal_int": { + "title": "short description" + }, + "small_int": { + "title": "short description", + "maximum": 100 + } + }, + "required": [ + "big_int", + "normal_int", + "small_int" + ] + }, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-494.json b/tests/schema/core/issues/issue-494.json new file mode 100644 index 0000000000000000000000000000000000000000..489bf020e5c9275a0be004e1b848c4b858ef0454 --- /dev/null +++ b/tests/schema/core/issues/issue-494.json @@ -0,0 +1,67 @@ +[ + { + "description": "will use alwaysFakeOptionals", + "tests": [ + { + "description": "should work if enabled", + "schema": { + "account-response": { + "title": "Account Response", + "type": "object", + "properties": { + "current": { + "type": "array", + "items": { + "$ref": "#/definitions/current-account" + } + }, + "escrow": { + "type": "array", + "items": { + "$ref": "#/definitions/current-account" + } + } + }, + "required": [ + "current" + ] + }, + "definitions": { + "current-account": { + "title": "Current Account", + "type": "object", + "properties": { + "number": { + "type": "string" + }, + "name": { + "type": "string" + }, + "balance": { + "type": "number" + }, + "currency": { + "type": "string" + } + }, + "required": [ + "number", + "name", + "balance", + "currency" + ] + } + } + }, + "set": { + "alwaysFakeOptionals": true + }, + "notEmpty": [ + "account-response.current", + "account-response.escrow" + ], + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-498.json b/tests/schema/core/issues/issue-498.json new file mode 100644 index 0000000000000000000000000000000000000000..c77c342117658e962625a8450b543c65b4c3d98b --- /dev/null +++ b/tests/schema/core/issues/issue-498.json @@ -0,0 +1,26 @@ +[ + { + "description": "support consider patternProperties", + "tests": [ + { + "description": "ensure first schema passes", + "schema": { + "required": [ + "longitude" + ], + "type": "object", + "properties": { + "longitude": { + "title": "Longitude", + "type": "string", + "format": "decimal" + } + } + }, + "set": { + "failOnInvalidFormat": false + } + } + ] + } +] diff --git a/tests/schema/core/issues/issue-502.json b/tests/schema/core/issues/issue-502.json new file mode 100644 index 0000000000000000000000000000000000000000..176ca2b600bd827c5ccbe21c866256e74a972fef --- /dev/null +++ b/tests/schema/core/issues/issue-502.json @@ -0,0 +1,50 @@ +[ + { + "description": "will use alwaysFakeOptionals (regression)", + "tests": [ + { + "description": "will fix #502", + "schema": { + "title": "Forum", + "type": "object", + "properties": { + "Id": { + "type": "string" + }, + "Topics": { + "type": "array", + "items": { + "$ref": "#/definitions/Topic" + } + } + }, + "definitions": { + "Topic": { + "type": "object", + "properties": { + "Id": { + "type": "string" + }, + "Posts": { + "type": "array", + "items": { + "$ref": "#/definitions/Post" + } + } + } + }, + "Post": { + "type": "object", + "properties": { + "Id": { + "type": "string" + } + } + } + } + }, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-504.json b/tests/schema/core/issues/issue-504.json new file mode 100644 index 0000000000000000000000000000000000000000..df424470cd4f47a826e3380855f246836acf8579 --- /dev/null +++ b/tests/schema/core/issues/issue-504.json @@ -0,0 +1,27 @@ +[ + { + "description": "pattern and min/max length constraints", + "tests": [ + { + "description": "should support minLength", + "schema": { + "type": "object", + "properties": { + "rodo": { + "type": "string" + } + }, + "required": [ + "rodo" + ], + "minProperties": 1, + "maxProperties": 3, + "additionalProperties": { + "type": "boolean" + } + }, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-506.json b/tests/schema/core/issues/issue-506.json new file mode 100644 index 0000000000000000000000000000000000000000..6a3974a2870f34df2446c910d8d6fce02ae7cca4 --- /dev/null +++ b/tests/schema/core/issues/issue-506.json @@ -0,0 +1,31 @@ +[ + { + "description": "about uniqueItems and enum", + "tests": [ + { + "description": "should not take forever", + "schema": { + "type": "object", + "properties": { + "test": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "a", + "b", + "c", + "d", + "e" + ] + }, + "maxItems": 10, + "uniqueItems": true + } + } + }, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-508.json b/tests/schema/core/issues/issue-508.json new file mode 100644 index 0000000000000000000000000000000000000000..043028f3a63f0d7f3aaa3120f34a5b1a6b0f6837 --- /dev/null +++ b/tests/schema/core/issues/issue-508.json @@ -0,0 +1,22 @@ +[ + { + "description": "null issues", + "tests": [ + { + "description": "should not iterate nulls", + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null + }, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/option/failOnInvalidType.js b/tests/schema/core/option/failOnInvalidType.js index c1d45d76a9da596c9b927f6aec891540a830f31d..5470f91ff5e1b0fdf4822ed6b0a4414a26552774 100644 --- a/tests/schema/core/option/failOnInvalidType.js +++ b/tests/schema/core/option/failOnInvalidType.js @@ -5,4 +5,3 @@ module.exports = { }); }, }; - diff --git a/tests/schema/core/option/random.js b/tests/schema/core/option/random.js index 51de6ffedfe1322b38584d1241cd39320b253fc2..6fba248d60247e41cbfca1e4824687ce7fd8cdfc 100644 --- a/tests/schema/core/option/random.js +++ b/tests/schema/core/option/random.js @@ -2,8 +2,6 @@ const seedrandom = require('seedrandom'); module.exports = { register(jsf) { - return jsf.option({ - random: seedrandom('some seed'), - }); + return jsf.option('random', seedrandom('some seed')); }, }; diff --git a/tests/schema/core/option/random.json b/tests/schema/core/option/random.json index cce07cdfae7cfbb782c7e0b704b1dcac312a18ec..e126961c8d3c9285559f8c462a5768880e1c39be 100644 --- a/tests/schema/core/option/random.json +++ b/tests/schema/core/option/random.json @@ -23,9 +23,9 @@ }, "valid": true, "equal": { - "a": 93, - "b": "enim veniam", - "c": "ff353" + "a": 71, + "b": "do cupidatat", + "c": "wj382" }, "repeat": 1, "require": "core/option/random" diff --git a/tests/schema/core/option/useDefaultValue.js b/tests/schema/core/option/useDefaultValue.js index 9150c32ca6d1a4032f331e54586f08412eab7b8d..fe027ba0ec3f022f46ac41d7233b99c878c892fe 100644 --- a/tests/schema/core/option/useDefaultValue.js +++ b/tests/schema/core/option/useDefaultValue.js @@ -5,4 +5,3 @@ module.exports = { }); }, }; - diff --git a/tests/schema/core/option/useExamplesValue.js b/tests/schema/core/option/useExamplesValue.js index 52964df691f8b89bd190427efbeb15295b2e17c0..4ad2e79cdfbe839655529e1a6f0c972ded5d34ef 100644 --- a/tests/schema/core/option/useExamplesValue.js +++ b/tests/schema/core/option/useExamplesValue.js @@ -5,4 +5,3 @@ module.exports = { }); }, }; - diff --git a/tests/schema/core/refs/sync.json b/tests/schema/core/refs/sync.json index 42b8596430aaf4c13df933e2f7426b94a51c1d7e..156f12378ed24fa35d182614e45f1be341376a11 100644 --- a/tests/schema/core/refs/sync.json +++ b/tests/schema/core/refs/sync.json @@ -24,6 +24,9 @@ } } }, + "FIXME": "this eventually fails, but because other is resolved to undefined; it just happen when repeating too much (too many requests?)", + "_repeat": 100, + "_only": true, "valid": true }, { diff --git a/tests/schema/core/types/object.json b/tests/schema/core/types/object.json index 01ba47f89f991ac9c1f4774fe117a9b304241684..433a326699c7762667256fb44b594e4981636eff 100644 --- a/tests/schema/core/types/object.json +++ b/tests/schema/core/types/object.json @@ -269,13 +269,13 @@ "type": "object", "properties": { "$count": { - "type": "string" + "type": "string" } }, "minProperties": 20, "additionalProperties": false }, - "throws": "properties constraints were too strong to successfully generate a valid object for:\n[\\s\\S]+? in \\/" + "throws": "properties '\\$count' were not found while additionalProperties is false" }, { "description": "should handle inferred type (when possible)", diff --git a/tests/schema/core/types/string.json b/tests/schema/core/types/string.json index 6d29ab93f5c524c8f86a49b09039501942120f69..612bdcc1f74872018fdbf49cd62616473aafcb33 100644 --- a/tests/schema/core/types/string.json +++ b/tests/schema/core/types/string.json @@ -114,7 +114,7 @@ }, "required": ["test"] }, - "throws": "Error: unknown registry key .+? in \\/properties\\/test" + "throws": "Error: unknown registry key .+?" }, { "description": "should validate custom formats", diff --git a/tests/schema/helpers.js b/tests/schema/helpers.js index 137c0164cc4ba726cb61770c4e3d6b88a8eba72c..9e9bfdf767d034672bb6a095c07edc51b060dd7a 100644 --- a/tests/schema/helpers.js +++ b/tests/schema/helpers.js @@ -78,6 +78,16 @@ export function tryTest(test, refs, schema) { expect(sample.length).to.eql(test.length); } + if (test.notEmpty) { + test.notEmpty.forEach(x => { + const value = pick(sample, x); + + if (value.length === 0) { + throw new Error(`${x} should not be empty`); + } + }); + } + if (test.hasProps) { test.hasProps.forEach(prop => { if (Array.isArray(sample)) { @@ -108,12 +118,16 @@ export function tryTest(test, refs, schema) { }).catch(error => { if (typeof test.throws === 'string') { expect(error).to.match(new RegExp(test.throws, 'im')); + return; } if (typeof test.throws === 'boolean') { if (test.throws !== true) { throw error; } + return; } + + throw error; }); } diff --git a/tests/schema/main.spec.js b/tests/schema/main.spec.js index c959318019ce102d99447ba2494d39068954ccc9..0dba88bcbdd09cdc319ba4075f4ded1a3dcec854 100644 --- a/tests/schema/main.spec.js +++ b/tests/schema/main.spec.js @@ -1,11 +1,21 @@ -import { jsf, pick, tryTest, getTests } from './helpers'; +import { + jsf, pick, tryTest, getTests, +} from './helpers'; const { only, all } = getTests(__dirname); /* global describe, it */ +const seeds = []; + +function seed() { + const value = Math.random(); + seeds.push(value); + return value; +} + (only.length ? only : all).forEach(suite => { - describe(`${suite.description} (${suite.file.replace(`${__dirname}/`, '')})`, () => { + describe(`${suite.description} (${suite.file.replace(`${process.cwd()}/`, '')})`, () => { suite.tests.forEach(test => { it(test.description, () => { jsf.option(jsf.option.getDefaults()); @@ -14,11 +24,9 @@ const { only, all } = getTests(__dirname); jsf.option(test.set); } - if (test.seed) { - jsf.option({ - random: () => test.seed, - }); - } + jsf.option({ + random: () => ((test.seed && (Array.isArray(test.seed) ? test.seed.shift() : test.seed)) || seed()), + }); if (test.require) { require(`./${test.require}`).register(jsf); @@ -48,7 +56,11 @@ const { only, all } = getTests(__dirname); nth -= 1; } - return Promise.all(tasks); + return Promise.all(tasks).catch(e => { + // FIXME: find a way to debug this + console.log('---> Used seeds:', seeds.slice(-10).join(', ') || test.seed); + throw e; + }); }).timeout(process.CI ? 30000 : 10000); }); });