Skip to content

Commit

Permalink
Abstract error handling for addresses into AbstractService (#842)
Browse files Browse the repository at this point in the history
* Abstract error handling for addresses into AbstractService

* Add yarn cache, jest cov and fixed unit test
  • Loading branch information
0xslipk authored Feb 16, 2022
1 parent 84a6614 commit 88e176b
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 58 deletions.
53 changes: 31 additions & 22 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,37 @@ on:
- master

jobs:
lint:
check:
# The type of runner that the job will run on
runs-on: ubuntu-latest
steps:
- name: Install wasm-pack
uses: jetli/wasm-pack-action@v0.3.0
with:
version: v0.10.0
- uses: actions/checkout@v2
- name: Lint
run: |
yarn install
yarn lint

test:
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Install wasm-pack
uses: jetli/wasm-pack-action@v0.3.0
with:
version: v0.10.0
- uses: actions/checkout@v2
- name: Test
run: |
yarn install
yarn test
- name: Checkout files
uses: actions/checkout@v2

- name: Install Node v16
uses: actions/setup-node@v2
with:
node-version: '16.13'

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"

- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install JS dependencies
run: yarn install

- name: Linter.
run: yarn lint:ci

- name: Unit tests Cov.
run: yarn test:cov
21 changes: 20 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/.vscode
**/node_modules
/build
/e2e-tests/build
Expand All @@ -17,3 +16,23 @@ yarn-error.log
!.yarn/releases
!.yarn/plugins
/docs/.yarn

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
2 changes: 1 addition & 1 deletion docs/dist/app.bundle.js

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions docs/src/openapi-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/AccountAssetsBalances'
"400":
description: Invalid Address
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/accounts/{accountId}/asset-approvals:
get:
tags:
Expand Down Expand Up @@ -115,6 +121,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/AccountAssetsApproval'
"400":
description: Invalid Address
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/accounts/{accountId}/balance-info:
get:
tags:
Expand Down Expand Up @@ -158,7 +170,7 @@ paths:
schema:
$ref: '#/components/schemas/AccountBalanceInfo'
"400":
description: invalid blockId supplied for at query param
description: Invalid Address
content:
application/json:
schema:
Expand Down Expand Up @@ -313,7 +325,7 @@ paths:
schema:
$ref: '#/components/schemas/AccountVestingInfo'
"400":
description: invalid blockId supplied for at query param
description: Invalid Address
content:
application/json:
schema:
Expand Down
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,15 @@ const base = require('@substrate/dev/config/jest')

module.exports = {
...base,
verbose: true,
coverageThreshold: {
global: {
branches: 50,
functions: 70,
lines: 70,
statements: 85
}
},
testEnvironment: 'node',
testPathIgnorePatterns: ['/build/', '/node_modules/', '/docs/', '/e2e-tests/'],
};
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@
"build:docker": "docker build -t substrate-api-sidecar .",
"build:docs": "(cd docs && yarn && yarn build)",
"main": "node ./build/src/main.js",
"lint": "substrate-dev-run-lint",
"lint": "substrate-dev-run-lint --fix",
"lint:ci": "substrate-dev-run-lint",
"deploy": "yarn build && npm publish",
"start": "yarn run main",
"start:log-rpc": "yarn run build && NODE_ENV=test yarn run main ",
"dev": "tsc-watch --onSuccess \"yarn run main\"",
"test": "substrate-exec-jest --silent",
"test": "substrate-exec-jest",
"test:watch": "substrate-exec-jest --watch",
"test:cov": "substrate-exec-jest --coverage",
"lint:e2e-tests": "cd e2e-tests && substrate-dev-run-lint",
"build:e2e-tests": "(cd e2e-tests && substrate-exec-tsc)",
"test:e2e-tests": "yarn build:e2e-tests && node ./e2e-tests/build/index.js --config=./e2e-tests/jest.config.js",
Expand Down
62 changes: 62 additions & 0 deletions src/services/AbstractService.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ApiPromise } from '@polkadot/api';
import { Hash } from '@polkadot/types/interfaces';
import { BadRequest, HttpError } from 'http-errors';

import {
AbstractService,
EtheuremAddressNotSupported,
} from './AbstractService';
import { defaultMockApi } from './test-helpers/mock';

class TestAbstractService extends AbstractService {
public handleEtheuremAddressError(): HttpError {
return this.createHttpErrorForAddr(
'0x0000000000000000000000000000000000000000',
new Error('Ups! something is wrong.')
);
}

public handlePolkadotAddressError(): HttpError {
return this.createHttpErrorForAddr(
'EQBwtmKWCyRrQ8yGWg7LkB8p7hpEKXZz4qUg9WR8hZmieCM',
new Error('Ups! something is wrong.')
);
}
}

const mockApi = {
...defaultMockApi,
at: (_hash: Hash) => ({}),
} as unknown as ApiPromise;

const testService = new TestAbstractService(mockApi);

describe('AbstractService', () => {
describe('createHttpErrorForAddr', () => {
it('should throws an error instanceof EtheuremAddressNotSupported', () => {
const err = testService.handleEtheuremAddressError();

expect(err.expose).toBeTruthy();
expect(err.headers).toBeUndefined();
expect(err.message).not.toBeUndefined();
expect(err.name).toEqual(EtheuremAddressNotSupported.name);
expect(err.stack).not.toBeUndefined();
expect(err.status).toEqual(400);
expect(err.statusCode).toEqual(400);
expect(err).toBeInstanceOf(EtheuremAddressNotSupported);
});

it('should throws an error instanceof BadRequest', () => {
const err = testService.handlePolkadotAddressError();

expect(err.expose).toBeTruthy();
expect(err.headers).toBeUndefined();
expect(err.message).not.toBeUndefined();
expect(err.name).toEqual(BadRequest.name);
expect(err.stack).not.toBeUndefined();
expect(err.status).toEqual(400);
expect(err.statusCode).toEqual(400);
expect(err).toBeInstanceOf(BadRequest);
});
});
});
32 changes: 32 additions & 0 deletions src/services/AbstractService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { ApiPromise } from '@polkadot/api';
import { Text, Vec } from '@polkadot/types';
import { isEthereumAddress } from '@polkadot/util-crypto';
import { BadRequest, HttpError } from 'http-errors';

export class EtheuremAddressNotSupported extends Error implements HttpError {
public readonly status: number;
public readonly statusCode: number;
public readonly expose: boolean = true;
public readonly name: string;

constructor(msg: string) {
super(`Etheurem addresses may not be supported on this network: ${msg}`);
this.status = 400;
this.statusCode = 400;
this.name = EtheuremAddressNotSupported.name;
}
}

export abstract class AbstractService {
constructor(protected api: ApiPromise) {}
Expand All @@ -16,4 +32,20 @@ export abstract class AbstractService {
)
.join('');
}

/**
* Returns HttpError with the correct err message for querying accounts balances.
*
* @function
* @param {string} address Address that was queried
* @param {Error} err Error returned from the promise
* @returns {HttpError}
*/
protected createHttpErrorForAddr(address: string, err: Error): HttpError {
if (isEthereumAddress(address)) {
return new EtheuremAddressNotSupported(err.message);
}

return new BadRequest(err.message);
}
}
8 changes: 6 additions & 2 deletions src/services/accounts/AccountsAssetsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ export class AccountsAssetsService extends AbstractService {
const [{ number }, assetApproval] = await Promise.all([
api.rpc.chain.getHeader(hash),
historicApi.query.assets.approvals(assetId, address, delegate),
]);
]).catch((err: Error) => {
throw this.createHttpErrorForAddr(address, err);
});

let amount = null,
deposit = null;
Expand Down Expand Up @@ -211,7 +213,9 @@ export class AccountsAssetsService extends AbstractService {
isSufficient: historicApi.registry.createType('bool', false),
};
})
);
).catch((err: Error) => {
throw this.createHttpErrorForAddr(address, err);
});
}

/**
Expand Down
23 changes: 4 additions & 19 deletions src/services/accounts/AccountsBalanceInfoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import {
BlockHash,
Index,
} from '@polkadot/types/interfaces';
import { isEthereumAddress } from '@polkadot/util-crypto';
import { BadRequest, HttpError } from 'http-errors';
import { BadRequest } from 'http-errors';
import { IAccountBalanceInfo } from 'src/types/responses';

import { AbstractService } from '../AbstractService';
Expand Down Expand Up @@ -47,7 +46,7 @@ export class AccountsBalanceInfoService extends AbstractService {
historicApi.query.balances.reservedBalance(address) as Promise<Balance>,
historicApi.query.system.accountNonce(address) as Promise<Index>,
]).catch((err: Error) => {
throw this.createHttpError(address, err);
throw this.createHttpErrorForAddr(address, err);
});

// Values dont exist for these historic runtimes
Expand Down Expand Up @@ -79,7 +78,7 @@ export class AccountsBalanceInfoService extends AbstractService {
historicApi.query.system.account(address),
historicApi.query.balances.locks(address),
]).catch((err: Error) => {
throw this.createHttpError(address, err);
throw this.createHttpErrorForAddr(address, err);
});

const {
Expand Down Expand Up @@ -118,7 +117,7 @@ export class AccountsBalanceInfoService extends AbstractService {
historicApi.query.balances.locks(address),
historicApi.query.system.account(address),
]).catch((err: Error) => {
throw this.createHttpError(address, err);
throw this.createHttpErrorForAddr(address, err);
});

accountData =
Expand Down Expand Up @@ -172,18 +171,4 @@ export class AccountsBalanceInfoService extends AbstractService {
throw new BadRequest('Account not found');
}
}

/**
* Returns HttpError with the correct err message for querying accounts balances.
*
* @param address Address that was queried
* @param err Error returned from the promise
*/
private createHttpError(address: string, err: Error): HttpError {
return isEthereumAddress(address)
? new BadRequest(
`Etheurem addresses may not be supported on this network: ${err.message}`
)
: new BadRequest(err.message);
}
}
8 changes: 6 additions & 2 deletions src/services/accounts/AccountsStakingInfoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export class AccountsStakingInfoService extends AbstractService {
const [header, controllerOption] = await Promise.all([
api.rpc.chain.getHeader(hash),
historicApi.query.staking.bonded(stash), // Option<AccountId> representing the controller
]);
]).catch((err: Error) => {
throw this.createHttpErrorForAddr(stash, err);
});

const at = {
hash,
Expand All @@ -39,7 +41,9 @@ export class AccountsStakingInfoService extends AbstractService {
historicApi.query.staking.ledger(controller),
historicApi.query.staking.payee(stash),
historicApi.query.staking.slashingSpans(stash),
]);
]).catch((err: Error) => {
throw this.createHttpErrorForAddr(stash, err);
});

const stakingLedger = stakingLedgerOption.unwrapOr(null);

Expand Down
4 changes: 3 additions & 1 deletion src/services/accounts/AccountsStakingPayoutsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ export class AccountsStakingPayoutsService extends AbstractService {
startEra,
// Create an array of `DeriveEraExposure`
allErasGeneral.map((eraGeneral) => eraGeneral[0])
);
).catch((err: Error) => {
throw this.createHttpErrorForAddr(address, err);
});

// Group together data by Era so we can easily associate parts that are used congruently downstream
const allEraData = allErasGeneral.map(
Expand Down
4 changes: 3 additions & 1 deletion src/services/accounts/AccountsVestingInfoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ export class AccountsVestingInfoService extends AbstractService {
const [{ number }, vesting] = await Promise.all([
api.rpc.chain.getHeader(hash),
historicApi.query.vesting.vesting(address),
]);
]).catch((err: Error) => {
throw this.createHttpErrorForAddr(address, err);
});

const at = {
hash,
Expand Down
Loading

0 comments on commit 88e176b

Please sign in to comment.