Make the chai helper for rich reverts in dev-utils more robust.
				
					
				
			Add rich reverts chai helper tests to `dev-utils`
This commit is contained in:
		
				
					committed by
					
						
						Amir Bandeali
					
				
			
			
				
	
			
			
			
						parent
						
							ff1a3ab307
						
					
				
				
					commit
					ed78bde359
				
			@@ -49,6 +49,9 @@
 | 
				
			|||||||
        "@0x/web3-wrapper": "^6.0.7",
 | 
					        "@0x/web3-wrapper": "^6.0.7",
 | 
				
			||||||
        "@types/web3-provider-engine": "^14.0.0",
 | 
					        "@types/web3-provider-engine": "^14.0.0",
 | 
				
			||||||
        "chai": "^4.0.1",
 | 
					        "chai": "^4.0.1",
 | 
				
			||||||
 | 
					        "chai-as-promised": "^7.1.0",
 | 
				
			||||||
 | 
					        "chai-bignumber": "^3.0.0",
 | 
				
			||||||
 | 
					        "dirty-chai": "^2.0.1",
 | 
				
			||||||
        "ethereum-types": "^2.1.3",
 | 
					        "ethereum-types": "^2.1.3",
 | 
				
			||||||
        "lodash": "^4.17.11"
 | 
					        "lodash": "^4.17.11"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { RichRevertReason } from '@0x/utils';
 | 
					import { RichRevertReason, StandardError } from '@0x/utils';
 | 
				
			||||||
import * as chai from 'chai';
 | 
					import * as chai from 'chai';
 | 
				
			||||||
import chaiAsPromised = require('chai-as-promised');
 | 
					import chaiAsPromised = require('chai-as-promised');
 | 
				
			||||||
import ChaiBigNumber = require('chai-bignumber');
 | 
					import ChaiBigNumber = require('chai-bignumber');
 | 
				
			||||||
@@ -7,44 +7,116 @@ import * as dirtyChai from 'dirty-chai';
 | 
				
			|||||||
export const chaiSetup = {
 | 
					export const chaiSetup = {
 | 
				
			||||||
    configure(): void {
 | 
					    configure(): void {
 | 
				
			||||||
        chai.config.includeStack = true;
 | 
					        chai.config.includeStack = true;
 | 
				
			||||||
 | 
					        // Order matters.
 | 
				
			||||||
        chai.use(ChaiBigNumber());
 | 
					        chai.use(ChaiBigNumber());
 | 
				
			||||||
        chai.use(dirtyChai);
 | 
					 | 
				
			||||||
        chai.use(chaiAsPromised);
 | 
					        chai.use(chaiAsPromised);
 | 
				
			||||||
        chai.use(richRevertEquality);
 | 
					        chai.use(richRevertExtension);
 | 
				
			||||||
 | 
					        chai.use(dirtyChai);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function richRevertEquality(_chai: any): void {
 | 
					// tslint:disable: only-arrow-functions prefer-conditional-expression
 | 
				
			||||||
    // tslint:disable:only-arrow-functions
 | 
					
 | 
				
			||||||
    const Assertion = _chai.Assertion;
 | 
					type ChaiPromiseHandler = (x: any) => Promise<void>;
 | 
				
			||||||
    Assertion.overwriteMethod('rejectedWith', function(_super: (x: any) => Promise<void>): (b: any) => Promise<void> {
 | 
					type ChaiAssertHandler = (x: any) => void;
 | 
				
			||||||
        return async function(this: any, b: any | RichRevertReason): Promise<void> {
 | 
					
 | 
				
			||||||
            const p = this._obj;
 | 
					interface Chai {
 | 
				
			||||||
            let a;
 | 
					    Assertion: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ChaiUtils {
 | 
				
			||||||
 | 
					    flag: (assertion: any, name: string, value?: any) => any;
 | 
				
			||||||
 | 
					    overwriteMethod: (ctx: any, name: string, _super: (expected: any) => any) => void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ChaiExtension {
 | 
				
			||||||
 | 
					    assert: ChaiAssert;
 | 
				
			||||||
 | 
					    _obj: any;
 | 
				
			||||||
 | 
					    __flags: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ChaiAssert = (
 | 
				
			||||||
 | 
					    condition: boolean,
 | 
				
			||||||
 | 
					    failMessage?: string,
 | 
				
			||||||
 | 
					    negatedFailMessage?: string,
 | 
				
			||||||
 | 
					    expected?: any,
 | 
				
			||||||
 | 
					    actual?: any,
 | 
				
			||||||
 | 
					) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function richRevertExtension(_chai: Chai, utils: ChaiUtils): void {
 | 
				
			||||||
 | 
					    utils.overwriteMethod(_chai.Assertion.prototype, 'rejectedWith', function(
 | 
				
			||||||
 | 
					        _super: ChaiPromiseHandler,
 | 
				
			||||||
 | 
					    ): ChaiPromiseHandler {
 | 
				
			||||||
 | 
					        return async function(this: ChaiExtension, expected: any): Promise<void> {
 | 
				
			||||||
 | 
					            const maybePromise = this._obj;
 | 
				
			||||||
 | 
					            // Make sure we're working with a promise.
 | 
				
			||||||
 | 
					            new _chai.Assertion().assert(maybePromise instanceof Promise, `Expected ${maybePromise} to be a promise`);
 | 
				
			||||||
 | 
					            // Wait for the promise to reject.
 | 
				
			||||||
 | 
					            let err: any;
 | 
				
			||||||
 | 
					            let didReject = false;
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                if (p instanceof Promise) {
 | 
					                await maybePromise;
 | 
				
			||||||
                    await p;
 | 
					            } catch (_err) {
 | 
				
			||||||
                    this.assert(false, 'Expected promise to reject');
 | 
					                err = _err;
 | 
				
			||||||
                } else {
 | 
					                didReject = true;
 | 
				
			||||||
                    a = p;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } catch (err) {
 | 
					 | 
				
			||||||
                a = err;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (b instanceof RichRevertReason) {
 | 
					            if (!didReject) {
 | 
				
			||||||
                // Try to decode the result as a rich revert reason.
 | 
					                new _chai.Assertion().assert(false, `Expected promise to reject`);
 | 
				
			||||||
                if (a instanceof Buffer) {
 | 
					 | 
				
			||||||
                    const hex = a.toString('hex');
 | 
					 | 
				
			||||||
                    a = `0x${hex}`;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (typeof a === 'string') {
 | 
					 | 
				
			||||||
                    a = RichRevertReason.decode(a);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (a instanceof RichRevertReason) {
 | 
					 | 
				
			||||||
                    this.assert(a.equals(b), `${a} != ${b}`);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            _super.call(this, b);
 | 
					            return compareRichRevertReasons.call(this, _chai, _super, err, expected);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    utils.overwriteMethod(_chai.Assertion.prototype, 'become', function(
 | 
				
			||||||
 | 
					        _super: ChaiPromiseHandler,
 | 
				
			||||||
 | 
					    ): ChaiPromiseHandler {
 | 
				
			||||||
 | 
					        return async function(this: ChaiExtension, expected: any): Promise<void> {
 | 
				
			||||||
 | 
					            const maybePromise = this._obj;
 | 
				
			||||||
 | 
					            // Make sure we're working with a promise.
 | 
				
			||||||
 | 
					            new _chai.Assertion().assert(maybePromise instanceof Promise, `Expected ${maybePromise} to be a promise`);
 | 
				
			||||||
 | 
					            // Wait for the promise to resolve.
 | 
				
			||||||
 | 
					            return compareRichRevertReasons.call(this, _chai, _super, await maybePromise, expected);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    utils.overwriteMethod(_chai.Assertion.prototype, 'equal', function(_super: ChaiAssertHandler): ChaiAssertHandler {
 | 
				
			||||||
 | 
					        return function(this: ChaiExtension, expected: any): void {
 | 
				
			||||||
 | 
					            compareRichRevertReasons.call(this, _chai, _super, this._obj, expected);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    utils.overwriteMethod(_chai.Assertion.prototype, 'eql', function(_super: ChaiAssertHandler): ChaiAssertHandler {
 | 
				
			||||||
 | 
					        return function(this: ChaiExtension, expected: any): void {
 | 
				
			||||||
 | 
					            compareRichRevertReasons.call(this, _chai, _super, this._obj, expected);
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function compareRichRevertReasons(
 | 
				
			||||||
 | 
					    this: ChaiExtension,
 | 
				
			||||||
 | 
					    _chai: Chai,
 | 
				
			||||||
 | 
					    _super: ChaiAssertHandler,
 | 
				
			||||||
 | 
					    _actual: any,
 | 
				
			||||||
 | 
					    _expected: any,
 | 
				
			||||||
 | 
					): void {
 | 
				
			||||||
 | 
					    let actual = _actual;
 | 
				
			||||||
 | 
					    let expected = _expected;
 | 
				
			||||||
 | 
					    // If either subject is a StandardError, try to coerce the other into the same.
 | 
				
			||||||
 | 
					    if (expected instanceof StandardError || actual instanceof StandardError) {
 | 
				
			||||||
 | 
					        // `actual` can be a RichRevertReason, string, or an Error type.
 | 
				
			||||||
 | 
					        if (typeof actual === 'string') {
 | 
				
			||||||
 | 
					            actual = new StandardError(actual);
 | 
				
			||||||
 | 
					        } else if (actual instanceof Error) {
 | 
				
			||||||
 | 
					            actual = new StandardError(actual.message);
 | 
				
			||||||
 | 
					        } else if (!(actual instanceof RichRevertReason)) {
 | 
				
			||||||
 | 
					            new _chai.Assertion().assert(false, `Result is not of type RichRevertReason: ${actual}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // `expected` can be a RichRevertReason or string.
 | 
				
			||||||
 | 
					        if (typeof expected === 'string') {
 | 
				
			||||||
 | 
					            expected = new StandardError(expected);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (expected instanceof RichRevertReason && actual instanceof RichRevertReason) {
 | 
				
			||||||
 | 
					        // Check for equality.
 | 
				
			||||||
 | 
					        this.assert(actual.equals(expected), `${actual} != ${expected}`, `${actual} == ${expected}`, expected, actual);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _super.call(this, _expected);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										121
									
								
								packages/dev-utils/test/chai_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								packages/dev-utils/test/chai_test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
				
			|||||||
 | 
					import { StandardError } from '@0x/utils';
 | 
				
			||||||
 | 
					import * as chai from 'chai';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { chaiSetup } from '../src';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					chaiSetup.configure();
 | 
				
			||||||
 | 
					const expect = chai.expect;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CustomError extends StandardError {
 | 
				
			||||||
 | 
					    constructor(msg: string) {
 | 
				
			||||||
 | 
					        super(msg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Chai tests', () => {
 | 
				
			||||||
 | 
					    describe('RichRevertReasons', () => {
 | 
				
			||||||
 | 
					        describe('#equal', () => {
 | 
				
			||||||
 | 
					            it('should equate two identical RichRevertReasons', () => {
 | 
				
			||||||
 | 
					                const message = 'foo';
 | 
				
			||||||
 | 
					                const reason1 = new StandardError(message);
 | 
				
			||||||
 | 
					                const reason2 = new StandardError(message);
 | 
				
			||||||
 | 
					                expect(reason1).is.equal(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should equate two RichRevertReasons where one has missing fields', () => {
 | 
				
			||||||
 | 
					                const reason1 = new StandardError('foo');
 | 
				
			||||||
 | 
					                const reason2 = new StandardError();
 | 
				
			||||||
 | 
					                expect(reason1).is.equal(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should not equate two RichRevertReasons with diferent fields', () => {
 | 
				
			||||||
 | 
					                const reason1 = new StandardError('foo1');
 | 
				
			||||||
 | 
					                const reason2 = new StandardError('foo2');
 | 
				
			||||||
 | 
					                expect(reason1).is.not.equal(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should not equate two RichRevertReasons with diferent types', () => {
 | 
				
			||||||
 | 
					                const message = 'foo';
 | 
				
			||||||
 | 
					                const reason1 = new StandardError(message);
 | 
				
			||||||
 | 
					                const reason2 = new CustomError(message);
 | 
				
			||||||
 | 
					                expect(reason1).is.not.equal(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should equate a StandardError to a string equal to message', () => {
 | 
				
			||||||
 | 
					                const message = 'foo';
 | 
				
			||||||
 | 
					                const reason = new StandardError(message);
 | 
				
			||||||
 | 
					                expect(message).is.equal(reason);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should equate an Error to a StandardError with an equal message', () => {
 | 
				
			||||||
 | 
					                const message = 'foo';
 | 
				
			||||||
 | 
					                const reason = new StandardError(message);
 | 
				
			||||||
 | 
					                const error = new Error(message);
 | 
				
			||||||
 | 
					                expect(error).is.equal(reason);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should equate a string to a StandardError with the same message', () => {
 | 
				
			||||||
 | 
					                const message = 'foo';
 | 
				
			||||||
 | 
					                const reason = new StandardError(message);
 | 
				
			||||||
 | 
					                expect(reason).is.equal(message);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should not equate a StandardError to a string not equal to message', () => {
 | 
				
			||||||
 | 
					                const reason = new StandardError('foo1');
 | 
				
			||||||
 | 
					                expect('foo2').is.not.equal(reason);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should not equate a string to a StandardError with a different message', () => {
 | 
				
			||||||
 | 
					                const reason = new StandardError('foo1');
 | 
				
			||||||
 | 
					                expect(reason).is.not.equal('foo2');
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should not equate an Error to a StandardError with a different message', () => {
 | 
				
			||||||
 | 
					                const reason = new StandardError('foo1');
 | 
				
			||||||
 | 
					                const error = new Error('foo2');
 | 
				
			||||||
 | 
					                expect(error).is.not.equal(reason);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        describe('#rejectedWith', () => {
 | 
				
			||||||
 | 
					            it('should equate a promise that rejects to an identical RichRevertReasons', async () => {
 | 
				
			||||||
 | 
					                const message = 'foo';
 | 
				
			||||||
 | 
					                const reason1 = new StandardError(message);
 | 
				
			||||||
 | 
					                const reason2 = new StandardError(message);
 | 
				
			||||||
 | 
					                const promise = (async () => {
 | 
				
			||||||
 | 
					                    throw reason1;
 | 
				
			||||||
 | 
					                })();
 | 
				
			||||||
 | 
					                return expect(promise).to.be.rejectedWith(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should not equate a promise that rejects to a StandardError with a different messages', async () => {
 | 
				
			||||||
 | 
					                const reason1 = new StandardError('foo1');
 | 
				
			||||||
 | 
					                const reason2 = new StandardError('foo2');
 | 
				
			||||||
 | 
					                const promise = (async () => {
 | 
				
			||||||
 | 
					                    throw reason1;
 | 
				
			||||||
 | 
					                })();
 | 
				
			||||||
 | 
					                return expect(promise).to.not.be.rejectedWith(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should not equate a promise that rejects to different RichRevertReason types', async () => {
 | 
				
			||||||
 | 
					                const message = 'foo';
 | 
				
			||||||
 | 
					                const reason1 = new StandardError(message);
 | 
				
			||||||
 | 
					                const reason2 = new CustomError(message);
 | 
				
			||||||
 | 
					                const promise = (async () => {
 | 
				
			||||||
 | 
					                    throw reason1;
 | 
				
			||||||
 | 
					                })();
 | 
				
			||||||
 | 
					                return expect(promise).to.not.be.rejectedWith(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        describe('#become', () => {
 | 
				
			||||||
 | 
					            it('should equate a promise that resolves to an identical RichRevertReasons', async () => {
 | 
				
			||||||
 | 
					                const message = 'foo';
 | 
				
			||||||
 | 
					                const reason1 = new StandardError(message);
 | 
				
			||||||
 | 
					                const reason2 = new StandardError(message);
 | 
				
			||||||
 | 
					                const promise = (async () => reason1)();
 | 
				
			||||||
 | 
					                return expect(promise).to.become(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should not equate a promise that resolves to a StandardError with a different messages', async () => {
 | 
				
			||||||
 | 
					                const reason1 = new StandardError('foo1');
 | 
				
			||||||
 | 
					                const reason2 = new StandardError('foo2');
 | 
				
			||||||
 | 
					                const promise = (async () => reason1)();
 | 
				
			||||||
 | 
					                return expect(promise).to.not.become(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            it('should not equate a promise that resolves to different RichRevertReason types', async () => {
 | 
				
			||||||
 | 
					                const message = 'foo';
 | 
				
			||||||
 | 
					                const reason1 = new StandardError(message);
 | 
				
			||||||
 | 
					                const reason2 = new CustomError(message);
 | 
				
			||||||
 | 
					                const promise = (async () => reason1)();
 | 
				
			||||||
 | 
					                return expect(promise).to.not.become(reason2);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Reference in New Issue
	
	Block a user