From 161c31a39774e67c6cf6823e5285d3bcb22eaad3 Mon Sep 17 00:00:00 2001 From: danielhumgon Date: Wed, 4 Mar 2020 20:33:20 -0400 Subject: [PATCH] refactor(libraries): Refactored control, mining and raw transactions --- src/app.js | 9 +- src/routes/v3/full-node/control.js | 177 ++-- src/routes/v3/full-node/mining.js | 274 +++--- src/routes/v3/full-node/rawtransactions.js | 920 +++++++++------------ test/v3/control.js | 66 +- test/v3/mining.js | 103 ++- test/v3/raw-transactions.js | 633 ++++++++++---- 7 files changed, 1236 insertions(+), 946 deletions(-) diff --git a/src/app.js b/src/app.js index b96df5b..b317fc8 100644 --- a/src/app.js +++ b/src/app.js @@ -25,10 +25,10 @@ const jwtAuth = require('./middleware/jwt-auth') // v3 const healthCheckV3 = require('./routes/v3/health-check') const BlockchainV3 = require('./routes/v3/full-node/blockchain') -const controlV3 = require('./routes/v3/full-node/control') -const miningV3 = require('./routes/v3/full-node/mining') +const ControlV3 = require('./routes/v3/full-node/control') +const MiningV3 = require('./routes/v3/full-node/mining') const networkV3 = require('./routes/v3/full-node/network') -const rawtransactionsV3 = require('./routes/v3/full-node/rawtransactions') +const RawtransactionsV3 = require('./routes/v3/full-node/rawtransactions') const utilV3 = require('./routes/v3/util') const slpV3 = require('./routes/v3/slp') const xpubV3 = require('./routes/v3/xpub') @@ -39,6 +39,9 @@ require('dotenv').config() // Instantiate route libraries. const blockchainV3 = new BlockchainV3() +const controlV3 = new ControlV3() +const miningV3 = new MiningV3() +const rawtransactionsV3 = new RawtransactionsV3() const app = express() diff --git a/src/routes/v3/full-node/control.js b/src/routes/v3/full-node/control.js index 607177e..886bd87 100644 --- a/src/routes/v3/full-node/control.js +++ b/src/routes/v3/full-node/control.js @@ -2,101 +2,114 @@ const express = require('express') const router = express.Router() -// const axios = require('axios') -const routeUtils = require('../route-utils') +const axios = require('axios') + +// const routeUtils = require('../route-utils') const wlogger = require('../../../util/winston-logging') +const RouteUtils = require('../route-utils2') +const routeUtils = new RouteUtils() + // Used for processing error messages before sending them to the user. const util = require('util') util.inspect.defaultOptions = { depth: 1 } -router.get('/', root) -router.get('/getnetworkinfo', getNetworkInfo) +let _this -function root (req, res, next) { - return res.json({ status: 'control' }) -} +class Control { + constructor () { + _this = this + this.axios = axios + this.routeUtils = routeUtils -/** - * @api {get} /control/getnetworkinfo Get Network Info - * @apiName GetNetworkInfo - * @apiGroup Control - * @apiDescription RPC call which gets basic full node information. - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/control/getnetworkinfo" -H "accept: application/json" - * - */ -async function getNetworkInfo (req, res, next) { - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() + this.router = router + this.router.get('/', this.root) + this.router.get('/getNetworkInfo', this.getNetworkInfo) + } - requestConfig.data.id = 'getnetworkinfo' - requestConfig.data.method = 'getnetworkinfo' - requestConfig.data.params = [] + root (req, res, next) { + return res.json({ status: 'control' }) + } - try { - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (error) { - wlogger.error('Error in control.ts/getNetworkInfo().', error) - - // Write out error to error log. - // logger.error(`Error in control/getInfo: `, error) + // DRY error handler. + errorHandler (err, res) { + // Attempt to decode the error message. + const { msg, status } = _this.routeUtils.decodeError(err) + if (msg) { + res.status(status) + return res.json({ error: msg }) + } res.status(500) - if (error.response && error.response.data && error.response.data.error) { return res.json({ error: error.response.data.error }) } - return res.json({ error: util.inspect(error) }) + return res.json({ error: util.inspect(err) }) } + + /** + * @api {get} /control/getnetworkinfo Get Network Info + * @apiName GetNetworkInfo + * @apiGroup Control + * @apiDescription RPC call which gets basic full node information. + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/control/getnetworkinfo" -H "accept: application/json" + * + */ + async getNetworkInfo (req, res, next) { + // Axios options + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = 'getnetworkinfo' + options.data.method = 'getnetworkinfo' + options.data.params = [] + + try { + // const response = await BitboxHTTP(requestConfig) + const response = await this.axios.request(options) + + return res.json(response.data.result) + } catch (error) { + wlogger.error('Error in control.ts/getNetworkInfo().', error) + + return this.errorHandler(error, res) + } + } + // router.get('/getMemoryInfo', (req, res, next) => { + // BitboxHTTP({ + // method: 'post', + // auth: { + // username: username, + // password: password + // }, + // data: { + // jsonrpc: "1.0", + // id:"getmemoryinfo", + // method: "getmemoryinfo" + // } + // }) + // .then((response) => { + // res.json(response.data.result); + // }) + // .catch((error) => { + // res.send(error.response.data.error.message); + // }); + // }); + // + // router.get('/help', (req, res, next) => { + // BITBOX.Control.help() + // .then((result) => { + // res.json(result); + // }, (err) => { console.log(err); + // }); + // }); + // + // router.post('/stop', (req, res, next) => { + // BITBOX.Control.stop() + // .then((result) => { + // res.json(result); + // }, (err) => { console.log(err); + // }); + // }); } -// router.get('/getMemoryInfo', (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"getmemoryinfo", -// method: "getmemoryinfo" -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -// -// router.get('/help', (req, res, next) => { -// BITBOX.Control.help() -// .then((result) => { -// res.json(result); -// }, (err) => { console.log(err); -// }); -// }); -// -// router.post('/stop', (req, res, next) => { -// BITBOX.Control.stop() -// .then((result) => { -// res.json(result); -// }, (err) => { console.log(err); -// }); -// }); - -module.exports = { - router, - testableComponents: { - root, - getNetworkInfo - } -} +module.exports = Control diff --git a/src/routes/v3/full-node/mining.js b/src/routes/v3/full-node/mining.js index c68ce18..b952d4d 100644 --- a/src/routes/v3/full-node/mining.js +++ b/src/routes/v3/full-node/mining.js @@ -2,107 +2,102 @@ const express = require('express') const router = express.Router() -// const axios = require('axios') +const axios = require('axios') + +const RouteUtils = require('../route-utils2') +const routeUtils = new RouteUtils() -const routeUtils = require('../route-utils') const wlogger = require('../../../util/winston-logging') // Used to convert error messages to strings, to safely pass to users. const util = require('util') util.inspect.defaultOptions = { depth: 1 } -// const BitboxHTTP = axios.create({ -// baseURL: process.env.RPC_BASEURL -// }) -// const username = process.env.RPC_USERNAME -// const password = process.env.RPC_PASSWORD +let _this +class Mining { + constructor () { + _this = this + this.axios = axios + this.routeUtils = routeUtils -// const requestConfig = { -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: '1.0' -// } -// } + this.router = router + this.router.get('/', this.root) + this.router.get('/getMiningInfo', this.getMiningInfo) + this.router.get('/getNetworkHashPS', this.getNetworkHashPS) + } -router.get('/', root) -router.get('/getMiningInfo', getMiningInfo) -router.get('/getNetworkHashps', getNetworkHashPS) + root (req, res, next) { + return res.json({ status: 'mining' }) + } -function root (req, res, next) { - return res.json({ status: 'mining' }) -} - -// -// router.get('/getBlockTemplate/:templateRequest', (req, res, next) => { -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"getblocktemplate", -// method: "getblocktemplate", -// params: [ -// req.params.templateRequest -// ] -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); -/** - * @api {get} /mining/getMiningInfo Get Mining Info. - * @apiName Mining info. - * @apiGroup Mining - * @apiDescription Returns a json object containing mining-related information. - * - * - * @apiExample Example usage: - * curl -X GET "https://mainnet.bchjs.cash/v3/mining/getMiningInfo" -H "accept: application/json" - * - * - */ -async function getMiningInfo (req, res, next) { - try { - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = 'getmininginfo' - requestConfig.data.method = 'getmininginfo' - requestConfig.data.params = [] - - const response = await BitboxHTTP(requestConfig) - - return res.json(response.data.result) - } catch (err) { + // DRY error handler. + errorHandler (err, res) { // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) + const { msg, status } = _this.routeUtils.decodeError(err) if (msg) { res.status(status) return res.json({ error: msg }) } - wlogger.error('Error in mining.ts/getMiningInfo().', err) - res.status(500) return res.json({ error: util.inspect(err) }) } -} -/** + + // router.get('/getBlockTemplate/:templateRequest', (req, res, next) => { + // BitboxHTTP({ + // method: 'post', + // auth: { + // username: username, + // password: password + // }, + // data: { + // jsonrpc: "1.0", + // id:"getblocktemplate", + // method: "getblocktemplate", + // params: [ + // req.params.templateRequest + // ] + // } + // }) + // .then((response) => { + // res.json(response.data.result); + // }) + // .catch((error) => { + // res.send(error.response.data.error.message); + // }); + // }); + + /** + * @api {get} /mining/getMiningInfo Get Mining Info. + * @apiName Mining info. + * @apiGroup Mining + * @apiDescription Returns a json object containing mining-related information. + * + * + * @apiExample Example usage: + * curl -X GET "https://mainnet.bchjs.cash/v3/mining/getMiningInfo" -H "accept: application/json" + * + * + */ + async getMiningInfo (req, res, next) { + try { + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = 'getmininginfo' + options.data.method = 'getmininginfo' + options.data.params = [] + + const response = await _this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + wlogger.error('Error in mining.ts/getMiningInfo().', err) + + return this.errorHandler(err, res) + } + } + + /** * @api {get} /mining/getNetworkHashps?nblocks=&height= Get Estimated network hashes per second. * @apiName Estimated network hashes per second. * @apiGroup Mining @@ -114,78 +109,59 @@ async function getMiningInfo (req, res, next) { * * */ -async function getNetworkHashPS (req, res, next) { - try { - let nblocks = 120 // Default - let height = -1 // Default - if (req.query.nblocks) nblocks = parseInt(req.query.nblocks) - if (req.query.height) height = parseInt(req.query.height) - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() + async getNetworkHashPS (req, res, next) { + try { + let nblocks = 120 // Default + let height = -1 // Default + if (req.query.nblocks) nblocks = parseInt(req.query.nblocks) + if (req.query.height) height = parseInt(req.query.height) - requestConfig.data.id = 'getnetworkhashps' - requestConfig.data.method = 'getnetworkhashps' - requestConfig.data.params = [nblocks, height] + const options = _this.routeUtils.getAxiosOptions() - const response = await BitboxHTTP(requestConfig) + options.data.id = 'getnetworkhashps' + options.data.method = 'getnetworkhashps' + options.data.params = [nblocks, height] - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) + const response = await this.axios.request(options) + + return res.json(response.data.result) + } catch (err) { + wlogger.error('Error in mining.ts/getNetworkHashPS().', err) + + return this.errorHandler(err, res) } - - wlogger.error('Error in mining.ts/getNetworkHashPS().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) } + + // router.post('/submitBlock/:hex', (req, res, next) => { + // let parameters = ''; + // if(req.query.parameters && req.query.parameters !== '') { + // parameters = true; + // } + // + // BitboxHTTP({ + // method: 'post', + // auth: { + // username: username, + // password: password + // }, + // data: { + // jsonrpc: "1.0", + // id:"submitblock", + // method: "submitblock", + // params: [ + // req.params.hex, + // parameters + // ] + // } + // }) + // .then((response) => { + // res.json(response.data.result); + // }) + // .catch((error) => { + // res.send(error.response.data.error.message); + // }); + // }); } -// -// router.post('/submitBlock/:hex', (req, res, next) => { -// let parameters = ''; -// if(req.query.parameters && req.query.parameters !== '') { -// parameters = true; -// } -// -// BitboxHTTP({ -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: "1.0", -// id:"submitblock", -// method: "submitblock", -// params: [ -// req.params.hex, -// parameters -// ] -// } -// }) -// .then((response) => { -// res.json(response.data.result); -// }) -// .catch((error) => { -// res.send(error.response.data.error.message); -// }); -// }); - -module.exports = { - router, - testableComponents: { - root, - getMiningInfo, - getNetworkHashPS - } -} +module.exports = Mining diff --git a/src/routes/v3/full-node/rawtransactions.js b/src/routes/v3/full-node/rawtransactions.js index ac88b0b..2a38691 100644 --- a/src/routes/v3/full-node/rawtransactions.js +++ b/src/routes/v3/full-node/rawtransactions.js @@ -4,47 +4,54 @@ const express = require('express') const router = express.Router() const axios = require('axios') -const routeUtils = require('../route-utils') +const RouteUtils = require('../route-utils2') +const routeUtils = new RouteUtils() + const wlogger = require('../../../util/winston-logging') // Used to convert error messages to strings, to safely pass to users. const util = require('util') util.inspect.defaultOptions = { depth: 1 } -// const BitboxHTTP = axios.create({ -// baseURL: process.env.RPC_BASEURL -// }) -// const username = process.env.RPC_USERNAME -// const password = process.env.RPC_PASSWORD +let _this +class RawTransactions { + constructor () { + _this = this + this.axios = axios + this.routeUtils = routeUtils -// const requestConfig = { -// method: 'post', -// auth: { -// username: username, -// password: password -// }, -// data: { -// jsonrpc: '1.0' -// } -// } + this.router = router + this.router.get('/', this.root) + this.router.get('/decodeRawTransaction/:hex', this.decodeRawTransactionSingle) + this.router.post('/decodeRawTransaction', this.decodeRawTransactionBulk) + this.router.get('/decodeScript/:hex', this.decodeScriptSingle) + this.router.post('/decodeScript', this.decodeScriptBulk) + this.router.post('/getRawTransaction', this.getRawTransactionBulk) + this.router.get('/getRawTransaction/:txid', this.getRawTransactionSingle) + this.router.post('/sendRawTransaction', this.sendRawTransactionBulk) + this.router.get('/sendRawTransaction/:hex', this.sendRawTransactionSingle) + } -router.get('/', root) -router.get('/decodeRawTransaction/:hex', decodeRawTransactionSingle) -router.post('/decodeRawTransaction', decodeRawTransactionBulk) -router.get('/decodeScript/:hex', decodeScriptSingle) -router.post('/decodeScript', decodeScriptBulk) -router.post('/getRawTransaction', getRawTransactionBulk) -router.get('/getRawTransaction/:txid', getRawTransactionSingle) -router.post('/sendRawTransaction', sendRawTransactionBulk) -router.get('/sendRawTransaction/:hex', sendRawTransactionSingle) + root (req, res, next) { + return res.json({ status: 'rawtransactions' }) + } -function root (req, res, next) { - return res.json({ status: 'rawtransactions' }) -} + // DRY error handler. + errorHandler (err, res) { + // Attempt to decode the error message. + const { msg, status } = _this.routeUtils.decodeError(err) + if (msg) { + res.status(status) + return res.json({ error: msg }) + } -// Decode transaction hex into a JSON object. -// GET -/** + res.status(500) + return res.json({ error: util.inspect(err) }) + } + + // Decode transaction hex into a JSON object. + // GET + /** * @api {get} /rawtransactions/decodeRawTransaction/{hex} Decode Single Raw Transaction. * @apiName Decode Single Raw Transaction * @apiGroup Raw Transaction @@ -56,49 +63,34 @@ function root (req, res, next) { * * */ -async function decodeRawTransactionSingle (req, res, next) { - try { - const hex = req.params.hex + async decodeRawTransactionSingle (req, res, next) { + try { + const hex = req.params.hex - // Throw an error if hex is empty. - if (!hex || hex === '') { - res.status(400) - return res.json({ error: 'hex can not be empty' }) + // Throw an error if hex is empty. + if (!hex || hex === '') { + res.status(400) + return res.json({ error: 'hex can not be empty' }) + } + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = 'decoderawtransaction' + options.data.method = 'decoderawtransaction' + options.data.params = [hex] + + const response = await _this.axios.request(options) + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) + wlogger.error( + 'Error in rawtransactions.ts/decodeRawTransactionSingle().', + err + ) + return this.errorHandler(err, res) } - - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = 'decoderawtransaction' - requestConfig.data.method = 'decoderawtransaction' - requestConfig.data.params = [hex] - - const response = await BitboxHTTP(requestConfig) - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error( - 'Error in rawtransactions.ts/decodeRawTransactionSingle().', - err - ) - - res.status(500) - return res.json({ error: util.inspect(err) }) } -} -/** + /** * @api {post} /rawtransactions/decodeRawTransaction Decode Bulk Raw Transactions. * @apiName Decode Bulk Raw Transactions * @apiGroup Raw Transaction @@ -111,119 +103,101 @@ async function decodeRawTransactionSingle (req, res, next) { * */ -async function decodeRawTransactionBulk (req, res, next) { - try { - const hexes = req.body.hexes + async decodeRawTransactionBulk (req, res, next) { + try { + const hexes = req.body.hexes - if (!Array.isArray(hexes)) { - res.status(400) - return res.json({ error: 'hexes must be an array' }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, hexes)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // const results = [] - - // Validate each element in the address array. - for (let i = 0; i < hexes.length; i++) { - const thisHex = hexes[i] - - // Reject if id is empty - if (!thisHex || thisHex === '') { + if (!Array.isArray(hexes)) { res.status(400) - return res.json({ error: 'Encountered empty hex' }) + return res.json({ error: 'hexes must be an array' }) } - } - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, hexes)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: 'Array too large.' + }) + } - // Loop through each height and creates an array of requests to call in parallel - const promises = hexes.map(async hex => { - requestConfig.data.id = 'decoderawtransaction' - requestConfig.data.method = 'decoderawtransaction' - requestConfig.data.params = [hex] + // const results = [] - return BitboxHTTP(requestConfig) - }) + // Validate each element in the address array. + for (let i = 0; i < hexes.length; i++) { + const thisHex = hexes[i] - // Wait for all parallel Insight requests to return. - const axiosResult = await axios.all(promises) + // Reject if id is empty + if (!thisHex || thisHex === '') { + res.status(400) + return res.json({ error: 'Encountered empty hex' }) + } + } - // Retrieve the data part of the result. - const result = axiosResult.map(x => x.data.result) + const options = _this.routeUtils.getAxiosOptions() - res.status(200) - return res.json(result) + // Loop through each height and creates an array of requests to call in parallel + const promises = hexes.map(async hex => { + options.data.id = 'decoderawtransaction' + options.data.method = 'decoderawtransaction' + options.data.params = [hex] + + return _this.axios.request(options) + }) + + // Wait for all parallel Insight requests to return. + const axiosResult = await _this.axios.all(promises) + + // Retrieve the data part of the result. + const result = axiosResult.map(x => x.data.result) + + res.status(200) + return res.json(result) /* - // Loop through each hex and creates an array of requests to call in parallel - hexes = hexes.map(async (hex: any) => { - if (!hex || hex === "") { - res.status(400) - return res.json({ error: "Encountered empty hex" }) - } - - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = "decoderawtransaction" - requestConfig.data.method = "decoderawtransaction" - requestConfig.data.params = [hex] - - return await BitboxHTTP(requestConfig) - }) - - const result: Array = [] - return axios.all(hexes).then( - axios.spread((...args) => { - args.forEach((arg: any) => { - if (arg) { - result.push(arg.data.result) + // Loop through each hex and creates an array of requests to call in parallel + hexes = hexes.map(async (hex: any) => { + if (!hex || hex === "") { + res.status(400) + return res.json({ error: "Encountered empty hex" }) } + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + requestConfig.data.id = "decoderawtransaction" + requestConfig.data.method = "decoderawtransaction" + requestConfig.data.params = [hex] + return await BitboxHTTP(requestConfig) }) - res.status(200) - return res.json(result) - }) - ) -*/ - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) + const result: Array = [] + return axios.all(hexes).then( + axios.spread((...args) => { + args.forEach((arg: any) => { + if (arg) { + result.push(arg.data.result) + } + }) + res.status(200) + return res.json(result) + }) + ) + */ + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/getRawTransaction: `, err) + wlogger.error( + 'Error in rawtransactions.ts/decodeRawTransactionBulk().', + err + ) + return this.errorHandler(err, res) } - - // Write out error to error log. - // logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error( - 'Error in rawtransactions.ts/decodeRawTransactionBulk().', - err - ) - - res.status(500) - return res.json({ error: util.inspect(err) }) } -} -// Decode a raw transaction from hex to assembly. -// GET single -/** + // Decode a raw transaction from hex to assembly. + // GET single + /** * @api {get} /rawtransactions/decodeScript/{hex} Decode Single Script. * @apiName Decode Single Script * @apiGroup Raw Transaction @@ -235,49 +209,36 @@ async function decodeRawTransactionBulk (req, res, next) { * * */ -async function decodeScriptSingle (req, res, next) { - try { - const hex = req.params.hex + async decodeScriptSingle (req, res, next) { + try { + const hex = req.params.hex - // Throw an error if hex is empty. - if (!hex || hex === '') { - res.status(400) - return res.json({ error: 'hex can not be empty' }) + // Throw an error if hex is empty. + if (!hex || hex === '') { + res.status(400) + return res.json({ error: 'hex can not be empty' }) + } + + const options = _this.routeUtils.getAxiosOptions() + + options.data.id = 'decodescript' + options.data.method = 'decodescript' + options.data.params = [hex] + + const response = await _this.axios.request(options) + return res.json(response.data.result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeScript: `, err) + wlogger.error('Error in rawtransactions.ts/decodeScriptSingle().', err) + + return this.errorHandler(err, res) } - - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = 'decodescript' - requestConfig.data.method = 'decodescript' - requestConfig.data.params = [hex] - - const response = await BitboxHTTP(requestConfig) - return res.json(response.data.result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeScript: `, err) - wlogger.error('Error in rawtransactions.ts/decodeScriptSingle().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) } -} -// Decode a raw transaction from hex to assembly. -// POST bulk -/** + // Decode a raw transaction from hex to assembly. + // POST bulk + /** * @api {post} /rawtransactions/decodeScript Bulk Decode Script. * @apiName Bulk Decode Script * @apiGroup Raw Transaction @@ -289,104 +250,85 @@ async function decodeScriptSingle (req, res, next) { * * */ -async function decodeScriptBulk (req, res, next) { - try { - const hexes = req.body.hexes + async decodeScriptBulk (req, res, next) { + try { + const hexes = req.body.hexes - // Validation - if (!Array.isArray(hexes)) { - res.status(400) - return res.json({ error: 'hexes must be an array' }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, hexes)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Validate each hex in the array - for (let i = 0; i < hexes.length; i++) { - const hex = hexes[i] - - // Throw an error if hex is empty. - if (!hex || hex === '') { + // Validation + if (!Array.isArray(hexes)) { res.status(400) - return res.json({ error: 'Encountered empty hex' }) + return res.json({ error: 'hexes must be an array' }) } + + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, hexes)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: 'Array too large.' + }) + } + + // Validate each hex in the array + for (let i = 0; i < hexes.length; i++) { + const hex = hexes[i] + + // Throw an error if hex is empty. + if (!hex || hex === '') { + res.status(400) + return res.json({ error: 'Encountered empty hex' }) + } + } + + const options = _this.routeUtils.getAxiosOptions() + + // Loop through each hex and create an array of promises + const promises = hexes.map(async hex => { + options.data.id = 'decodescript' + options.data.method = 'decodescript' + options.data.params = [hex] + + const response = await _this.axios.request(options) + return response + }) + + // Wait for all parallel promises to return. + const resolved = await Promise.all(promises) + + // Retrieve the data from each resolved promise. + const result = resolved.map(x => x.data.result) + + res.status(200) + return res.json(result) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/decodeScript: `, err) + wlogger.error('Error in rawtransactions.ts/decodeScriptBulk().', err) + return this.errorHandler(err, res) } + } - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() + // Retrieve raw transactions details from the full node. - // Loop through each hex and create an array of promises - const promises = hexes.map(async hex => { - requestConfig.data.id = 'decodescript' - requestConfig.data.method = 'decodescript' - requestConfig.data.params = [hex] + async getRawTransactionsFromNode (txid, verbose) { + try { + const options = _this.routeUtils.getAxiosOptions() - const response = await BitboxHTTP(requestConfig) - return response - }) + options.data.id = 'getrawtransaction' + options.data.method = 'getrawtransaction' + options.data.params = [txid, verbose] - // Wait for all parallel promises to return. - const resolved = await Promise.all(promises) + const response = await _this.axios.request(options) - // Retrieve the data from each resolved promise. - const result = resolved.map(x => x.data.result) - - res.status(200) - return res.json(result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) + return response.data.result + } catch (err) { + wlogger.error('Error in rawtransactions.ts/getRawTransactionsFromNode().') + throw err } - - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeScript: `, err) - wlogger.error('Error in rawtransactions.ts/decodeScriptBulk().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) } -} -// Retrieve raw transactions details from the full node. - -async function getRawTransactionsFromNode (txid, verbose) { - try { - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() - - requestConfig.data.id = 'getrawtransaction' - requestConfig.data.method = 'getrawtransaction' - requestConfig.data.params = [txid, verbose] - - const response = await BitboxHTTP(requestConfig) - - return response.data.result - } catch (err) { - wlogger.error('Error in rawtransactions.ts/getRawTransactionsFromNode().') - throw err - } -} - -// Get a JSON object breakdown of transaction details. -// POST -/** + // Get a JSON object breakdown of transaction details. + // POST + /** * @api {post} /rawtransactions/getRawTransaction Get Bulk Raw Transactions. * @apiName Get Bulk Raw Transactions. * @apiGroup Raw Transaction @@ -397,80 +339,71 @@ async function getRawTransactionsFromNode (txid, verbose) { * curl -X POST "https://mainnet.bchjs.cash/v3/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}' * */ -async function getRawTransactionBulk (req, res, next) { - try { - let verbose = 0 - if (req.body.verbose) verbose = 1 + async getRawTransactionBulk (req, res, next) { + try { + let verbose = 0 + if (req.body.verbose) verbose = 1 - const txids = req.body.txids - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ error: 'txids must be an array' }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, txids)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // stub response object - // const returnResponse = { - // status: 100, - // json: { - // error: '' - // } - // } - - // Validate each txid in the array. - for (let i = 0; i < txids.length; i++) { - const txid = txids[i] - - if (!txid || txid === '') { + const txids = req.body.txids + if (!Array.isArray(txids)) { res.status(400) - return res.json({ error: 'Encountered empty TXID' }) + return res.json({ error: 'txids must be an array' }) } - if (txid.length !== 64) { - res.status(400) + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, txids)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 return res.json({ - error: `parameter 1 must be of length 64 (not ${txid.length})` + error: 'Array too large.' }) } + + // stub response object + // const returnResponse = { + // status: 100, + // json: { + // error: '' + // } + // } + + // Validate each txid in the array. + for (let i = 0; i < txids.length; i++) { + const txid = txids[i] + + if (!txid || txid === '') { + res.status(400) + return res.json({ error: 'Encountered empty TXID' }) + } + + if (txid.length !== 64) { + res.status(400) + return res.json({ + error: `parameter 1 must be of length 64 (not ${txid.length})` + }) + } + } + + // Loop through each txid and create an array of promises + const promises = txids.map(async txid => + _this.getRawTransactionsFromNode(txid, verbose) + ) + + // Wait for all parallel promises to return. + const axiosResult = await _this.axios.all(promises) + + res.status(200) + return res.json(axiosResult) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/getRawTransaction: `, err) + wlogger.error('Error in rawtransactions.ts/getRawTransactionBulk().', err) + return this.errorHandler(err, res) } - - // Loop through each txid and create an array of promises - const promises = txids.map(async txid => - getRawTransactionsFromNode(txid, verbose) - ) - - // Wait for all parallel promises to return. - const axiosResult = await axios.all(promises) - - res.status(200) - return res.json(axiosResult) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - // logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error('Error in rawtransactions.ts/getRawTransactionBulk().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) } -} -// Get a JSON object breakdown of transaction details. -// GET -/** + // Get a JSON object breakdown of transaction details. + // GET + /** * @api {get} /rawtransactions/getRawTransaction/{txid} Return the raw transaction data. * @apiName Get Raw Transaction * @apiGroup Raw Transaction @@ -482,39 +415,31 @@ async function getRawTransactionBulk (req, res, next) { * * */ -async function getRawTransactionSingle (req, res, next) { - try { - let verbose = 0 - if (req.query.verbose === 'true') verbose = 1 + async getRawTransactionSingle (req, res, next) { + try { + let verbose = 0 + if (req.query.verbose === 'true') verbose = 1 - const txid = req.params.txid - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) + const txid = req.params.txid + if (!txid || txid === '') { + res.status(400) + return res.json({ error: 'txid can not be empty' }) + } + + const data = await _this.getRawTransactionsFromNode(txid, verbose) + + return res.json(data) + } catch (err) { + // Write out error to error log. + // logger.error(`Error in rawtransactions/getRawTransaction: `, err) + wlogger.error('Error in rawtransactions.ts/getRawTransactionSingle().', err) + + return this.errorHandler(err, res) } - - const data = await getRawTransactionsFromNode(txid, verbose) - - return res.json(data) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Write out error to error log. - // logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error('Error in rawtransactions.ts/getRawTransactionSingle().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) } -} -// Transmit a raw transaction to the BCH network. -/** + // Transmit a raw transaction to the BCH network. + /** * @api {post} /rawtransactions/sendRawTransaction Send Bulk Raw Transactions. * @apiName Send Bulk Raw Transactions * @apiGroup Raw Transaction @@ -526,102 +451,85 @@ async function getRawTransactionSingle (req, res, next) { * * */ -async function sendRawTransactionBulk (req, res, next) { - try { + async sendRawTransactionBulk (req, res, next) { + try { // Validation - const hexes = req.body.hexes + const hexes = req.body.hexes - // Reject if input is not an array - if (!Array.isArray(hexes)) { - res.status(400) - return res.json({ error: 'hex must be an array' }) - } - - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, hexes)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Validate each element - for (let i = 0; i < hexes.length; i++) { - const hex = hexes[i] - - if (hex === '') { + // Reject if input is not an array + if (!Array.isArray(hexes)) { res.status(400) + return res.json({ error: 'hex must be an array' }) + } + + const options = _this.routeUtils.getAxiosOptions() + + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, hexes)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 return res.json({ - error: 'Encountered empty hex' + error: 'Array too large.' }) } - } - // Dev Note CT 1/31/2019: - // Sending the 'sendrawtrnasaction' RPC call to a full node in parallel will - // not work. Testing showed that the full node will return the same TXID for - // different TX hexes. I believe this is by design, to prevent double spends. - // In parallel, we are essentially asking the node to broadcast a new TX before - // it's finished broadcast the previous one. Serial execution is required. + // Validate each element + for (let i = 0; i < hexes.length; i++) { + const hex = hexes[i] - // How to send TX hexes in parallel the WRONG WAY: - /* + if (hex === '') { + res.status(400) + return res.json({ + error: 'Encountered empty hex' + }) + } + } + + // Dev Note CT 1/31/2019: + // Sending the 'sendrawtrnasaction' RPC call to a full node in parallel will + // not work. Testing showed that the full node will return the same TXID for + // different TX hexes. I believe this is by design, to prevent double spends. + // In parallel, we are essentially asking the node to broadcast a new TX before + // it's finished broadcast the previous one. Serial execution is required. + + // How to send TX hexes in parallel the WRONG WAY: + /* // Collect an array of promises. const promises = hexes.map(async (hex: any) => { requestConfig.data.id = "sendrawtransaction" requestConfig.data.method = "sendrawtransaction" requestConfig.data.params = [hex] - return await BitboxHTTP(requestConfig) }) - // Wait for all parallel Insight requests to return. const axiosResult: Array = await axios.all(promises) - // Retrieve the data part of the result. const result = axiosResult.map(x => x.data.result) */ - // Sending them serially. - const result = [] - for (let i = 0; i < hexes.length; i++) { - const hex = hexes[i] + // Sending them serially. + const result = [] + for (let i = 0; i < hexes.length; i++) { + const hex = hexes[i] - requestConfig.data.id = 'sendrawtransaction' - requestConfig.data.method = 'sendrawtransaction' - requestConfig.data.params = [hex] + options.data.id = 'sendrawtransaction' + options.data.method = 'sendrawtransaction' + options.data.params = [hex] - const rpcResult = await BitboxHTTP(requestConfig) + const rpcResult = await _this.axios.request(options) - result.push(rpcResult.data.result) + result.push(rpcResult.data.result) + } + + res.status(200) + return res.json(result) + } catch (err) { + wlogger.error('Error in rawtransactions.ts/sendRawTransactionBulk().', err) + return this.errorHandler(err, res) } - - res.status(200) - return res.json(result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - wlogger.error('Error in rawtransactions.ts/sendRawTransactionBulk().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) } -} -// Transmit a raw transaction to the BCH network. -/** + // Transmit a raw transaction to the BCH network. + /** * @api {get} /rawtransactions/sendRawTransaction/{hex} Send Single Raw Transaction. * @apiName Send Single Raw Transaction * @apiGroup Raw Transaction @@ -633,72 +541,44 @@ async function sendRawTransactionBulk (req, res, next) { * * */ -async function sendRawTransactionSingle (req, res, next) { - try { - const hex = req.params.hex // URL parameter + async sendRawTransactionSingle (req, res, next) { + try { + const hex = req.params.hex // URL parameter - // Reject if input is not an array or a string - if (typeof hex !== 'string') { - res.status(400) - return res.json({ error: 'hex must be a string' }) + // Reject if input is not an array or a string + if (typeof hex !== 'string') { + res.status(400) + return res.json({ error: 'hex must be a string' }) + } + + // Validation + if (hex === '') { + res.status(400) + return res.json({ + error: 'Encountered empty hex' + }) + } + + const options = _this.routeUtils.getAxiosOptions() + + // RPC call + options.data.id = 'sendrawtransaction' + options.data.method = 'sendrawtransaction' + options.data.params = [hex] + + const rpcResult = await _this.axios.request(options) + + const result = rpcResult.data.result + + res.status(200) + return res.json(result) + } catch (err) { + wlogger.error( + 'Error in rawtransactions.ts/sendRawTransactionSingle().', + err + ) + return this.errorHandler(err, res) } - - // Validation - if (hex === '') { - res.status(400) - return res.json({ - error: 'Encountered empty hex' - }) - } - - const { - BitboxHTTP, - // username, - // password, - requestConfig - } = routeUtils.setEnvVars() - - // RPC call - requestConfig.data.id = 'sendrawtransaction' - requestConfig.data.method = 'sendrawtransaction' - requestConfig.data.params = [hex] - - const rpcResult = await BitboxHTTP(requestConfig) - - const result = rpcResult.data.result - - res.status(200) - return res.json(result) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - wlogger.error( - 'Error in rawtransactions.ts/sendRawTransactionSingle().', - err - ) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -module.exports = { - router, - getRawTransactionsFromNode, - testableComponents: { - root, - decodeRawTransactionSingle, - decodeRawTransactionBulk, - decodeScriptSingle, - decodeScriptBulk, - getRawTransactionBulk, - getRawTransactionSingle, - sendRawTransactionBulk, - sendRawTransactionSingle } } +module.exports = RawTransactions diff --git a/test/v3/control.js b/test/v3/control.js index 313349a..e37fcbc 100644 --- a/test/v3/control.js +++ b/test/v3/control.js @@ -11,8 +11,9 @@ const chai = require('chai') const assert = chai.assert -const controlRoute = require('../../src/routes/v3/full-node/control') -const nock = require('nock') // HTTP mocking +const ControlRoute = require('../../src/routes/v3/full-node/control') +const uut = new ControlRoute() +const sinon = require('sinon') let originalEnvVars // Used during transition from integration to unit tests. @@ -26,6 +27,7 @@ util.inspect.defaultOptions = { depth: 1 } describe('#ControlRouter', () => { let req, res + let sandbox before(() => { // Save existing environment variables. @@ -37,13 +39,6 @@ describe('#ControlRouter', () => { } // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - if (process.env.TEST === 'unit') { - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api' - process.env.RPC_USERNAME = 'fakeusername' - process.env.RPC_PASSWORD = 'fakepassword' - } }) // Setup the mocks before each test. @@ -57,14 +52,12 @@ describe('#ControlRouter', () => { req.body = {} req.query = {} - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() + sandbox = sinon.createSandbox() }) afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() + // Restore Sandbox + sandbox.restore() }) after(() => { @@ -77,10 +70,10 @@ describe('#ControlRouter', () => { describe('#root', async () => { // root route handler. - const root = controlRoute.testableComponents.root + // const root = controlRoute.testableComponents.root it('should respond to GET for base route', async () => { - const result = root(req, res) + const result = uut.root(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(result.status, 'control', 'Returns static string') @@ -88,7 +81,7 @@ describe('#ControlRouter', () => { }) describe('#GetNetworkInfo', () => { - const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo + // const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo it('should throw 500 when network issues', async () => { // Save the existing RPC URL. @@ -97,7 +90,7 @@ describe('#ControlRouter', () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = 'http://fakeurl/api/' - await getNetworkInfo(req, res) + await uut.getNetworkInfo(req, res) // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. @@ -110,16 +103,45 @@ describe('#ControlRouter', () => { ) // assert.include(result.error, "ENOTFOUND", "Error message expected") }) + it('returns proper error when downstream service stalls', async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.getNetworkInfo(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + }) + + it('returns proper error when downstream service is down', async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.getNetworkInfo(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + }) it('should get info on the full node', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockGetNetworkInfo }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockGetNetworkInfo } }) } - const result = await getNetworkInfo(req, res) + const result = await uut.getNetworkInfo(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAnyKeys(result, [ diff --git a/test/v3/mining.js b/test/v3/mining.js index 33a0fca..bc51cc3 100644 --- a/test/v3/mining.js +++ b/test/v3/mining.js @@ -10,8 +10,11 @@ const chai = require('chai') const assert = chai.assert -const miningRoute = require('../../src/routes/v3/full-node/mining') -const nock = require('nock') // HTTP mocking +const MiningRoute = require('../../src/routes/v3/full-node/mining') +const uut = new MiningRoute() + +// const nock = require('nock') // HTTP mocking +const sinon = require('sinon') let originalEnvVars // Used during transition from integration to unit tests. @@ -25,7 +28,7 @@ util.inspect.defaultOptions = { depth: 1 } describe('#Mining', () => { let req, res - + let sandbox before(() => { // Save existing environment variables. originalEnvVars = { @@ -56,14 +59,12 @@ describe('#Mining', () => { req.body = {} req.query = {} - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() + sandbox = sinon.createSandbox() }) afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() + // Restore Sandbox + sandbox.restore() }) after(() => { @@ -76,10 +77,8 @@ describe('#Mining', () => { describe('#root', async () => { // root route handler. - const root = miningRoute.testableComponents.root - it('should respond to GET for base route', async () => { - const result = root(req, res) + const result = uut.root(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(result.status, 'mining', 'Returns static string') @@ -87,8 +86,6 @@ describe('#Mining', () => { }) describe('#getMiningInfo', async () => { - const getMiningInfo = miningRoute.testableComponents.getMiningInfo - it('should throw 503 when network issues', async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -96,7 +93,7 @@ describe('#Mining', () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = 'http://fakeurl/api/' - await getMiningInfo(req, res) + await uut.getMiningInfo(req, res) // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. @@ -109,16 +106,45 @@ describe('#Mining', () => { ) // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") }) + it('returns proper error when downstream service stalls', async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.getMiningInfo(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + }) + + it('returns proper error when downstream service is down', async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.getMiningInfo(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + }) it('should GET mining information', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockMiningInfo }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockMiningInfo } }) } - const result = await getMiningInfo(req, res) + const result = await uut.getMiningInfo(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, [ @@ -136,8 +162,6 @@ describe('#Mining', () => { }) describe('#getNetworkHashPS', async () => { - const getNetworkHashPS = miningRoute.testableComponents.getNetworkHashPS - it('should throw 503 when network issues', async () => { // Save the existing RPC URL. const savedUrl2 = process.env.RPC_BASEURL @@ -145,7 +169,7 @@ describe('#Mining', () => { // Manipulate the URL to cause a 500 network error. process.env.RPC_BASEURL = 'http://fakeurl/api/' - await getNetworkHashPS(req, res) + await uut.getNetworkHashPS(req, res) // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. @@ -158,16 +182,45 @@ describe('#Mining', () => { ) // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") }) + it('returns proper error when downstream service stalls', async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.getNetworkHashPS(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + }) + + it('returns proper error when downstream service is down', async () => { + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.getNetworkHashPS(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + }) it('should GET Network Hash per second', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: 517604755.6648782 }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: 517604755.6648782 } }) } - const result = await getNetworkHashPS(req, res) + const result = await uut.getNetworkHashPS(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isNumber(result) diff --git a/test/v3/raw-transactions.js b/test/v3/raw-transactions.js index ff7e3af..cd28992 100644 --- a/test/v3/raw-transactions.js +++ b/test/v3/raw-transactions.js @@ -14,9 +14,12 @@ const chai = require('chai') const assert = chai.assert -const rawtransactions = require('../../src/routes/v3/full-node/rawtransactions') -const nock = require('nock') // HTTP mocking +const Rawtransactions = require('../../src/routes/v3/full-node/rawtransactions') +const uut = new Rawtransactions() +// const nock = require('nock') // HTTP mocking + +const sinon = require('sinon') let originalEnvVars // Used during transition from integration to unit tests. // Mocking data. @@ -32,7 +35,7 @@ util.inspect.defaultOptions = { depth: 5 } describe('#Raw-Transactions', () => { // let req, res, next let req, res - + let sandbox before(() => { // Save existing environment variables. originalEnvVars = { @@ -65,14 +68,12 @@ describe('#Raw-Transactions', () => { req.query = {} req.locals = {} - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() + sandbox = sinon.createSandbox() }) afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() + // Restore Sandbox + sandbox.restore() }) after(() => { @@ -84,11 +85,8 @@ describe('#Raw-Transactions', () => { }) describe('#root', async () => { - // root route handler. - const root = rawtransactions.testableComponents.root - it('should respond to GET for base route', async () => { - const result = root(req, res) + const result = uut.root(req, res) // console.log(`result: ${util.inspect(result)}`) assert.equal(result.status, 'rawtransactions', 'Returns static string') @@ -96,12 +94,8 @@ describe('#Raw-Transactions', () => { }) describe('decodeRawTransactionSingle()', () => { - // block route handler. - const decodeRawTransaction = - rawtransactions.testableComponents.decodeRawTransactionSingle - it('should throw error if hex is missing', async () => { - const result = await decodeRawTransaction(req, res) + const result = await uut.decodeRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -118,7 +112,7 @@ describe('#Raw-Transactions', () => { req.params.hex = '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - await decodeRawTransaction(req, res) + await uut.decodeRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. @@ -131,19 +125,70 @@ describe('#Raw-Transactions', () => { ) // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") }) + it('returns proper error when downstream service stalls', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.params.hex = + '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.decodeRawTransactionSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) + + it('returns proper error when downstream service is down', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.params.hex = + '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.decodeRawTransactionSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) it('should GET /decodeRawTransaction', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockDecodeRawTransaction }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockDecodeRawTransaction } }) } req.params.hex = '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - const result = await decodeRawTransaction(req, res) + const result = await uut.decodeRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAnyKeys(result, [ @@ -161,11 +206,8 @@ describe('#Raw-Transactions', () => { }) describe('decodeRawTransactionBulk()', () => { - const decodeRawTransactionBulk = - rawtransactions.testableComponents.decodeRawTransactionBulk - it('should throw 400 error if hexes array is missing', async () => { - const result = await decodeRawTransactionBulk(req, res) + const result = await uut.decodeRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -178,7 +220,7 @@ describe('#Raw-Transactions', () => { req.body.hexes = testArray - const result = await decodeRawTransactionBulk(req, res) + const result = await uut.decodeRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -188,7 +230,7 @@ describe('#Raw-Transactions', () => { it('should throw 400 error if hexes is empty', async () => { req.body.hexes = [''] - const result = await decodeRawTransactionBulk(req, res) + const result = await uut.decodeRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -199,7 +241,7 @@ describe('#Raw-Transactions', () => { req.body.hexes = '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - const result = await decodeRawTransactionBulk(req, res) + const result = await uut.decodeRawTransactionBulk(req, res) assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') assert.include( @@ -208,20 +250,55 @@ describe('#Raw-Transactions', () => { 'Proper error message' ) }) + it('returns proper error when downstream service stalls', async () => { + req.body.hexes = [ + '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' + ] + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.decodeRawTransactionBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + }) + + it('returns proper error when downstream service is down', async () => { + req.body.hexes = [ + '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' + ] + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.decodeRawTransactionBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + }) it('should decode an array with a single hex', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockDecodeRawTransaction }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockDecodeRawTransaction } }) } req.body.hexes = [ '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' ] - const result = await decodeRawTransactionBulk(req, res) + const result = await uut.decodeRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) @@ -241,10 +318,9 @@ describe('#Raw-Transactions', () => { it('should decode an array with multiple hexes', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .times(2) - .reply(200, { result: mockData.mockDecodeRawTransaction }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockDecodeRawTransaction } }) } req.body.hexes = [ @@ -252,7 +328,7 @@ describe('#Raw-Transactions', () => { '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' ] - const result = await decodeRawTransactionBulk(req, res) + const result = await uut.decodeRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) @@ -271,12 +347,8 @@ describe('#Raw-Transactions', () => { }) describe('decodeScriptSingle()', () => { - // block route handler. - const decodeScriptSingle = - rawtransactions.testableComponents.decodeScriptSingle - it('should throw error if hex is missing', async () => { - const result = await decodeScriptSingle(req, res) + const result = await uut.decodeScriptSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -293,7 +365,7 @@ describe('#Raw-Transactions', () => { req.params.hex = '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - await decodeScriptSingle(req, res) + await uut.decodeScriptSingle(req, res) // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. @@ -306,19 +378,69 @@ describe('#Raw-Transactions', () => { ) // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") }) + it('returns proper error when downstream service stalls', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.params.hex = + '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.decodeScriptSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) + + it('returns proper error when downstream service is down', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.params.hex = + '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.decodeScriptSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) it('should GET /decodeScriptSingle', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockDecodeScript }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockDecodeScript } }) } req.params.hex = '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - const result = await decodeScriptSingle(req, res) + const result = await uut.decodeScriptSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['asm', 'type', 'p2sh']) @@ -326,10 +448,8 @@ describe('#Raw-Transactions', () => { }) describe('decodeScriptBulk()', () => { - const decodeScriptBulk = rawtransactions.testableComponents.decodeScriptBulk - it('should throw 400 error if hexes array is missing', async () => { - const result = await decodeScriptBulk(req, res) + const result = await uut.decodeScriptBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -342,7 +462,7 @@ describe('#Raw-Transactions', () => { req.body.hexes = testArray - const result = await decodeScriptBulk(req, res) + const result = await uut.decodeScriptBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -352,7 +472,7 @@ describe('#Raw-Transactions', () => { it('should throw 400 error if hexes is empty', async () => { req.body.hexes = [''] - const result = await decodeScriptBulk(req, res) + const result = await uut.decodeScriptBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -363,7 +483,7 @@ describe('#Raw-Transactions', () => { req.body.hexes = '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - const result = await decodeScriptBulk(req, res) + const result = await uut.decodeScriptBulk(req, res) assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') assert.include( @@ -372,20 +492,73 @@ describe('#Raw-Transactions', () => { 'Proper error message' ) }) + it('returns proper error when downstream service stalls', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.body.hexes = [ + '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' + ] + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.decodeScriptBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) + + it('returns proper error when downstream service is down', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.body.hexes = [ + '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' + ] + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.decodeScriptBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) it('should decode an array with a single hex', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockDecodeScript }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockDecodeScript } }) } req.body.hexes = [ '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' ] - const result = await decodeScriptBulk(req, res) + const result = await uut.decodeScriptBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) @@ -395,10 +568,9 @@ describe('#Raw-Transactions', () => { it('should decode an array with a multiple hexes', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .times(2) - .reply(200, { result: mockData.mockDecodeScript }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockDecodeScript } }) } req.body.hexes = [ @@ -406,7 +578,7 @@ describe('#Raw-Transactions', () => { '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' ] - const result = await decodeScriptBulk(req, res) + const result = await uut.decodeScriptBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) @@ -416,12 +588,8 @@ describe('#Raw-Transactions', () => { }) describe('getRawTransactionBulk()', () => { - // block route handler. - const getRawTransactionBulk = - rawtransactions.testableComponents.getRawTransactionBulk - it('should throw 400 error if txids array is missing', async () => { - const result = await getRawTransactionBulk(req, res) + const result = await uut.getRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -434,7 +602,7 @@ describe('#Raw-Transactions', () => { req.body.txids = testArray - const result = await getRawTransactionBulk(req, res) + const result = await uut.getRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -444,7 +612,7 @@ describe('#Raw-Transactions', () => { it('should throw 400 error if txid is empty', async () => { req.body.txids = [''] - const result = await getRawTransactionBulk(req, res) + const result = await uut.getRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -454,36 +622,86 @@ describe('#Raw-Transactions', () => { it('should throw 400 error if txid is invalid', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(500, { - error: { message: 'parameter 1 must be of length 64 (not 6)' } - }) + sandbox + .stub(uut.axios, 'request') + .rejects('parameter 1 must be of length 64 (not 6)') } req.body.txids = ['abc123'] - const result = await getRawTransactionBulk(req, res) + const result = await uut.getRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') assert.include(result.error, 'parameter 1 must be of length 64 (not 6)') }) + it('returns proper error when downstream service stalls', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.body.txids = [ + '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' + ] + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.getRawTransactionBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) + + it('returns proper error when downstream service is down', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + req.body.txids = [ + '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' + ] + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.getRawTransactionBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) it('should get concise transaction data', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockRawTransactionConcise }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockRawTransactionConcise } }) } req.body.txids = [ '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' ] - const result = await getRawTransactionBulk(req, res) + const result = await uut.getRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) @@ -493,9 +711,9 @@ describe('#Raw-Transactions', () => { it('should get verbose transaction data', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockRawTransactionVerbose }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockRawTransactionVerbose } }) } req.body.txids = [ @@ -503,7 +721,7 @@ describe('#Raw-Transactions', () => { ] req.body.verbose = true - const result = await getRawTransactionBulk(req, res) + const result = await uut.getRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isArray(result) @@ -527,12 +745,8 @@ describe('#Raw-Transactions', () => { }) describe('getRawTransactionSingle()', () => { - // block route handler. - const getRawTransactionSingle = - rawtransactions.testableComponents.getRawTransactionSingle - it('should throw 400 error if txid is missing', async () => { - const result = await getRawTransactionSingle(req, res) + const result = await uut.getRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -542,35 +756,83 @@ describe('#Raw-Transactions', () => { it('should throw 400 error if txid is invalid', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(500, { - error: { message: 'parameter 1 must be of length 64 (not 6)' } - }) + sandbox + .stub(uut.axios, 'request') + .rejects('parameter 1 must be of length 64 (not 6)') } req.params.txid = 'abc123' - const result = await getRawTransactionSingle(req, res) + const result = await uut.getRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + assert.equal(res.statusCode, 500, 'HTTP status code 400 expected.') assert.include(result.error, 'parameter 1 must be of length 64 (not 6)') }) + it('returns proper error when downstream service stalls', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.params.txid = + '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.getRawTransactionSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) + + it('returns proper error when downstream service is down', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + req.params.txid = + '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.getRawTransactionSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) it('should get concise transaction data', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockRawTransactionConcise }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockRawTransactionConcise } }) } req.params.txid = '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - const result = await getRawTransactionSingle(req, res) + const result = await uut.getRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.isString(result) @@ -579,16 +841,16 @@ describe('#Raw-Transactions', () => { it('should get verbose transaction data', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { result: mockData.mockRawTransactionVerbose }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: mockData.mockRawTransactionVerbose } }) } req.params.txid = '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' req.query.verbose = 'true' - const result = await getRawTransactionSingle(req, res) + const result = await uut.getRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAnyKeys(result, [ @@ -611,11 +873,8 @@ describe('#Raw-Transactions', () => { }) describe('sendRawTransactionBulk()', () => { - const sendRawTransaction = - rawtransactions.testableComponents.sendRawTransactionBulk - it('should throw 400 error if hexs array is missing', async () => { - const result = await sendRawTransaction(req, res) + const result = await uut.sendRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -628,7 +887,7 @@ describe('#Raw-Transactions', () => { req.body.hexes = testArray - const result = await sendRawTransaction(req, res) + const result = await uut.sendRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -638,7 +897,7 @@ describe('#Raw-Transactions', () => { it('should throw 400 error if hex array element is empty', async () => { req.body.hexes = [''] - const result = await sendRawTransaction(req, res) + const result = await uut.sendRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) @@ -648,23 +907,70 @@ describe('#Raw-Transactions', () => { it('should throw 500 error if hex is invalid', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(500, { - error: { message: 'TX decode failed' } - }) + sandbox + .stub(uut.axios, 'request') + .rejects('TX decode failed') } req.body.hexes = ['abc123'] - const result = await sendRawTransaction(req, res) + const result = await uut.sendRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + assert.equal(res.statusCode, 500, 'HTTP status code 400 expected.') assert.include(result.error, 'TX decode failed') }) + it('returns proper error when downstream service stalls', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.body.hexes = [ + '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' + ] + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.sendRawTransactionBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) + + it('returns proper error when downstream service is down', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + req.body.hexes = [ + '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' + ] + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.sendRawTransactionBulk(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) it('should submit hex encoded transaction', async () => { // This is a difficult test to run as transaction hex is invalid after a // block confirmation. So the unit tests simulates what the output 'should' @@ -672,19 +978,16 @@ describe('#Raw-Transactions', () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { - result: - 'aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118' - }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: 'aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118' } }) } req.body.hexes = [ '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' ] - const result = await sendRawTransaction(req, res) + const result = await uut.sendRawTransactionBulk(req, res) // console.log(`result: ${util.inspect(result)}`) if (process.env.TEST === 'unit') { @@ -700,14 +1003,10 @@ describe('#Raw-Transactions', () => { }) describe('sendRawTransactionSingle()', () => { - // block route handler. - const sendRawTransaction = - rawtransactions.testableComponents.sendRawTransactionSingle - it('should throw an error for an empty hex', async () => { req.params.hex = '' - const result = await sendRawTransaction(req, res) + const result = await uut.sendRawTransactionSingle(req, res) assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') assert.include( @@ -720,7 +1019,7 @@ describe('#Raw-Transactions', () => { it('should throw an error for a non-string', async () => { req.params.hex = 456 - const result = await sendRawTransaction(req, res) + const result = await uut.sendRawTransactionSingle(req, res) assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') assert.include( @@ -741,7 +1040,7 @@ describe('#Raw-Transactions', () => { req.params.hex = '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - await sendRawTransaction(req, res) + await uut.sendRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) // Restore the saved URL. @@ -760,21 +1059,68 @@ describe('#Raw-Transactions', () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(500, { - error: { message: 'TX decode failed' } - }) + sandbox + .stub(uut.axios, 'request') + .rejects('TX decode failed') } - const result = await sendRawTransaction(req, res) + const result = await uut.sendRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') + assert.equal(res.statusCode, 500, 'HTTP status code 400 expected.') assert.include(result.error, 'TX decode failed') }) + it('returns proper error when downstream service stalls', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + + req.params.hex = + '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) + + const result = await uut.sendRawTransactionSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) + + it('returns proper error when downstream service is down', async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = 'http://fakeurl/api/' + req.params.hex = + '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' + + // Mock the timeout error. + sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + const result = await uut.sendRawTransactionSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') + assert.include( + result.error, + 'Could not communicate with full node', + 'Error message expected' + ) + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + }) it('should GET /sendRawTransaction/:hex', async () => { // This is a difficult test to run as transaction hex is invalid after a // block confirmation. So the unit tests simulates what the output 'should' @@ -782,18 +1128,15 @@ describe('#Raw-Transactions', () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post(uri => uri.includes('/')) - .reply(200, { - result: - 'aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118' - }) + sandbox + .stub(uut.axios, 'request') + .resolves({ data: { result: 'aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118' } }) } req.params.hex = '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - const result = await sendRawTransaction(req, res) + const result = await uut.sendRawTransactionSingle(req, res) // console.log(`result: ${util.inspect(result)}`) if (process.env.TEST === 'unit') {