diff --git a/src/routes/v3/blockbook.js b/src/routes/v3/blockbook.js index e946718..7ae57f8 100644 --- a/src/routes/v3/blockbook.js +++ b/src/routes/v3/blockbook.js @@ -393,7 +393,7 @@ async function transactionsFromBlockbook(txid) { // GET handler for single transaction details. async function txSingle(req, res, next) { try { - const address = req.params.txid + const txid = req.params.txid if (!txid || txid === "") { res.status(400) @@ -404,16 +404,22 @@ async function txSingle(req, res, next) { if (Array.isArray(txid)) { res.status(400) return res.json({ - error: "address can not be an array. Use POST for bulk upload." + error: "txid can not be an array. Use POST for bulk upload." + }) + } + + // TODO: Add regex comparison of txid to ensure it's valid. + if (txid.length !== 64) { + res.status(400) + return res.json({ + error: `txid must be of length 64 (not ${txid.length})` }) } wlogger.debug(`Executing blockbook/txSingle with this txid: `, txid) - // TODO: Add regex comparison of txid to ensure it's valid. - // Query the Blockbook Node API. - const retData = await transactionsFromBlockbook(address) + const retData = await transactionsFromBlockbook(txid) // Return the retrieved address information. res.status(200) @@ -468,6 +474,12 @@ async function txBulk(req, res, next) { } // TODO: Add regex comparison of txid to ensure it's valid. + if (thisTxid.length !== 64) { + res.status(400) + return res.json({ + error: `txid must be of length 64 (not ${thisTxid.length})` + }) + } } // Loops through each address and creates an array of Promises, querying diff --git a/test/v3/blockbook.js b/test/v3/blockbook.js index 557fc35..a0d4cfe 100644 --- a/test/v3/blockbook.js +++ b/test/v3/blockbook.js @@ -37,10 +37,9 @@ describe("#Blockbook Router", () => { process.env.BLOCKBOOK_URL = "http://fakeurl/api/" mockServerUrl = `http://fakeurl` } + // console.log(`Testing type is: ${process.env.TEST}`) if (!process.env.NETWORK) process.env.NETWORK = "testnet" - - // console.log(`Testing type is: ${process.env.TEST}`) }) // Setup the mocks before each test. @@ -589,4 +588,118 @@ describe("#Blockbook Router", () => { assert.equal(result.length, 2, "2 outputs for 2 inputs") }) }) + + describe("#txSingle", () => { + // route handler + const txSingle = blockbookRoute.testableComponents.txSingle + + it("should throw 400 if txid is empty", async () => { + const result = await txSingle(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "txid can not be empty") + }) + + it("should error on an array", async () => { + req.params.txid = [ + `5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392` + ] + + const result = await txSingle(req, res) + + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include( + result.error, + "txid can not be an array", + "Proper error message" + ) + }) + + it("should throw 400 if txid is not a valid txid", async () => { + req.params.txid = `abc` + + const result = await txSingle(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "txid must be of length 64") + }) + + it("should throw 500 when network issues", async () => { + const savedUrl = process.env.BLOCKBOOK_URL + + try { + req.params.txid = `5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392` + + // Switch the Insight URL to something that will error out. + process.env.BLOCKBOOK_URL = "http://fakeurl/api/" + + const result = await txSingle(req, res) + + // Restore the saved URL. + process.env.BLOCKBOOK_URL = savedUrl + + assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") + assert.include(result.error, "ENOTFOUND", "Error message expected") + } catch (err) { + // Restore the saved URL. + process.env.BLOCKBOOK_URL = savedUrl + } + }) + + it("should get tx details for a single txid", async () => { + req.params.txid = `5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392` + + // Mock the Insight URL for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.BLOCKBOOK_URL}`) + .get(uri => uri.includes("/")) + .reply(200, mockData.mockTx) + } + + // process.env.BLOCKBOOK_URL = `https://157.230.178.198:19131/` + // process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0 + + // Call the details API. + const result = await txSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "txid", + "version", + "vin", + "vout", + "blockHash", + "blockHeight", + "confirmations", + "blockTime", + "value", + "valueIn", + "fees", + "hex" + ]) + + // Vin + assert.isArray(result.vin) + assert.hasAnyKeys(result.vin[0], [ + "txid", + "sequence", + "n", + "addresses", + "value", + "hex" + ]) + + // Vout + assert.isArray(result.vout) + assert.hasAnyKeys(result.vout[0], [ + "value", + "n", + "spent", + "hex", + "addresses" + ]) + }) + }) }) diff --git a/test/v3/mocks/blockbook-mock.js b/test/v3/mocks/blockbook-mock.js index 1a3b89c..8246e80 100644 --- a/test/v3/mocks/blockbook-mock.js +++ b/test/v3/mocks/blockbook-mock.js @@ -28,7 +28,48 @@ const mockUtxos = [ } ] +const mockTx = { + txid: "5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392", + version: 2, + vin: [ + { + txid: "85ddb8215fc3701a493cf1c450644c5ef32c55aaa2f48ae2d008944394f3e4d3", + sequence: 4294967295, + n: 0, + addresses: ["bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35"], + value: "16983000648", + hex: + "47304402202378e55f4d02bb932498deef22dfc1f7a4984858c3b55017e225dd567172252e0220373d27710b5d42a72ac9725959f1605a912fee0ae86a7ad36e7d6d796f14ca29412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792" + } + ], + vout: [ + { + value: "16973000422", + n: 0, + spent: true, + hex: "76a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac", + addresses: ["bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35"] + }, + { + value: "10000000", + n: 1, + hex: "76a9140e5b4ad9008bb9a027b7e2d0ef958914e12db20788ac", + addresses: ["bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4"] + } + ], + blockHash: "00000000005242edac4635ac2375a454e801cc1be8b131b622328089731e5e30", + blockHeight: 1265275, + confirmations: 65402, + blockTime: 1540912733, + value: "16983000422", + valueIn: "16983000648", + fees: "226", + hex: + "0200000001d3e4f394439408d0e28af4a2aa552cf35e4c6450c4f13c491a70c35f21b8dd85000000006a47304402202378e55f4d02bb932498deef22dfc1f7a4984858c3b55017e225dd567172252e0220373d27710b5d42a72ac9725959f1605a912fee0ae86a7ad36e7d6d796f14ca29412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792ffffffff02e66eabf3030000001976a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac80969800000000001976a9140e5b4ad9008bb9a027b7e2d0ef958914e12db20788ac00000000" +} + module.exports = { mockBalance, - mockUtxos + mockUtxos, + mockTx }