From b0797d92f5c88046757bbc1a4b8d8b09b0d310c1 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 23 Mar 2024 17:42:59 -0700 Subject: [PATCH] Increased test coverage to 100% --- src/adapters/wallet.adapter.js | 107 ++++++++++++++++ test/unit/adapters/wallet.adapter.unit.js | 141 ++++++++++++++++++++++ test/unit/misc/config.js | 6 +- 3 files changed, 253 insertions(+), 1 deletion(-) diff --git a/src/adapters/wallet.adapter.js b/src/adapters/wallet.adapter.js index 52ef636..9dd2052 100644 --- a/src/adapters/wallet.adapter.js +++ b/src/adapters/wallet.adapter.js @@ -122,6 +122,113 @@ class Wallet { throw err } } + + // Create an instance of minimal-slp-wallet. Same as + // instanceWalletWithoutInitialization(), but waits for the wallet to initialize + // its UTXOs (wallet balance and tokens). + async instanceWallet (walletData = {}, advancedConfig = {}) { + try { + // Instance the wallet without initialization. + await this.instanceWalletWithoutInitialization(walletData, advancedConfig) + + // Initialize the wallet + await this.bchWallet.initialize() + + return this.bchWallet + } catch (err) { + console.error('Error in wallet.js/instanceWallet()') + throw err + } + } + + // Increments the 'nextAddress' property in the wallet file. This property + // indicates the HD index that should be used to generate a key pair for + // storing funds for Offers. + // This function opens the wallet file, increments the nextAddress property, + // then saves the change to the wallet file. + async incrementNextAddress () { + try { + const walletData = await this.openWallet() + // console.log('original walletdata: ', walletData) + walletData.nextAddress++ + // console.log('walletData finish: ', walletData) + await this.jsonFiles.writeJSON(walletData, this.WALLET_FILE) + // Update the working instance of the wallet. + this.bchWallet.walletInfo.nextAddress++ + // console.log('this.bchWallet.walletInfo: ', this.bchWallet.walletInfo) + return walletData.nextAddress + } catch (err) { + console.error('Error in incrementNextAddress()') + throw err + } + } + + // This method returns an object that contains a private key WIF, public address, + // and the index of the HD wallet that the key pair was generated from. + // TODO: Allow input integer. If input is used, use that as the index. If no + // input is provided, then call incrementNextAddress(). + async getKeyPair (hdIndex = 0) { + try { + if (!hdIndex) { + // Increment the HD index and generate a new key pair. + hdIndex = await this.incrementNextAddress() + } + const mnemonic = this.bchWallet.walletInfo.mnemonic + // root seed buffer + const rootSeed = await this.bchWallet.bchjs.Mnemonic.toSeed(mnemonic) + const masterHDNode = this.bchWallet.bchjs.HDNode.fromSeed(rootSeed) + // HDNode of BIP44 account + // const account = this.bchWallet.bchjs.HDNode.derivePath(masterHDNode, "m/44'/245'/0'") + const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`) + const cashAddress = this.bchWallet.bchjs.HDNode.toCashAddress(childNode) + console.log('Generating a new key pair for cashAddress: ', cashAddress) + const wif = this.bchWallet.bchjs.HDNode.toWIF(childNode) + const outObj = { + cashAddress, + wif, + hdIndex + } + return outObj + } catch (err) { + console.error('Error in getKeyPair()') + throw err + } + } + + // Optimize the wallet by consolidating the UTXOs. + async optimize () { + const UTXO_THREASHOLD = 7 + // Do a dry-run first to see if there are enough UTXOs worth consolidating. + const dryRunOut = await this.bchWallet.optimize(true) + if (dryRunOut.bchUtxoCnt > UTXO_THREASHOLD) { + // Consolidate BCH UTXOs if the count is above the threashold. + const txids = await this.bchWallet.optimize() + console.log(`Wallet optimized with these return values: ${JSON.stringify(txids, null, 2)}`) + } + return true + } + + // Get the balance of the wallet in sats and PSF tokens. + // This function is called by the GET /entry/balance controller. + async getBalance () { + const balance = await this.bchWallet.getBalance() + // console.log('balance: ', balance) + const tokens = await this.bchWallet.listTokens() + // console.log('tokens: ', tokens) + // Find the array entry for the PSF token + const psfTokens = tokens.find(x => x.tokenId === '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0') + // console.log('psfTokens: ', psfTokens) + let psfBalance = 0 + if (psfTokens) { + psfBalance = psfTokens.qty + } + const outObj = { + satBalance: balance, + psfBalance, + success: true + } + return outObj + } } export default Wallet diff --git a/test/unit/adapters/wallet.adapter.unit.js b/test/unit/adapters/wallet.adapter.unit.js index 2f919d6..7ac98ac 100644 --- a/test/unit/adapters/wallet.adapter.unit.js +++ b/test/unit/adapters/wallet.adapter.unit.js @@ -190,6 +190,147 @@ describe('#wallet', () => { assert.property(result, 'walletInfo') }) }) + + describe('#instanceWallet', () => { + it('should create an instance of BchWallet', async () => { + // Create a mock wallet. + const mockWallet = new BchWallet() + await mockWallet.walletInfoPromise + sandbox.stub(mockWallet, 'initialize').resolves() + + // Mock dependencies + sandbox.stub(uut, '_instanceWallet').resolves(mockWallet) + + // Ensure we open the test file, not the production wallet file. + uut.WALLET_FILE = testWalletFile + const walletData = await uut.openWallet() + // console.log('walletData: ', walletData) + const result = await uut.instanceWallet(walletData) + // console.log('result: ', result) + + assert.property(result, 'walletInfoPromise') + assert.property(result, 'walletInfo') + }) + + it('should catch and throw an error', async () => { + try { + // Force an error + sandbox.stub(uut, 'instanceWalletWithoutInitialization').rejects(new Error('test error')) + + await uut.instanceWallet() + assert.fail('Unexpected code path') + } catch (err) { + // console.log('err: ', err) + assert.include(err.message, 'test error') + } + }) + + it('should create an instance of BchWallet using web2 infra', async () => { + // Create a mock wallet. + const mockWallet = new BchWallet() + await mockWallet.walletInfoPromise + sandbox.stub(mockWallet, 'initialize').resolves() + + // Mock dependencies + sandbox.stub(uut, '_instanceWallet').resolves(mockWallet) + + // Ensure we open the test file, not the production wallet file. + uut.WALLET_FILE = testWalletFile + const walletData = await uut.openWallet() + // console.log('walletData: ', walletData) + + // Force desired code path + uut.config.useFullStackCash = true + const result = await uut.instanceWallet(walletData) + // console.log('result: ', result) + + assert.property(result, 'walletInfoPromise') + assert.property(result, 'walletInfo') + }) + }) + + describe('#incrementNextAddress', () => { + it('should increment the nextAddress property', async () => { + // Ensure we open the test file, not the production wallet file. + uut.WALLET_FILE = testWalletFile + // mock instance of minimal-slp-wallet + uut.bchWallet = new MockBchWallet() + const result = await uut.incrementNextAddress() + assert.equal(result, 2) + }) + + it('should catch and throw an error', async () => { + try { + // Force an error + sandbox.stub(uut, 'openWallet').rejects(new Error('test error')) + await uut.incrementNextAddress() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'test error') + } + }) + }) + + describe('#getKeyPair', () => { + it('should return an object with a key pair', async () => { + // Ensure we open the test file, not the production wallet file. + uut.WALLET_FILE = testWalletFile + // mock instance of minimal-slp-wallet + uut.bchWallet = new MockBchWallet() + const result = await uut.getKeyPair() + // console.log('result: ', result) + assert.property(result, 'cashAddress') + assert.property(result, 'wif') + assert.property(result, 'hdIndex') + }) + + it('should catch and throw an error', async () => { + try { + // Force an error + sandbox + .stub(uut, 'incrementNextAddress') + .rejects(new Error('test error')) + await uut.getKeyPair() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'test error') + } + }) + }) + + describe('#optimize', () => { + it('should call the wallet optimize function', async () => { + // mock instance of minimal-slp-wallet + uut.bchWallet = new MockBchWallet() + sandbox.stub(uut.bchWallet, 'optimize').resolves({ bchUtxoCnt: 10 }) + const result = await uut.optimize() + assert.equal(result, true) + }) + }) + + describe('#getBalance', () => { + it('should get the balance for the wallet', async () => { + // mock instance of minimal-slp-wallet + uut.bchWallet = new MockBchWallet() + // Mock dependencies and force desired code path + sandbox.stub(uut.bchWallet, 'getBalance').resolves(41012) + sandbox.stub(uut.bchWallet, 'listTokens').resolves([{ + tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', + ticker: 'PSF', + name: 'Permissionless Software Foundation', + decimals: 8, + tokenType: 1, + url: 'psfoundation.cash', + qty: 2 + }]) + const result = await uut.getBalance() + // console.log('result: ', result) + // Assert the expected properties exist and have the expected values. + assert.equal(result.satBalance, 41012) + assert.equal(result.psfBalance, 2) + assert.equal(result.success, true) + }) + }) }) const deleteFile = (filepath) => { diff --git a/test/unit/misc/config.js b/test/unit/misc/config.js index 6a55b7e..54bce08 100644 --- a/test/unit/misc/config.js +++ b/test/unit/misc/config.js @@ -38,9 +38,13 @@ describe('#config', () => { assert.equal(config.env, 'test') }) - it('Should return test environment config', async () => { + it('Should return prod environment config', async () => { process.env.SVC_ENV = 'prod' + process.env.WALLET_INTERFACE = 'web2' + process.env.APISERVER = 'https://api.fullstack.cash/v5/' + + await import('../../../config/env/common.js?foo=bar2') const importedConfig3 = await import('../../../config/index.js?foo=bar2') const config = importedConfig3.default // console.log('config: ', config)