diff options
Diffstat (limited to 'packages')
20 files changed, 1370 insertions, 6 deletions
diff --git a/packages/assert/CHANGELOG.md b/packages/assert/CHANGELOG.md index b37a810e3..35bf4faef 100644 --- a/packages/assert/CHANGELOG.md +++ b/packages/assert/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.2.0 - _TBD, 2018_ + + * Rename `isHttpUrl` to `isWebUri` (#412) + ## v0.1.0 - _March 4, 2018_ * Remove isETHAddressHex checksum address check and assume address will be lowercased (#373) diff --git a/packages/assert/src/index.ts b/packages/assert/src/index.ts index 40d083cb6..171909c93 100644 --- a/packages/assert/src/index.ts +++ b/packages/assert/src/index.ts @@ -75,9 +75,9 @@ Encountered: ${JSON.stringify(value, null, '\t')} Validation errors: ${validationResult.errors.join(', ')}`; this.assert(!hasValidationErrors, msg); }, - isHttpUrl(variableName: string, value: any): void { + isWebUri(variableName: string, value: any): void { const isValidUrl = !_.isUndefined(validUrl.isWebUri(value)); - this.assert(isValidUrl, this.typeAssertionMessage(variableName, 'http url', value)); + this.assert(isValidUrl, this.typeAssertionMessage(variableName, 'web uri', value)); }, isUri(variableName: string, value: any): void { const isValidUri = !_.isUndefined(validUrl.isUri(value)); diff --git a/packages/assert/test/assert_test.ts b/packages/assert/test/assert_test.ts index b0fa398d6..1d9a34ed9 100644 --- a/packages/assert/test/assert_test.ts +++ b/packages/assert/test/assert_test.ts @@ -183,7 +183,7 @@ describe('Assertions', () => { ); }); }); - describe('#isHttpUrl', () => { + describe('#isWebUri', () => { it('should not throw for valid input', () => { const validInputs = [ 'http://www.google.com', @@ -191,7 +191,7 @@ describe('Assertions', () => { 'https://api.radarrelay.com/0x/v0/', 'https://zeroex.beta.radarrelay.com:8000/0x/v0/', ]; - validInputs.forEach(input => expect(assert.isHttpUrl.bind(assert, variableName, input)).to.not.throw()); + validInputs.forEach(input => expect(assert.isWebUri.bind(assert, variableName, input)).to.not.throw()); }); it('should throw for invalid input', () => { const invalidInputs = [ @@ -205,7 +205,7 @@ describe('Assertions', () => { 'user:password@api.example-relayer.net', '//api.example-relayer.net', ]; - invalidInputs.forEach(input => expect(assert.isHttpUrl.bind(assert, variableName, input)).to.throw()); + invalidInputs.forEach(input => expect(assert.isWebUri.bind(assert, variableName, input)).to.throw()); }); }); describe('#isUri', () => { diff --git a/packages/connect/src/http_client.ts b/packages/connect/src/http_client.ts index cf0aaef0d..a221b54e8 100644 --- a/packages/connect/src/http_client.ts +++ b/packages/connect/src/http_client.ts @@ -61,7 +61,7 @@ export class HttpClient implements Client { * @return An instance of HttpClient */ constructor(url: string) { - assert.isHttpUrl('url', url); + assert.isWebUri('url', url); this._apiEndpointUrl = url.replace(TRAILING_SLASHES_REGEX, ''); // remove trailing slashes } /** diff --git a/packages/sra-report/.npmignore b/packages/sra-report/.npmignore new file mode 100644 index 000000000..87bc30436 --- /dev/null +++ b/packages/sra-report/.npmignore @@ -0,0 +1,5 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json diff --git a/packages/sra-report/CHANGELOG.md b/packages/sra-report/CHANGELOG.md new file mode 100644 index 000000000..a0cf709bc --- /dev/null +++ b/packages/sra-report/CHANGELOG.md @@ -0,0 +1 @@ +# CHANGELOG diff --git a/packages/sra-report/README.md b/packages/sra-report/README.md new file mode 100644 index 000000000..812535f45 --- /dev/null +++ b/packages/sra-report/README.md @@ -0,0 +1,118 @@ +# SRA Report + +This tool allows you to generate reports that detail an endpoint's [standard relayer API HTTP specification](https://github.com/0xProject/standard-relayer-api/blob/master/http/v0.md) compliance. The tool will perform a [Postman collection](https://www.getpostman.com/docs/v6/postman/collections/creating_collections) run and either print a report to the console or save it to disk as a json file. SRA report can also output a Postman collection file and [Postman environment](https://www.getpostman.com/docs/v6/postman/environments_and_globals/manage_environments) file in order to facilitate replication and debugging of collection runs using the [Postman native app](https://www.getpostman.com/docs/v6/postman/launching_postman/installation_and_updates). + +The tool currently performs the following checks for each endpoint: + +* `application/json` Content-Type header validation +* JSON schema validation +* Correct filtering when a query paramater is provided (ex. when querying for a specific maker address, all orders returned have the same maker address that was provided by the query) + +Features to come: + +* Correct sorting (ex. the `/orderbook` endpoint should return orders in order of price) +* Tests for pagination +* Tests for the `POST /order` endpoint +* Tests for failure cases and errors + +## Installation + +`yarn add -g @0xproject/sra-report` + +## Options + +``` +sra-report +Options: + --help Show help [boolean] + --version Show version number [boolean] + --endpoint-url, -e API endpoint url to test for standard relayer API + compliance [string] [required] + --output, -o, --out The relative path to write the report generated by + the collection run, prints to console by default + [string] + --network-id, -n ID of the network that the API is serving orders + from [number] [default: 1] + --environment, --env The relative path to a postman environment file + for the collection run [string] + --export-collection, --ec The relative path to write the postman collection + file used by the collection run [string] + --export-environment, --ee The relative path to write the postman environment + file used by the collection run [string] +``` + +## Example Usage + +### Print report to console + +```bash +sra-report --endpoint-url 'http://api.example.com' +``` + +### Save a report to disk + +```bash +sra-report --endpoint-url 'http://api.example.com' --output 'path/to/report.json' +``` + +### Generate report for an endpoint that serves kovan testnet orders + +```bash +sra-report --endpoint-url 'http://kovan.api.example.com' --network-id 42 +``` + +### Write Postman collection and environment files for use in the Postman native app + +```bash +sra-report --endpoint-url 'http://.api.example.com' --export-collection 'path/to/collection.json' --export-environment 'path/to/environment.json' +``` + +### Run the report using a custom environment + +```bash +sra-report --endpoint-url 'http://.api.example.com' --environment 'path/to/custom/environment.json' +``` + +## Custom environments + +When testing your standard relayer API endpoint in development, it may be useful to modify the Postman environment file generated by this tool such that specific query parameters are used during the collection run. For example, by default, this tool will grab the first order it can from the `/orders` endpoint and use properties from that order as query parameters for the rest of the run. Another example is the tool will default to the [WETH](https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2) and [ZRX](https://etherscan.io/address/0xe41d2489571d322189246dafa5ebde1f4699f498) token contracts when querying some endpoints but you may want to specify these. + +In order to provide a custom environment to the tool, perform the following steps: + +1. Export a Postman environment file using the tool: [example](#Write-Postman-collection-and-environment-files-for-use-in-the-Postman-native-app) +2. Open the Postman environment file using your favorite text editor or in the Postman native app +3. Modify the specific values you want +4. Save the environment file and export it if using the Postman native app +5. Run the tool and supply a path to your modified environment file: [example](#Run-the-report-using-a-custom-environment) + +## Contributing + +We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +```bash +yarn build +``` + +### Lint + +```bash +yarn lint +``` diff --git a/packages/sra-report/package.json b/packages/sra-report/package.json new file mode 100644 index 000000000..c248cff30 --- /dev/null +++ b/packages/sra-report/package.json @@ -0,0 +1,48 @@ +{ + "name": "@0xproject/sra-report", + "version": "0.0.1", + "description": "Generate reports for standard relayer API compliance", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build:watch": "tsc -w", + "lint": "tslint --project . 'src/**/*.ts'", + "clean": "shx rm -rf lib", + "build": "tsc" + }, + "bin": { + "sra-report": "lib/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/packages/sra-report/README.md", + "dependencies": { + "0x.js": "^0.33.0", + "@0xproject/assert": "^0.1.0", + "@0xproject/connect": "^0.6.2", + "@0xproject/json-schemas": "^0.7.13", + "@0xproject/utils": "^0.4.0", + "chalk": "^2.3.0", + "lodash": "^4.17.4", + "newman": "^3.9.3", + "yargs": "^10.0.3" + }, + "devDependencies": { + "@0xproject/tslint-config": "^0.4.10", + "@types/lodash": "^4.14.86", + "@types/node": "^8.0.53", + "@types/yargs": "^10.0.0", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "2.7.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json new file mode 100644 index 000000000..9a8e8e0fa --- /dev/null +++ b/packages/sra-report/postman_configs/collections/sra_report.postman_collection.json @@ -0,0 +1,910 @@ +{ + "info": { + "name": "sra_report", + "_postman_id": "d5828163-ddb9-46a9-ec39-c2b81417b6c0", + "description": + "[Standard Relayer API](\nhttps://github.com/0xProject/standard-relayer-api)\n\n\n0x Protocol is an open standard. Because of this, we expect many independent applications to be built that will want to use the protocol. In order to make it easier for anyone to source liquidity that conforms to the 0x order format, relayers can opt-in to implementing a set of standard relayer API endpoints. In doing so, they allow clients of the standard relayer API to access the orders on their orderbook.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "GET /token_pairs", + "description": "", + "item": [ + { + "name": "default request", + "event": [ + { + "listen": "test", + "script": { + "id": "42cb5e3f-6013-4a7c-b341-0d10cb3f2c9c", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs", + "host": ["{{url}}"], + "path": ["token_pairs"] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenA param", + "event": [ + { + "listen": "test", + "script": { + "id": "0b0712ff-7846-40a3-9253-4bca2551350c", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'tokenContractAddress1';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", + " const condition = tokenAIsFilterToken || tokenBIsFilterToken", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs?tokenA={{tokenContractAddress1}}", + "host": ["{{url}}"], + "path": ["token_pairs"], + "query": [ + { + "key": "tokenA", + "value": "{{tokenContractAddress1}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenB param", + "event": [ + { + "listen": "test", + "script": { + "id": "ef0e6be8-06d8-4975-a1c4-2199bc8b5aa6", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'tokenContractAddress1';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterToken = _.get(tokenPair, 'tokenA.address') === filterTokenAddress;", + " const tokenBIsFilterToken = _.get(tokenPair, 'tokenB.address') === filterTokenAddress;", + " const condition = tokenAIsFilterToken || tokenBIsFilterToken", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/token_pairs?tokenB={{tokenContractAddress1}}", + "host": ["{{url}}"], + "path": ["token_pairs"], + "query": [ + { + "key": "tokenB", + "value": "{{tokenContractAddress1}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "tokenA and tokenB params", + "event": [ + { + "listen": "test", + "script": { + "id": "9ca4bed3-b8e0-4a90-96ba-42f0da3a7d68", + "type": "text/javascript", + "exec": [ + "const filterTokenAEnvKey = 'tokenContractAddress1';", + "const filterTokenBEnvKey = 'tokenContractAddress2';", + "const filterTokenAAddress = pm.environment.get(filterTokenAEnvKey);", + "const filterTokenBAddress = pm.environment.get(filterTokenBEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Token pairs are properly filtered', function() {", + " _.forEach(responseJsonData, function(tokenPair) {", + " const tokenAIsFilterTokenA = _.get(tokenPair, 'tokenA.address') === filterTokenAAddress;", + " const tokenAIsFilterTokenB = _.get(tokenPair, 'tokenA.address') === filterTokenBAddress;", + " const tokenBIsFilterTokenA = _.get(tokenPair, 'tokenB.address') === filterTokenAAddress;", + " const tokenBIsFilterTokenB = _.get(tokenPair, 'tokenB.address') === filterTokenBAddress;", + " const condition = (tokenAIsFilterTokenA && tokenBIsFilterTokenB) || (tokenBIsFilterTokenA && tokenAIsFilterTokenB)", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": + "{{url}}/token_pairs?tokenA={{tokenContractAddress1}}&tokenB={{tokenContractAddress2}}", + "host": ["{{url}}"], + "path": ["token_pairs"], + "query": [ + { + "key": "tokenA", + "value": "{{tokenContractAddress1}}", + "equals": true + }, + { + "key": "tokenB", + "value": "{{tokenContractAddress2}}", + "equals": true + } + ] + }, + "description": "" + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "3d032e92-6a17-49f0-8115-bae1c7298b55", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "25addb38-bd1c-4eb3-a193-5617119dc0d6", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiTokenPairsResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "GET /orders", + "description": "", + "item": [ + { + "name": "default request", + "event": [ + { + "listen": "test", + "script": { + "id": "118f47dd-1d93-4288-841f-de88783eff3b", + "type": "text/javascript", + "exec": [""] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders", + "host": ["{{url}}"], + "path": ["orders"] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "exchangeContract param", + "event": [ + { + "listen": "test", + "script": { + "id": "374c4b28-5672-400c-8c23-9cb1a3e63117", + "type": "text/javascript", + "exec": [ + "const exchangeContractEnvKey = 'exchangeContractAddress';", + "const requestedExchangeContractAddress = pm.environment.get(exchangeContractEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedExchangeContractAddress = _.get(order, 'exchangeContractAddress');", + " pm.expect(requestedExchangeContractAddress).to.equal(returnedExchangeContractAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?exchangeContractAddress={{exchangeContractAddress}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "exchangeContractAddress", + "value": "{{exchangeContractAddress}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "tokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "e74a9069-18b9-42d3-b2d0-e18580ad73f2", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'tokenContractAddress2';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " const makerTokenAddressIsFilterToken = makerTokenAddress === filterTokenAddress;", + " const takerTokenAddressIsFilterToken = takerTokenAddress === filterTokenAddress;", + " const condition = makerTokenAddressIsFilterToken || takerTokenAddressIsFilterToken;", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?tokenAddress={{tokenContractAddress2}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "tokenAddress", + "value": "{{tokenContractAddress2}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "makerTokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "c539f306-aa03-495d-a90a-0179e1b751aa", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'tokenContractAddress2';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(filterTokenAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?makerTokenAddress={{tokenContractAddress2}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "makerTokenAddress", + "value": "{{tokenContractAddress2}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "takerTokenAddress param", + "event": [ + { + "listen": "test", + "script": { + "id": "49b2fcaf-5fe2-471f-ae10-e48a440d4c6d", + "type": "text/javascript", + "exec": [ + "const filterTokenEnvKey = 'tokenContractAddress2';", + "const filterTokenAddress = pm.environment.get(filterTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(takerTokenAddress).to.be.equal(filterTokenAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?takerTokenAddress={{tokenContractAddress2}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "takerTokenAddress", + "value": "{{tokenContractAddress2}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "maker param", + "event": [ + { + "listen": "test", + "script": { + "id": "1f5960de-117f-44fb-82e0-581626cbf62b", + "type": "text/javascript", + "exec": [ + "const orderMakerEnvKey = 'orderMaker';", + "const referenceOrderMakerAddress = pm.environment.get(orderMakerEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedMakerAddress = _.get(order, 'maker');", + " pm.expect(referenceOrderMakerAddress).to.be.equal(returnedMakerAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?maker={{orderMaker}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "maker", + "value": "{{orderMaker}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "taker param", + "event": [ + { + "listen": "test", + "script": { + "id": "f23de2eb-b444-49d3-93b7-14ae712d6502", + "type": "text/javascript", + "exec": [ + "const orderTakerEnvKey = 'orderTaker';", + "const referenceOrderTakerAddress = pm.environment.get(orderTakerEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedTakerAddress = _.get(order, 'taker');", + " pm.expect(referenceOrderTakerAddress).to.be.equal(returnedTakerAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?taker={{orderTaker}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "taker", + "value": "{{orderTaker}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "trader param", + "event": [ + { + "listen": "test", + "script": { + "id": "9689639a-47ce-4c3b-8180-859fd28437be", + "type": "text/javascript", + "exec": [ + "const orderTraderEnvKey = 'orderMaker';", + "const referenceOrderTraderAddress = pm.environment.get(orderTraderEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedMakerAddress = _.get(order, 'maker');", + " const returnedTakerAddress = _.get(order, 'taker');", + " const condition = (referenceOrderTraderAddress === returnedMakerAddress) || (referenceOrderTraderAddress === returnedTakerAddress);", + " pm.expect(condition).to.be.true;", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?trader={{orderMaker}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "trader", + "value": "{{orderMaker}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + }, + { + "name": "feeRecipient param", + "event": [ + { + "listen": "test", + "script": { + "id": "010c0cf6-8d5b-4fe3-8b92-b2009ea43a3e", + "type": "text/javascript", + "exec": [ + "const orderFeeRecipientEnvKey = 'orderFeeRecipient';", + "const referenceOrderFeeRecipientAddress = pm.environment.get(orderFeeRecipientEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orders are properly filtered', function() {", + " _.forEach(responseJsonData, function(order) {", + " const returnedFeeRecipientAddress = _.get(order, 'feeRecipient');", + " pm.expect(referenceOrderFeeRecipientAddress).to.be.equal(returnedFeeRecipientAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/orders?feeRecipient={{orderFeeRecipient}}", + "host": ["{{url}}"], + "path": ["orders"], + "query": [ + { + "key": "feeRecipient", + "value": "{{orderFeeRecipient}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "4eafcc26-fb01-4182-b963-67a0b418fcbc", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "d28effd1-4a73-4ee8-82cc-21b1ab06928d", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/signedOrdersSchema');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "GET /order", + "description": "", + "item": [ + { + "name": "orderHash param", + "event": [ + { + "listen": "test", + "script": { + "id": "18876df2-384e-43d5-93a1-7e24571e1308", + "type": "text/javascript", + "exec": [ + "const orderEnvKey = 'order';", + "const referenceOrderString = pm.environment.get(orderEnvKey);", + "const referenceOrderJson = JSON.parse(referenceOrderString);", + "const orderPropertyNames = [", + " 'maker',", + " 'taker',", + " 'makerFee',", + " 'takerFee',", + " 'makerTokenAmount',", + " 'takerTokenAmount',", + " 'makerTokenAddress',", + " 'takerTokenAddress',", + " 'salt',", + " 'feeRecipient',", + " 'expirationUnixTimestampSec',", + " 'exchangeContractAddress'", + "];", + "const signaturePropertyNames = [", + " 'v',", + " 'r',", + " 's'", + "];", + "const returnedOrderJson = pm.response.json();", + "pm.test('Order is properly retreived', function() {", + " _.forEach(orderPropertyNames, function(propertyName) {", + " const returnedProperty = _.get(returnedOrderJson, propertyName);", + " const referenceProperty = _.get(referenceOrderJson, propertyName);", + " pm.expect(returnedProperty).to.be.equal(referenceProperty);", + " });", + " const returnedSignature = _.get(returnedOrderJson, 'ecSignature');", + " const referenceSignature = _.get(returnedOrderJson, 'ecSignature');", + " _.forEach(signaturePropertyNames, function(propertyName) {", + " const returnedSignatureProperty = _.get(returnedSignature, propertyName);", + " const referenceSignatureProperty = _.get(referenceSignature, propertyName);", + " pm.expect(returnedSignatureProperty).to.be.equal(referenceSignatureProperty);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{url}}/order/{{orderHash}}", + "host": ["{{url}}"], + "path": ["order", "{{orderHash}}"] + }, + "description": "Retrieves a specific order by orderHash." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "e356d2ff-d105-42c1-b679-d9d917dcd68d", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "8d2d4717-6f76-47ab-8e5a-f383192f6ee4", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/SignedOrder');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "GET /orderbook", + "description": "", + "item": [ + { + "name": "baseTokenAddress and quoteTokenAddress params", + "event": [ + { + "listen": "test", + "script": { + "id": "9ed05327-1a2f-4e50-b4aa-e21f961dbe78", + "type": "text/javascript", + "exec": [ + "const baseTokenEnvKey = 'tokenContractAddress2';", + "const quoteTokenEnvKey = 'tokenContractAddress1';", + "const baseTokenAddress = pm.environment.get(baseTokenEnvKey);", + "const quoteTokenAddress = pm.environment.get(quoteTokenEnvKey);", + "const responseJsonData = pm.response.json();", + "pm.test('Orderbook is properly filtered', function() {", + " const bids = _.get(responseJsonData, 'bids');", + " const asks = _.get(responseJsonData, 'asks');", + " _.forEach(bids, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(quoteTokenAddress);", + " pm.expect(takerTokenAddress).to.be.equal(baseTokenAddress);", + " });", + " _.forEach(asks, function(order) {", + " const makerTokenAddress = _.get(order, 'makerTokenAddress');", + " const takerTokenAddress = _.get(order, 'takerTokenAddress');", + " pm.expect(makerTokenAddress).to.be.equal(baseTokenAddress);", + " pm.expect(takerTokenAddress).to.be.equal(quoteTokenAddress);", + " });", + "});", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": + "{{url}}/orderbook?baseTokenAddress={{tokenContractAddress2}}"eTokenAddress={{tokenContractAddress1}}", + "host": ["{{url}}"], + "path": ["orderbook"], + "query": [ + { + "key": "baseTokenAddress", + "value": "{{tokenContractAddress2}}", + "equals": true + }, + { + "key": "quoteTokenAddress", + "value": "{{tokenContractAddress1}}", + "equals": true + } + ] + }, + "description": + "Retrieves a list of orders given query parameters. For querying an entire orderbook snapshot, the orderbook endpoint is recommended.\n\nParameters\n * exchangeContractAddress [string]: returns orders created for this exchange address\n * tokenAddress [string]: returns orders where makerTokenAddress or takerTokenAddress is token address\n * makerTokenAddress [string]: returns orders with specified makerTokenAddress\n * takerTokenAddress [string]: returns orders with specified makerTokenAddress\n * maker [string]: returns orders where maker is maker address\n * taker [string]: returns orders where taker is taker address\n * trader [string]: returns orders where maker or taker is trader address\n * feeRecipient [string]: returns orders where feeRecipient is feeRecipient address\n\nAll parameters are optional." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "bdf90dbc-9217-4089-8bc0-351baadddd3e", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "d5080a34-57c4-4d5d-8e01-5e79599282ec", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiOrderBookResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + }, + { + "name": "POST /fees", + "description": "", + "item": [ + { + "name": "default request", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": + "{\n \"exchangeContractAddress\": \"0x12459c951127e0c374ff9105dda097662a027093\",\n \"maker\": \"0x9e56625509c2f60af937f23b7b532600390e8c8b\",\n \"taker\": \"0x0000000000000000000000000000000000000000\",\n \"makerTokenAddress\": \"0x323b5d4c32345ced77393b3530b1eed0f346429d\",\n \"takerTokenAddress\": \"0xef7fff64389b814a946f3e92105513705ca6b990\",\n \"makerTokenAmount\": \"10000000000000000\",\n \"takerTokenAmount\": \"20000000000000000\",\n \"expirationUnixTimestampSec\": \"42\",\n \"salt\": \"67006738228878699843088602623665307406148487219438534730168799356281242528500\"\n}" + }, + "url": { + "raw": "{{url}}/fees", + "host": ["{{url}}"], + "path": ["fees"] + }, + "description": + "Given an unsigned order without the fee-related properties, returns the required feeRecipient, makerFee, and takerFee of that order." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "75d66506-0fa9-4b0e-982b-ef53bf3310f8", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "4c5f9f7b-8635-4bdb-9240-a74754a2de4f", + "type": "text/javascript", + "exec": [ + "const schema = tv4.getSchema('/RelayerApiFeesResponse');", + "const responseJsonData = pm.response.json();", + "", + "pm.test('Schema is valid', function() {", + " pm.expect(tv4.validate(responseJsonData, schema)).to.be.true;", + "});", + "" + ] + } + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "da60f639-df79-4f4d-9861-79219f5fc341", + "type": "text/javascript", + "exec": [ + "const schemaKeysString = pm.environment.get('schemaKeys');", + "const schemaKeys = JSON.parse(schemaKeysString);", + "_.forEach(schemaKeys, function(schemaKey) {", + " const schemaString = pm.environment.get(schemaKey);", + " const schema = JSON.parse(schemaString);", + " tv4.addSchema(schema);", + "});", + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "b4917e72-ac87-421d-b7a5-21b64285ba5b", + "type": "text/javascript", + "exec": [ + "pm.test('Has Content-Type header with value application/json', function () {", + " pm.response.to.have.header('Content-Type');", + " const contentType = postman.getResponseHeader('Content-Type');", + " pm.expect(contentType).to.include('application/json');", + "});", + "" + ] + } + } + ] +} diff --git a/packages/sra-report/scripts/postpublish.js b/packages/sra-report/scripts/postpublish.js new file mode 100644 index 000000000..639656c7e --- /dev/null +++ b/packages/sra-report/scripts/postpublish.js @@ -0,0 +1,5 @@ +const postpublish_utils = require('../../../scripts/postpublish_utils'); +const packageJSON = require('../package.json'); + +const subPackageName = packageJSON.name; +postpublish_utils.standardPostPublishAsync(subPackageName); diff --git a/packages/sra-report/src/contract_addresses/kovan_addresses.ts b/packages/sra-report/src/contract_addresses/kovan_addresses.ts new file mode 100644 index 000000000..e06568f52 --- /dev/null +++ b/packages/sra-report/src/contract_addresses/kovan_addresses.ts @@ -0,0 +1,5 @@ +export const addresses = { + WETH: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', + ZRX: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + EXCHANGE: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', +}; diff --git a/packages/sra-report/src/contract_addresses/mainnet_addresses.ts b/packages/sra-report/src/contract_addresses/mainnet_addresses.ts new file mode 100644 index 000000000..e9aa0f167 --- /dev/null +++ b/packages/sra-report/src/contract_addresses/mainnet_addresses.ts @@ -0,0 +1,5 @@ +export const addresses = { + WETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + ZRX: '0xe41d2489571d322189246dafa5ebde1f4699f498', + EXCHANGE: '0x12459c951127e0c374ff9105dda097662a027093', +}; diff --git a/packages/sra-report/src/contract_addresses/rinkeby_addresses.ts b/packages/sra-report/src/contract_addresses/rinkeby_addresses.ts new file mode 100644 index 000000000..b1e0848d0 --- /dev/null +++ b/packages/sra-report/src/contract_addresses/rinkeby_addresses.ts @@ -0,0 +1,5 @@ +export const addresses = { + WETH: '0xc778417e063141139fce010982780140aa0cd5ab', + ZRX: '0x00f58d6d585f84b2d7267940cede30ce2fe6eae8', + EXCHANGE: '0x1d16ef40fac01cec8adac2ac49427b9384192c05', +}; diff --git a/packages/sra-report/src/contract_addresses/ropsten_addresses.ts b/packages/sra-report/src/contract_addresses/ropsten_addresses.ts new file mode 100644 index 000000000..80e6e5e7e --- /dev/null +++ b/packages/sra-report/src/contract_addresses/ropsten_addresses.ts @@ -0,0 +1,5 @@ +export const addresses = { + WETH: '0xc778417e063141139fce010982780140aa0cd5ab', + ZRX: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', + EXCHANGE: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', +}; diff --git a/packages/sra-report/src/globals.d.ts b/packages/sra-report/src/globals.d.ts new file mode 100644 index 000000000..0d3beb446 --- /dev/null +++ b/packages/sra-report/src/globals.d.ts @@ -0,0 +1,9 @@ +declare module 'newman' { + // tslint:disable-next-line:completed-docs + export function run(options: any, callback?: () => void): void; +} + +declare module '*.json' { + const value: any; + export default value; +} diff --git a/packages/sra-report/src/index.ts b/packages/sra-report/src/index.ts new file mode 100644 index 000000000..d23549916 --- /dev/null +++ b/packages/sra-report/src/index.ts @@ -0,0 +1,102 @@ +#!/usr/bin/env node +import { assert } from '@0xproject/assert'; +import { Schema, schemas } from '@0xproject/json-schemas'; +import { promisify } from '@0xproject/utils'; +import chalk from 'chalk'; +import * as _ from 'lodash'; +import * as newman from 'newman'; +import * as yargs from 'yargs'; + +import * as sraReportCollectionJSON from '../postman_configs/collections/sra_report.postman_collection.json'; + +import { postmanEnvironmentFactory } from './postman_environment_factory'; +import { utils } from './utils'; + +const newmanRunAsync = promisify<void>(newman.run); +const DEFAULT_NETWORK_ID = 1; +const SUPPORTED_NETWORK_IDS = [1, 3, 4, 42]; + +// extract command line arguments +const args = yargs + .option('endpoint-url', { + alias: ['e'], + describe: 'API endpoint url to test for standard relayer API compliance', + type: 'string', + demandOption: true, + }) + .option('output', { + alias: ['o', 'out'], + describe: 'The relative path to write the report generated by the collection run, prints to console by default', + type: 'string', + normalize: true, + demandOption: false, + }) + .option('network-id', { + alias: ['n'], + describe: 'ID of the network that the API is serving orders from', + type: 'number', + default: DEFAULT_NETWORK_ID, + }) + .option('environment', { + alias: ['env'], + describe: 'The relative path to a postman environment file for the collection run', + type: 'string', + normalize: true, + demandOption: false, + }) + .option('export-collection', { + alias: ['ec'], + describe: 'The relative path to write the postman collection file used by the collection run', + type: 'string', + normalize: true, + demandOption: false, + }) + .option('export-environment', { + alias: ['ee'], + describe: 'The relative path to write the postman environment file used by the collection run', + type: 'string', + normalize: true, + demandOption: false, + }) + .example( + "$0 --endpoint-url 'http://api.example.com' --out 'path/to/report.json' --network-id 42 --environment 'path/to/custom/environment.json' --export-collection 'path/to/collection.json' --export-environment 'path/to/environment.json'", + 'Full usage example', + ).argv; +// perform extra validation on command line arguments +try { + assert.isWebUri('args', args.endpointUrl); +} catch (err) { + utils.log(`${chalk.red(`Invalid url format:`)} ${args.endpointUrl}`); + process.exit(1); +} +if (!_.includes(SUPPORTED_NETWORK_IDS, args.networkId)) { + utils.log(`${chalk.red(`Unsupported network id:`)} ${args.networkId}`); + utils.log(`${chalk.bold(`Supported network ids:`)} ${SUPPORTED_NETWORK_IDS}`); + process.exit(1); +} +const mainAsync = async () => { + const newmanReporterOptions = !_.isUndefined(args.output) + ? { + reporters: 'json', + reporter: { + json: { + export: args.output, + }, + }, + } + : { + reporters: 'cli', + }; + const environment = !_.isUndefined(args.environment) + ? args.environment + : await postmanEnvironmentFactory.createPostmanEnvironmentAsync(args.endpointUrl, args.networkId); + const newmanRunOptions = { + collection: sraReportCollectionJSON, + environment, + exportCollection: args.exportCollection, + exportEnvironment: args.exportEnvironment, + ...newmanReporterOptions, + }; + await newmanRunAsync(newmanRunOptions); +}; +mainAsync().catch(utils.log); diff --git a/packages/sra-report/src/postman_environment_factory.ts b/packages/sra-report/src/postman_environment_factory.ts new file mode 100644 index 000000000..ffac7ac2e --- /dev/null +++ b/packages/sra-report/src/postman_environment_factory.ts @@ -0,0 +1,123 @@ +import { SignedOrder, ZeroEx } from '0x.js'; +import { HttpClient } from '@0xproject/connect'; +import { Schema, schemas as schemasByName } from '@0xproject/json-schemas'; +import chalk from 'chalk'; +import * as _ from 'lodash'; + +import { addresses as kovanAddresses } from './contract_addresses/kovan_addresses'; +import { addresses as mainnetAddresses } from './contract_addresses/mainnet_addresses'; +import { addresses as rinkebyAddresses } from './contract_addresses/rinkeby_addresses'; +import { addresses as ropstenAddresses } from './contract_addresses/ropsten_addresses'; +import { utils } from './utils'; + +const ENVIRONMENT_NAME = 'SRA Report'; + +interface EnvironmentValue { + key: string; +} + +export const postmanEnvironmentFactory = { + /** + * Dynamically generates a postman environment (https://www.getpostman.com/docs/v6/postman/environments_and_globals/manage_environments) + * When running the postman collection via newman, we provide it a set of environment variables + * These variables include: + * - 0x JSON schemas for response body validation + * - Contract addresses based on the network id for making specific queries (ex. baseTokenAddress=ZRX_address) + * - Order properties for making specific queries (ex. maker=orderMaker) + */ + async createPostmanEnvironmentAsync(url: string, networkId: number) { + const orderEnvironmentValues = await createOrderEnvironmentValuesAsync(url); + const allEnvironmentValues = _.concat( + createSchemaEnvironmentValues(), + createContractAddressEnvironmentValues(networkId), + orderEnvironmentValues, + createEnvironmentValue('url', url), + ); + const environment = { + name: ENVIRONMENT_NAME, + values: allEnvironmentValues, + }; + return environment; + }, +}; +function createSchemaEnvironmentValues() { + const schemas: Schema[] = _.values(schemasByName); + const schemaEnvironmentValues = _.compact( + _.map(schemas, (schema: Schema) => { + if (_.isUndefined(schema.id)) { + return undefined; + } else { + const schemaKey = convertSchemaIdToKey(schema.id); + const stringifiedSchema = JSON.stringify(schema); + const schemaEnvironmentValue = createEnvironmentValue(schemaKey, stringifiedSchema); + return schemaEnvironmentValue; + } + }), + ); + const schemaKeys = _.map(schemaEnvironmentValues, (environmentValue: EnvironmentValue) => { + return environmentValue.key; + }); + const result = _.concat(schemaEnvironmentValues, createEnvironmentValue('schemaKeys', JSON.stringify(schemaKeys))); + return result; +} +function createContractAddressEnvironmentValues(networkId: number) { + const contractAddresses = getContractAddresses(networkId); + return [ + createEnvironmentValue('tokenContractAddress1', contractAddresses.WETH), + createEnvironmentValue('tokenContractAddress2', contractAddresses.ZRX), + createEnvironmentValue('exchangeContractAddress', contractAddresses.EXCHANGE), + ]; +} +async function createOrderEnvironmentValuesAsync(url: string) { + const httpClient = new HttpClient(url); + const orders = await httpClient.getOrdersAsync(); + const orderIfExists = _.head(orders); + if (!_.isUndefined(orderIfExists)) { + return [ + createEnvironmentValue('order', JSON.stringify(orderIfExists)), + createEnvironmentValue('orderMaker', orderIfExists.maker), + createEnvironmentValue('orderTaker', orderIfExists.taker), + createEnvironmentValue('orderFeeRecipient', orderIfExists.feeRecipient), + createEnvironmentValue('orderHash', ZeroEx.getOrderHashHex(orderIfExists)), + ]; + } else { + utils.log(`${chalk.red(`No orders from /orders found`)}`); + return [ + createEnvironmentValue('order', ''), + createEnvironmentValue('orderMaker', ''), + createEnvironmentValue('orderTaker', ''), + createEnvironmentValue('orderFeeRecipient', ''), + createEnvironmentValue('orderHash', ''), + ]; + } +} +function getContractAddresses(networkId: number) { + switch (networkId) { + case 1: + return mainnetAddresses; + case 3: + return ropstenAddresses; + case 4: + return rinkebyAddresses; + case 42: + return kovanAddresses; + default: + throw new Error('Unsupported network id'); + } +} +function convertSchemaIdToKey(schemaId: string) { + let result = schemaId; + if (_.startsWith(result, '/')) { + result = result.substr(1); + } + result = `${result}Schema`; + return result; +} +function createEnvironmentValue(key: string, value: string) { + return { + key, + value, + enabled: true, + type: 'text', + }; +} diff --git a/packages/sra-report/src/utils.ts b/packages/sra-report/src/utils.ts new file mode 100644 index 000000000..5423cabd9 --- /dev/null +++ b/packages/sra-report/src/utils.ts @@ -0,0 +1,5 @@ +export const utils = { + log(...args: any[]): void { + console.log(...args); // tslint:disable-line:no-console + }, +}; diff --git a/packages/sra-report/tsconfig.json b/packages/sra-report/tsconfig.json new file mode 100644 index 000000000..8114d99cd --- /dev/null +++ b/packages/sra-report/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": [ + "./src/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/ethers-typescript-typings/index.d.ts" + ] +} diff --git a/packages/sra-report/tslint.json b/packages/sra-report/tslint.json new file mode 100644 index 000000000..ffaefe83a --- /dev/null +++ b/packages/sra-report/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0xproject/tslint-config"] +} |