UNPKG

@mdf.js/faker

Version:

MMS - API - Faker

343 lines 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Factory = void 0; const tslib_1 = require("tslib"); /** * Copyright 2024 Mytra Control S.L. All rights reserved. * * Use of this source code is governed by an MIT-style license that can be found in the LICENSE file * or at https://opensource.org/licenses/MIT. */ const crash_1 = require("@mdf.js/crash"); const chance_1 = require("chance"); const lodash_1 = tslib_1.__importStar(require("lodash")); /** Factory for building JavaScript objects, mostly useful for setting up test data */ class Factory { /** Create a new factory instance */ constructor() { /** * Wrap a callback function to add type safety and avoid lost of data of extended factories * @param callback - Callback function */ this.wrapCallback = (callback) => { return (object, options) => { const result = callback(object, options); return (0, lodash_1.merge)(object, result); }; }; this._opts = {}; this._attrs = {}; this._seques = {}; this._callbacks = []; this._chance = new chance_1.Chance(); } attr(attr, generatorOptions, builder) { this._attrs[attr] = this._SafeType(generatorOptions, builder); return this; } /** * Define multiple attributes on this factory using a default value (e.g. a string or number) or * generator function. If you need to define dependencies on options or other attributes, use the * `attr` method instead. * @param attributes - Object with multiple attributes * @example * ```typescript * factory.attrs({ * name: 'John Doe', * age: function() { return 21; }, * }); * ``` */ attrs(attributes) { for (const attr in attributes) { if (attributes.hasOwnProperty(attr)) { this.attr(attr, attributes[attr]); } } return this; } option(opt, generatorOptions, builder) { this._opts[opt] = this._SafeType(generatorOptions, builder); return this; } sequence(attr, generatorOptions, builder) { const _attribute = this._SafeType(generatorOptions, builder); if (generatorOptions === undefined && builder === undefined) { _attribute.builder = i => i + 1; _attribute.dependencies = []; } return this.attr(attr, _attribute.dependencies || [], (...args) => { var _a; const value = _attribute.builder((_a = this._seques[attr]) !== null && _a !== void 0 ? _a : 0, ...args); this._seques[attr] = value; return value; }); } /** * Register a callback function to be called after the object is generated. The callback function * receives the generated object as first argument and the resolved options as second argument. * @param callback - Callback function * @example * ```typescript * factory.after((user) => { * user.name = user.name.toUpperCase(); * }); * ``` */ after(callback) { this._callbacks.push(callback); return this; } /** * Returns an object that is generated by the factory. * The optional option `likelihood` is a number between 0 and 100 that defines the probability * that the generated object contains wrong data. This is useful for testing if your code can * handle wrong data. The default value is 100, which means that the generated object always * contains correct data. * @param attributes - object containing attribute override key value pairs * @param options - object containing option key value pairs */ build(attributes = {}, options = { likelihood: 100 }) { if (options && options['likelihood'] === undefined) { options = { ...options, likelihood: 100 }; } if (typeof options['likelihood'] !== 'number' || options['likelihood'] < 0 || options['likelihood'] > 100) { throw new crash_1.Crash('Likelihood must be a number between 0 and 100', { likelihood: options['likelihood'], }); } const _attributes = lodash_1.default.merge(lodash_1.default.cloneDeep(this._attrs), this._convertToAttributes(attributes)); const _options = lodash_1.default.merge(this._opts, this._ConvertToOptions(options)); let returnableObject = {}; const resolvedOptions = {}; for (const attr in this._attrs) { const stack = []; returnableObject[attr] = this._Build(_attributes[attr], returnableObject, resolvedOptions, _attributes, _options, stack, options['likelihood']); } if ((Object.keys(this._attrs).length === 0 || Object.keys(resolvedOptions).length === 0) && Object.keys(_options).length > 0) { for (const opts in _options) { const stack = []; resolvedOptions[opts] = this._Build(_options[opts], returnableObject, resolvedOptions, _attributes, _options, stack); } } for (const callback of this._callbacks) { const obj = callback(returnableObject, resolvedOptions); if (obj !== undefined) { returnableObject = obj; } } return returnableObject; } /** * Returns an array of objects that are generated by the factory. * The optional option `likelihood` is a number between 0 and 100 that defines the probability * that the generated object contains wrong data. This is useful for testing if your code can * handle wrong data. The default value is 100, which means that the generated object always * contains correct data. * @param size - number of objects to generate * @param attributes - object containing attribute override key value pairs * @param options - object containing option key value pairs * @example * ```typescript * factory.buildList(3, { name: 'John Doe' }); * ``` */ buildList(size, attributes = {}, options = { likelihood: 100 }) { const objs = []; for (let i = 0; i < size; i++) { objs.push(this.build(attributes, options)); } return objs; } /** * Extend this factory with another factory. The attributes and options of the other factory are * merged into this factory. If an attribute or option with the same name already exists, it is * overwritten. * @param factory - Factory to extend this factory with */ extend(factory) { Object.assign(this._attrs, factory._attrs); Object.assign(this._opts, factory._opts); this._callbacks.push(...factory._callbacks.map(this.wrapCallback)); return this; } /** Reset all the sequences of this factory */ reset() { this._seques = {}; } /** * Create an object with standard Options from a key-value pairs object * @param options - object containing option key value pairs */ _ConvertToOptions(options) { const opts = {}; for (const opt in options) { opts[opt] = { dependencies: undefined, builder: () => options[opt] }; } return opts; } /** * Resolve the value of the options or attributes * @param meta - metadata information of option or attribute * @param object - object with resolved attributes * @param resolvedOptions - object with resolved options * @param attributes - attributes object * @param options - options object * @param stack - stack of recursive calls */ _Build(meta, object, resolvedOptions, attributes, options, stack, likelihood = 100) { if (!meta) { throw new crash_1.Crash('Error in factory build process', { meta, object, resolvedOptions, attributes, options, stack, }); } if (!meta.dependencies) { if (!this._chance.bool({ likelihood })) { return this._wrongData(typeof meta.builder()); } else { return meta.builder(); } } else { const args = this._BuildWithDependencies(meta.dependencies, object, resolvedOptions, attributes, options, stack); if (!this._chance.bool({ likelihood })) { return this._wrongData(typeof meta.builder(...args)); } else { return meta.builder(...args); } } } /** * Resolve the value of the options or attributes if this has dependencies * @param dependencies - option or attribute dependencies * @param object - object with resolved attributes * @param resolvedOptions - object with resolved options * @param attributes - attributes object * @param options - options object * @param stack - stack of recursive calls */ _BuildWithDependencies(dependencies, object, resolvedOptions, attributes, options, stack) { return dependencies.map(dep => { if (stack.indexOf(dep) >= 0) { throw new crash_1.Crash(`Detect a dependency cycle: ${stack.concat([dep]).join(' -> ')}`, { stack: stack.concat([dep]), }); } let value; if (object[dep] !== undefined) { value = object[dep]; } else if (resolvedOptions[dep] !== undefined) { value = resolvedOptions[dep]; } else if (options[dep]) { resolvedOptions[dep] = this._Build(options[dep], object, resolvedOptions, attributes, options, stack.concat([dep])); value = resolvedOptions[dep]; } else if (attributes[dep]) { object[dep] = this._Build(attributes[dep], object, resolvedOptions, attributes, options, stack.concat([dep])); value = object[dep]; } return value; }); } /** * Return Generator function if the argument is not a function * @param generator - Generator function or value */ _ReturnFunction(generator) { if (generator instanceof Function) { return generator; } else { return () => generator; } } /** * Return a Entry object with dependencies and builder function * @param generatorOptions - Default value or generator function or dependencies * @param generator - Generator function or value */ _SafeType(generatorOptions, generator) { let _dependencies; let _builder; if (generator === undefined) { if (Array.isArray(generatorOptions)) { throw new crash_1.Crash('Generator function is required if dependencies are defined', { attributeDependencies: generatorOptions, }); } _dependencies = undefined; _builder = this._ReturnFunction(generatorOptions); } else if (Array.isArray(generatorOptions)) { _dependencies = generatorOptions; _builder = this._ReturnFunction(generator); } else { throw new crash_1.Crash('Dependencies must be an array', { attributeDependencies: generatorOptions, }); } return { dependencies: _dependencies, builder: _builder }; } /** * Generate wrong data, excluding the correct data type * @param type - type of good data */ _wrongData(type) { const _typeof = [ 'undefined', 'boolean', 'number', 'string', 'object', 'symbol', 'bigint', 'function', ].filter(entry => entry !== type); const selected = this._chance.pickone(_typeof); switch (selected) { case 'undefined': return undefined; case 'object': return undefined; case 'boolean': return this._chance.bool(); case 'number': if (this._chance.bool()) { return this._chance.floating(); } else { return this._chance.natural(); } case 'string': return this._chance.string(); default: return null; } } /** * Create an object with standard Attributes from a key-value pairs object * @param attributes - object containing attribute override key value pairs */ _convertToAttributes(attributes) { const attrs = {}; for (const attr in attributes) { attrs[attr] = { dependencies: undefined, builder: () => attributes[attr] }; } return attrs; } } exports.Factory = Factory; //# sourceMappingURL=Factory.js.map