fakingoose
Version:
Data mocker for mongoose schema
261 lines (260 loc) • 11.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.generate = void 0;
const Chance = require("chance");
const set = require("lodash.set");
const get = require("lodash.get");
const flatten = require("flat");
const bson_objectid_1 = require("bson-objectid");
const mocker_1 = require("./mocker");
const types_1 = require("./types");
const UNDEFINED_PROP_VIA_PARENT = Symbol('UndefinedPropFromParent');
const OBJECT_ID_STRINGIFY_BY_DEFAULT = true;
const DECIMAL_128_STRINGIFY_BY_DEFAULT = true;
const ARRAY_SIZE_DEFAULT = 2;
const chance = new Chance();
function getTypeOf(type) {
if (typeof type.name === 'string') {
return type.name.toLowerCase();
}
if (type.constructor.name === 'Schema') {
return 'map_value_embedded';
}
throw new Error('Unknown type');
}
function getRandomInclusive(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
const generators = {
string: (pathDef, { options }) => {
if (Array.isArray(pathDef.enumValues) && pathDef.enumValues.length) {
return chance.pickone(pathDef.enumValues);
}
if ((0, types_1.isStringFieldOptions)(options)) {
switch (options.type) {
case 'email':
return chance.email();
case 'firstname':
return chance.first();
case 'lastname':
return chance.last();
default:
return chance.string();
}
}
return pathDef.lowercase ? chance.string().toLowerCase() : chance.string();
},
array: (pathDef, { options, staticFields, globalOptions }) => {
const num = get(options, 'size', ARRAY_SIZE_DEFAULT);
if (typeof pathDef.caster === 'object') {
const innerType = pathDef.caster.instance.toLowerCase();
const generator = generators[innerType];
return Array.from(Array(num)).map(() => generator(pathDef.caster, { options, globalOptions }));
}
if (typeof pathDef.caster === 'function') {
// eslint-disable-next-line no-use-before-define
return Array.from(Array(num)).map(() => generate(pathDef.schema, { options, staticFields, globalOptions }));
}
return null;
},
objectid: (pathDef, { options, globalOptions }) => {
if ((0, types_1.isPopulateWithFactory)(options)) {
return options.populateWithFactory.generate();
}
else if ((0, types_1.isPopulateWithSchema)(options)) {
const mockGen = (0, mocker_1.factory)(options.populateWithSchema);
return mockGen.generate();
}
if (get(options, 'tostring', OBJECT_ID_STRINGIFY_BY_DEFAULT) === false || get(globalOptions, 'objectid.tostring', OBJECT_ID_STRINGIFY_BY_DEFAULT) === false) {
return new bson_objectid_1.default();
}
return new bson_objectid_1.default().toString();
},
number: (pathDef) => {
if (pathDef.options && Array.isArray(pathDef.options.enum) && pathDef.options.enum.length) {
return chance.pickone(pathDef.options.enum);
}
const numOpt = ['min', 'max'].reduce((opts, att) => {
if (pathDef.options && (att in pathDef.options)) {
const attrValue = Array.isArray(pathDef.options[att]) ? pathDef.options[att][0]
: pathDef.options[att];
return Object.assign(opts, { [att]: attrValue });
}
return opts;
}, {});
return chance.integer(numOpt);
},
decimal128: (pathDef, { options, globalOptions }) => {
if (get(options, 'tostring', DECIMAL_128_STRINGIFY_BY_DEFAULT) === false || get(globalOptions, 'decimal128.tostring', DECIMAL_128_STRINGIFY_BY_DEFAULT) === false) {
return chance.floating();
}
return `${chance.floating()}`;
},
boolean: () => chance.pickone([true, false]),
date: (pathDef) => {
let max = 8640000000000000;
let min = -8640000000000000;
if (pathDef.options && (pathDef.options.min || pathDef.options.max)) {
min = pathDef.options.min ? Date.parse(pathDef.options.min) : min;
max = pathDef.options.max ? Date.parse(pathDef.options.max) : max;
}
const timestamp = getRandomInclusive(min, max);
return new Date(timestamp);
},
buffer: () => Buffer.alloc(1),
mixed: (pathDef, { options, globalOptions }) => {
const generatorName = chance.pickone(['boolean', 'date', 'number', 'string', 'objectid']);
if (!generators[generatorName]) {
throw new Error(`Type ${generatorName} not supported`);
}
return generators[generatorName](pathDef, { options, globalOptions });
},
embedded: (pathDef, { options, staticFields, globalOptions }) => {
const { schema } = pathDef;
// eslint-disable-next-line no-use-before-define
return generate(schema, { options, staticFields, globalOptions });
},
map_value_embedded: (pathDef, { options, staticFields, globalOptions }) => {
const schema = pathDef.options.of;
// eslint-disable-next-line no-use-before-define
return generate(schema, { options, staticFields, globalOptions });
},
map: (pathDef, { options, globalOptions }) => {
const generatorName = pathDef.options.of ? getTypeOf(pathDef.options.of) : 'mixed';
if (!generators[generatorName]) {
throw new Error(`Type ${generatorName} not supported`);
}
const generator = generators[generatorName];
const keyCount = chance.integer({ min: 1, max: 10 });
const keys = Array.from(new Array(keyCount))
.map(() => chance.string({ pool: 'abcdefghijklmnopqrstuvwxyz' }));
const mock = keys.reduce((obj, key) => Object.assign(obj, { [key]: generator(pathDef, { options, globalOptions }) }), {});
return mock;
},
};
const getPropertyByPath = (object, path) => get(object, path);
const getPropertyByName = (object, propName) => object[propName];
const propertyIsSkipped = (path, options) => {
const fieldOptions = getPropertyByName(options, path) || getPropertyByPath(options, path) || {};
return fieldOptions.skip === true;
};
const propertyParentIsSkipped = (path, options) => {
const pathProps = path.split('.');
if (pathProps.length > 1) {
let parentPath = '';
// eslint-disable-next-line no-restricted-syntax
for (const pathProp of pathProps) {
parentPath = parentPath.length === 0 ? pathProp : `${parentPath}.${pathProp}`;
const parentObject = getPropertyByPath(options, parentPath)
|| getPropertyByName(options, parentPath) || {};
if (parentObject.skip === true) {
return true;
}
}
}
return false;
};
const shouldSkipProperty = (path, options) => propertyIsSkipped(path, options)
|| propertyParentIsSkipped(path, options);
function populateField(mockObject, { schema, path, options, staticValue, indirectValues = {}, globalOptions = {}, }) {
const fieldOptions = getPropertyByName(options, path) || getPropertyByPath(options, path) || {};
if (!shouldSkipProperty(path, options)) {
let value;
const pathDef = schema.path(path);
const type = pathDef.instance.toLowerCase();
if (staticValue !== undefined) {
value = staticValue;
}
else if (staticValue !== undefined && type === 'map') {
value = staticValue;
}
else if (fieldOptions.value && typeof fieldOptions.value === 'function') {
value = fieldOptions.value(mockObject);
}
else if (fieldOptions.value) {
({ value } = fieldOptions);
}
else if (indirectValues[path]) {
value = indirectValues[path];
}
else {
if (!generators[type]) {
throw new Error(`Type ${pathDef.instance} not supported`);
}
const generator = generators[type];
value = generator(pathDef, { options: fieldOptions, staticFields: staticValue, globalOptions });
}
set(mockObject, path, value);
}
}
const getOptionFromParentProperty = (path, options, optionName) => {
const pathProps = path.split('.');
if (pathProps.length > 1) {
let parentPath = '';
// eslint-disable-next-line no-restricted-syntax
for (const pathProp of pathProps) {
parentPath = parentPath.length === 0 ? pathProp : `${parentPath}.${pathProp}`;
const parentObject = getPropertyByPath(options, parentPath)
|| getPropertyByName(options, parentPath) || {};
if (parentObject[optionName]) {
return [parentPath, parentObject[optionName]];
}
}
}
return UNDEFINED_PROP_VIA_PARENT;
};
const setIndirectValues = (parentPath, rootValue, indirectVals) => {
const rootValues = flatten(rootValue, { safe: true });
Object.entries(rootValues).forEach(([keyPath, value]) => {
// eslint-disable-next-line no-param-reassign
indirectVals[`${parentPath}.${keyPath}`] = value;
});
return indirectVals;
};
function generate(schema, opts) {
const { options, staticFields, globalOptions } = opts;
const mockObject = {};
const fieldGeneration = [];
const delayedFieldGeneration = [];
const indirectValues = {};
const indirectValuesTasks = {};
schema.eachPath((path) => {
if (!path.includes('$*')) {
if (typeof get(options, `${path}.value`) === 'function' || typeof get(options, `['${path}'].value`, undefined) === 'function') {
delayedFieldGeneration.push(() => populateField(mockObject, {
schema, path, options, staticValue: get(staticFields, path, undefined), globalOptions,
}));
}
else {
const indirectVals = getOptionFromParentProperty(path, options, 'value');
if (indirectVals !== UNDEFINED_PROP_VIA_PARENT) {
const [parentPath, value] = indirectVals;
if (!indirectValuesTasks[parentPath]) {
if (typeof value === 'function') {
fieldGeneration.push(() => {
const rootValue = value(mockObject);
setIndirectValues(parentPath, rootValue, indirectValues);
});
}
else {
const rootValue = value;
setIndirectValues(parentPath, rootValue, indirectValues);
}
indirectValuesTasks[parentPath] = true;
}
}
fieldGeneration.push(() => populateField(mockObject, {
schema,
path,
options,
staticValue: get(staticFields, path, undefined),
indirectValues,
globalOptions,
}));
}
}
});
[...fieldGeneration, ...delayedFieldGeneration].forEach((fn) => fn());
return mockObject;
}
exports.generate = generate;