Introduce framework for running basic tests for entities (#1344)

* Introduce framework for running basic tests for entities

* Add pipeline tests to CircleCI config

* Make pipeline tests more configurable and fix CircleCI config

* Add coverage dir to pipeline package

* Add basic tests for all exchange event entities

* Add tests for remaining entities

* Create separate test scripts in package.json and add new info to README

* Update db_setup.ts to revert migrations even if you are using docker

* Automatically pull the postgres image if needed

* Add comment about why NumberToBigIntTransformer is needed
This commit is contained in:
Alex Browne
2018-11-28 13:21:04 -08:00
committed by Fred Carlsen
parent 3ca876c574
commit d14d38dabd
27 changed files with 767 additions and 55 deletions

View File

@@ -0,0 +1,23 @@
import 'mocha';
import 'reflect-metadata';
import { Block } from '../../src/entities';
import { createDbConnectionOnceAsync } from '../db_setup';
import { chaiSetup } from '../utils/chai_setup';
import { testSaveAndFindEntityAsync } from './util';
chaiSetup.configure();
// tslint:disable:custom-no-magic-numbers
describe('Block entity', () => {
it('save/find', async () => {
const connection = await createDbConnectionOnceAsync();
const block = new Block();
block.hash = '0x12345';
block.number = 1234567;
block.timestamp = 5432154321;
const blocksRepository = connection.getRepository(Block);
await testSaveAndFindEntityAsync(blocksRepository, block);
});
});

View File

@@ -0,0 +1,57 @@
import 'mocha';
import * as R from 'ramda';
import 'reflect-metadata';
import { ExchangeCancelEvent } from '../../src/entities';
import { AssetType } from '../../src/types';
import { createDbConnectionOnceAsync } from '../db_setup';
import { chaiSetup } from '../utils/chai_setup';
import { testSaveAndFindEntityAsync } from './util';
chaiSetup.configure();
const baseCancelEvent = {
contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
logIndex: 1234,
blockNumber: 6276262,
rawData: '0x000000000000000000000000f6da68519f78b0d0bc93c701e86affcb75c92428',
transactionHash: '0x6dd106d002873746072fc5e496dd0fb2541b68c77bcf9184ae19a42fd33657fe',
makerAddress: '0xf6da68519f78b0d0bc93c701e86affcb75c92428',
takerAddress: '0xf6da68519f78b0d0bc93c701e86affcb75c92428',
feeRecipientAddress: '0xc370d2a5920344aa6b7d8d11250e3e861434cbdd',
senderAddress: '0xf6da68519f78b0d0bc93c701e86affcb75c92428',
orderHash: '0xab12ed2cbaa5615ab690b9da75a46e53ddfcf3f1a68655b5fe0d94c75a1aac4a',
rawMakerAssetData: '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
makerAssetProxyId: '0xf47261b0',
makerTokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
rawTakerAssetData: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498',
takerAssetProxyId: '0xf47261b0',
takerTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
};
const erc20CancelEvent = R.merge(baseCancelEvent, {
makerAssetType: 'erc20' as AssetType,
makerTokenId: null,
takerAssetType: 'erc20' as AssetType,
takerTokenId: null,
});
const erc721CancelEvent = R.merge(baseCancelEvent, {
makerAssetType: 'erc721' as AssetType,
makerTokenId: '19378573',
takerAssetType: 'erc721' as AssetType,
takerTokenId: '63885673888',
});
// tslint:disable:custom-no-magic-numbers
describe('ExchangeCancelEvent entity', () => {
it('save/find', async () => {
const connection = await createDbConnectionOnceAsync();
const events = [erc20CancelEvent, erc721CancelEvent];
const cancelEventRepository = connection.getRepository(ExchangeCancelEvent);
for (const event of events) {
await testSaveAndFindEntityAsync(cancelEventRepository, event);
}
});
});

View File

@@ -0,0 +1,29 @@
import { BigNumber } from '@0x/utils';
import 'mocha';
import 'reflect-metadata';
import { ExchangeCancelUpToEvent } from '../../src/entities';
import { createDbConnectionOnceAsync } from '../db_setup';
import { chaiSetup } from '../utils/chai_setup';
import { testSaveAndFindEntityAsync } from './util';
chaiSetup.configure();
// tslint:disable:custom-no-magic-numbers
describe('ExchangeCancelUpToEvent entity', () => {
it('save/find', async () => {
const connection = await createDbConnectionOnceAsync();
const cancelUpToEventRepository = connection.getRepository(ExchangeCancelUpToEvent);
const cancelUpToEvent = new ExchangeCancelUpToEvent();
cancelUpToEvent.blockNumber = 6276262;
cancelUpToEvent.contractAddress = '0x4f833a24e1f95d70f028921e27040ca56e09ab0b';
cancelUpToEvent.logIndex = 42;
cancelUpToEvent.makerAddress = '0xf6da68519f78b0d0bc93c701e86affcb75c92428';
cancelUpToEvent.orderEpoch = new BigNumber('123456789123456789');
cancelUpToEvent.rawData = '0x000000000000000000000000f6da68519f78b0d0bc93c701e86affcb75c92428';
cancelUpToEvent.senderAddress = '0xf6da68519f78b0d0bc93c701e86affcb75c92428';
cancelUpToEvent.transactionHash = '0x6dd106d002873746072fc5e496dd0fb2541b68c77bcf9184ae19a42fd33657fe';
await testSaveAndFindEntityAsync(cancelUpToEventRepository, cancelUpToEvent);
});
});

View File

@@ -0,0 +1,62 @@
import { BigNumber } from '@0x/utils';
import 'mocha';
import * as R from 'ramda';
import 'reflect-metadata';
import { ExchangeFillEvent } from '../../src/entities';
import { AssetType } from '../../src/types';
import { createDbConnectionOnceAsync } from '../db_setup';
import { chaiSetup } from '../utils/chai_setup';
import { testSaveAndFindEntityAsync } from './util';
chaiSetup.configure();
const baseFillEvent = {
contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
blockNumber: 6276262,
logIndex: 102,
rawData: '0x000000000000000000000000f6da68519f78b0d0bc93c701e86affcb75c92428',
transactionHash: '0x6dd106d002873746072fc5e496dd0fb2541b68c77bcf9184ae19a42fd33657fe',
makerAddress: '0xf6da68519f78b0d0bc93c701e86affcb75c92428',
takerAddress: '0xf6da68519f78b0d0bc93c701e86affcb75c92428',
feeRecipientAddress: '0xc370d2a5920344aa6b7d8d11250e3e861434cbdd',
senderAddress: '0xf6da68519f78b0d0bc93c701e86affcb75c92428',
makerAssetFilledAmount: new BigNumber('10000000000000000'),
takerAssetFilledAmount: new BigNumber('100000000000000000'),
makerFeePaid: new BigNumber('0'),
takerFeePaid: new BigNumber('12345'),
orderHash: '0xab12ed2cbaa5615ab690b9da75a46e53ddfcf3f1a68655b5fe0d94c75a1aac4a',
rawMakerAssetData: '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
makerAssetProxyId: '0xf47261b0',
makerTokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
rawTakerAssetData: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498',
takerAssetProxyId: '0xf47261b0',
takerTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
};
const erc20FillEvent = R.merge(baseFillEvent, {
makerAssetType: 'erc20' as AssetType,
makerTokenId: null,
takerAssetType: 'erc20' as AssetType,
takerTokenId: null,
});
const erc721FillEvent = R.merge(baseFillEvent, {
makerAssetType: 'erc721' as AssetType,
makerTokenId: '19378573',
takerAssetType: 'erc721' as AssetType,
takerTokenId: '63885673888',
});
// tslint:disable:custom-no-magic-numbers
describe('ExchangeFillEvent entity', () => {
it('save/find', async () => {
const connection = await createDbConnectionOnceAsync();
const events = [erc20FillEvent, erc721FillEvent];
const fillEventsRepository = connection.getRepository(ExchangeFillEvent);
for (const event of events) {
await testSaveAndFindEntityAsync(fillEventsRepository, event);
}
});
});

View File

@@ -0,0 +1,55 @@
import 'mocha';
import * as R from 'ramda';
import 'reflect-metadata';
import { Relayer } from '../../src/entities';
import { createDbConnectionOnceAsync } from '../db_setup';
import { chaiSetup } from '../utils/chai_setup';
import { testSaveAndFindEntityAsync } from './util';
chaiSetup.configure();
const baseRelayer = {
uuid: 'e8d27d8d-ddf6-48b1-9663-60b0a3ddc716',
name: 'Radar Relay',
homepageUrl: 'https://radarrelay.com',
appUrl: null,
sraHttpEndpoint: null,
sraWsEndpoint: null,
feeRecipientAddresses: [],
takerAddresses: [],
};
const relayerWithUrls = R.merge(baseRelayer, {
uuid: 'e8d27d8d-ddf6-48b1-9663-60b0a3ddc717',
appUrl: 'https://app.radarrelay.com',
sraHttpEndpoint: 'https://api.radarrelay.com/0x/v2/',
sraWsEndpoint: 'wss://ws.radarrelay.com/0x/v2',
});
const relayerWithAddresses = R.merge(baseRelayer, {
uuid: 'e8d27d8d-ddf6-48b1-9663-60b0a3ddc718',
feeRecipientAddresses: [
'0xa258b39954cef5cb142fd567a46cddb31a670124',
'0xa258b39954cef5cb142fd567a46cddb31a670125',
'0xa258b39954cef5cb142fd567a46cddb31a670126',
],
takerAddresses: [
'0xa258b39954cef5cb142fd567a46cddb31a670127',
'0xa258b39954cef5cb142fd567a46cddb31a670128',
'0xa258b39954cef5cb142fd567a46cddb31a670129',
],
});
// tslint:disable:custom-no-magic-numbers
describe('Relayer entity', () => {
it('save/find', async () => {
const connection = await createDbConnectionOnceAsync();
const relayers = [baseRelayer, relayerWithUrls, relayerWithAddresses];
const relayerRepository = connection.getRepository(Relayer);
for (const relayer of relayers) {
await testSaveAndFindEntityAsync(relayerRepository, relayer);
}
});
});

View File

@@ -0,0 +1,84 @@
import { BigNumber } from '@0x/utils';
import 'mocha';
import * as R from 'ramda';
import 'reflect-metadata';
import { Repository } from 'typeorm';
import { SraOrder, SraOrdersObservedTimeStamp } from '../../src/entities';
import { AssetType } from '../../src/types';
import { createDbConnectionOnceAsync } from '../db_setup';
import { chaiSetup } from '../utils/chai_setup';
import { testSaveAndFindEntityAsync } from './util';
chaiSetup.configure();
const baseOrder = {
sourceUrl: 'https://api.radarrelay.com/0x/v2',
exchangeAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
makerAddress: '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81',
takerAddress: '0x0000000000000000000000000000000000000000',
feeRecipientAddress: '0xa258b39954cef5cb142fd567a46cddb31a670124',
senderAddress: '0x0000000000000000000000000000000000000000',
makerAssetAmount: new BigNumber('1619310371000000000'),
takerAssetAmount: new BigNumber('8178335207070707070707'),
makerFee: new BigNumber('100'),
takerFee: new BigNumber('200'),
expirationTimeSeconds: new BigNumber('1538529488'),
salt: new BigNumber('1537924688891'),
signature: '0x1b5a5d672b0d647b5797387ccbb89d8',
rawMakerAssetData: '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
makerAssetProxyId: '0xf47261b0',
makerTokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
rawTakerAssetData: '0xf47261b000000000000000000000000042d6622dece394b54999fbd73d108123806f6a18',
takerAssetProxyId: '0xf47261b0',
takerTokenAddress: '0x42d6622dece394b54999fbd73d108123806f6a18',
metadataJson: '{"isThisArbitraryData":true,"powerLevel":9001}',
};
const erc20Order = R.merge(baseOrder, {
orderHashHex: '0x1bdbeb0d088a33da28b9ee6d94e8771452f90f4a69107da2fa75195d61b9a1c9',
makerAssetType: 'erc20' as AssetType,
makerTokenId: null,
takerAssetType: 'erc20' as AssetType,
takerTokenId: null,
});
const erc721Order = R.merge(baseOrder, {
orderHashHex: '0x1bdbeb0d088a33da28b9ee6d94e8771452f90f4a69107da2fa75195d61b9a1d0',
makerAssetType: 'erc721' as AssetType,
makerTokenId: '19378573',
takerAssetType: 'erc721' as AssetType,
takerTokenId: '63885673888',
});
// tslint:disable:custom-no-magic-numbers
describe('SraOrder and SraOrdersObservedTimeStamp entities', () => {
// Note(albrow): SraOrder and SraOrdersObservedTimeStamp are tightly coupled
// and timestamps have a foreign key constraint such that they have to point
// to an existing SraOrder. For these reasons, we are testing them together
// in the same test.
it('save/find', async () => {
const connection = await createDbConnectionOnceAsync();
const orderRepository = connection.getRepository(SraOrder);
const timestampRepository = connection.getRepository(SraOrdersObservedTimeStamp);
const orders = [erc20Order, erc721Order];
for (const order of orders) {
await testOrderWithTimestampAsync(orderRepository, timestampRepository, order);
}
});
});
async function testOrderWithTimestampAsync(
orderRepository: Repository<SraOrder>,
timestampRepository: Repository<SraOrdersObservedTimeStamp>,
order: SraOrder,
): Promise<void> {
await testSaveAndFindEntityAsync(orderRepository, order);
const timestamp = new SraOrdersObservedTimeStamp();
timestamp.exchangeAddress = order.exchangeAddress;
timestamp.orderHashHex = order.orderHashHex;
timestamp.sourceUrl = order.sourceUrl;
timestamp.observedTimestamp = 1543377376153;
await testSaveAndFindEntityAsync(timestampRepository, timestamp);
}

View File

@@ -0,0 +1,38 @@
import 'mocha';
import 'reflect-metadata';
import { TokenMetadata } from '../../src/entities';
import { createDbConnectionOnceAsync } from '../db_setup';
import { chaiSetup } from '../utils/chai_setup';
import { testSaveAndFindEntityAsync } from './util';
chaiSetup.configure();
const metadataWithoutNullFields = {
address: '0xe41d2489571d322189246dafa5ebde1f4699f498',
authority: 'https://website-api.0xproject.com/tokens',
decimals: 18,
symbol: 'ZRX',
name: '0x',
};
const metadataWithNullFields = {
address: '0xe41d2489571d322189246dafa5ebde1f4699f499',
authority: 'https://website-api.0xproject.com/tokens',
decimals: null,
symbol: null,
name: null,
};
// tslint:disable:custom-no-magic-numbers
describe('TokenMetadata entity', () => {
it('save/find', async () => {
const connection = await createDbConnectionOnceAsync();
const tokenMetadata = [metadataWithoutNullFields, metadataWithNullFields];
const tokenMetadataRepository = connection.getRepository(TokenMetadata);
for (const tokenMetadatum of tokenMetadata) {
await testSaveAndFindEntityAsync(tokenMetadataRepository, tokenMetadatum);
}
});
});

View File

@@ -0,0 +1,25 @@
import 'mocha';
import 'reflect-metadata';
import { Transaction } from '../../src/entities';
import { createDbConnectionOnceAsync } from '../db_setup';
import { chaiSetup } from '../utils/chai_setup';
import { testSaveAndFindEntityAsync } from './util';
chaiSetup.configure();
// tslint:disable:custom-no-magic-numbers
describe('Transaction entity', () => {
it('save/find', async () => {
const connection = await createDbConnectionOnceAsync();
const transactionRepository = connection.getRepository(Transaction);
const transaction = new Transaction();
transaction.blockHash = '0x6ff106d00b6c3746072fc06bae140fb2549036ba7bcf9184ae19a42fd33657fd';
transaction.blockNumber = 6276262;
transaction.gasPrice = 3000000;
transaction.gasUsed = 125000;
transaction.transactionHash = '0x6dd106d002873746072fc5e496dd0fb2541b68c77bcf9184ae19a42fd33657fe';
await testSaveAndFindEntityAsync(transactionRepository, transaction);
});
});

View File

@@ -0,0 +1,25 @@
import * as chai from 'chai';
import 'mocha';
import { Repository } from 'typeorm';
const expect = chai.expect;
/**
* First saves the given entity to the database, then finds it and makes sure
* that the found entity is exactly equal to the original one. This is a bare
* minimum basic test to make sure that the entity type definition and our
* database schema are aligned and that it is possible to save and find the
* entity.
* @param repository A TypeORM repository corresponding with the type of the entity.
* @param entity An instance of a TypeORM entity which will be saved/retrieved from the database.
*/
export async function testSaveAndFindEntityAsync<T>(repository: Repository<T>, entity: T): Promise<void> {
// Note(albrow): We are forced to use an 'as any' hack here because
// TypeScript complains about stack depth when checking the types.
await repository.save(entity as any);
const gotEntity = await repository.findOneOrFail({
where: entity,
});
expect(gotEntity).deep.equal(entity);
}