@mdf.js/faker
Version:
MMS - API - Faker
343 lines • 13.2 kB
JavaScript
"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