gittyeric / ts-auto-fuzzer

ts-auto-fuzzer

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

WIP: Not quite published to NPM yet!

ts-auto-fuzzer

This NPM library lets you automatically generate randomized object instances from just your Typescript types in 1 line of boilerplate!

Example Usage:

Let's say we have a complex TS type or interface such as:

interface MyJSONType {
  name: string;
  age: number;
  birth: Date;
  isCool: boolean;
  flatArray: string[],
  array2d: number[][],
  nestedObj: {
    key1: number[];
    nestedObj2: {
      key2: number;
    };
  };
  objArray: {
    num: number,
    bools: boolean[]
  }[],
};

Now instantly generate random valid object instances by reflection with:

import { newRandomFactory, fill } from 'ts-auto-fuzzer';

// Create a factory that infuses mystical power into the fill<> lib call.
const fuzzFactory = newRandomFactory((empty) => fill<MyJSONType>(empty));

// And ready to insta-mint some healthy workouts for your tests
console.log(fuzzFactory());

Output:

{
  name: "?k.='",
  age: 96943,
  birth: 2022-05-12T08:25:14.478Z,
  isCool: false,
  flatArray: [ 'F0P' ],
  array2d: [ [ 66824 ] ],
  nestedObj: { key1: [ 25048 ], nestedObj2: { key2: 95687 } },
  objArray: [ { num: 10497, bools: [true, false] } ]
}

API

function newRandomFactory<T>(
  fillCallback: (obj: any) => T,
  options?: Partial<Options>
): (overrideOptions?: Partial<Options>) => T

fillCallback: (obj: Partial<T>) => T: A callback that takes a fuzzed (potentially empty) object, potentially overwrites values on it, and always ends in a call to fill(obj) for magical reasons that ensure the randomized object is compliant with T. T must be a JSON-compatible type; no functions, classes (eww) etc. You must specify fill's type parameter (ex. fill<MyJSONType>) with a concrete (non-generic) type! That's how the magic knows what fields to randomize. options?: FactoryOptions: Optional. Specify general behavior of randomly generated ranges and values. Returns (overwriteOptions?: FactoryOptions) => T: A factory function that returns random-valued instances of T when called. The factory function also be invoked with options that take precedence over the Factory-scoped options. ``:

function fill<T>(obj: any): T

Fills a fuzzed obj with random valid values. fill must be the return value of your fillCallback. obj MUST be the same (or copied/modified version of) the obj passed to your fillCallback. You generally should not be calling fill() outside of returning in your fillCallback.

obj: any: This MUST be the same (or modified copy of) the object handed to your fillCallback when creating a newRandomFactory

The magical trick is to specify a concrete, JSON-compatible type / interface in the fill call of your fillCallback callback. This library is largely a hack on top of (and alias of!) typescript-is's assertType that automatically generates a TS type predicate function using JS code generation in a pre-compile phase, see Under the Hood for more.

NPM Installation

npm install ts-auto-fuzzer

Then follow the steps to tweak your TS compilation by following the instructions for the typescript-is module which this library cowardly wraps and blames for now if you have issues. This lib includes both typescript-is and ttypscript so neither are required for individual install, this lib exposes all the components you need to be warm and fuzzy.

After ttypscript / typescript-is setup is complete, A good test to make sure typescript-is works properly for you would be:

import { fill } from 'ts-auto-fuzzer';

// This shouldn't throw an Error if compilation is hooked up proper
fill<string>('test');

(ts-node tip: ts-node can run using ttypescript by adding the -C ttypscript option, otherwise you should only be running your compiled JS directly after building)

Now the Examples below should work...

Examples

1. Configure random ranges to produce tiny objects

import { newRandomFactory, fill, FactoryOptions } from 'ts-auto-fuzzer';

const tinyObjOptions: FactoryOptions = {
  numberBounds: [0, 100000],
  stringLengthBounds: [0, 5],
  integerNumbersOnly: true,
  arrayLengthBounds: [1, 1],
  booleanTrueOdds: 0.5,
  dateBounds: [new Date(Date.parse('2022-05-12T08:20:13.901Z')), new Date()],
}

const tinyObjFactory = newRandomFactory((fuzzed) => fill<MyJSONType>(fuzzed), tinyObjOptions);
console.log(tinyObjFactory());

// Can also override options on a per call basis:
tinyObjFactory({ booleanTrueOdds: 1 }).isCool === true;

Output:

{
  name: '?X2',
  age: 86800,
  birth: 2022-05-12T08:54:14.643Z,
  isCool: true,
  flatArray: [ 'w' ],
  array2d: [ [ 37312 ] ],
  nestedObj: { key1: [ 37236 ], nestedObj2: { key2: 98895 } },
  objArray: [ { num: 88497, bools: [Array] } ]
}

2. Works on primitives too

Pretty boring but if you insist, let's at least generate with a 1 liner to make it fun:

console.log(newRandomFactory((f) => fill<string>(f))());
console.log(newRandomFactory((f) => fill<boolean>(f))());
console.log(newRandomFactory((f) => fill<Date>(f))());

Output:

E?v`p3r7O".^YmGdP7cZk;_lLev3HW/1GP;mletjJ)7F}~TL^PO'9bxf"2S[R<\0:3,doLNqhu]WJ&B\ov;,k,>fJH.XS2,uc3S5$\w&0krHiA-FPb]M-yCrP9f|CFIhJVtX?8:rC%LnQ5JM38k$0gu(W"2>?;n0PSI~+g'Mo&n,swFd?UONW:KiUJ{-E4W3dfJ6f?bJB9!rXwjmXyQ<fj64'dAV;(o8N?4ARkSd%Ph`Ug1EJd^Ccv9bsje+cq.~s,PqG|T[yZ(o6xRG<KQKMd>,AZ-PLD/xm1K(Mh7\K{u
false
2014-11-30T15:56:57.950Z

3. Enforce your own additional constraints

Hardcode a field for example:

const customObjFactory = newRandomFactory((fuzzed) => 
  fill<MyJSONType>({...fuzzed, name: 'Hardcoded'}));
console.log(customObjFactory());

Output:

{
  age: 34736,
  birth: 2022-05-12T08:41:28.938Z,
  isCool: false,
  flatArray: [ 'e{(v4' ],
  array2d: [ [ 84454 ] ],
  nestedObj: { key1: [ 90319 ], nestedObj2: { key2: 72158 } },
  objArray: [ { num: 46539, bools: [false] } ],
  name: 'Hardcoded'
}

Of course you can always mangle your object after generation, but this way maintains the assertion that your object is still 100% validly typed after you customize it in your fill callback.

4. Fix string templates and literal types

The v0 approach of this lib makes randomly generating string templates and literals a real PITA, so here's some simple workarounds until v1:

type ReallyGoodType = {
  id: `${string}-${string}`,
  num: 1 | 2,
  someStr: string,
}

const reallyTypedFactory = newRandomFactory((fuzzed) =>
  fill<ReallyGoodType>({
    ...fuzzed, 
    // Overwrite dumb, inconsistent fuzz values with your validly typed values
    id: '1-2', 
    num: 1
  }));
console.log(reallyTypedFactory());

Output:

{
  someStr: 'P`{a!/%bvUg10cUl-np)wx/n|B]vN0Ek0|.Y;;J\\L>_p+R?Jx\\x#>E@gQhQ{(\\S7Q&")X[2}>^al3W\'.?',
  id: '1-2',
  num: 1
}

No errors!

Options

You can configure options at both the newRandomFactory(fillCallback, options) scope and at the last second when invoking the factory(takesPrecedenceOptions).

type FactoryOptions = {
  randomSeed?: string; // Defaults to a constant for deterministic runs
  integerNumbersOnly?: boolean;
  numberBounds?: [number, number];
  stringLengthBounds?: [number, number];
  arrayLengthBounds?: [number, number];
  dateBounds?: [Date, Date];
  booleanTrueOdds?: number;
};

Default Options are:

const defaultOptions = {
  randomSeed: '1435243er524352345',
  integerNumbersOnly: false,
  numberBounds: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
  stringLengthBounds: [0, 1024],
  arrayLengthBounds: [0, 101],
  dateBounds: [new Date(0), new Date(now() + YEAR)],
  booleanTrueOdds: 0.5,
};

Under the Hood

This lib (ab)uses the typescript-is library, which uses brilliant (but sligthly hacky) pre-compile tricks using "ttypescript" to basically build the Typescript twice; the 2nd being the tsc you know and love but the 1st being a compilation strictly to power typscript-is's TS tranformer hook, allowing codegen based on tsc's loaded type information. Because of this, you must run the ttsc / ttypscript that wraps your normal tsc / typescript tooling.

Since fill is written well and gives exact errors for type mismatches, you can basically keep throwing stuff at the wall until you stumble into a DerivedSchema, then you're golden for quickly generating random mock objects that are guranteed to obey the factory type or die trying.

This lib also uses random-seed for most operations, allowing you to set the seed yourself as well for consistent, deterministic test runs.

About

ts-auto-fuzzer

License:GNU General Public License v3.0


Languages

Language:TypeScript 100.0%