src/index.js
import mongoose from 'mongoose';
import { v4 as uuid } from 'uuid';
const { Schema, Types, SchemaType } = mongoose;
class MongooseDummy {
constructor(mongoose) {
if (!mongoose) throw new Error('Pass a valid mongoose instance.');
this.mongooseInstance = mongoose;
this.schemas = mongoose.models;
this.config = { generators: {} };
}
/**
* Setup custom config params
* @param parameters {Object} - Custom parameters
* @returns {MongooseDummy}
*/
setup(parameters = {}) {
this.config = Object.assign(this.config, parameters);
return this;
}
set generators(generators) {
this.config.generators = Object.assign(this.config.generators, generators);
}
get generators() {
return this.config.generators;
}
/**
* Generate fake document
* @param filter {Function} - Filter keys by queries
* @returns {Promise<unknown>}
*/
generate(filter = () => true) {
return this.iterate(this.baseModel, {}, 0, filter);
}
/**
* Set model work
* @param modelName {String} - Model name
* @returns {MongooseDummy}
*/
model(modelName = '') {
this.baseModel = this.getModel(modelName);
return this;
}
/**
* Get model
* @param modelName - Model name
* @returns {Object}
*/
getModel(modelName) {
if (!(modelName in this.schemas)) throw new Error(`The model name "${modelName}" is not present in schema models. Is case sensitive!`);
return this.schemas[modelName].schema;
}
/**
* Retrieves the object model associated with this instance.
*
* @returns {Object} The object model.
*/
get objectModel() {
if (!this.baseModel) throw new Error('Set the model before try get the object with model() method.');
return this.baseModel.obj;
}
iterate(schema, output = {}, iteration = 0, filter = () => true) {
const { paths } = schema;
for (const schemaType of Object.values(paths)) {
if (this.constructor.canParse(schemaType, this.config.dummyKey) && this.constructor.query(schemaType, filter)) {
schemaType
.path
.split('.')
.reduce((accumulator, key, index, array) => accumulator[key] = accumulator[key] || (index === array.length - 1 ? this.evaluateDummy(schemaType, iteration, output, filter) : {}), output);
}
}
return output;
}
evaluateDummy(schema, iteration = 0, output = {}, filter = () => true) {
const { arrayLength = 3, dummyKey = 'dummy' } = this.config || {};
const { instance } = schema;
if (iteration > 2) return this.constructor.getFallbackValue(schema);
const { length = arrayLength } = schema.options[dummyKey] || {};
if (schema.options[dummyKey] instanceof Function) {
try {
return schema.options[dummyKey].call(output, this.generators);
} catch (error) {
return this.constructor.getFallbackValue(schema);
}
}
else if (schema instanceof Schema.Types.ObjectId || instance === 'ObjectId') return this.evaluateObjectId(schema, filter);
else if (schema instanceof Schema.Types.DocumentArray || schema instanceof Schema.Types.Array || instance === 'Array') return [...Array(length)].map(() => this.getArrayItem(schema, output, filter));
else if (schema instanceof Types.Subdocument || schema.schema?.paths || instance === 'Embedded') return this.iterate(schema.schema, {}, iteration);
return this.constructor.getFallbackValue(schema);
}
getArrayItem(schema, output, filter = () => true) {
if (schema?.schema instanceof Schema) return this.iterate(schema.schema, {}, 2);
const itemSchema = schema.caster;
if (itemSchema instanceof Schema.Types.ObjectId) return this.evaluateObjectId(itemSchema, filter);
return this.evaluateDummy(itemSchema, 2, output, filter);
}
evaluateObjectId(schema, filter = () => true) {
const { ref, populate } = schema.options;
if (ref && populate) return this.iterate(this.getModel(ref), {}, 2, filter);
else if (this.constructor.canParse(schema)) return new Types.ObjectId();
return undefined;
}
static getFallbackValue(schema) {
const { Mixed, ObjectId, Number, Boolean, String, Map, Buffer, BigInt = Number, DocumentArray, Decimal128 } = Schema.Types;
const { max, min, default: defaultValue } = schema.options;
const { instance } = schema;
if (typeof defaultValue !== 'undefined') return defaultValue;
if (schema instanceof Schema.Types.Array || schema instanceof DocumentArray || instance === 'Array') return new Types.Array();
else if (schema instanceof Types.Subdocument || schema instanceof Mixed || schema instanceof Map || instance === 'Embedded') return {};
else if (schema instanceof ObjectId || instance === 'ObjectId') return new Types.ObjectId();
else if (schema instanceof Number || schema instanceof BigInt || schema instanceof Decimal128 || instance === 'Number') return this.randomNumber(min, max);
else if (schema instanceof Boolean || instance === 'Boolean') return Math.random() < 0.5;
else if (schema instanceof String || schema instanceof Buffer || instance === 'String') return this.generateStringBasedOnSchemaOptions(schema.options);
else if (schema instanceof Schema.Types.Date || instance === 'Date') return new Date();
else if (schema instanceof (Schema.Types.UUID || String)) return uuid();
return null;
}
static randomNumber(min = 0, max = 99) {
return Math.floor(Math.random() * max) + (min);
}
static generateStringBasedOnSchemaOptions(options = {}) {
const { enum: enumList, maxLength = 99, minLength = 3 } = options;
if (Array.isArray(enumList) && enumList.length > 0) return enumList[this.randomNumber(0, enumList.length - 1)];
return [...Array(Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength)].map(() => Math.random().toString(36)[2]).join('');
}
static canParse(schema, dummyKey = 'dummy') {
return [dummyKey, 'populate'].some(key => schema.options.hasOwnProperty(key));
}
static query(schema, filter = () => true) {
return filter(schema.options);
}
}
export default MongooseDummy;