Skip to content

Commit

Permalink
refactor test node setup
Browse files Browse the repository at this point in the history
  • Loading branch information
tgntr committed May 30, 2023
1 parent 5a09197 commit e934d24
Show file tree
Hide file tree
Showing 31 changed files with 5,499 additions and 653 deletions.
47 changes: 37 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,42 @@ jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
name: Clone repository and checkout branch
- uses: actions/setup-node@v2
name: Setup node.js
- name: Clone repository and checkout branch
uses: actions/checkout@v3
- name: Setup node.js
uses: actions/setup-node@v3
with:
node-version: 16
- run: rm -r .npmrc
name: Remove .npmrc for the test run
- run: yarn
name: Install dependencies
- run: yarn test
name: Run tests
cache: "yarn"

- name: Remove .npmrc for the test run
run: rm -r .npmrc

- name: Install dependencies
run: yarn

- name: Get cudos-node target version from .env
uses: falti/[email protected]
id: dotenv
with:
path: src/jest/cudos-node.local.env

- name: Clone cudos-node repository
uses: actions/checkout@v3
with:
repository: CudoVentures/cudos-node
ref: ${{ steps.dotenv.outputs.BINARY_VERSION }}
path: src/jest/cudos-node

- name: Setup go
uses: actions/setup-go@v4
with:
go-version: 1.18
cache-dependency-path: src/jest/cudos-node/go.sum

- name: Install cudos-node binary
run: make install
working-directory: src/jest/cudos-node

- name: Run tests
run: yarn test
6 changes: 1 addition & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ yarn-debug.log*
yarn-error.log*

# Temporary testing data
tests/**/test-data/
tests/**/cudos-data/
cudos-node

# Dependency directories
node_modules/
Expand Down Expand Up @@ -42,6 +41,3 @@ build/

# package-lock.json
package-lock.json

# yarn.lock
yarn.lock
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,6 @@ const queryClient = await StargateClient.connect(rpc)

First we need an RPC endpoint, that will be used to connect to the network. Then a `StargateClient` is created by connecting it to the network.

** In this example the client is connecting to a local test network, created using [cudos-blast](https://github.com/CudoVentures/cudos-blast).

```
...
Expand Down
8 changes: 8 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
globalSetup: '<rootDir>/src/jest/global-setup.ts',
globalTeardown: '<rootDir>/src/jest/global-teardown.ts',
testEnvironment: '<rootDir>/src/jest/cudos-environment.ts',
testMatch: ['<rootDir>/src/**/*.{spec,test}.ts'],
testTimeout: 20000,
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"registry": "https://registry.npmjs.org"
},
"scripts": {
"test": "cd tests && chmod u+x run-tests.sh && ./run-tests.sh",
"test": "jest --runInBand",
"build": "rm -rf ./build && tsc",
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
Expand Down Expand Up @@ -46,6 +46,7 @@
"@babel/preset-typescript": "^7.18.6",
"@types/jest": "^28.1.3",
"babel-jest": "^28.1.1",
"dotenv": "^16.0.3",
"jest": "^28.1.2",
"ts-jest": "^28.0.5",
"ts-loader": "^9.2.6",
Expand Down
19 changes: 10 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export * from './cosmwasm-stargate'
export * from './proto-signing'
export * from './stargate'
export * from './tendermint-rpc'
export * from './crypto'
export * from './amino'
export * from './utils'
export * from './ledgers'
export * from './math'
export * from './cosmwasm-stargate';
export * from './proto-signing';
export * from './stargate';
export * from './tendermint-rpc';
export * from './crypto';
export * from './amino';
export * from './utils';
export * from './ledgers';
export * from './math';
export * from './jest';
20 changes: 20 additions & 0 deletions src/jest/cudos-environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import NodeEnvironment from 'jest-environment-node';
import { CudosNode } from './cudos-node';
import { GasPrice } from '@cosmjs/stargate';

export default class CudosEnvironment extends NodeEnvironment {
// setup() is called once before each test file
override async setup() {
await super.setup();
await CudosNode.instance?.start();

this.global.node = CudosNode.instance;
this.global.gasPrice = GasPrice.fromString(CudosNode.instance?.env['GAS_PRICE']!);
}

// teardown() is called once after each test file
override async teardown() {
await super.teardown();
await CudosNode.instance?.reset();
}
}
13 changes: 13 additions & 0 deletions src/jest/cudos-node.local.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
BINARY_VERSION="v1.1.0"
MONIKER="cudos-node-local"
CHAIN_ID="cudos-node-local"
ORCH_ETH_ADDRESS="0x41d0b5762341b0fce6adccf69572c663481c7286"
CUDOS_TOKEN_CONTRACT_ADDRESS="0x28ea52f3ee46cac5a72f72e8b3a387c0291d586d"
DENOM="acudos"
MIN_SELF_DELEGATION="2000000000000000000000000"
VALIDATOR_BALANCE="90000000000000000000000000acudos,1000cudosAdmin"
ORCH_BALANCE="90000000000000000000000000acudos"
VALIDATOR_MNEMONIC="ordinary witness such toddler tag mouse helmet perfect venue eyebrow upgrade rabbit"
ORCH_MNEMONIC="course hurdle stand heart rescue trap upset cousin dish embody business equip"
GAS_PRICE="0acudos"
CUDOS_HOME="/tmp/cudos-local-data"
52 changes: 52 additions & 0 deletions src/jest/cudos-node.local.setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash -e

# set current working directory to cudosjs/src/jest
cd $(dirname $0)

# load environment variables
source ./cudos-node.local.env
# set CUDOS_HOME global env to be used as default --home folder
export CUDOS_HOME=$CUDOS_HOME
# cleanup data folder from previous runs
rm -rf $CUDOS_HOME

# the tests require cudos-noded binary
# our github actions workflow does this
# otherwise we need to manually clone the repo and build from source
if [[ $GITHUB_ACTIONS != true ]]; then
rm -rf cudos-node
git clone --branch=$BINARY_VERSION https://github.com/CudoVentures/cudos-node.git &> /dev/null || true
cd cudos-node
make install &> /dev/null
fi

# initialize data folder with config and genesis
cudos-noded init $MONIKER --chain-id=$CHAIN_ID &> /dev/null

# create self-delegate account
echo $VALIDATOR_MNEMONIC | cudos-noded keys add validator --keyring-backend=test --recover
# get self-delegate address
validatorAddress=$(cudos-noded keys show validator -a --keyring-backend=test)
# fund self-delegate and add to genesis
cudos-noded add-genesis-account $validatorAddress ${VALIDATOR_BALANCE}

# create orchestrator account
echo $ORCH_MNEMONIC | cudos-noded keys add orch --keyring-backend=test --recover
# get orchestrator address
orchAddress=$(cudos-noded keys show orch -a --keyring-backend=test)
# fund orchestrator and add to genesis
cudos-noded add-genesis-account $orchAddress ${ORCH_BALANCE}

# create validator account
cudos-noded gentx validator ${MIN_SELF_DELEGATION}${DENOM} ${ORCH_ETH_ADDRESS} ${orchAddress} --min-self-delegation=$MIN_SELF_DELEGATION --chain-id=$CHAIN_ID --keyring-backend=test &> /dev/null
# add to genesis
cudos-noded collect-gentxs &> /dev/null

# set minimum-gas-prices
sed -i.bak "s/minimum-gas-prices = \"\"/minimum-gas-prices = \"$GAS_PRICE\"/" $CUDOS_HOME/config/app.toml
# set denom name
sed -i.bak "s/\"stake\"/\"$DENOM\"/g" $CUDOS_HOME/config/genesis.json
# set validator self-delegate address
sed -i.bak "s/\"static_val_cosmos_addrs\"\: \[\]/\"static_val_cosmos_addrs\": [\"$validatorAddress\"]/" $CUDOS_HOME/config/genesis.json
# set token contract address on ethereum
sed -i.bak "s/\"erc20_to_denoms\"\: \[\]/\"erc20_to_denoms\": [{\"erc20\":\"$CUDOS_TOKEN_CONTRACT_ADDRESS\",\"denom\":\"$DENOM\"}]/" $CUDOS_HOME/config/genesis.json
118 changes: 118 additions & 0 deletions src/jest/cudos-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
DirectSecp256k1HdWallet,
OfflineSigner,
SigningStargateClient,
StargateClient,
Tendermint34Client,
} from '..';
import { execSync } from 'child_process';
import { setTimeout } from 'timers/promises';
import dotenv from 'dotenv';

const DEFAULT_RPC = 'http://localhost:26657';

// CudosAccount extends CudosSigningStargateClient by adding address helper field for test purposes
class CudosAccount extends SigningStargateClient {
private _address: string;

public static fromMnemonic = async (mnemonic: string, rpcUrl: string) => {
const tmClient = await Tendermint34Client.connect(rpcUrl);
const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic);
const address = (await signer.getAccounts())[0].address;
return new CudosAccount(tmClient, signer, address);
};

private constructor(tmClient: Tendermint34Client, signer: OfflineSigner, address: string) {
super(tmClient, signer, {});
this._address = address;
}

public get address() {
return this._address;
}
}

export class CudosNode {
// we use singleton pattern to make sure we only have one node instance
private static _instance: CudosNode | undefined;
private started: boolean = false;
private _queryClient: StargateClient | undefined;
private _validator: CudosAccount | undefined;
private _orchestrator: CudosAccount | undefined;
public readonly env: dotenv.DotenvParseOutput;

// init is called once before jest starts executing the tests
public static init = () => {
if (CudosNode._instance) {
throw new Error('Node is already instantiated.');
}

execSync(`${__dirname}/cudos-node.local.setup.sh`);

// create singleton instance once the node is initializeed
CudosNode._instance = new CudosNode();
};

private constructor() {
this.env = dotenv.config({ path: `${__dirname}/cudos-node.local.env` }).parsed!;
}

public start = async () => {
if (this.started) {
throw new Error('Node is already started.');
}

execSync(`cudos-noded start --log_level=fatal &`, { stdio: 'inherit' });

// wait 8sec for node to start producing blocks then verify height
await setTimeout(8000);
const queryClient = await StargateClient.connect(DEFAULT_RPC);
const height = await queryClient.getHeight();
if (height !== 1) {
throw new Error(`error while starting local cudos node, expected height 1, received ${height}`);
}

this.started = true;

if (!this._validator || !this._orchestrator || !this._queryClient) {
this._validator = await CudosAccount.fromMnemonic(this.env['VALIDATOR_MNEMONIC'], DEFAULT_RPC);
this._orchestrator = await CudosAccount.fromMnemonic(this.env['ORCH_MNEMONIC'], DEFAULT_RPC);
this._queryClient = queryClient;
}
};

public reset = () => {
execSync('pkill cudos-noded || true');
execSync(`cudos-noded unsafe-reset-all --log_level=error`);
this.started = false;
};

// cleanup is called once after jest finishes test execution
public cleanup = () => {
if (this.started) {
this.reset();
}

execSync(`rm -rf ${__dirname}/cudos-node`);
execSync(`rm -rf ${this.env['CUDOS_HOME']}`);

// delete singleton instance after tests are completed
delete CudosNode._instance;
};

public get queryClient() {
return this._queryClient;
}

public get validator() {
return this._validator;
}

public get orchestrator() {
return this._orchestrator;
}

public static get instance() {
return CudosNode._instance;
}
}
9 changes: 9 additions & 0 deletions src/jest/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { CudosNode } from './cudos-node';

// jestGlobalSetup is called once before executing tests
export const jestGlobalSetup = () => {
console.log('Setup local cudos node...');
CudosNode.init();
};

module.exports = jestGlobalSetup;
9 changes: 9 additions & 0 deletions src/jest/global-teardown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { CudosNode } from "./cudos-node";

// jestGlobalTeardown is called once after executing tests
export const jestGlobalTeardown = () => {
console.log('Cleanup cudos node...');
CudosNode.instance?.cleanup();
};

module.exports = jestGlobalTeardown;
8 changes: 8 additions & 0 deletions src/jest/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Coin, GasPrice } from '@cosmjs/stargate';
import { CudosNode } from './cudos-node';

// types for globalThis
export declare global {
var node: CudosNode | undefined;
var gasPrice: GasPrice;
}
4 changes: 4 additions & 0 deletions src/jest/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './cudos-environment';
export * from './cudos-node';
export * from './global-setup';
export * from './global-teardown';
20 changes: 20 additions & 0 deletions src/stargate/modules/addressbook/addressbook.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
describe('addressbook', () => {
const alice = globalThis.node?.validator!;
const queryClient = globalThis.node?.queryClient!;
const gas = globalThis.gasPrice;
const network = 'BTC';
const label = '1@denom';

jest.setTimeout(40000);

// positive test case
test('general flow', async () => {
await alice.addressbookCreateAddress(alice.address, network, label, 'addr1', gas);
await alice.addressbookUpdateAddress(alice.address, network, label, 'addr2', gas);
const value = await queryClient.addressbookModule.getAddress(alice.address, network, label);
expect(value.address).toBeDefined();
expect(value.address!.value).toEqual('addr2');
await alice.addressbookDeleteAddress(alice.address, network, label, gas);
await queryClient.addressbookModule.getAllAddresses();
});
});
Loading

0 comments on commit e934d24

Please sign in to comment.