From c0f8e3a7189a4d1278c104ce66fec964e961305e Mon Sep 17 00:00:00 2001 From: Anmol Sharma Date: Sun, 21 Jan 2024 23:58:04 +0530 Subject: [PATCH] test: added test for scan() and getBalance() Signed-off-by: Anmol Sharma --- .github/workflows/test.yml | 4 + test/helpers/bitcoin-rpc-client.ts | 134 +++++++++++++++++++++++++++++ test/wallet.spec.ts | 33 ++++++- 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 test/helpers/bitcoin-rpc-client.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c45295e..394b2e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,6 +38,10 @@ jobs: command: curl --fail -X GET http://localhost:8094/regtest/api/blocks/tip/height - name: Run unit tests run: npm run test + env: + BITCOIN_RPC_USER: alice + BITCOIN_RPC_PASSWORD: password + BITCOIN_RPC_HOST: localhost:18443 - name: Fetch esplora logs if: always() run: docker-compose -f "./test/helpers/docker-compose.yaml" logs esplora diff --git a/test/helpers/bitcoin-rpc-client.ts b/test/helpers/bitcoin-rpc-client.ts new file mode 100644 index 0000000..2bbff9b --- /dev/null +++ b/test/helpers/bitcoin-rpc-client.ts @@ -0,0 +1,134 @@ +import axios, { AxiosError, AxiosRequestConfig } from 'axios'; + +export class BitcoinRpcClient { + url: string; + config: AxiosRequestConfig; + + constructor() { + const user = process.env.BITCOIN_RPC_USER; + const password = process.env.BITCOIN_RPC_PASSWORD; + const host = process.env.BITCOIN_RPC_HOST; + this.url = `http://${user}:${password}@${host}`; + this.config = { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + }; + } + + async init() { + let loadWallet = false; + try { + await this.createWallet('default'); + } catch (e) { + if ( + e instanceof AxiosError && + e.response?.data.error.message.includes( + 'Database already exists.', + ) + ) { + loadWallet = true; + } else { + throw e; + } + } + try { + const result = await this.getWalletInfo(); + if (result['walletname'] === 'default') { + loadWallet = false; + } + } catch (e) { + if ( + e instanceof AxiosError && + !e.response?.data.error.message.includes('No wallet is loaded.') + ) { + throw e; + } + } + try { + if (loadWallet) { + await this.loadWallet('default'); + const address = await this.getNewAddress(); + await this.mineToAddress(150, address); + } + } catch (e) { + if ( + e instanceof AxiosError && + !e.response?.data.error.message.includes( + 'Unable to obtain an exclusive lock on the database', + ) + ) { + throw e; + } + } + } + + private async request(config: AxiosRequestConfig) { + const response = await axios.request({ + ...this.config, + ...config, + }); + return response.data?.result; + } + + async createWallet(walletName: string) { + return await this.request({ + url: this.url, + data: { + method: 'createwallet', + params: [walletName], + }, + }); + } + + async getWalletInfo() { + return await this.request({ + url: this.url, + data: { + method: 'getwalletinfo', + params: [], + }, + }); + } + + async loadWallet(walletName: string) { + return await this.request({ + url: this.url, + data: { + method: 'loadwallet', + params: [walletName], + }, + }); + } + + async getNewAddress() { + return await this.request({ + url: this.url, + data: { + method: 'getnewaddress', + params: [], + }, + }); + } + + async mineToAddress(numBlocks: number, address: string) { + return await this.request({ + url: this.url, + data: { + method: 'generatetoaddress', + params: [numBlocks, address], + }, + }); + } + + async sendToAddress(address: string, amount: number) { + return await this.request({ + url: this.url, + data: { + method: 'sendtoaddress', + params: [address, amount], + }, + }); + } +} diff --git a/test/wallet.spec.ts b/test/wallet.spec.ts index b08aaab..cea7bb9 100644 --- a/test/wallet.spec.ts +++ b/test/wallet.spec.ts @@ -1,8 +1,11 @@ import { EsploraClient, Wallet, WalletDB } from '../src/wallet'; import * as fs from 'fs'; +import { BitcoinRpcClient } from './helpers/bitcoin-rpc-client'; describe('Wallet', () => { let wallet: Wallet; + let address: string; + let bitcoinRpcClient: BitcoinRpcClient; beforeAll(async () => { const walletDB = new WalletDB({ @@ -17,6 +20,9 @@ describe('Wallet', () => { network: 'regtest', }), }); + + bitcoinRpcClient = new BitcoinRpcClient(); + await bitcoinRpcClient.init(); }); it('should initialise the wallet', async () => { @@ -26,7 +32,7 @@ describe('Wallet', () => { }); it('should derive first receive address', async () => { - const address = await wallet.deriveReceiveAddress(); + address = await wallet.deriveReceiveAddress(); expect(address).toBe('bcrt1qcr8te4kr609gcawutmrza0j4xv80jy8zeqchgx'); }); @@ -40,6 +46,31 @@ describe('Wallet', () => { expect(address).toBe('bcrt1q8c6fshw2dlwun7ekn9qwf37cu2rn755ufhry49'); }); + it('should rescan all addresses', async () => { + await bitcoinRpcClient.sendToAddress(address, 0.1); + await bitcoinRpcClient.mineToAddress( + 3, + await bitcoinRpcClient.getNewAddress(), + ); + + await wallet.scan(); + }); + + it('should get balance', async () => { + // we have to do this because esplora is not always in sync with the node + let retryCount = 5; + while (retryCount > 0) { + const balance = await wallet.getBalance(); + if (balance === 0) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + await wallet.scan(); + } + retryCount--; + } + + expect(await wallet.getBalance()).toBe(10000000); + }); + afterAll(async () => { await wallet.close(); fs.rmSync('./test/wallet', { recursive: true, force: true });