Skip to content
This repository has been archived by the owner on May 7, 2024. It is now read-only.

Commit

Permalink
112 develop suitable unit testing approach (#117)
Browse files Browse the repository at this point in the history
* test: fundamental approach

* test: poolHandlers testing

* test: run pools and tranche tests in CI

* test: include generation of entities

* ci: execute testing in correct order
  • Loading branch information
filo87 authored Oct 25, 2022
1 parent 9d91d2c commit 4dfb56a
Show file tree
Hide file tree
Showing 16 changed files with 3,456 additions and 507 deletions.
5 changes: 5 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export DB_USER=postgres
export DB_PASS=postgres
export DB_DATABASE=postgres
export DB_HOST=localhost
export DB_PORT=5432
18 changes: 18 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,23 @@ jobs:
node-version: '16'
- name: 'Install Dependencies'
run: yarn install
- name: 'Generate Entities'
run: yarn codegen
- name: 'Run Linter'
run: yarn lint
test:
name: 'Run Tests'
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: 'Setup Node'
uses: actions/setup-node@v3
with:
node-version: '16'
- name: 'Install Dependencies'
run: yarn install
- name: 'Generate Entities'
run: yarn codegen
- name: 'Run Tests'
run: yarn test
21 changes: 21 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Jest Tests",
"type": "node",
"request": "launch",
"runtimeArgs": ["--inspect-brk", "${workspaceRoot}/node_modules/.bin/jest", "--runInBand"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"name": "Debug SubQL Node",
"type": "node",
"request": "launch",
"runtimeArgs": ["--inspect-brk", "${workspaceRoot}/node_modules/.bin/subql-node", "-f", "${workspaceRoot}"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
20 changes: 20 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Config } from 'jest'

const config: Config = {
verbose: true,
preset: 'ts-jest',
testEnvironment: 'node',
setupFiles: ['./jest/globals.ts'],
transform: {
// '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
// '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
'^.+\\.ts?$': [
'ts-jest',
{
tsconfig: './jest/tsconfig.jest.json',
},
],
},
}

export default config
4 changes: 4 additions & 0 deletions jest/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare const api: any
declare const logger: any
declare const store: any
26 changes: 26 additions & 0 deletions jest/globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import { jest } from '@jest/globals'

const globalHere: any = global

globalHere.store = {
get: jest.fn(),
getByField: jest.fn(),
getOneByField: jest.fn(),
set: jest.fn((...args) => args[2]),
bulkCreate: jest.fn(),
bulkUpdate: jest.fn(),
remove: jest.fn(),
}

globalHere.logger = {
fatal: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
trace: jest.fn(),
}

globalHere.api = { query: {}, rpc: {} }
21 changes: 21 additions & 0 deletions jest/tsconfig.jest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"declaration": true,
"importHelpers": true,
"resolveJsonModule": true,
"module": "commonjs",
"outDir": "dist",
"rootDir": "src",
"target": "es2017",
"paths": {
"centrifuge-subql/*": ["./src/*"]
}
},
"include": ["./src/**/*", "./jest/*.ts"],
"exports": {
"chaintypes": "./src/chaintypes.ts"
}
}
15 changes: 10 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,29 @@
"license": "MIT",
"dependencies": {},
"devDependencies": {
"node-fetch": "2.6.7",
"@commitlint/cli": "^17.0.2",
"@commitlint/config-conventional": "^17.0.2",
"@polkadot/api": "^9",
"@polkadot/typegen": "^9",
"@polkadot/types": "^9",
"@polkadot/util": "^9",
"@subql/cli": "latest",
"@subql/types": "latest",
"@subql/node": "1.9.2",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"husky": "^7.0.0",
"eslint": "^8.17.0",
"lint-staged": "^13.0.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^7.0.0",
"lint-staged": "^13.0.1",
"jest": "^29.2.0",
"@types/jest": "^29.1.2",
"@jest/globals": "^29.2.0",
"prettier": "^2.7.0",
"prettier-eslint": "^15.0.1",
"ts-jest": "^29.0.3",
"ts-node": "^8.6.2",
"typescript": "^4.4.4"
"typescript": "^4.1.3"
},
"lint-staged": {
"src/**/*.{js,ts}": [
Expand Down
14 changes: 8 additions & 6 deletions src/helpers/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//find out types: const a = createType(api.registry, '[u8;32]', 18)
import { AugmentedRpc, PromiseRpcResult } from '@polkadot/api/types'
import { Enum, Null, Struct, u128, u32, u64, U8aFixed, Option, Vec, Bytes } from '@polkadot/types'
import { AccountId32, Perquintill } from '@polkadot/types/interfaces'
import { ITuple, Observable } from '@polkadot/types/types'
import { AccountId32, Address, Perquintill } from '@polkadot/types/interfaces'
import { AnyTuple, ITuple, Observable } from '@polkadot/types/types'

export interface PoolDetails extends Struct {
reserve: { total: u128; available: u128; max: u128 }
Expand Down Expand Up @@ -145,10 +145,10 @@ export interface AccountData extends Struct {
frozen: u128
}

export type LoanAsset = ITuple<[u64, u128]>
export type LoanAsset = ITuple<[u64, u128]> & AnyTuple

// poolId
export type PoolCreatedUpdatedEvent = ITuple<[u64]>
export type PoolCreatedUpdatedEvent = ITuple<[u64, Address]>

// poolId, loanId, collateral
export type LoanCreatedClosedEvent = ITuple<[u64, u128, LoanAsset]>
Expand All @@ -175,7 +175,9 @@ export type TokensEndowedDepositedWithdrawnEvent = ITuple<[TokensCurrencyId, Acc

export type ExtendedRpc = typeof api.rpc & {
pools: {
trancheTokenPrice: PromiseRpcResult<AugmentedRpc<(poolId: u64, trancheId: number[]) => Observable<u128>>>
trancheTokenPrices: PromiseRpcResult<AugmentedRpc<(poolId: u64) => Observable<Vec<u128>>>>
trancheTokenPrice: PromiseRpcResult<
AugmentedRpc<(poolId: number | string, trancheId: number[]) => Observable<u128>>
>
trancheTokenPrices: PromiseRpcResult<AugmentedRpc<(poolId: number | string) => Observable<Vec<u128>>>>
}
}
10 changes: 10 additions & 0 deletions src/mappings/services/accountService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { AccountService } from './accountService'

test('Account is created in database', async () => {
const id = 'ABCDE'
const account = AccountService.init(id)
await account.save()

expect(logger.info).toHaveBeenCalled()
expect(store.set).toHaveBeenCalledWith('Account', id, { id })
})
23 changes: 23 additions & 0 deletions src/mappings/services/currencyService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CurrencyService } from './currencyService'

const entityName = 'Currency'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
api.query['ormlAssetRegistry'] = { metadata: jest.fn(() => ({ isSome: false })) } as any

const usdDecimals = 12
const stdDecimals = 18

test('AUSD is initialised with 12 decimals', async () => {
const ticker = 'AUSD'
await CurrencyService.getOrInit(ticker)

expect(store.set).toBeCalledWith(entityName, ticker, { id: ticker, decimals: usdDecimals })
})

test('ACA is initialised with 18 decimals', async () => {
const ticker = 'ACA'
await CurrencyService.getOrInit(ticker)

expect(store.set).toBeCalledWith(entityName, ticker, { id: ticker, decimals: stdDecimals })
})
110 changes: 110 additions & 0 deletions src/mappings/services/poolService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { PoolService } from './poolService'

api.query['pools'] = {
pool: jest.fn(() => ({
isSome: true,
isNone: false,
toHuman: jest.fn(),
unwrap: () => ({
currency: 'AUSD',
metadata: { isSome: true, unwrap: () => ({ toUtf8: () => 'AAA' }) },
reserve: {
total: { toBigInt: () => BigInt(91000000000000) },
available: { toBigInt: () => BigInt(92000000000000) },
max: { toBigInt: () => BigInt(192000000000000) },
},
parameters: {
minEpochTime: { toNumber: () => 500 },
maxNavAge: { toNumber: () => 500 },
},
}),
})),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any

api.query['loans'] = {
poolNAV: jest.fn(() => ({
isSome: true,
unwrap: jest.fn(() => ({
latest: {
toBigInt: () => BigInt(100000000000000),
},
})),
})),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any

const poolId = '4355663',
now = new Date(),
block = 235443

const pool = PoolService.init(poolId, now, block)

describe('Given a new pool, when initialised', () => {
test('then type is set to "ALL"', () => {
expect(pool.pool.type).toBe('ALL')
})

test('then "created at" property is set correctly', () => {
expect(pool.pool.createdAt).toBe(now)
})

test('then "created at block" property is set correctly', () => {
expect(pool.pool.createdAtBlockNumber).toBe(block)
})

test('then reset accumulators are set to 0', () => {
const resetAccumulators = Object.getOwnPropertyNames(pool.poolState).filter((prop) => prop.endsWith('_'))
for (const resetAccumulator of resetAccumulators) {
expect(pool.poolState[resetAccumulator]).toBe(BigInt(0))
}
})

test('when the pool data is initialised, then the correct values are fetched and set', async () => {
await pool.initData(async () => 'AUSD')
expect(api.query.pools.pool).toBeCalledWith(poolId)
expect(pool.pool).toMatchObject({ currencyId: 'AUSD', metadata: 'AAA', minEpochTime: 500, maxNavAge: 500 })
})

test('then it can be saved to the database', async () => {
await pool.save()
expect(store.set).toHaveBeenNthCalledWith(1, 'PoolState', poolId, expect.anything())
expect(store.set).toHaveBeenNthCalledWith(2, 'Pool', poolId, expect.anything())
})
})

describe('Given an existing pool,', () => {
test('when the nav is updated, then the value is fetched and set correctly', async () => {
await pool.updateNav()
expect(api.query.loans.poolNAV).toBeCalled()
expect(pool.poolState.netAssetValue).toBe(BigInt(100000000000000))
})

test('when the pool state is updated, then the values are fetched and set correctly', async () => {
await pool.updateState()
expect(api.query.pools.pool).toBeCalledWith(poolId)
expect(pool.poolState).toMatchObject({
totalReserve: BigInt(91000000000000),
availableReserve: BigInt(92000000000000),
maxReserve: BigInt(192000000000000),
})
})

test('when total borrowings are registered, then values are incremented correctly', async () => {
await pool.increaseTotalBorrowings(BigInt('34999000000000000'))
expect(pool.poolState).toMatchObject({
totalBorrowed_: BigInt('34999000000000000'),
totalEverBorrowed: BigInt('34999000000000000'),
totalNumberOfLoans_: BigInt(1),
totalEverNumberOfLoans: BigInt(1),
})
})

test('when an epoch is closed, then the corresponding current epoch and last counters are increased', async () => {
await pool.closeEpoch(1)
expect(pool.pool).toMatchObject({
currentEpoch: 2,
lastEpochClosed: 1,
})
})
})
12 changes: 6 additions & 6 deletions src/mappings/services/poolService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Option, u128, u64, Vec } from '@polkadot/types'
import { Option, u128, Vec } from '@polkadot/types'
import { bnToBn, nToBigInt } from '@polkadot/util'
import { paginatedGetter } from '../../helpers/paginatedGetter'
import { errorHandler } from '../../helpers/errorHandler'
Expand Down Expand Up @@ -112,10 +112,10 @@ export class PoolService {
}

public increaseTotalBorrowings = (borrowedAmount: bigint) => {
this.poolState.totalBorrowed_ = this.poolState.totalBorrowed_ + borrowedAmount
this.poolState.totalEverBorrowed = this.poolState.totalEverBorrowed + borrowedAmount
this.poolState.totalNumberOfLoans_ = this.poolState.totalNumberOfLoans_ + BigInt(1)
this.poolState.totalEverNumberOfLoans = this.poolState.totalEverNumberOfLoans + BigInt(1)
this.poolState.totalBorrowed_ += borrowedAmount
this.poolState.totalEverBorrowed += borrowedAmount
this.poolState.totalNumberOfLoans_ += BigInt(1)
this.poolState.totalEverNumberOfLoans += BigInt(1)
}

public increaseTotalInvested = (currencyAmount: bigint) => {
Expand Down Expand Up @@ -156,7 +156,7 @@ export class PoolService {

private _getTrancheTokenPrices = async () => {
logger.info(`Qerying RPC tranche token prices for pool ${this.pool.id}`)
const poolId = new u64(api.registry, this.pool.id)
const poolId = this.pool.id
let tokenPrices: Vec<u128>
try {
tokenPrices = await (api.rpc as ExtendedRpc).pools.trancheTokenPrices(poolId)
Expand Down
Loading

0 comments on commit 4dfb56a

Please sign in to comment.