fix(blockbook): Added blockbook API for balance and utxo

This commit is contained in:
Chris Troutner
2019-06-09 14:11:16 -07:00
parent e9e09be140
commit a2447bd07c
2 changed files with 982 additions and 0 deletions
+382
View File
@@ -0,0 +1,382 @@
/*
Bitcore Node API route
*/
"use strict"
const express = require("express")
const requestUtils = require("./services/requestUtils")
const axios = require("axios")
const routeUtils = require("./route-utils")
const wlogger = require("../../util/winston-logging")
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()
//const BITCORE_URL = process.env.BITCORE_URL
// Connect the route endpoints to their handler functions.
router.get("/", root)
router.get("/balance/:address", balanceSingle)
// Root API endpoint. Simply acknowledges that it exists.
function root(req, res, next) {
return res.json({ status: "address" })
}
// Query the Bitcore Node API for a balance on a single BCH address.
// Returns a Promise.
async function balanceFromBlockbook(thisAddress) {
try {
//console.log(`BITCORE_URL: ${BITCORE_URL}`)
// Convert the address to a cashaddr without a prefix.
const addr = BITBOX.Address.toCashAddress(thisAddress, false)
// Determine if we are working with the testnet or mainnet networks.
let network = "mainnet"
if (process.env.NETWORK === "testnet") network = "testnet"
const path = `${process.env.BLOCKBOOK_URL}api/v2/address/${addr}`
// Query the Bitcore Node API.
const axiosResponse = await axios.get(path)
const retData = axiosResponse.data
//console.log(`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
}
}
// GET handler for single balance
async function balanceSingle(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."
})
}
wlogger.debug(
`Executing bitcore/balanceSingle with this address: `,
address
)
// Ensure the input is a valid BCH address.
try {
const 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 Bitcore Node API.
const retData = await balanceFromBlockbook(address)
// 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.
wlogger.error(`Error in blockbook.js/balanceSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
/*
// POST handler for bulk queries on address details
async function balanceBulk(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.`
})
}
wlogger.debug(
`Executing bitcore.js/balanceBulk 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) =>
balanceFromBitcore(address)
)
// 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 })
}
wlogger.error(`Error in bitcore.js/balanceBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Query the Bitcore Node API for utxos associated with a BCH address.
// Returns a Promise.
async function utxosFromBitcore(thisAddress) {
try {
//console.log(`BITCORE_URL: ${BITCORE_URL}`)
// Convert the address to a cashaddr without a prefix.
const addr = BITBOX.Address.toCashAddress(thisAddress, false)
// Determine if we are working with the testnet or mainnet networks.
let network = "mainnet"
if (process.env.NETWORK === "testnet") network = "testnet"
const path = `${process.env.BITCORE_URL}api/BCH/${network}/address/${addr}/?unspent=true`
// Query the Bitcore Node API.
const axiosResponse = await axios.get(path)
const retData = axiosResponse.data
//console.log(`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
}
}
// GET handler for single balance
async function utxosSingle(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."
})
}
wlogger.debug(
`Executing bitcore/balanceSingle with this address: `,
address
)
// Ensure the input is a valid BCH address.
try {
const 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 Bitcore Node API.
const retData = await utxosFromBitcore(address)
// 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.
wlogger.error(`Error in bitcore.js/utxosSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// POST handler for bulk queries on address utxos
async function utxosBulk(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.`
})
}
wlogger.debug(
`Executing bitcore.js/utxosBulk 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) =>
utxosFromBitcore(address)
)
// 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 })
}
wlogger.error(`Error in bitcore.js/utxosBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
*/
module.exports = {
router,
testableComponents: {
root,
balanceSingle
//balanceBulk,
//utxosSingle,
//utxosBulk
}
}
+600
View File
@@ -0,0 +1,600 @@
/*
TESTS FOR THE BLOCKBOOK.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:
*/
"use strict"
const chai = require("chai")
const assert = chai.assert
const blockbookRoute = require("../../src/routes/v3/blockbook")
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/bitcore-mock")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#Blockbook Router", () => {
let req, res, mockServerUrl
before(() => {
originalUrl = process.env.BLOCKBOOK_URL
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BLOCKBOOK_URL = "http://fakeurl/api/"
mockServerUrl = `http://fakeurl`
}
// 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.BLOCKBOOK_URL = originalUrl
})
describe("#root", () => {
// root route handler.
const root = blockbookRoute.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("#Balance Single", () => {
// details route handler.
const balanceSingle = blockbookRoute.testableComponents.balanceSingle
it("should throw 400 if address is empty", async () => {
const result = await balanceSingle(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 balanceSingle(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 balanceSingle(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 balanceSingle(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.BLOCKBOOK_URL
try {
req.params.address = `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`
// Switch the Insight URL to something that will error out.
process.env.BLOCKBOOK_URL = "http://fakeurl/api/"
const result = await balanceSingle(req, res)
// Restore the saved URL.
process.env.BLOCKBOOK_URL = savedUrl
assert.equal(res.statusCode, 500, "HTTP status code 500 expected.")
assert.include(result.error, "ENOTFOUND", "Error message expected")
} catch (err) {
// Restore the saved URL.
process.env.BLOCKBOOK_URL = savedUrl
}
})
it("should get balance 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.BLOCKBOOK_URL}`)
.get(uri => uri.includes("/"))
.reply(200, mockData.mockBalance)
}
// Call the details API.
const result = await balanceSingle(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["confirmed", "unconfirmed", "balance"])
assert.isNumber(result.confirmed)
assert.isNumber(result.unconfirmed)
assert.isNumber(result.balance)
})
})
/*
describe("#Balance Bulk", () => {
// details route handler.
const balanceBulk = blockbookRoute.testableComponents.balanceBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await balanceBulk(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 balanceBulk(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 balanceBulk(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 balanceBulk(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 balanceBulk(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 balanceBulk(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 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(mockServerUrl)
.get(uri => uri.includes("/"))
.reply(200, mockData.mockBalance)
}
// Call the details API.
const result = await balanceBulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], ["confirmed", "unconfirmed", "balance"])
assert.isNumber(result[0].confirmed)
assert.isNumber(result[0].unconfirmed)
assert.isNumber(result[0].balance)
})
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(mockServerUrl)
.get(uri => uri.includes("/"))
.times(2)
.reply(200, mockData.mockBalance)
}
// Call the details API.
const result = await balanceBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.equal(result.length, 2, "2 outputs for 2 inputs")
})
})
describe("#UTXOs Single", () => {
// details route handler.
const utxosSingle = blockbookRoute.testableComponents.utxosSingle
it("should throw 400 if address is empty", async () => {
const result = await utxosSingle(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 utxosSingle(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 utxosSingle(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 utxosSingle(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.BLOCKBOOK_URL
try {
req.params.address = `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`
// Switch the Insight URL to something that will error out.
process.env.BLOCKBOOK_URL = "http://fakeurl/api/"
const result = await utxosSingle(req, res)
// Restore the saved URL.
process.env.BLOCKBOOK_URL = savedUrl
assert.equal(res.statusCode, 500, "HTTP status code 500 expected.")
assert.include(result.error, "ENOTFOUND", "Error message expected")
} catch (err) {
// Restore the saved URL.
process.env.BLOCKBOOK_URL = savedUrl
}
})
it("should get utxos 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.BLOCKBOOK_URL}`)
.get(uri => uri.includes("/"))
.reply(200, mockData.mockUtxos)
}
// Call the details API.
const result = await utxosSingle(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAnyKeys(result[0], [
"_id",
"chain",
"network",
"coinbase",
"mintIndex",
"spentTxid",
"mintTxid",
"mintHeight",
"spentHeight",
"address",
"script",
"value",
"confirmations"
])
})
})
describe("#UTXO Bulk", () => {
// details route handler.
const utxosBulk = blockbookRoute.testableComponents.utxosBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await utxosBulk(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 utxosBulk(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 utxosBulk(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 utxosBulk(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 utxosBulk(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 utxosBulk(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 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(mockServerUrl)
.get(uri => uri.includes("/"))
.reply(200, mockData.mockUtxos)
}
// Call the details API.
const result = await utxosBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.isArray(result[0])
assert.hasAnyKeys(result[0][0], [
"_id",
"chain",
"network",
"coinbase",
"mintIndex",
"spentTxid",
"mintTxid",
"mintHeight",
"spentHeight",
"address",
"script",
"value",
"confirmations"
])
})
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(mockServerUrl)
.get(uri => uri.includes("/"))
.times(2)
.reply(200, mockData.mockUtxos)
}
// Call the details API.
const result = await utxosBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.isArray(result[0])
assert.hasAnyKeys(result[0][0], [
"_id",
"chain",
"network",
"coinbase",
"mintIndex",
"spentTxid",
"mintTxid",
"mintHeight",
"spentHeight",
"address",
"script",
"value",
"confirmations"
])
})
})
*/
})