From 5d3ea2b5599971ad9ffd1b4f796233275cda5ffb Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 24 Mar 2021 07:24:08 -0700 Subject: [PATCH] fix(Transaction.get()): Added unit tests --- package.json | 4 +- src/transaction.js | 79 +-------- test/integration/transaction-integration.js | 2 +- test/unit/fixtures/transaction-mock.js | 184 ++++++++++++++++++++ test/unit/transaction-unit.js | 116 ++++++++++++ 5 files changed, 304 insertions(+), 81 deletions(-) create mode 100644 test/unit/fixtures/transaction-mock.js diff --git a/package.json b/package.json index 71d93b5..f8d2e07 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v4/ && mocha --timeout 30000 test/integration/", "test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v4/ && mocha --timeout 30000 test/integration/", "test:temp": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#Encryption' test/integration/", - "test:temp2": "mocha --timeout=30000 -g '#_hydrateUtxo' test/unit/", - "test:temp3": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#transaction' test/integration/", + "test:temp2": "mocha --timeout=30000 -g '#TransactionLib' test/unit/", + "test:temp3": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#Transaction' test/integration/", "test:temp4": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#decodeOpReturn' test/integration/", "coverage": "nyc report --reporter=text-lcov | coveralls", "coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/", diff --git a/src/transaction.js b/src/transaction.js index 023b042..fb22cd3 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -21,6 +21,7 @@ class Transaction { } const txDetails = await this.rawTransaction.getTxData(txid) + // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) // Setup default SLP properties. txDetails.isValidSLPTx = false @@ -119,84 +120,6 @@ class Transaction { throw err } } - - // Wraps getTxData(), but also appends SLP token information to each input - // and output of the transaction. This is a very API-heavy call. - // Returns false if the txid is not an SLP transaction. - // - // Warning! This is a prototype function and the output can change at any time - // without reflecting a change in the semantic version. DO NOT USE IN PRODUCTION. - // async getTxDataSlp (txid) { - // try { - // if (typeof txid !== 'string') { - // throw new Error('Input must be a string or array of strings.') - // } - // - // const txDetails = await this.getTxData(txid) - // - // // First get the token information for the output. If that fails, then - // // this is not an SLP transaction, and this method can return false. - // let outTokenData - // try { - // outTokenData = await this.slpUtils.decodeOpReturn(txid) - // // console.log(`outTokenData: ${JSON.stringify(outTokenData, null, 2)}`) - // - // // Add token information to the tx details object. - // txDetails.tokenTxType = outTokenData.txType - // txDetails.tokenId = outTokenData.tokenId - // - // // Add the token quantity to each output. - // for (let i = 0; i < outTokenData.amounts.length; i++) { - // txDetails.vout[i + 1].tokenQty = outTokenData.amounts[i] - // } - // - // // Loop through each input and retrieve the token data. - // for (let i = 0; i < txDetails.vin.length; i++) { - // const thisVin = txDetails.vin[i] - // - // try { - // // If decodeOpReturn() throws an error, then this input is not - // // from an SLP transaction and can be ignored. - // const inTokenData = await this.slpUtils.decodeOpReturn(thisVin.txid) - // // console.log( - // // `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}` - // // ) - // - // // Get the appropriate vout token amount. This may throw an error, - // // which means this Vin is not actually a token UTXO, it was just - // // associated with a previous token TX. - // const tokenQty = inTokenData.amounts[thisVin.vout - 1] - // // console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`) - // - // if (tokenQty) { - // thisVin.tokenQty = tokenQty - // // txDetails.vin[i].tokenQty = tokenQty - // } else { - // thisVin.tokenQty = null - // } - // } catch (err) { - // // console.log('catch 2: ', err) - // // If decodeOpReturn() throws an error, then this input is not - // // from an SLP transaction and can be ignored. - // // thisVin.tokenQty = null - // thisVin.tokenQty = null - // continue - // } - // // console.log( - // // `2: txDetails.vin[i]: ${JSON.stringify(txDetails.vin[i], null, 2)}` - // // ) - // } - // } catch (err) { - // // console.log('catch 1: ', err) - // return false - // } - // - // return txDetails - // } catch (error) { - // if (error.response && error.response.data) throw error.response.data - // else throw error - // } - // } } module.exports = Transaction diff --git a/test/integration/transaction-integration.js b/test/integration/transaction-integration.js index d47289e..3a924b8 100644 --- a/test/integration/transaction-integration.js +++ b/test/integration/transaction-integration.js @@ -6,7 +6,7 @@ const assert = require('chai').assert const BCHJS = require('../../src/bch-js') const bchjs = new BCHJS() -describe('#transaction', () => { +describe('#Transaction', () => { beforeEach(async () => { if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000) }) diff --git a/test/unit/fixtures/transaction-mock.js b/test/unit/fixtures/transaction-mock.js new file mode 100644 index 0000000..abf9ace --- /dev/null +++ b/test/unit/fixtures/transaction-mock.js @@ -0,0 +1,184 @@ +/* + Mocking data used in the transaction-unit.js tests. +*/ + +const nonSlpTxDetails = { + txid: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7', + hash: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7', + version: 2, + size: 225, + locktime: 0, + vin: [ + { + txid: '5f09d317e24c5d376f737a2711f3bd1d381abdb41743fff3819b4f76382e1eac', + vout: 1, + scriptSig: { + asm: + '3044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c[ALL|FORKID] 038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9', + hex: + '473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9' + }, + sequence: 4294967295, + address: 'bitcoincash:qqxy8hycqe89j7wa79gnggq6z3gaqu2uvqy26xehfe', + value: 0.00047504 + } + ], + vout: [ + { + value: 0.00001, + n: 0, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 2fe2c4c5ef359bb2fe1a849f891cecffbcfb4f77 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'] + } + }, + { + value: 0.00046256, + n: 1, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 2dbf5e1804c39a497b908c876097d63210c84902 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a9142dbf5e1804c39a497b908c876097d63210c8490288ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qqkm7hscqnpe5jtmjzxgwcyh6ceppjzfqg3jdn422e'] + } + } + ], + hex: + '0200000001ac1e2e38764f9b81f3ff4317b4bd1a381dbdf311277a736f375d4ce217d3095f010000006a473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9ffffffff02e8030000000000001976a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788acb0b40000000000001976a9142dbf5e1804c39a497b908c876097d63210c8490288ac00000000', + blockhash: '0000000000000000010903a1fc4274499037c9339be9ec7338ee980331c20ce5', + confirmations: 77741, + time: 1569792892, + blocktime: 1569792892 +} + +const slpTxDetails = { + txid: '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1', + hash: '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1', + version: 2, + size: 479, + locktime: 0, + vin: [ + { + txid: 'abc685f1f2a95f51e5e05a350f3fb9c74676e9f78c835b2a019c888ac0a2a736', + vout: 2, + scriptSig: { + asm: + '3045022100e5f0f6f1212fcbb10eedb7fdc38fca6e86629b4e7e8356a3ad7371a109fc37a602204bfff37d1a34d2e2908b81c23677706fb59ff4ab639fa3299da6c303de74e1f7[ALL|FORKID] 0245b9b3586fab3cfd46db6d116c4588004fe7fe9798216ccb8e55a89bcebc07ac', + hex: + '483045022100e5f0f6f1212fcbb10eedb7fdc38fca6e86629b4e7e8356a3ad7371a109fc37a602204bfff37d1a34d2e2908b81c23677706fb59ff4ab639fa3299da6c303de74e1f741210245b9b3586fab3cfd46db6d116c4588004fe7fe9798216ccb8e55a89bcebc07ac' + }, + sequence: 4294967295, + address: 'bitcoincash:qzv7t2pzn2d0pklnetdjt65crh6fe8vnhuwvhsk2nn', + value: 0.00000546 + }, + { + txid: '58c8576404c01c23a224053307399483d3a070599b3e9eb6d45be9714b8d6856', + vout: 1, + scriptSig: { + asm: + '30430220784f6d81fa8f54db8a4948259e8c15972a0285f8b1640c433d4e9f606dc38f0c021f14eecc2e8af2efede0867ce459c400dde54186a0e64babdbe89f795db12753[ALL|FORKID] 0209ebe6d9da5043945ed1d81bec0fcace299eba05e5f46b72d6838c790d31c505', + hex: + '4630430220784f6d81fa8f54db8a4948259e8c15972a0285f8b1640c433d4e9f606dc38f0c021f14eecc2e8af2efede0867ce459c400dde54186a0e64babdbe89f795db1275341210209ebe6d9da5043945ed1d81bec0fcace299eba05e5f46b72d6838c790d31c505' + }, + sequence: 4294967295, + address: 'bitcoincash:qppzuxemgqyxf07nz3kan33gmc83mf3z3yz295c4s7', + value: 0.68369626 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + 'OP_RETURN 5262419 1 1145980243 497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7 0000000005f5e100 00005ad7e49d9100', + hex: + '6a04534c500001010453454e4420497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7080000000005f5e1000800005ad7e49d9100', + type: 'nulldata' + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 36be3b7d185a85b6cf6fc61d63c16f2f10e54260 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a91436be3b7d185a85b6cf6fc61d63c16f2f10e5426088ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qqmtuwmarpdgtdk0dlrp6c7pduh3pe2zvqrkys2ex8'] + } + }, + { + value: 0.00000546, + n: 2, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 99e5a8229a9af0dbf3cadb25ea981df49c9d93bf OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a91499e5a8229a9af0dbf3cadb25ea981df49c9d93bf88ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qzv7t2pzn2d0pklnetdjt65crh6fe8vnhuwvhsk2nn'] + } + }, + { + value: 0.68368564, + n: 3, + scriptPubKey: { + asm: + 'OP_DUP OP_HASH160 422e1b3b400864bfd3146dd9c628de0f1da62289 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a914422e1b3b400864bfd3146dd9c628de0f1da6228988ac', + reqSigs: 1, + type: 'pubkeyhash', + addresses: ['bitcoincash:qppzuxemgqyxf07nz3kan33gmc83mf3z3yz295c4s7'] + } + } + ], + hex: + '020000000236a7a2c08a889c012a5b838cf7e97646c7b93f0f355ae0e5515fa9f2f185c6ab020000006b483045022100e5f0f6f1212fcbb10eedb7fdc38fca6e86629b4e7e8356a3ad7371a109fc37a602204bfff37d1a34d2e2908b81c23677706fb59ff4ab639fa3299da6c303de74e1f741210245b9b3586fab3cfd46db6d116c4588004fe7fe9798216ccb8e55a89bcebc07acffffffff56688d4b71e95bd4b69e3e9b5970a0d383943907330524a2231cc0046457c85801000000694630430220784f6d81fa8f54db8a4948259e8c15972a0285f8b1640c433d4e9f606dc38f0c021f14eecc2e8af2efede0867ce459c400dde54186a0e64babdbe89f795db1275341210209ebe6d9da5043945ed1d81bec0fcace299eba05e5f46b72d6838c790d31c505ffffffff040000000000000000406a04534c500001010453454e4420497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7080000000005f5e1000800005ad7e49d910022020000000000001976a91436be3b7d185a85b6cf6fc61d63c16f2f10e5426088ac22020000000000001976a91499e5a8229a9af0dbf3cadb25ea981df49c9d93bf88acb4381304000000001976a914422e1b3b400864bfd3146dd9c628de0f1da6228988ac00000000', + blockhash: '0000000000000000015284202422a688554b7fc80c54f18122847a99c4f79607', + confirmations: 76722, + time: 1570392893, + blocktime: 1570392893 +} + +const mockOpReturnData01 = { + tokenType: 1, + txType: 'SEND', + tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', + amounts: ['100000000', '99883300000000'] +} + +const mockOpReturnData02 = { + tokenType: 1, + txType: 'GENESIS', + ticker: 'TOK-CH', + name: 'TokyoCash', + tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', + documentUri: '', + documentHash: '', + decimals: 8, + mintBatonVout: 0, + qty: '2100000000000000' +} + +const mockOpReturnData03 = { + tokenType: 1, + txType: 'SEND', + tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', + amounts: ['1000000000', '99883400000000'] +} + +module.exports = { + nonSlpTxDetails, + slpTxDetails, + mockOpReturnData01, + mockOpReturnData02, + mockOpReturnData03 +} diff --git a/test/unit/transaction-unit.js b/test/unit/transaction-unit.js index 4fc123c..803d90d 100644 --- a/test/unit/transaction-unit.js +++ b/test/unit/transaction-unit.js @@ -1,3 +1,119 @@ /* Unit tests for the transaction.js library. */ + +// Public npm libraries +const assert = require('chai').assert +const sinon = require('sinon') + +const BCHJS = require('../../src/bch-js') +const bchjs = new BCHJS() + +const mockData = require('./fixtures/transaction-mock.js') + +describe('#TransactionLib', () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + describe('#get', () => { + it('should throw an error if txid is not specified', async () => { + try { + await bchjs.Transaction.get() + + assert.fail('Unexpected code path!') + } catch (err) { + assert.include( + err.message, + 'Input must be a string or array of strings.' + ) + } + }) + + it('should get details about a non-SLP transaction', async () => { + const txid = + '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7' + + // Mock dependencies + sandbox + .stub(bchjs.Transaction.rawTransaction, 'getTxData') + .resolves(mockData.nonSlpTxDetails) + + const result = await bchjs.Transaction.get(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + // Assert that there are stanardized properties. + assert.property(result, 'txid') + assert.property(result, 'vin') + assert.property(result, 'vout') + assert.property(result.vout[0], 'value') + assert.property(result.vout[0].scriptPubKey, 'addresses') + + // Assert that added properties exist. + assert.property(result.vin[0], 'address') + assert.property(result.vin[0], 'value') + assert.property(result, 'isValidSLPTx') + assert.equal(result.isValidSLPTx, false) + }) + + it('should get details about a SLP transaction', async () => { + // Mock dependencies + sandbox + .stub(bchjs.Transaction.rawTransaction, 'getTxData') + .resolves(mockData.slpTxDetails) + sandbox + .stub(bchjs.Transaction.slpUtils, 'decodeOpReturn') + .onCall(0) + .resolves(mockData.mockOpReturnData01) + .onCall(1) + .resolves(mockData.mockOpReturnData02) + .onCall(2) + .resolves(mockData.mockOpReturnData03) + .onCall(3) + .rejects(new Error('No OP_RETURN')) + sandbox + .stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid') + .resolves(true) + + const txid = + '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1' + + const result = await bchjs.Transaction.get(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + // Assert that there are stanardized properties. + assert.property(result, 'txid') + assert.property(result, 'vin') + assert.property(result, 'vout') + assert.property(result.vout[0], 'value') + assert.property(result.vout[1].scriptPubKey, 'addresses') + + // Assert that added properties exist. + assert.property(result.vout[0], 'tokenQty') + assert.equal(result.vout[0].tokenQty, null) + assert.property(result.vin[0], 'address') + assert.property(result.vin[0], 'value') + assert.property(result.vin[0], 'tokenQty') + assert.property(result, 'isValidSLPTx') + assert.equal(result.isValidSLPTx, true) + }) + + it('should catch and throw error on network error', async () => { + try { + const txid = + '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7' + + // Force an error + sandbox + .stub(bchjs.Transaction.rawTransaction, 'getTxData') + .rejects(new Error('test error')) + + await bchjs.Transaction.get(txid) + + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'test error') + } + }) + }) +})