mongoose-dummy
Version:
Mongoose dummy data generator
364 lines (346 loc) • 14.6 kB
JavaScript
/*!
* mongoose-dummy
* Mongoose dummy objects generator
*/
const flat = require('flat');
const unflatten = flat.unflatten;
const faker = require('faker');
const mongoose = require('mongoose')
const ObjectId = mongoose.Schema.ObjectId;
/**
* getPaths(model)
*
* Extracts all paths and defintion from mongoose schema
*
* @param {MongooseSchmea} model The model schema object
*
* Returns an object for paths definition with the following format:
* {
* <path name>: <path description>
* }
* where path descrtiption is in the following format:
* {
* required: Boolean <this path is required>,
* validators: Array <defined validation functions>,
* default: Mixed <default path value>,
* isEnum: Boolean <if path only accepts enumerations>,
* enum: Array <path enumration if isEnum>,
* isPathDef: Boolean <if it is a path definition object>,
* lowercase: Boolean <lowercase filter>,
* uppercase: Boolean <uppercase filter>,
* trim: Boolean <trim text filter>,
* max: Number <Maximum value if has maximum>,
* min: Number <Minimum value if has minimum>,
* isArray: Boolean <if the path is an array>
* arrayDefinition: Object <Path Definition Object for array element>
* ref: String <Referenced object if has reference>
* }
*/
let getPaths = (model) => {
let res = {}; /* Final return element */
let _generateReturnObj = (path, name, schema, context) => { /* Generate definition for a single path */
let result = {
type: path.instance,
required: !!path.isRequired,
validators: path.validators,
default: path.defaultValue,
isEnum: Array.isArray(path.enumValues) && path.enumValues.length,
enum: path.enumValues,
isPathDef: true,
lowercase: !!path.options.lowercase,
uppercase: !!path.options.uppercase,
trim: !!path.options.trim
};
if (Array.isArray(path.validators)) {
path.validators.forEach(val => {
if (val.type === "max") {
result.max = val.max;
}
if (val.type === "min") {
result.min = val.min;
}
});
}
if (path.instance.toLowerCase() === "array") { /* Recurse the function for array element definitions */
result.isArray = true;
if (!path.schema) {
result.arrayDefinition = _generateReturnObj(path.caster);
} else {
result.arrayDefinition = getPaths(path.schema);
}
}
if (path.instance.toLowerCase() === "objectid" && path.options.ref) { /* Add referenced object */
result.ref = path.options.ref;
}
return result
}
let _fillObject = function (name, schema, context) { /* Extract definition object from schema and path name */
let path = schema.path(name);
if (path) {
return context[name] = _generateReturnObj(path, name, schema, context);
}
}
/* Loop over paths and extract definitions */
if (model.schema) {
model.schema.eachPath(function (path) {
_fillObject(path, model.schema, res);
});
} else {
model.eachPath(function (path) {
_fillObject(path, model, res);
});
}
return res;
}
/**
* generateRandomModel(paths, opts)
*
* Extracts all paths using getPaths() then generate random object from these paths
*
* @param {Object} paths Path definitions generated from getPaths()
* @param {Object} opts Dummy object generation options
* format of opts:
* {
* ignore: Array (paths to ignore during generation),
* autoDetect: Boolean (attempt to detect e-mail, phone, or password and generate corresponding random data, defaults to true),
* applyFilter: Boolean (apply lowercase, uppercase, and trim filters on generated object if defined in the path),
* returnDate: Boolean (weather to return dates as Date or String),
* custom: { (Special generator for specified fields)
* email: String (field to generate a random e-mail), Array of Strings (fields to generate a random e-mail), or Object {field: String or Array of String, value: Function (custom generator function)},
* phone: (Same as above but for phone),
* password: (Same as above but for password),
* },
* force: Object (paths to set it to certain values)
* }
* Returns the generated dummy object
*/
let generateRandomModel = (paths, opts) => {
let generated = {};
if (opts == null) {
opts = {};
}
if (opts.autoDetect == null) {
opts.autoDetect = true;
}
if (opts.applyFilter == null) {
opts.applyFilter = true;
}
let hasCustom = false;
let customEmail, customPhone, customAddress, customPassword;
/** Default custom generators */
let emailGenerator = () => {
return faker.internet.email();
}
let phoneGenerator = () => {
return faker.phone.phoneNumber();
}
let addressGenerator = () => {
return faker.address.streetAddress();
}
let passwordGenerator = () => {
return faker.name.firstName() + faker.name.lastName() + Math.random() + "_";
}
if (opts.custom) { /* Check for custom fields */
hasCustom = true;
if (opts.custom.email) {
if (typeof opts.custom.email === "string") {
customEmail = [opts.custom.email]
} else if (Array.isArray(opts.custom.email)) {
customEmail = opts.custom.email
} else if (typeof opts.custom.email === "object") {
customEmail = opts.custom.email.field;
if (typeof customEmail === "string") {
customEmail = [customEmail];
}
if (opts.custom.email.value) {
emailGenerator = opts.custom.email.value;
}
} else {
throw new Error(`Invalid opts ${opts.custom.email}`);
}
}
if (opts.custom.phone) {
if (typeof opts.custom.phone === "string") {
customPhone = [opts.custom.phone]
} else if (Array.isArray(opts.custom.phone)) {
customPhone = opts.custom.phone
} else if (typeof opts.custom.phone === "object") {
customPhone = opts.custom.phone.field;
if (typeof customPhone === "string") {
customPhone = [customPhone];
}
if (opts.custom.phone.value) {
phoneGenerator = opts.custom.phone.value;
}
} else {
throw new Error(`Invalid opts ${opts.custom.phone}`);
}
}
if (opts.custom.address) {
if (typeof opts.custom.address === "string") {
customAddres = [opts.custom.address]
} else if (Array.isArray(opts.custom.address)) {
customAddres = opts.custom.address
} else if (typeof opts.custom.address === "object") {
customAddres = opts.custom.address.field;
if (typeof customAddres === "string") {
customAddres = [customAddres];
}
if (opts.custom.address.value) {
addressGenerator = opts.custom.address.value;
}
} else {
throw new Error(`Invalid opts ${opts.custom.address}`);
}
}
if (opts.custom.password) {
if (typeof opts.custom.password === "string") {
customPassword = [opts.custom.password]
} else if (Array.isArray(opts.custom.password)) {
customPassword = opts.custom.password
} else if (typeof opts.custom.password === "object") {
customPassword = opts.custom.password.field;
if (typeof customPassword === "string") {
customPassword = [customEmail];
}
if (opts.custom.password.value) {
passwordGenerator = opts.custom.password.value;
}
} else {
throw new Error(`Invalid opts ${opts.custom.password}`);
}
}
}
for (let field in paths) { /* Loop over paths and generated data */
const mustIgnore = opts.ignore && opts.ignore.findIndex(p => {
if (typeof p === 'string')
return field === p;
else
return p.test(field);
}) !== -1;
if (mustIgnore) { /* Ignore paths in opts.ignore */
continue;
}
if(opts.force) {
let forcedField
Object.keys(opts.force).map(key => {
if(field === key) {
forcedField = key
}
});
if(forcedField) {
generated[field] = opts.force[forcedField];
continue;
}
}
let desc = paths[field];
if (desc.isEnum) { /* Handle enumerations paths */
let randomIndex = Math.round((Math.random() * (desc.enum.length - 1)));
generated[field] = desc.enum[randomIndex];
} else {
let type = desc.type.toLowerCase();
if (hasCustom && type === "string") { /* Handle custom paths */
let isCustom = false;
if (opts.custom.email.indexOf(field) !== -1) {
generated[field] = emailGenerator();
isCustom = true;
} else if (opts.custom.phone.indexOf(field) !== -1) {
generated[field] = phoneGenerator();
isCustom = true;
} else if (opts.custom.address.indexOf(field) !== -1) {
generated[field] = addressGenerator();
isCustom = true
} else if (opts.custom.password.indexOf(field) !== -1) {
generated[field] = passwordGenerator();
isCustom = true;
}
if (isCustom) {
if (opts.applyFilter && desc.lowercase) { /* Handle lowercase filter */
generated[field] = generated[field].toLowerCase();
}
if (opts.applyFilter && desc.uppercase) { /* Handle uppercase filter */
generated[field] = generated[field].toUpperrCase();
}
if (opts.applyFilter && desc.trim) { /* Handle trim filter */
generated[field] = generated[field].trim();
}
continue;
}
}
if (type === "string") {
if (opts.autoDetect) { /* Attempt to detect e-mail, password, or phone */
if (/e?\-?mail/mi.test(field)) {
generated[field] = faker.internet.email();
} else if (/password/mi.test(field)) {
generated[field] = faker.name.firstName() + faker.name.lastName() + Math.random() + "_";
} else if (/phone/mi.test(field)) {
generated[field] = faker.phone.phoneNumber();
} else {
generated[field] = faker.internet.userName();
}
} else {
generated[field] = faker.internet.userName(); /* Default string generation */
}
if (opts.applyFilter && desc.lowercase) { /* Handle lowercase filter */
generated[field] = generated[field].toLowerCase();
}
if (opts.applyFilter && desc.uppercase) { /* Handle uppercase filter */
generated[field] = generated[field].toUpperrCase();
}
if (opts.applyFilter && desc.trim) { /* Handle trim filter */
generated[field] = generated[field].trim();
}
} else if (type === "number") { /* Default number generation*/
generated[field] = faker.random.number(desc.max || 80);
} else if (type === "date") {
generated[field] = faker.date.recent();
if (!opts.returnDate) {
generated[field] = generated[field].toString();
}
} else if (type === "boolean") {
generated[field] = Math.random() < 0.5 ? false : true;
} else if (type === "mixed") { /* Handle mixed objects */
generated[field] = {};
let firstCity = faker.random.locale();
let secondCity = faker.random.locale();
generated[field][firstCity] = faker.helpers.createCard();
generated[field][secondCity] = faker.helpers.createCard();
} else if (type === "objectid") { /* Handle ObjectId*/
if (desc.ref) {
generated[field] = mongoose.Types.ObjectId().toString()
} else {
generated[field] = mongoose.Types.ObjectId().toString()
}
} else if (type === "array") { /*Handle array recursively */
generated[field] = [];
for (let i = 0; i < faker.random.number(15); i++) {
let arrayDef = desc.arrayDefinition;
if (arrayDef.isPathDef) {
generated[field].push(generateRandomModel({ generate: desc.arrayDefinition }, opts).generate); /* Handle arrays with primitives */
} else {
generated[field].push(generateRandomModel(desc.arrayDefinition, opts)); /* Handle arrays with defined objects */
}
}
} else {
throw ("Unsupported type " + type)
}
}
}
return unflatten(generated); /* Unflatten dot notation */
};
/**
* getPathsAndGenerate(model, opts)
*
* Extracts all paths using getPaths() then generate random object from these paths
*
* @param {MongooseSchmea} model The model schema object
* @param {Object} opts Options object passed to generateRandomModel()
*
* Returns the generated dummy object
*/
let getPathsAndGenerate = (model, opts) => {
let paths = getPaths(model);
return generateRandomModel(paths, opts);
}
getPathsAndGenerate.getPaths = getPaths;
module.exports = getPathsAndGenerate;