From bf214190170dad0a71173a8adfae6a9eef4cbec2 Mon Sep 17 00:00:00 2001 From: Alejo Acosta Date: Fri, 20 Dec 2024 18:23:35 -0300 Subject: [PATCH] add unit test for qiwallet scan() method --- src/_tests/unit/mockProvider.ts | 77 +++++++------ src/_tests/unit/qihdwallet-scan.unit.test.ts | 109 +++++++++++++++++++ src/_tests/unit/qihdwallet-scan.unit.ts | 109 +++++++++++++++++++ testcases/qi-wallet-scan.json.gz | Bin 0 -> 2758 bytes 4 files changed, 263 insertions(+), 32 deletions(-) create mode 100644 src/_tests/unit/qihdwallet-scan.unit.test.ts create mode 100644 src/_tests/unit/qihdwallet-scan.unit.ts create mode 100644 testcases/qi-wallet-scan.json.gz diff --git a/src/_tests/unit/mockProvider.ts b/src/_tests/unit/mockProvider.ts index 7e0a32ed..5045f95f 100644 --- a/src/_tests/unit/mockProvider.ts +++ b/src/_tests/unit/mockProvider.ts @@ -28,6 +28,8 @@ export class MockProvider implements Provider { private _transactions: Map = new Map(); private _blocks: Map = new Map(); private _balances: Map = new Map(); + private _lockedBalances: Map = new Map(); + private _outpoints: Map> = new Map(); private _eventHandlers: Map< string, Array<{ @@ -54,11 +56,27 @@ export class MockProvider implements Provider { this._transactions.set(hash.toLowerCase(), tx); } + public setOutpoints(address: string, outpoints: Array): void { + this._outpoints.set(address.toLowerCase(), outpoints); + } + // Implementation of Provider interface methods async getNetwork(): Promise { return this._network; } + public setBlock(key: string, block: Block): void { + this._blocks.set(key, block); + } + + async getBlock(shard: Shard, blockHashOrBlockTag: BlockTag | string, prefetchTxs?: boolean): Promise { + if (typeof blockHashOrBlockTag === 'string') { + return this._blocks.get(blockHashOrBlockTag) ?? null; + } + // Handle block tag (latest, pending, etc.) + throw new Error('Method not implemented.'); + } + async getBlockNumber(shard: Shard): Promise { return this._blockNumber; } @@ -87,17 +105,8 @@ export class MockProvider implements Provider { throw new Error('Method not implemented.'); } - async getOutpointsByAddress(): Promise> { - // TODO: Implement - throw new Error('Method not implemented.'); - } - - async getBlock(shard: Shard, blockHashOrBlockTag: BlockTag | string, prefetchTxs?: boolean): Promise { - if (typeof blockHashOrBlockTag === 'string') { - return this._blocks.get(blockHashOrBlockTag) ?? null; - } - // Handle block tag (latest, pending, etc.) - throw new Error('Method not implemented.'); + async getOutpointsByAddress(address: AddressLike): Promise> { + return this._outpoints.get(address.toString().toLowerCase()) ?? []; } async estimateFeeForQi(tx: QiPerformActionTransaction): Promise { @@ -233,64 +242,68 @@ export class MockProvider implements Provider { throw new Error('Method not implemented.'); } - async getLockedBalance(): Promise { - throw new Error('Method not implemented.'); + public setLockedBalance(address: AddressLike, balance: bigint): void { + this._lockedBalances.set(address.toString().toLowerCase(), balance); + } + + async getLockedBalance(address: AddressLike): Promise { + return this._lockedBalances.get(address.toString().toLowerCase()) ?? BigInt(0); } async getTransactionCount(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getTransactionCount: Method not implemented.'); } async getCode(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getCode: Method not implemented.'); } async getStorage(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getStorage: Method not implemented.'); } async estimateGas(): Promise { - throw new Error('Method not implemented.'); + throw new Error('estimateGas: Method not implemented.'); } async createAccessList(): Promise { - throw new Error('Method not implemented.'); + throw new Error('createAccessList: Method not implemented.'); } async call(): Promise { - throw new Error('Method not implemented.'); + throw new Error('call: Method not implemented.'); } async getTransaction(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getTransaction: Method not implemented.'); } async getTransactionReceipt(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getTransactionReceipt: Method not implemented.'); } async getTransactionResult(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getTransactionResult: Method not implemented.'); } async getLogs(): Promise> { - throw new Error('Method not implemented.'); + throw new Error('getLogs: Method not implemented.'); } async waitForTransaction(): Promise { - throw new Error('Method not implemented.'); + throw new Error('waitForTransaction: Method not implemented.'); } async waitForBlock(): Promise { - throw new Error('Method not implemented.'); + throw new Error('waitForBlock: Method not implemented.'); } async getProtocolExpansionNumber(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getProtocolExpansionNumber: Method not implemented.'); } async getTxPoolContent(zone: Zone): Promise { - throw new Error('Method not implemented.'); + throw new Error('getTxPoolContent: Method not implemented.'); } async txPoolInspect(zone: Zone): Promise { - throw new Error('Method not implemented.'); + throw new Error('txPoolInspect: Method not implemented.'); } async getQiRateAtBlock(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getQiRateAtBlock: Method not implemented.'); } async getLatestQiRate(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getLatestQiRate: Method not implemented.'); } async getQuaiRateAtBlock(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getQuaiRateAtBlock: Method not implemented.'); } async getLatestQuaiRate(): Promise { - throw new Error('Method not implemented.'); + throw new Error('getLatestQuaiRate: Method not implemented.'); } } diff --git a/src/_tests/unit/qihdwallet-scan.unit.test.ts b/src/_tests/unit/qihdwallet-scan.unit.test.ts new file mode 100644 index 00000000..1988e843 --- /dev/null +++ b/src/_tests/unit/qihdwallet-scan.unit.test.ts @@ -0,0 +1,109 @@ +import assert from 'assert'; +import { loadTests } from '../utils.js'; +import { Mnemonic, QiHDWallet, Zone, OutpointInfo, Block, QiAddressInfo } from '../../index.js'; +import { Outpoint } from '../../transaction/utxo.js'; +import { MockProvider } from './mockProvider.js'; + +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason); + process.exit(1); +}); + +import dotenv from 'dotenv'; +const env = process.env.NODE_ENV || 'development'; +dotenv.config({ path: `.env.${env}` }); +dotenv.config({ path: `.env`, override: false }); + +interface ScanTestCase { + name: string; + mnemonic: string; + provider_outpoints: Array<{ + address: string; + outpoints: Array; + }>; + provider_locked_balance: Array<{ + address: string; + balance: number; + }>; + provider_balance: Array<{ + address: string; + balance: number; + }>; + provider_blocks: Array<{ + key: string; + block: Block; + }>; + expected_external_addresses: Array; + expected_change_addresses: Array; + expected_outpoints_info: Array; + expected_balance: number; +} + +describe('QiHDWallet scan', async function () { + const tests = loadTests('qi-wallet-scan'); + + for (const test of tests) { + this.timeout(1200000); + + const mockProvider = new MockProvider(); + + // set the provider outpoints + for (const outpoint of test.provider_outpoints) { + mockProvider.setOutpoints(outpoint.address, outpoint.outpoints); + } + + // set the provider blocks + for (const block of test.provider_blocks) { + mockProvider.setBlock(block.key, block.block); + } + + // set the provider locked balace + for (const lockedBalance of test.provider_locked_balance) { + mockProvider.setLockedBalance(lockedBalance.address, BigInt(lockedBalance.balance)); + } + + // set the provider balance + for (const balance of test.provider_balance) { + mockProvider.setBalance(balance.address, BigInt(balance.balance)); + } + + const mnemonic = Mnemonic.fromPhrase(test.mnemonic); + const wallet = QiHDWallet.fromMnemonic(mnemonic); + wallet.connect(mockProvider); + it('it scans the wallet with no errors', async function () { + try { + await wallet.scan(Zone.Cyprus1); + assert.ok(true, '====> TESTING: scan completed'); + } catch (error) { + console.error('====> TESTING: error: ', error); + assert.fail('====> TESTING: error: ', error); + } + }); + it('validates expected external addresses', async function () { + const externalAddresses = wallet.getAddressesForZone(Zone.Cyprus1); + const sortedExternalAddresses = externalAddresses.sort((a, b) => a.address.localeCompare(b.address)); + const sortedExpectedExternalAddresses = test.expected_external_addresses.sort((a, b) => + a.address.localeCompare(b.address), + ); + assert.deepEqual(sortedExternalAddresses, sortedExpectedExternalAddresses); + }); + + it('validates expected change addresses', async function () { + const changeAddresses = wallet.getChangeAddressesForZone(Zone.Cyprus1); + const sortedChangeAddresses = changeAddresses.sort((a, b) => a.address.localeCompare(b.address)); + const sortedExpectedChangeAddresses = test.expected_change_addresses.sort((a, b) => + a.address.localeCompare(b.address), + ); + assert.deepEqual(sortedChangeAddresses, sortedExpectedChangeAddresses); + }); + + it('validates wallet balance', async function () { + const balance = await wallet.getBalanceForZone(Zone.Cyprus1); + assert.equal(balance.toString(), test.expected_balance.toString()); + }); + + it('validates expected outpoints info', async function () { + assert.deepEqual(wallet.getOutpoints(Zone.Cyprus1), test.expected_outpoints_info); + }); + } +}); diff --git a/src/_tests/unit/qihdwallet-scan.unit.ts b/src/_tests/unit/qihdwallet-scan.unit.ts new file mode 100644 index 00000000..cc7b9f47 --- /dev/null +++ b/src/_tests/unit/qihdwallet-scan.unit.ts @@ -0,0 +1,109 @@ +import assert from 'assert'; +import { loadTests } from '../utils.js'; +import { Mnemonic, QiHDWallet, Zone, OutpointInfo, Block, QiAddressInfo } from '../../index.js'; +import { Outpoint } from '../../transaction/utxo.js'; +import { MockProvider } from './mockProvider.js'; + +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason); + process.exit(1); +}); + +import dotenv from 'dotenv'; +const env = process.env.NODE_ENV || 'development'; +dotenv.config({ path: `.env.${env}` }); +dotenv.config({ path: `.env`, override: false }); + +interface ScanTestCase { + name: string; + nemonic: string; + provider_outpoints: Array<{ + address: string; + outpoints: Array; + }>; + provider_locked_balance: Array<{ + address: string; + balance: number; + }>; + provider_balance: Array<{ + address: string; + balance: number; + }>; + provider_blocks: Array<{ + key: string; + block: Block; + }>; + expected_external_addresses: Array; + expected_change_addresses: Array; + expected_outpoints_info: Array; + expected_balance: number; +} + +describe('QiHDWallet scan', async function () { + const tests = loadTests('qi-wallet-scan'); + + for (const test of tests) { + this.timeout(1200000); + + const mockProvider = new MockProvider(); + + // set the provider outpoints + for (const outpoint of test.provider_outpoints) { + mockProvider.setOutpoints(outpoint.address, outpoint.outpoints); + } + + // set the provider blocks + for (const block of test.provider_blocks) { + mockProvider.setBlock(block.key, block.block); + } + + // set the provider locked balace + for (const lockedBalance of test.provider_locked_balance) { + mockProvider.setLockedBalance(lockedBalance.address, BigInt(lockedBalance.balance)); + } + + // set the provider balance + for (const balance of test.provider_balance) { + mockProvider.setBalance(balance.address, BigInt(balance.balance)); + } + + const mnemonic = Mnemonic.fromPhrase(test.nemonic); + const wallet = QiHDWallet.fromMnemonic(mnemonic); + wallet.connect(mockProvider); + it('it scans the wallet with no errors', async function () { + try { + await wallet.scan(Zone.Cyprus1); + assert.ok(true, '====> TESTING: scan completed'); + } catch (error) { + console.error('====> TESTING: error: ', error); + assert.fail('====> TESTING: error: ', error); + } + }); + it('validates expected external addresses', async function () { + const externalAddresses = wallet.getAddressesForZone(Zone.Cyprus1); + const sortedExternalAddresses = externalAddresses.sort((a, b) => a.address.localeCompare(b.address)); + const sortedExpectedExternalAddresses = test.expected_external_addresses.sort((a, b) => + a.address.localeCompare(b.address), + ); + assert.deepEqual(sortedExternalAddresses, sortedExpectedExternalAddresses); + }); + + it('validates expected change addresses', async function () { + const changeAddresses = wallet.getChangeAddressesForZone(Zone.Cyprus1); + const sortedChangeAddresses = changeAddresses.sort((a, b) => a.address.localeCompare(b.address)); + const sortedExpectedChangeAddresses = test.expected_change_addresses.sort((a, b) => + a.address.localeCompare(b.address), + ); + assert.deepEqual(sortedChangeAddresses, sortedExpectedChangeAddresses); + }); + + it('validates wallet balance', async function () { + const balance = await wallet.getBalanceForZone(Zone.Cyprus1); + assert.equal(balance.toString(), test.expected_balance.toString()); + }); + + it('validates expected outpoints info', async function () { + assert.deepEqual(wallet.getOutpoints(Zone.Cyprus1), test.expected_outpoints_info); + }); + } +}); diff --git a/testcases/qi-wallet-scan.json.gz b/testcases/qi-wallet-scan.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d336dbccbc0856882b75ea99c36b31b09915843f GIT binary patch literal 2758 zcmV;%3OV&3iwFpS2Xkit1953BcVTR7WppibV_|MCYIARH0PR}YavR4LeU`ogg=atX zzW9w;7^jlB9MgVssZ_VSZ<|vBFf=fb$fWe|IfI=97|Su0iYp?EAlN;)-F?qp&Kb^6 zuV4N1^{ZFI1X%`H&|s_48Q8<=L?>Xp>1b_?gP)>^PpM0Z~Nc)7!OLjppFN$?rz)VU{3QnmRZrP8!X$0 z{_Sn^9yU|6>~06`>|uV_^FGQ@;8XJ`+dnJWcS0tUf4mPk# zQQ6@1xai5oHa>i2bZ-`Og%|C-!7R_jLh2n?$JPZe@m;|{ibTg zPpFlWN!m-vk)n|{R%Kiip0qHscvWo;CKglJhvEYpL&bPA+#3D3`Ss@Y?(Lh$?&){H z`T2)g@!-GjCoc?&4-eoJkX8w+s zf1<6|hlgZcF9j?yZh;^`MF6mzL_Dr_m~}A|DSz5THuDe?^`7G#xh$a zbn>Mj`faK)EAOk$#u%9-hGgcX**NQyFr`Q**s0`@85C2A`o!}4`O$2wF0oC5y~{Cb zsglW(s}-b_)Ilpi83h^XVzSDmM42n_)7C~)Qa&+ze@BZI693!vJZ;=8P7m{aJDaWV zjtkqU9R+KRcAkx`-udJs^17W?y|pUGilDGLD{`7;5S$}Ygy6l?Lb2A` z;8ld+um*g`T4{|}8JsgtdO+l&p=upgbYvENiFxB@c^ijfF@#dlp7v zf6~DF zJa*pcc2C}G6Rot`Yb&)fQW>j_JKxRL`X$5CY45f12sG`44#*p+!s!y9a)pm7#wc+O zQXB=;D|yK&+ihHkj7{hUjv4rsO2Pnt5~$Bs$^uL%3o9PQBu)75*x@~7=j56Onom`^pQ0^J$=kriG#?R^6>+Zfi zu9;BwE&5}Q3hl}m^e{?buK|?Exr%lfW6`>(gmQ#9321<5t>ASDO@ryQ%C_{{18vQtMerW&RY+~k&MEugv;fl+%E6(kr(DGpnKHpQJ-^e?69D7f% z4xOrxrwt+c>A>brQF=77j}s}vp~4SW>-4u>S9)xv?Pzua1@Hwi0WFD81SS}uD6Bs8 zwk>v#*Za_ZbH)Pu?7rXyucnhYnW8?%dKDej@fGOEM2v!t&>=Um5wWSNdSB!sC(nsJGRqz1v&89`KYK=)$AQ>s2>ra%tkH3-l@iTFKX zKOpFPxwWfEC#}7?e^RVogqkket<30Y2uc|$1L8T^fIK_7Xx~qbx!g|n%pTkwsew?- z#es;ibdY(dFQm}>jjO5Xtg-|*b?`*iNrUEUB$-bqRRHxF1^XlqKD5hsH@7(IHu5&3A0I0*aAnRNv?sVFFBdd>yHkGrRF(asT^s^(FUnA2 zc7<@qP@ub)&HGIld`I2poTEQ~XUx}o^M_CV9({SYPJh{IM0{2y7GP=?ut62mP=hx8g4(B`c7^8PrV~(NHo}GLng9~c zfF`9ve-qN6i_qor*x#OHkTpuZ*n;eJgl6NT6`EXvTG;e*t+lkhIs>CXdg3B)Ab+cq zXQ+AazM%FgsF5F48ubpALfdd%qW7w1KqcI0nyvt*RrYeONn31*V0`FWg=~-+j&LSL z%7R=BF$7djr4a=xlk6iPQ7}0uYlJNqnwpbeQ2P|r#@DP-9KF{Bc-*zLPLj%#k&vni zD~$I=`OCRhv#Jo_(%M)I7>LEYq?2G&P4A2dppwWnQIZlt0#on26OdxaN*|8ha*rAy z|5PsWzla)M&xz*tAfJh5?!0sv*=h9XIPrQEXwu)6j^0q_QGYXTTu?@1&{rdMIUga2Aa>sILUbiMIReudS3M~ujv%!BwKF<-WQ?>b zT|J`-k`@&*&J_X<=#9)0N-jGz&;YD8%5d)86OFB*MjsinI6xd1Q`O<$O2>{T;upkz z9kJ2GIV)lu$E*HdMH9{egdTKVK-*1(a8p@Emk~pw6Op5d$<|&NB!OC(Dr9!_8pqYB zi@sEAMa@}w$HlVZl!|1XRkTkLGyY5td_n9Sv9Z&Er_-qqQxBw^4gSiElweY5V=8Ma z&E={HpZ9mr9i+6?z|C8WzNd`#j24CTtSWJ$%4lMmLn>NnneZ*F%Q}R!s_5k(IL9uL zlfxq=Z<|GJpHP$i