Joe8Bit/fuzzur

View on GitHub
src/index.js

Summary

Maintainability
A
0 mins
Test Coverage
'use strict';

var type  = require('type-detect');
var _     = require('lodash');
var merge = require('deepmerge');

/**
 * The public mutate method, takes data, mutates it and returns a mutate copy
 * @param  {Any} data Any data format
 * @param  {Object} config An options block, that corressponds to the defaults laid out below
 * @return {Any}      Returns whatever data type the function is called with
 */
function mutate(data, config) {
  var _mutateInstance = new MutateData(data, (config || {}));
  var typeOf = type(data);

  return _mutateInstance[typeOf]();
}

/**
 * The mutation constructor
 * @param  {Any} data Any data format
 * @param  {Object} config A config block that corresponds to the defaults below, and overrides them
 */
function MutateData(data, config) {
  this.data = data;
  this.defaults = {
    string: {
      sampleSet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
      randomisationPasses: 10
    },
    number: {
      integer: {
        min: -1000,
        max: 10000
      },
      float: {
        min: -1000,
        max: 10000,
        arbitraryPrecision: 2
      }
    },
    date: {
      endYear: 2022
    }
  };
  this.config = merge(this.defaults, config);
}

/**
 * Recursivley mutates an object and returns a copy with it's values mutated in place
 * @param  {Object} obj The object to mutte
 * @return {Object}     The mutates object, a copy of the mutator
 */
MutateData.prototype.object = function(obj) {
  var _this = this;
  var copy = _.extend({}, (obj || this.data));

  Object.keys(copy).forEach(function(key) {
    copy[key] = _this[type(copy[key])](copy[key]);
  });

  return copy;
};

/**
 * Recursivley mutate an array and it's contents
 * @param  {Array} arr The array to mutate
 * @return {Array}     The mutated array
 */
MutateData.prototype.array = function(arr) {
  var _this = this;
  var mutator = arr || this.data;

  return _.map(mutator, function(item) {
    return _this[type(item)](item);
  });
};

/**
 * Mutate a string
 * @param  {String} str The string to mutate
 * @return {String}     The mutated string
 */
MutateData.prototype.string = function(str) {
  var mutator = str || this.data;
  var split = mutator.split('');
  var passes = _.random(1, this.config.string.randomisationPasses);
  var i;

  for (i = 0; i < passes; i++) {
    split.splice(_.random(0, split.length), 0, this.config.string.sampleSet.split('')[_.random(0, this.config.string.sampleSet.length)]);
  }

  return _.shuffle(split).join('');
};

/**
 * Mutate a number, if it's a float, return a mutated float and the same for int's
 * @param  {Number} num The Number to mutate
 * @return {Number}     The mutated number
 */
MutateData.prototype.number = function(num) {
  var mutator = num || this.data;
  return (mutator % 1 === 0) ? this.integer() : this.float(mutator);
};

/**
 * Return a random integer between two bounds
 * @return {Number} The random number
 */
MutateData.prototype.integer = function() {
  return _.random(this.config.number.integer.min, this.config.number.integer.max);
};

/**
 * Return a random float, to the precision of the seed number
 * @param  {Number} num A float to mutate from
 * @return {Number}     A new random float the same precision as the seed float
 */
MutateData.prototype.float = function(num) {
  return parseFloat(Math.min(this.config.number.float.min + (Math.random() * (this.config.number.float.max - this.config.number.float.min)), this.config.number.float.max).toFixed(this.config.number.float.arbitraryPrecision));
};

/**
 * VERY naivley mutates regex's, be warned, this coule be flaky
 * @param  {RegExp} reg A regexp to mutate
 * @return {RegExp}     A mutated regex
 */
MutateData.prototype.regexp = function(reg) {
  // This is a VERY naive mutation
  var mutator = reg || this.data;
  var composition = mutator.toString().split('/');
  var pattern = composition[1];
  var newPattern = this.string(pattern.replace('-', ''));
  var flags = composition[2];

  return new RegExp([newPattern, flags].join('/'));
};

/**
 * NOT IMPLEMENRED: mutate a function
 */
MutateData.prototype.function = function(fn) {
  return fn || this.data;
};

/**
 * NOT IMPLEMENRED: mutate null
 */
MutateData.prototype.null = function() {
  return null;
};

/**
 * NOT IMPLEMENRED: mutate undefined
 */
MutateData.prototype.undefined = function() {
  return undefined;
};

/**
 * Mutates a date object to be between two arbitrary dates
 * @param  {Date} dt The Date object that serves as the start of the random range
 * @return {Date}    The new mutated date
 */
MutateData.prototype.date = function(dt) {
  var start = dt || this.data;
  var end = new Date(this.config.date.endYear, 1, 1);

  return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
};

module.exports.mutate = mutate;