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.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 00000000..68304007 Binary files /dev/null and b/testcases/qi-wallet-scan.json.gz differ