UNPKG

json-schema-test-data-generator

Version:
458 lines (376 loc) 11.2 kB
'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } var jsf = require('json-schema-faker'); var _ = require('lodash'); var Chance = require('chance'); var ps = require('prop-search'); var chance = new Chance(); var types = ['array', 'boolean', 'integer', 'number', 'null', 'object', 'string']; function validate(schema) { if ((typeof schema === 'undefined' ? 'undefined' : _typeof(schema)) !== 'object') { return false; } if (_typeof(schema.properties) !== 'object' && typeof schema.type !== 'string') { return false; } return true; } function getNegativeTypes(prop) { if (!prop || !prop.type || typeof prop.type !== 'string' && !Array.isArray(prop.type)) { return; } var allowedTypes = typeof prop.type === 'string' ? [prop.type] : prop.type; if (!allowedTypes.length) { return; } return _.difference(types, allowedTypes, ['object', 'array']); } function generateForProp(schema, type, key) { var d = jsf(schema); var value = void 0; if (type === 'string') { value = chance.string(); } else if (type === 'number') { value = chance.floating({ min: 0, max: 100, fixed: 2 }); } else if (type === 'integer') { value = chance.integer(); } else if (type === 'boolean') { value = chance.bool(); } else if (type === 'null') { value = null; } if (key) { d[key] = value; } else { d = value; } var ret = { valid: false, data: d, message: key ? 'should not work with \'' + key + '\' of type \'' + type + '\'' : 'should not work with type \'' + type + '\'' }; if (key) { ret.property = key; } return ret; } function generateNegativeType(schema, prop, key) { var negativeTypes = getNegativeTypes(prop); if (!negativeTypes || !negativeTypes.length) { return []; } var newType = _.sample(negativeTypes); return [generateForProp(schema, newType, key)]; } function generateNegativesForNumber(schema, prop, key) { var ret = []; if (typeof prop.multipleOf === 'number' && prop.multipleOf >= 0) { var d = jsf(schema); var nv = prop.multipleOf - 1; if (key) { d[key] = nv; } else { d = nv; } var r = { valid: false, data: d, message: key ? 'should not pass validation for multipleOf property: ' + key : 'should not pass validation for multipleOf' }; if (key) { r.property = key; } ret.push(r); } if (typeof prop.maximum === 'number') { var _d = jsf(schema); var _nv = prop.maximum + 1; if (key) { _d[key] = _nv; } else { _d = _nv; } var _r = { valid: false, data: _d, message: key ? 'should not pass validation for maximum of property: ' + key : 'should not pass validation for maximum' }; if (key) { _r.property = key; } ret.push(_r); } if (typeof prop.minimum === 'number') { var _d2 = jsf(schema); var _nv2 = prop.minimum - 1; if (key) { _d2[key] = _nv2; } else { _d2 = _nv2; } var _r2 = { valid: false, data: _d2, message: key ? 'should not pass validation for minimum of property: ' + key : 'should not pass validation for minimum' }; if (key) { _r2.property = key; } ret.push(_r2); } return ret; } function generateNegativesForString(schema, prop, key) { var ret = []; if (typeof prop.maxLength === 'number') { var d = jsf(schema); var nv = chance.string({ length: prop.maxLength + 1 }); if (key) { d[key] = nv; } else { d = nv; } var r = { valid: false, data: d, message: key ? 'should not pass validation for maxLength of property: ' + key : 'should not pass validation for maxLength' }; if (key) { r.property = key; } ret.push(r); } if (typeof prop.minLength === 'number' && prop.minLength > 0) { var _d3 = jsf(schema); var _nv3 = chance.string({ length: prop.minLength - 1 }); if (key) { _d3[key] = _nv3; } else { _d3 = _nv3; } var _r3 = { valid: false, data: _d3, message: key ? 'should not pass validation for minLength of property: ' + key : 'should not pass validation for minLength' }; if (key) { _r3.property = key; } ret.push(_r3); } if (typeof prop.format === 'string') { var _d4 = jsf(schema); var _nv4 = chance.string(); if (key) { _d4[key] = _nv4; } else { _d4 = _nv4; } var _r4 = { valid: false, data: _d4, message: key ? 'should not pass validation for format of property: ' + key : 'should not pass validation for format' }; if (key) { _r4.property = key; } ret.push(_r4); } return ret; } function generateNegativesForArray(schema, prop, key) { var ret = []; if (_typeof(prop.items) === 'object' && typeof prop.maxItems === 'number') { var d = jsf(schema); if (!Array.isArray(d[key])) { d[key] = []; } while (d[key].length <= prop.maxItems) { d[key].push(jsf(prop.items)); } ret.push({ valid: false, data: d, property: key, message: 'should not pass validation for maxItems of property: ' + key }); } if (_typeof(prop.items) === 'object' && typeof prop.minItems === 'number') { var _d5 = jsf(schema); if (!Array.isArray(_d5[key])) { _d5[key] = []; } while (_d5[key].length >= prop.minItems && _d5[key].length > 0) { _d5[key].pop(); } ret.push({ valid: false, data: _d5, property: key, message: 'should not pass validation for minItems of property: ' + key }); } if (_typeof(prop.items) === 'object' && prop.uniqueItems === true) { var _d6 = jsf(schema); if (!Array.isArray(_d6[key])) { _d6[key] = []; } if (!_d6[key].length) { _d6[key].push(jsf(prop.items)); } _d6[key].push(_d6[key][0]); ret.push({ valid: false, data: _d6, property: key, message: 'should not pass validation for uniqueItems of property: ' + key }); } // generate tests for items schema if (_typeof(prop.items) === 'object') { var itemData = generate(prop.items); itemData.forEach(function (i) { var d = jsf(schema); var sr = ps.search(d, function (e) { return Array.isArray(e[key]); }, { separator: '.' }); if (sr && sr.length && sr[0]) { var p = sr[0].path ? sr[0].path.concat('.' + key) : key; var nd = [i.data]; if (typeof prop.minItems === 'number') { var extra = _.times(prop.minItems, function () { return jsf(prop.items); }); nd.push.apply(nd, _toConsumableArray(extra)); } _.set(d, p, nd); ret.push({ valid: i.valid, data: d, property: key, message: 'within array test: ' + i.message }); } }); } return ret; } function generateNegativesForObject(schema, prop, key) { var ret = []; var testsForProp = generate(prop); testsForProp = _.reject(testsForProp, function (td) { return td.valid; }); testsForProp.forEach(function (td) { var d = jsf(schema); d[key] = td.data; ret.push({ valid: td.valid, data: d, property: key, message: 'nested object test: ' + td.message }); }); return ret; } function generateExtraConditions(schema, prop, key) { var ret = []; if (Array.isArray(prop.allOf) && prop.allOf.length) { var d = jsf(schema); var np = _.cloneDeep(prop); np.allOf.pop(); var newObject = jsf(np); d[key] = newObject; ret.push({ valid: false, data: d, property: key, message: 'should not pass validation for allOf of property: ' + key }); } return ret; } function generateNegativeDetailsForType(schema, prop, key) { var type = prop.type; var ret = []; if (['integer', 'number', 'string', 'array', 'object'].indexOf(type) === -1) { return ret; } if (type === 'integer' || type === 'number') { ret.push.apply(ret, _toConsumableArray(generateNegativesForNumber(schema, prop, key))); } else if (type === 'string') { ret.push.apply(ret, _toConsumableArray(generateNegativesForString(schema, prop, key))); } else if (type === 'array') { ret.push.apply(ret, _toConsumableArray(generateNegativesForArray(schema, prop, key))); } else if (type === 'object') { ret.push.apply(ret, _toConsumableArray(generateNegativesForObject(schema, prop, key))); } ret.push.apply(ret, _toConsumableArray(generateExtraConditions(schema, prop, key))); return ret; } function generateForTypes(schema) { var ret = []; var keys = schema.properties ? Object.keys(schema.properties) : [null]; if (!keys || !keys.length) { return ret; } for (var i = 0; i < keys.length; i++) { var key = keys[i]; var prop = key === null ? schema : schema.properties[key]; ret.push.apply(ret, _toConsumableArray(generateNegativeType(schema, prop, key))); ret.push.apply(ret, _toConsumableArray(generateNegativeDetailsForType(schema, prop, key))); } return ret; } function generateFromRequired(schema) { var positive = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; var ret = []; if (!Array.isArray(schema.required) || !schema.required.length) { return ret; } var keys = Object.keys(schema.properties); var props = positive ? _.difference(keys, schema.required) : schema.required; if (!Array.isArray(props) || !props.length) { return ret; } props.forEach(function (prop) { if (typeof prop === 'string') { var sample = jsf(schema); var msg = positive ? 'should work without optional property: ' + prop : 'should not work without required property: ' + prop; ret.push({ valid: positive, data: _.omit(sample, prop), message: msg, property: prop }); } }); return ret; } /** * Generates test data based on JSON schema * @param {Object} schema Fully expanded (no <code>$ref</code>) JSON Schema * @return {Array} Array of test data objects */ function generate(schema) { var ret = []; if (!validate(schema)) { return ret; } var fullSample = jsf(schema); if (!fullSample) { return ret; } ret.push({ valid: true, data: fullSample, message: 'should work with all required properties' }); ret.push.apply(ret, _toConsumableArray(generateFromRequired(schema))); ret.push.apply(ret, _toConsumableArray(generateFromRequired(schema, false))); ret.push.apply(ret, _toConsumableArray(generateForTypes(schema))); return ret; } generate.getNegativeTypes = getNegativeTypes; module.exports = generate;