diff --git a/package-lock.json b/package-lock.json index 1c08688..4922942 100644 --- a/package-lock.json +++ b/package-lock.json @@ -350,15 +350,15 @@ } }, "@semantic-release/npm": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-5.1.7.tgz", - "integrity": "sha512-4THiFGp9APX1a+EJJsOYurJCR8TrRUgNCU9u46AkZekWfvtyzacfIBKCrmEljpYG8qDDnHLZwHSqyW4ID4iteA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-5.1.9.tgz", + "integrity": "sha512-Hcwhiu7zC0hD0ZPTkEE5bsK9l0z7Ou+N/mcSpYT1kq96ETMQtyZee0gjxXiAGaIHJ7wlMuUhHbYp7iAaI1965w==", "dev": true, "requires": { "@semantic-release/error": "^2.2.0", "aggregate-error": "^3.0.0", "execa": "^1.0.0", - "fs-extra": "^7.0.0", + "fs-extra": "^8.0.0", "lodash": "^4.17.4", "nerf-dart": "^1.0.0", "normalize-url": "^4.0.0", @@ -368,17 +368,6 @@ "registry-auth-token": "^3.3.1" }, "dependencies": { - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, "read-pkg": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.1.1.tgz", diff --git a/src/app.js b/src/app.js index b9e77dd..73e5318 100644 --- a/src/app.js +++ b/src/app.js @@ -49,7 +49,6 @@ const slpV2 = require("./routes/v2/slp") // v3 const indexV3 = require("./routes/v3/index") const healthCheckV3 = require("./routes/v3/health-check") -const addressV3 = require("./routes/v3/address") const blockV3 = require("./routes/v3/block") const blockchainV3 = require("./routes/v3/blockchain") const controlV3 = require("./routes/v3/control") @@ -60,6 +59,7 @@ const rawtransactionsV3 = require("./routes/v3/rawtransactions") const transactionV3 = require("./routes/v3/transaction") const utilV3 = require("./routes/v3/util") const slpV3 = require("./routes/v3/slp") +const xpubV3 = require("./routes/v3/xpub") require("dotenv").config() @@ -129,7 +129,6 @@ app.use(`/${v2prefix}/` + `slp`, slpV2.router) // Rate limit on all v2 routes app.use(`/${v3prefix}/`, routeRateLimit) app.use(`/${v3prefix}/` + `health-check`, healthCheckV3) -app.use(`/${v3prefix}/` + `address`, addressV3.router) app.use(`/${v3prefix}/` + `blockchain`, blockchainV3.router) app.use(`/${v3prefix}/` + `block`, blockV3.router) app.use(`/${v3prefix}/` + `control`, controlV3.router) @@ -140,6 +139,7 @@ app.use(`/${v3prefix}/` + `rawtransactions`, rawtransactionsV3.router) app.use(`/${v3prefix}/` + `transaction`, transactionV3.router) app.use(`/${v3prefix}/` + `util`, utilV3.router) app.use(`/${v3prefix}/` + `slp`, slpV3.router) +app.use(`/${v3prefix}/` + `xpub`, xpubV3.router) // catch 404 and forward to error handler app.use((req, res, next) => { diff --git a/src/routes/v3/address.js b/src/routes/v3/address.js deleted file mode 100644 index efce84d..0000000 --- a/src/routes/v3/address.js +++ /dev/null @@ -1,807 +0,0 @@ -/* - Address route -*/ - -"use strict" - -const express = require("express") -const requestUtils = require("./services/requestUtils") -const axios = require("axios") -const logger = require("./logging.js") -const routeUtils = require("./route-utils") -const wlogger = require("../../util/winston-logging") - -//const router = express.Router() -const router = express.Router() - -// Used for processing error messages before sending them to the user. -const util = require("util") -util.inspect.defaultOptions = { depth: 1 } - -const BITBOXJS = require("@chris.troutner/bitbox-js") -const BITBOX = new BITBOXJS() - -// Use the default (and max) page size of 1000 -// https://github.com/bitpay/insight-api#notes-on-upgrading-from-v03 -const PAGE_SIZE = 1000 - -// Connect the route endpoints to their handler functions. -router.get("/", root) -router.get("/details/:address", detailsSingle) -router.post("/details", detailsBulk) -router.post("/utxo", utxoBulk) -router.get("/utxo/:address", utxoSingle) -router.post("/unconfirmed", unconfirmedBulk) -router.get("/unconfirmed/:address", unconfirmedSingle) -router.get("/transactions/:address", transactionsSingle) -router.post("/transactions", transactionsBulk) -router.get("/fromXPub/:xpub", fromXPubSingle) - -// Root API endpoint. Simply acknowledges that it exists. -function root(req, res, next) { - return res.json({ status: "address" }) -} - -// Query the Insight API for details on a single BCH address. -// Returns a Promise. -async function detailsFromInsight(thisAddress, currentPage = 0) { - try { - let addr - if ( - process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/" - ) - addr = BITBOX.Address.toCashAddress(thisAddress) - else addr = BITBOX.Address.toLegacyAddress(thisAddress) - - let path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}` - - // Set from and to params based on currentPage and pageSize - // https://github.com/bitpay/insight-api/blob/master/README.md#notes-on-upgrading-from-v02 - const from = currentPage * PAGE_SIZE - const to = from + PAGE_SIZE - path = `${path}?from=${from}&to=${to}` - - // Query the Insight server. - const axiosResponse = await axios.get(path) - const retData = axiosResponse.data - //console.log(`retData: ${util.inspect(retData)}`) - - // Calculate pagesTotal from response - const pagesTotal = Math.ceil(retData.txApperances / PAGE_SIZE) - - // Append different address formats to the return data. - retData.legacyAddress = BITBOX.Address.toLegacyAddress(retData.addrStr) - retData.cashAddress = BITBOX.Address.toCashAddress(retData.addrStr) - delete retData.addrStr - - // Append pagination information to the return data. - retData.currentPage = currentPage - retData.pagesTotal = pagesTotal - - return retData - } catch (err) { - // Dev Note: Do not log error messages here. Throw them instead and let the - // parent function handle it. - throw err - } -} - -// POST handler for bulk queries on address details -// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"]}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details -// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"], "from": 1, "to": 5}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details -async function detailsBulk(req, res, next) { - try { - let addresses = req.body.addresses - const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0 - - // Reject if addresses is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ - error: "addresses needs to be an array. Use GET for single address." - }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - logger.debug(`Executing address/details with these addresses: `, addresses) - wlogger.debug(`Executing address/details with these addresses: `, addresses) - - // Validate each element in the address array. - for (let i = 0; i < addresses.length; i++) { - const thisAddress = addresses[i] - - // Ensure the input is a valid BCH address. - try { - BITBOX.Address.toLegacyAddress(thisAddress) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = routeUtils.validateNetwork(thisAddress) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - // Loops through each address and creates an array of Promises, querying - // Insight API in parallel. - addresses = addresses.map(async (address, index) => - detailsFromInsight(address, currentPage) - ) - - // Wait for all parallel Insight requests to return. - const result = await axios.all(addresses) - - // Return the array of retrieved address information. - 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 }) - } - - //logger.error(`Error in detailsBulk(): `, err) - wlogger.error(`Error in address.ts/detailsBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// GET handler for single address details -async function detailsSingle(req, res, next) { - try { - const address = req.params.address - const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0 - - if (!address || address === "") { - res.status(400) - return res.json({ error: "address can not be empty" }) - } - - // Reject if address is an array. - if (Array.isArray(address)) { - res.status(400) - return res.json({ - error: "address can not be an array. Use POST for bulk upload." - }) - } - - logger.debug(`Executing address/detailsSingle with this address: `, address) - wlogger.debug( - `Executing address/detailsSingle with this address: `, - address - ) - - // Ensure the input is a valid BCH address. - try { - var legacyAddr = BITBOX.Address.toLegacyAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = routeUtils.validateNetwork(address) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - - // Query the Insight API. - const retData = await detailsFromInsight(address, currentPage) - - // Return the retrieved address information. - res.status(200) - return res.json(retData) - } 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 address.ts/detailsSingle: `, err) - wlogger.error(`Error in address.ts/detailsSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Retrieve UTXO data from the Insight API -async function utxoFromInsight(thisAddress) { - try { - let addr - if ( - process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/" - ) - addr = BITBOX.Address.toCashAddress(thisAddress) - else addr = BITBOX.Address.toLegacyAddress(thisAddress) - - const path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}/utxo` - - // Query the Insight server. - const response = await axios.get(path) - - // Append different address formats to the return data. - const retData = { - utxos: Array, - legacyAddress: String, - cashAddress: String, - scriptPubKey: String - } - if (response.data.length && response.data[0].scriptPubKey) { - const spk = response.data[0].scriptPubKey - retData.scriptPubKey = spk - } - retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress) - retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress) - retData.utxos = response.data.map(utxo => { - delete utxo.address - delete utxo.scriptPubKey - return utxo - }) - //console.log(`utxoFromInsight retData: ${util.inspect(retData)}`) - - return retData - } catch (err) { - // Dev Note: Do not log error messages here. Throw them instead and let the - // parent function handle it. - throw err - } -} - -// Retrieve UTXO information for an address. -async function utxoBulk(req, res, next) { - try { - let addresses = req.body.addresses - - // Reject if address is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ error: "addresses needs to be an array" }) - } - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - // Validate each element in the address array. - for (let i = 0; i < addresses.length; i++) { - const thisAddress = addresses[i] - - // Ensure the input is a valid BCH address. - try { - BITBOX.Address.toLegacyAddress(thisAddress) - } catch (er) { - //if (er.message.includes("Unsupported address format")) - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = routeUtils.validateNetwork(thisAddress) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - logger.debug(`Executing address/utxoBulk with these addresses: `, addresses) - wlogger.debug( - `Executing address/utxoBulk with these addresses: `, - addresses - ) - - // Loops through each address and creates an array of Promises, querying - // Insight API in parallel. - addresses = addresses.map(async (address, index) => - utxoFromInsight(address) - ) - - // Wait for all parallel Insight requests to return. - const result = await axios.all(addresses) - - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in address.ts/utxoBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// GET handler for single address details -async function utxoSingle(req, res, next) { - try { - const address = req.params.address - if (!address || address === "") { - res.status(400) - return res.json({ error: "address can not be empty" }) - } - - // Reject if address is an array. - if (Array.isArray(address)) { - res.status(400) - return res.json({ - error: "address can not be an array. Use POST for bulk upload." - }) - } - - logger.debug(`Executing address/utxoSingle with this address: `, address) - wlogger.debug(`Executing address/utxoSingle with this address: `, address) - - // Ensure the input is a valid BCH address. - try { - var legacyAddr = BITBOX.Address.toLegacyAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = routeUtils.validateNetwork(address) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - - // Query the Insight API. - const retData = await utxoFromInsight(address) - - // Return the array of retrieved address information. - res.status(200) - return res.json(retData) - } 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 address.ts/utxoSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Retrieve any unconfirmed TX information for a given address. -async function unconfirmedBulk(req, res, next) { - try { - const addresses = req.body.addresses - - // Reject if address is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ error: "addresses needs to be an array" }) - } - - logger.debug(`Executing address/utxo with these addresses: `, addresses) - wlogger.debug(`Executing address/utxo with these addresses: `, addresses) - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - // Validate each element in the address array. - for (let i = 0; i < addresses.length; i++) { - const thisAddress = addresses[i] - - // Ensure the input is a valid BCH address. - try { - BITBOX.Address.toLegacyAddress(thisAddress) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = routeUtils.validateNetwork(thisAddress) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - // Collect an array of promises. - const promises = addresses.map(address => utxoFromInsight(address)) - - // Wait for all parallel Insight requests to return. - const result = await axios.all(promises) - - // Loop through each result - const finalResult = result.map(elem => { - //console.log(`elem: ${util.inspect(elem)}`) - - // Filter out confirmed transactions. - const unconfirmedUtxos = elem.utxos.filter( - utxo => utxo.confirmations === 0 - ) - - elem.utxos = unconfirmedUtxos - - return elem - }) - - // Return the array of retrieved address information. - res.status(200) - return res.json(finalResult) - } 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 address.ts/unconfirmedBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// GET handler. Retrieve any unconfirmed TX information for a given address. -async function unconfirmedSingle(req, res, next) { - try { - const address = req.params.address - if (!address || address === "") { - res.status(400) - return res.json({ error: "address can not be empty" }) - } - - // Reject if address is an array. - if (Array.isArray(address)) { - res.status(400) - return res.json({ - error: "address can not be an array. Use POST for bulk upload." - }) - } - - logger.debug(`Executing address/utxoSingle with this address: `, address) - wlogger.debug(`Executing address/utxoSingle with this address: `, address) - - // Ensure the input is a valid BCH address. - try { - var legacyAddr = BITBOX.Address.toLegacyAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = routeUtils.validateNetwork(address) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - - // Query the Insight API. - const retData = await utxoFromInsight(address) - //console.log(`retData: ${JSON.stringify(retData,null,2)}`) - - // Loop through each returned UTXO. - const unconfirmedUTXOs = [] - for (let j = 0; j < retData.utxos.length; j++) { - const thisUtxo = retData.utxos[j] - - // Only interested in UTXOs with no confirmations. - if (thisUtxo.confirmations === 0) unconfirmedUTXOs.push(thisUtxo) - } - - retData.utxos = unconfirmedUTXOs - - // Return the array of retrieved address information. - res.status(200) - return res.json(retData) - } 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 address.ts/unconfirmedSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// Retrieve transaction data from the Insight API -async function transactionsFromInsight(thisAddress, currentPage = 0) { - try { - const path = `${ - process.env.BITCOINCOM_BASEURL - }txs/?address=${thisAddress}&pageNum=${currentPage}` - - // Query the Insight server. - const response = await axios.get(path) - - // Append different address formats to the return data. - const retData = response.data - retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress) - retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress) - retData.currentPage = currentPage - - return retData - } catch (err) { - // Dev Note: Do not log error messages here. Throw them instead and let the - // parent function handle it. - throw err - } -} - -// Get an array of TX information for a given address. -async function transactionsBulk(req, res, next) { - try { - let addresses = req.body.addresses - const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0 - - // Reject if address is not an array. - if (!Array.isArray(addresses)) { - res.status(400) - return res.json({ error: "addresses needs to be an array" }) - } - - logger.debug(`Executing address/utxo with these addresses: `, addresses) - wlogger.debug(`Executing address/utxo with these addresses: `, addresses) - - // Enforce array size rate limits - if (!routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: `Array too large.` - }) - } - - // Validate each element in the address array. - for (let i = 0; i < addresses.length; i++) { - const thisAddress = addresses[i] - - // Ensure the input is a valid BCH address. - try { - BITBOX.Address.toLegacyAddress(thisAddress) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${thisAddress}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = routeUtils.validateNetwork(thisAddress) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - } - - // Loop through each address and collect an array of promises. - addresses = addresses.map(async (address, index) => - transactionsFromInsight(address, currentPage) - ) - - // Wait for all parallel Insight requests to return. - const result = await axios.all(addresses) - - // Return the array of retrieved address information. - 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 }) - } - - // Write out error to error log. - //logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error(`Error in address.ts/transactionsBulk().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -// GET handler. Retrieve any unconfirmed TX information for a given address. -async function transactionsSingle(req, res, next) { - try { - const address = req.params.address - const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0 - - if (!address || address === "") { - res.status(400) - return res.json({ error: "address can not be empty" }) - } - - // Reject if address is an array. - if (Array.isArray(address)) { - res.status(400) - return res.json({ - error: "address can not be an array. Use POST for bulk upload." - }) - } - - logger.debug( - `Executing address/transactionsSingle with this address: `, - address - ) - wlogger.debug( - `Executing address/transactionsSingle with this address: `, - address - ) - - // Ensure the input is a valid BCH address. - try { - var legacyAddr = BITBOX.Address.toLegacyAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = routeUtils.validateNetwork(address) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.` - }) - } - - // Query the Insight API. - const retData = await transactionsFromInsight(address, currentPage) - //console.log(`retData: ${JSON.stringify(retData,null,2)}`) - - // Return the array of retrieved address information. - res.status(200) - return res.json(retData) - } 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 address.ts/transactionsSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -async function fromXPubSingle(req, res, next) { - try { - const xpub = req.params.xpub - const hdPath = req.query.hdPath ? req.query.hdPath : "0" - - if (!xpub || xpub === "") { - res.status(400) - return res.json({ error: "xpub can not be empty" }) - } - - // Reject if xpub is an array. - if (Array.isArray(xpub)) { - res.status(400) - return res.json({ - error: "xpub can not be an array. Use POST for bulk upload." - }) - } - - logger.debug(`Executing address/fromXPub with this xpub: `, xpub) - wlogger.debug(`Executing address/fromXPub with this xpub: `, xpub) - - const cashAddr = BITBOX.Address.fromXPub(xpub, hdPath) - const legacyAddr = BITBOX.Address.toLegacyAddress(cashAddr) - res.status(200) - return res.json({ - cashAddress: cashAddr, - legacyAddress: legacyAddr - }) - } 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 address.ts/fromXPubSingle().`, err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -module.exports = { - router, - testableComponents: { - root, - detailsBulk, - detailsSingle, - utxoBulk, - utxoSingle, - unconfirmedBulk, - unconfirmedSingle, - transactionsBulk, - transactionsSingle, - fromXPubSingle - } -} diff --git a/src/routes/v3/xpub.js b/src/routes/v3/xpub.js new file mode 100644 index 0000000..bd3256e --- /dev/null +++ b/src/routes/v3/xpub.js @@ -0,0 +1,84 @@ +/* + xpub route +*/ + +"use strict" + +const express = require("express") +const requestUtils = require("./services/requestUtils") +const axios = require("axios") +const logger = require("./logging.js") +const routeUtils = require("./route-utils") +const wlogger = require("../../util/winston-logging") + +//const router = express.Router() +const router = express.Router() + +// Used for processing error messages before sending them to the user. +const util = require("util") +util.inspect.defaultOptions = { depth: 1 } + +const BITBOXJS = require("@chris.troutner/bitbox-js") +const BITBOX = new BITBOXJS() + +// Connect the route endpoints to their handler functions. +router.get("/", root) +router.get("/fromXPub/:xpub", fromXPubSingle) + +// Root API endpoint. Simply acknowledges that it exists. +function root(req, res, next) { + return res.json({ status: "address" }) +} + +async function fromXPubSingle(req, res, next) { + try { + const xpub = req.params.xpub + const hdPath = req.query.hdPath ? req.query.hdPath : "0" + + if (!xpub || xpub === "") { + res.status(400) + return res.json({ error: "xpub can not be empty" }) + } + + // Reject if xpub is an array. + if (Array.isArray(xpub)) { + res.status(400) + return res.json({ + error: "xpub can not be an array. Use POST for bulk upload." + }) + } + + logger.debug(`Executing address/fromXPub with this xpub: `, xpub) + wlogger.debug(`Executing address/fromXPub with this xpub: `, xpub) + + const cashAddr = BITBOX.Address.fromXPub(xpub, hdPath) + const legacyAddr = BITBOX.Address.toLegacyAddress(cashAddr) + res.status(200) + return res.json({ + cashAddress: cashAddr, + legacyAddress: legacyAddr + }) + } 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 address.ts/fromXPubSingle().`, err) + + res.status(500) + return res.json({ error: util.inspect(err) }) + } +} + +module.exports = { + router, + testableComponents: { + root, + fromXPubSingle + } +} diff --git a/test/v3/address.js b/test/v3/address.js deleted file mode 100644 index 41f6de5..0000000 --- a/test/v3/address.js +++ /dev/null @@ -1,1373 +0,0 @@ -/* - TESTS FOR THE ADDRESS.JS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - - To-Do: - -/details/:address - --Verify to/from query options work correctly. - -GET /unconfirmed/:address & POST /unconfirmed - --Should initiate a transfer of BCH to verify unconfirmed TX. - ---This would be more of an e2e test. -*/ - -"use strict" - -const chai = require("chai") -const assert = chai.assert -const addressRoute = require("../../src/routes/v3/address") -const nock = require("nock") // HTTP mocking - -let originalUrl // Used during transition from integration to unit tests. - -// Mocking data. -const { mockReq, mockRes } = require("./mocks/express-mocks") -const mockData = require("./mocks/address-mock") - -// Used for debugging. -const util = require("util") -util.inspect.defaultOptions = { depth: 1 } - -describe("#AddressRouter", () => { - let req, res - - before(() => { - originalUrl = process.env.BITCOINCOM_BASEURL - - // 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/" - - // console.log(`Testing type is: ${process.env.TEST}`) - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() - }) - - afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() - }) - - after(() => { - process.env.BITCOINCOM_BASEURL = originalUrl - }) - - describe("#root", () => { - // root route handler. - const root = addressRoute.testableComponents.root - - it("should respond to GET for base route", async () => { - const result = root(req, res) - - assert.equal(result.status, "address", "Returns static string") - }) - }) - - describe("#AddressDetailsBulk", () => { - // details route handler. - const detailsBulk = addressRoute.testableComponents.detailsBulk - - it("should throw an error for an empty body", async () => { - req.body = {} - - const result = await detailsBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "addresses needs to be an array", - "Proper error message" - ) - }) - - it("should error on non-array single address", async () => { - req.body = { - address: `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - } - - const result = await detailsBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "addresses needs to be an array", - "Proper error message" - ) - }) - - it("should throw an error for an invalid address", async () => { - req.body = { - addresses: [`02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - } - - const result = await detailsBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "Invalid BCH address", - "Proper error message" - ) - }) - - it("should throw 400 error if addresses array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.addresses = testArray - - const result = await detailsBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should detect a network mismatch", async () => { - req.body = { - addresses: [`bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3`] - } - - const result = await detailsBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include(result.error, "Invalid network", "Proper error message") - }) - - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`] - } - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - - const result = await detailsBulk(req, res) - //console.log(`network issue result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.isAbove(res.statusCode, 499, "HTTP status code 500 expected.") - //assert.include(result.error, "ENOTFOUND", "Error message expected") - assert.include( - result.error, - "Network error: Could not communicate", - "Error message expected" - ) - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it("should default to page 0", async () => { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz?from=0&to=1000`) - .reply(200, mockData.mockAddressDetails) - } - - // Call the details API. - const result = await detailsBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - // Assert current page defaults to 0 - assert.equal(result[0].currentPage, 0) - }) - - it("should process the requested page", async () => { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`], - page: 5 - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz?from=5000&to=6000`) - .reply(200, mockData.mockAddressDetails) - } - - // Call the details API. - const result = await detailsBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - // Assert current page is same as requested - assert.equal(result[0].currentPage, 5) - }) - - it("should calculate the total number of pages", async () => { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz?from=0&to=1000`) - .reply(200, mockData.mockAddressDetails) - } - - // Call the details API. - const result = await detailsBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.equal(result[0].pagesTotal, 1) - }) - - it("should get details for a single address", async () => { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz?from=0&to=1000`) - .reply(200, mockData.mockAddressDetails) - } - - // Call the details API. - const result = await detailsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.equal(result.length, 1, "Array with one entry") - assert.hasAllKeys(result[0], [ - "balance", - "balanceSat", - "totalReceived", - "totalReceivedSat", - "totalSent", - "totalSentSat", - "unconfirmedBalance", - "unconfirmedBalanceSat", - "unconfirmedTxApperances", - "txApperances", - "transactions", - "legacyAddress", - "cashAddress", - "currentPage", - "pagesTotal" - ]) - }) - - it("should get details for multiple addresses", async () => { - req.body = { - addresses: [ - `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`, - `bchtest:qzknfggae0av6yvxk77gmyq7syc67yux6sk80haqyr` - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz?from=0&to=1000`) - .reply(200, mockData.mockAddressDetails) - - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mwJnEzXzKkveF2q5Af9jxi9j1zrtWAnPU8?from=0&to=1000`) - .reply(200, mockData.mockAddressDetails) - } - - // Call the details API. - const result = await detailsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2, "2 outputs for 2 inputs") - }) - }) - - describe("#AddressDetailsSingle", () => { - // details route handler. - const detailsSingle = addressRoute.testableComponents.detailsSingle - - it("should throw 400 if address is empty", async () => { - const result = await detailsSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - - it("should error on an array", async () => { - req.params.address = [`qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - - const result = await detailsSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "address can not be an array", - "Proper error message" - ) - }) - - it("should throw an error for an invalid address", async () => { - req.params.address = `02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - - const result = await detailsSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "Invalid BCH address", - "Proper error message" - ) - }) - - it("should detect a network mismatch", async () => { - req.params.address = `bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3` - - const result = await detailsSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include(result.error, "Invalid network", "Proper error message") - }) - - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.params.address = `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - - const result = await detailsSingle(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it("should default to page 0", async () => { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz?from=0&to=1000`) - .reply(200, mockData.mockAddressDetails) - } - - // Call the details API. - const result = await detailsSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - // Assert current page defaults to 0 - assert.equal(result.currentPage, 0) - }) - - it("should process the requested page", async () => { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - req.query.page = 5 - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz?from=5000&to=6000`) - .reply(200, mockData.mockAddressDetails) - } - - // Call the details API. - const result = await detailsSingle(req, res) - - // Assert current page is same as requested - assert.equal(result.currentPage, 5) - }) - - it("should calculate the total number of pages", async () => { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz?from=0&to=1000`) - .reply(200, mockData.mockAddressDetails) - } - - // Call the details API. - const result = await detailsSingle(req, res) - - assert.equal(result.pagesTotal, 1) - }) - - it("should get details for a single address", async () => { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz?from=0&to=1000`) - .reply(200, mockData.mockAddressDetails) - } - - // Call the details API. - const result = await detailsSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.hasAllKeys(result, [ - "balance", - "balanceSat", - "totalReceived", - "totalReceivedSat", - "totalSent", - "totalSentSat", - "unconfirmedBalance", - "unconfirmedBalanceSat", - "unconfirmedTxApperances", - "txApperances", - "transactions", - "legacyAddress", - "cashAddress", - "currentPage", - "pagesTotal" - ]) - }) - }) - - describe("#AddressUtxoBulk", () => { - // utxo route handler. - const utxoBulk = addressRoute.testableComponents.utxoBulk - - it("should throw an error for an empty body", async () => { - req.body = {} - - const result = await utxoBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "addresses needs to be an array", - "Proper error message" - ) - }) - - it("should error on non-array single address", async () => { - req.body = { - address: `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - } - - const result = await utxoBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "addresses needs to be an array", - "Proper error message" - ) - }) - - it("should throw an error for an invalid address", async () => { - req.body = { - addresses: [`02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - } - - const result = await utxoBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "Invalid BCH address", - "Proper error message" - ) - }) - - it("should detect a network mismatch", async () => { - req.body = { - addresses: [`bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3`] - } - - const result = await utxoBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include(result.error, "Invalid network", "Proper error message") - }) - - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.body = { - addresses: [`qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - } - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api" - - const result = await utxoBulk(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it("should get utxos for a single address", async () => { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz/utxo`) - .reply(200, mockData.mockUtxoDetails) - } - - // Call the details API. - const result = await utxoBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result, "result should be an array") - - // Each element should have these primary properties. - assert.hasAllKeys(result[0], [ - "utxos", - "legacyAddress", - "cashAddress", - "scriptPubKey" - ]) - - // Validate the UTXO data structure. - assert.hasAnyKeys(result[0].utxos[0], [ - "txid", - "vout", - "amount", - "satoshis", - "height", - "confirmations" - ]) - }) - - it("should get utxos for mulitple addresses", async () => { - req.body = { - addresses: [ - `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`, - `bchtest:qzknfggae0av6yvxk77gmyq7syc67yux6sk80haqyr` - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz/utxo`) - .reply(200, mockData.mockUtxoDetails) - - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mwJnEzXzKkveF2q5Af9jxi9j1zrtWAnPU8/utxo`) - .reply(200, mockData.mockUtxoDetails) - } - - // Call the details API. - const result = await utxoBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2, "2 outputs for 2 inputs") - }) - - it("should throw 400 error if addresses array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.addresses = testArray - - const result = await utxoBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - }) - - describe("#AddressUtxoSingle", () => { - // details route handler. - const utxoSingle = addressRoute.testableComponents.utxoSingle - - it("should throw 400 if address is empty", async () => { - const result = await utxoSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - - it("should error on an array", async () => { - req.params.address = [`qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - - const result = await utxoSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "address can not be an array", - "Proper error message" - ) - }) - - it("should throw an error for an invalid address", async () => { - req.params.address = `02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - - const result = await utxoSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "Invalid BCH address", - "Proper error message" - ) - }) - - it("should detect a network mismatch", async () => { - req.params.address = `bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3` - - const result = await utxoSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include(result.error, "Invalid network", "Proper error message") - }) - - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - - const result = await utxoSingle(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it("should get details for a single address", async () => { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz/utxo`) - .reply(200, mockData.mockUtxoDetails) - } - - // Call the details API. - const result = await utxoSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - // Each element should have these primary properties. - assert.hasAllKeys(result, [ - "utxos", - "legacyAddress", - "cashAddress", - "scriptPubKey" - ]) - - // Validate the UTXO data structure. - assert.hasAnyKeys(result.utxos[0], [ - "txid", - "vout", - "amount", - "satoshis", - "height", - "confirmations" - ]) - }) - }) - - describe("#AddressUnconfirmedBulk", () => { - // unconfirmed route handler. - const unconfirmedBulk = addressRoute.testableComponents.unconfirmedBulk - - it("should throw an error for an empty body", async () => { - req.body = {} - - const result = await unconfirmedBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "addresses needs to be an array", - "Proper error message" - ) - }) - - it("should error on non-array single address", async () => { - req.body = { - address: `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - } - - const result = await unconfirmedBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "addresses needs to be an array", - "Proper error message" - ) - }) - - it("should throw 400 error if addresses array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.addresses = testArray - - const result = await unconfirmedBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should throw an error for an invalid address", async () => { - req.body = { - addresses: [`02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - } - - const result = await unconfirmedBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "Invalid BCH address", - "Proper error message" - ) - }) - - it("should detect a network mismatch", async () => { - req.body = { - addresses: [`bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3`] - } - - const result = await unconfirmedBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include(result.error, "Invalid network", "Proper error message") - }) - - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.body = { - addresses: [`qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - } - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api" - - const result = await unconfirmedBulk(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it("should get unconfirmed data for a single address", async () => { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz/utxo`) - .reply(200, mockData.mockUtxoDetails) - } - - // Call the details API. - const result = await unconfirmedBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - //console.log(`result[0].utxos: ${util.inspect(result[0].utxos)}`) - - assert.isArray(result, "result should be an array") - - // Dev note: Unconfirmed TXs are hard to test in an integration test because - // the nature of an unconfirmed transation is transient. It quickly becomes - // confirmed and thus should not show up. - }) - - it("should get unconfirmed data for an array of addresses", async () => { - req.body = { - addresses: [ - `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`, - `bchtest:qzknfggae0av6yvxk77gmyq7syc67yux6sk80haqyr` - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz/utxo`) - .reply(200, mockData.mockUtxoDetails) - - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mwJnEzXzKkveF2q5Af9jxi9j1zrtWAnPU8/utxo`) - .reply(200, mockData.mockUtxoDetails) - } - - // Call the details API. - const result = await unconfirmedBulk(req, res) - - assert.isArray(result) - }) - }) - - describe("#AddressUnconfirmedSingle", () => { - // details route handler. - const unconfirmedSingle = addressRoute.testableComponents.unconfirmedSingle - - it("should throw 400 if address is empty", async () => { - const result = await unconfirmedSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - - it("should error on an array", async () => { - req.params.address = [`qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - - const result = await unconfirmedSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "address can not be an array", - "Proper error message" - ) - }) - - it("should throw an error for an invalid address", async () => { - req.params.address = `02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - - const result = await unconfirmedSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "Invalid BCH address", - "Proper error message" - ) - }) - - it("should detect a network mismatch", async () => { - req.params.address = `bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3` - - const result = await unconfirmedSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include(result.error, "Invalid network", "Proper error message") - }) - - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.params.address = `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - - const result = await unconfirmedSingle(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it("should get details for a single address", async () => { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get(`/addr/mgps7qxk2Z5ma4mXsviznnet8wx4VvMPFz/utxo`) - .reply(200, mockData.mockUtxoDetails) - } - - // Call the details API. - const result = await unconfirmedSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - // Each element should have these primary properties. - assert.hasAllKeys(result, [ - "utxos", - "legacyAddress", - "cashAddress", - "scriptPubKey" - ]) - - assert.isArray(result.utxos) - }) - }) - - describe("#AddressTransactionsBulk", () => { - // unconfirmed route handler. - const transactionsBulk = addressRoute.testableComponents.transactionsBulk - - it("should throw an error for an empty body", async () => { - req.body = {} - - const result = await transactionsBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "addresses needs to be an array", - "Proper error message" - ) - }) - - it("should error on non-array single address", async () => { - req.body = { - address: `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - } - - const result = await transactionsBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "addresses needs to be an array", - "Proper error message" - ) - }) - - it("should throw 400 error if addresses array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.addresses = testArray - - const result = await transactionsBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should throw an error for an invalid address", async () => { - req.body = { - addresses: [`02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - } - - const result = await transactionsBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "Invalid BCH address", - "Proper error message" - ) - }) - - it("should detect a network mismatch", async () => { - req.body = { - addresses: [`bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3`] - } - - const result = await transactionsBulk(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include(result.error, "Invalid network", "Proper error message") - }) - - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.body = { - addresses: [`qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - } - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api" - - const result = await transactionsBulk(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it("should default to page 0", async () => { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get( - `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=0` - ) - .reply(200, mockData.mockTransactions) - } - - // Call the endpoint - const result = await transactionsBulk(req, res) - - // Assert current page defaults to 0 - assert.equal(result[0].currentPage, 0) - }) - - it("should process the requested page", async () => { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`], - page: 5 - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get( - `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=5` - ) - .reply(200, mockData.mockTransactions) - } - - // Call the endpoint - const result = await transactionsBulk(req, res) - - // Assert current page is same as requested - assert.equal(result[0].currentPage, 5) - }) - - it("should get transactions for a single address", async () => { - req.body = { - addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get( - `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=0` - ) - .reply(200, mockData.mockTransactions) - } - - // Call the details API. - const result = await transactionsBulk(req, res) - - assert.isArray(result, "result should be an array") - - assert.exists(result[0].pagesTotal) - assert.exists(result[0].currentPage) - assert.exists(result[0].txs) - assert.isArray(result[0].txs) - assert.exists(result[0].legacyAddress) - assert.exists(result[0].cashAddress) - }) - - it("should get transactions for an array of addresses", async () => { - req.body = { - addresses: [ - `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`, - `bchtest:qzknfggae0av6yvxk77gmyq7syc67yux6sk80haqyr` - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get( - `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=0` - ) - .reply(200, mockData.mockTransactions) - - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get( - `/txs/?address=bchtest:qzknfggae0av6yvxk77gmyq7syc67yux6sk80haqyr&pageNum=0` - ) - .reply(200, mockData.mockTransactions) - } - - // Call the details API. - const result = await transactionsBulk(req, res) - - assert.isArray(result, "result should be an array") - - assert.equal(result.length, 2, "Array should have 2 elements") - }) - }) - - describe("#AddressTransactionsSingle", () => { - // details route handler. - const transactionsSingle = - addressRoute.testableComponents.transactionsSingle - - it("should throw 400 if address is empty", async () => { - const result = await transactionsSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - - it("should error on an array", async () => { - req.params.address = [`qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`] - - const result = await transactionsSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "address can not be an array", - "Proper error message" - ) - }) - - it("should throw an error for an invalid address", async () => { - req.params.address = `02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - - const result = await transactionsSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "Invalid BCH address", - "Proper error message" - ) - }) - - it("should detect a network mismatch", async () => { - req.params.address = `bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3` - - const result = await transactionsSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include(result.error, "Invalid network", "Proper error message") - }) - - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.params.address = `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c` - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - - const result = await transactionsSingle(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it("should default to page 0", async () => { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get( - `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=0` - ) - .reply(200, mockData.mockTransactions) - } - - // Call the endpoint - const result = await transactionsSingle(req, res) - - // Assert current page defaults to 0 - assert.equal(result.currentPage, 0) - }) - - it("should process the requested page", async () => { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - req.query.page = 5 - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get( - `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=5` - ) - .reply(200, mockData.mockTransactions) - } - - // Call the endpoint - const result = await transactionsSingle(req, res) - - // Assert current page is same as requested - assert.equal(result.currentPage, 5) - }) - - it("should get details for a single address", async () => { - req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4` - - // Mock the Insight URL for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.BITCOINCOM_BASEURL}`) - .get( - `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=0` - ) - .reply(200, mockData.mockTransactions) - } - - // Call the details API. - const result = await transactionsSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.exists(result.pagesTotal) - assert.exists(result.currentPage) - assert.exists(result.txs) - assert.isArray(result.txs) - assert.exists(result.legacyAddress) - assert.exists(result.cashAddress) - }) - }) - - describe("#AddressFromXPubSingle", () => { - // details route handler. - const fromXPubSingle = addressRoute.testableComponents.fromXPubSingle - - it("should throw 400 if xpub is empty", async () => { - const result = await fromXPubSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "xpub can not be empty") - }) - - it("should error on an array", async () => { - req.params.xpub = [ - `tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM` - ] - - const result = await fromXPubSingle(req, res) - - assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") - assert.include( - result.error, - "xpub can not be an array", - "Proper error message" - ) - }) - - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.params.xpub = `tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM` - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - - const result = await fromXPubSingle(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it("should create an address from xpub", async () => { - req.params.xpub = `tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM` - - // Mock the Insight URL for unit tests. - // TODO add unit test - // if (process.env.TEST === "unit") { - // nock(`${process.env.BITCOINCOM_BASEURL}`) - // .get( - // `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=0` - // ) - // .reply(200, mockData.mockTransactions) - // } - - // Call the details API. - const result = await fromXPubSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.exists(result.legacyAddress) - assert.exists(result.cashAddress) - }) - }) -}) diff --git a/test/v3/xpub.js b/test/v3/xpub.js new file mode 100644 index 0000000..0bb50a4 --- /dev/null +++ b/test/v3/xpub.js @@ -0,0 +1,143 @@ +/* + TESTS FOR THE XPUB.JS LIBRARY + + This test file uses the environment variable TEST to switch between unit + and integration tests. By default, TEST is set to 'unit'. Set this variable + to 'integration' to run the tests against BCH mainnet. +*/ + +"use strict" + +const chai = require("chai") +const assert = chai.assert +const xpubRoute = require("../../src/routes/v3/xpub") +const nock = require("nock") // HTTP mocking + +let originalUrl // Used during transition from integration to unit tests. + +// Mocking data. +const { mockReq, mockRes } = require("./mocks/express-mocks") +const mockData = require("./mocks/address-mock") + +// Used for debugging. +const util = require("util") +util.inspect.defaultOptions = { depth: 1 } + +describe("#XPUBRouter", () => { + let req, res + + before(() => { + // 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 + }) + + // Setup the mocks before each test. + beforeEach(() => { + // Mock the req and res objects used by Express routes. + req = mockReq + res = mockRes + + // Explicitly reset the parmas and body. + req.params = {} + req.body = {} + req.query = {} + + // Activate nock if it's inactive. + if (!nock.isActive()) nock.activate() + }) + + afterEach(() => { + // Clean up HTTP mocks. + nock.cleanAll() // clear interceptor list. + nock.restore() + }) + + after(() => { + //process.env.BITCOINCOM_BASEURL = originalUrl + }) + + describe("#root", () => { + // root route handler. + const root = xpubRoute.testableComponents.root + + it("should respond to GET for base route", async () => { + const result = root(req, res) + + assert.equal(result.status, "address", "Returns static string") + }) + }) + + describe("#FromXPubSingle", () => { + // details route handler. + const fromXPubSingle = xpubRoute.testableComponents.fromXPubSingle + + it("should throw 400 if xpub is empty", async () => { + const result = await fromXPubSingle(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "xpub can not be empty") + }) + + it("should error on an array", async () => { + req.params.xpub = [ + `tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM` + ] + + const result = await fromXPubSingle(req, res) + + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include( + result.error, + "xpub can not be an array", + "Proper error message" + ) + }) + /* + it("should throw 500 when network issues", async () => { + const savedUrl = process.env.BITCOINCOM_BASEURL + + try { + req.params.xpub = `tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM` + + // Switch the Insight URL to something that will error out. + process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" + + const result = await fromXPubSingle(req, res) + + // Restore the saved URL. + process.env.BITCOINCOM_BASEURL = savedUrl + + assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") + assert.include(result.error, "ENOTFOUND", "Error message expected") + } catch (err) { + // Restore the saved URL. + process.env.BITCOINCOM_BASEURL = savedUrl + } + }) +*/ + it("should create an address from xpub", async () => { + req.params.xpub = `tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM` + + // Mock the Insight URL for unit tests. + // TODO add unit test + // if (process.env.TEST === "unit") { + // nock(`${process.env.BITCOINCOM_BASEURL}`) + // .get( + // `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=0` + // ) + // .reply(200, mockData.mockTransactions) + // } + + // Call the details API. + const result = await fromXPubSingle(req, res) + //console.log(`result: ${util.inspect(result)}`) + + // Assert that required fields exist in the returned object. + assert.exists(result.legacyAddress) + assert.exists(result.cashAddress) + }) + }) +})