Merge pull request #1 from FullStack-cash/master

Adding PSF SLP indexer endpoints
This commit is contained in:
Chris Troutner
2022-01-16 06:52:42 -08:00
committed by GitHub
24 changed files with 11755 additions and 11718 deletions
+10732 -11588
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -25,7 +25,7 @@
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v4/",
"docs": "./node_modules/.bin/apidoc -i src/routes/v5 -o docs",
"test:temp1": "export NETWORK=mainnet && export TEST=integration && mocha -g '#utxosBulk' --exit --timeout 30000 test/v5/",
"test:temp2": "export NETWORK=mainnet && mocha -g '#utxosBulk' --exit --timeout 30000 test/v5/"
"test:temp2": "export SLP_INDEXER_API=http://fakeurl/api/ && export NETWORK=mainnet && mocha --exit --timeout 30000 test/v5/psf-slp-indexer"
},
"engines": {
"node": ">=10.15.1"
@@ -62,19 +62,19 @@
"bignumber.js": "^9.0.0",
"chai": "^4.1.2",
"coveralls": "^3.0.2",
"eslint": "5.16.0",
"eslint-config-prettier": "^6.0.0",
"eslint-config-standard": "^13.0.1",
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-standard": "^4.0.0",
"eslint": "7.17.0",
"eslint-config-prettier": "7.1.0",
"eslint-config-standard": "16.0.2",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-standard": "4.0.0",
"fs-extra": "^9.0.0",
"lodash.clonedeep": "^4.5.0",
"nock": "^13.0.5",
"nyc": "^15.0.0",
"prettier": "^2.0.0",
"semantic-release": "^17.4.2",
"sinon": "^9.0.0",
"standard": "^14.3.1"
"standard": "^16.0.4"
},
"release": {
"publish": [
+10 -3
View File
@@ -55,6 +55,7 @@ const EncryptionV5 = require('./routes/v5/encryption')
const PriceV5 = require('./routes/v5/price')
const JWTV5 = require('./routes/v5/jwt')
const BcashSLP = require('./routes/v5/bcash/slp')
const PsfSlpIndexer = require('./routes/v5/psf-slp-indexer')
// const Ninsight = require('./routes/v5/ninsight')
require('dotenv').config()
@@ -84,6 +85,7 @@ const utilV5 = new UtilV5({ electrumx: electrumxv5 })
const dsproofV5 = new DSProofV5()
const jwtV5 = new JWTV5()
const bcashSLP = new BcashSLP()
const psfSlpIndexer = new PsfSlpIndexer()
const app = express()
app.locals.env = process.env
@@ -100,7 +102,7 @@ app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade')
// Mount the docs
app.use('/docs', express.static(`${__dirname}/../docs`))
app.use('/docs', express.static(`${__dirname.toString()}/../docs`))
// Log each request to the console with IP addresses.
// app.use(logger("dev"))
@@ -200,6 +202,11 @@ app.use(`/${v5prefix}/` + 'ninsight', ninsight.router)
app.use(`/${v5prefix}/` + 'bcash/slp', bcashSLP.router)
app.use(`/${v5prefix}/` + 'psf/slp', psfSlpIndexer.router)
// Daniel:
// app.use(`/${v5prefix}/` + 'psfslp', psfSlp.router)
// catch 404 and forward to error handler
app.use((req, res, next) => {
const err = {
@@ -285,11 +292,11 @@ function onError (error) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`)
process.exit(1)
// break
break
case 'EADDRINUSE':
console.error(`${bind} is already in use`)
process.exit(1)
// break
break
default:
throw error
}
+2 -2
View File
@@ -105,8 +105,8 @@ class Slpdb {
const username = 'BITBOX'
const password = SLPDB_PASS
const combined = `${username}:${password}`
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
const base64Credential = Buffer.from(combined).toString('base64')
const readyCredential = `Basic ${base64Credential}`
const options = {
headers: {
+10 -11
View File
@@ -418,9 +418,8 @@ class Slp {
},
{
$match: {
'graphTxn.outputs.address': _this.bchjs.SLP.Address.toSLPAddress(
address
),
'graphTxn.outputs.address':
_this.bchjs.SLP.Address.toSLPAddress(address),
'graphTxn.outputs.status': 'UNSPENT',
'graphTxn.outputs.slpAmount': { $gte: 0 }
}
@@ -631,9 +630,8 @@ class Slp {
},
{
$match: {
'graphTxn.outputs.address': _this.bchjs.SLP.Address.toSLPAddress(
address
),
'graphTxn.outputs.address':
_this.bchjs.SLP.Address.toSLPAddress(address),
'graphTxn.outputs.status': 'UNSPENT',
'graphTxn.outputs.slpAmount': { $gte: 0 }
}
@@ -1234,7 +1232,7 @@ class Slp {
const opt = {
method: 'get',
baseURL: `${process.env.SLP_API_URL}slp/validate/${txid}`,
timeout: 10000 // Exit after 10 seconds.
timeout: 45000 // Exit after 45 seconds.
}
const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`)
@@ -1753,8 +1751,8 @@ class Slp {
const password = SLPDB_PASS_GP
const combined = `${username}:${password}`
// console.log(`combined: ${combined}`)
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
const base64Credential = Buffer.from(combined).toString('base64')
const readyCredential = `Basic ${base64Credential}`
const options = {
headers: {
@@ -1773,8 +1771,8 @@ class Slp {
const password = SLPDB_PASS_WL
const combined = `${username}:${password}`
// console.log(`combined: ${combined}`)
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
const base64Credential = Buffer.from(combined).toString('base64')
const readyCredential = `Basic ${base64Credential}`
const options = {
headers: {
@@ -1803,6 +1801,7 @@ class Slp {
tokenOutputs.map((x) => {
const string = parseFloat(x.amount) * 100000000
sendOutputs.push(string.toString())
return 1 // Making the linter happy
})
// Because you are not using Insight API indexer, you do not get the
+1 -1
View File
@@ -433,7 +433,7 @@ class UtilRoute {
// Loop through each input and sign
let redeemScript
for (var i = 0; i < utxos.length; i++) {
for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i]
transactionBuilder.sign(
+17 -9
View File
@@ -1,5 +1,5 @@
/*
Electrum API route
bcash node routes for working with SLP tokens.
*/
'use strict'
@@ -33,14 +33,6 @@ class BcashSlp {
this.bchjs = bchjs
// _this.bitcore = bitcore
this.bcashServer = process.env.BCASH_SERVER
if (!this.bcashServer) {
// console.warn('FULCRUM_API env var not set. Can not connect to Fulcrum indexer.')
throw new Error(
'BCASH_SERVER env var not set. Can not connect to bcash full node.'
)
}
this.router = router
this.router.get('/', this.root)
this.router.get('/utxos/:address', this.getUtxos)
@@ -63,6 +55,14 @@ class BcashSlp {
try {
const address = req.params.address
this.bcashServer = process.env.BCASH_SERVER
if (!this.bcashServer) {
// console.warn('FULCRUM_API env var not set. Can not connect to Fulcrum indexer.')
throw new Error(
'BCASH_SERVER env var not set. Can not connect to bcash full node.'
)
}
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
@@ -144,6 +144,14 @@ class BcashSlp {
throw new Error('tokenId must be string')
}
this.bcashServer = process.env.BCASH_SERVER
if (!this.bcashServer) {
// console.warn('FULCRUM_API env var not set. Can not connect to Fulcrum indexer.')
throw new Error(
'BCASH_SERVER env var not set. Can not connect to bcash full node.'
)
}
const result = await _this.axios.get(
`${_this.bcashServer}token/${tokenId}`
)
+257
View File
@@ -0,0 +1,257 @@
/*
Routes for interacting with the psf-slp-indexer
*/
// Public npm libraries
const express = require('express')
const router = express.Router()
const axios = require('axios')
const util = require('util')
// Local libraries
const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils()
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// Local libraries
// const wlogger = require('../../../util/winston-logging')
const config = require('../../../config')
let _this
class PsfSlpIndexer {
constructor () {
// Encapsulate dependencies
this.axios = axios
this.router = router
this.routeUtils = routeUtils
this.config = config
this.bchjs = bchjs
// Define routes
this.router.get('/', this.root)
this.router.get('/status', this.getStatus)
this.router.post('/address', this.getAddress)
this.router.post('/txid', this.getTxid)
this.router.post('/token', this.getTokenStats)
_this = this
}
// Root API endpoint. Simply acknowledges that it exists.
root (req, res, next) {
return res.json({ status: 'psf-slp-indexer' })
}
/**
* @api {get} /psf/slp/status/ Indexer Status.
* @apiName SLP indexer status.
* @apiGroup PSF SLP
* @apiDescription Return SLP indexer status
*
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:3000/v5/psf/slp/status
*
*
*/
async getStatus (req, res, next) {
try {
// Verify env var is set for interacting with the indexer.
_this.checkEnvVar()
const response = await _this.axios.get(
`${_this.psfSlpIndexerApi}slp/status/`
)
res.status(200)
return res.json(response.data)
} catch (err) {
return _this.errorHandler(err, res)
}
}
/**
* @api {post} /psf/slp/address/ SLP balance for address.
* @apiName SLP balance for address.
* @apiGroup PSF SLP
* @apiDescription Return SLP balance for address
*
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n" }' localhost:3000/v5/psf/slp/address
*
*
*/
async getAddress (req, res, next) {
try {
// Verify env var is set for interacting with the indexer.
_this.checkEnvVar()
// Validate the input data.
const address = req.body.address
if (!address || address === '') {
res.status(400)
return res.json({
success: false,
error: 'address can not be empty'
})
}
// Ensure the input is a valid BCH address.
try {
_this.bchjs.SLP.Address.toCashAddress(address)
} catch (err) {
res.status(400)
return res.json({
success: false,
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 cashAddr = _this.bchjs.SLP.Address.toCashAddress(address)
const networkIsValid = _this.routeUtils.validateNetwork(cashAddr)
if (!networkIsValid) {
res.status(400)
return res.json({
success: false,
error:
'Invalid network. Trying to use a testnet address on mainnet, or vice versa.'
})
}
const response = await _this.axios.post(
`${_this.psfSlpIndexerApi}slp/address/`,
{ address }
)
res.status(200)
return res.json(response.data)
} catch (err) {
// console.log('err', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {post} /psf/slp/txid/ SLP transaction data.
* @apiName SLP transaction data.
* @apiGroup PSF SLP
* @apiDescription Return slp transaction data.
*
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "f3e14cd871402a766e85045dc552f2c1e87857dd3ea1b15efab6334ccef5e315" }' localhost:3000/v5/psf/slp/txid
*
*
*/
async getTxid (req, res, next) {
try {
// Verify env var is set for interacting with the indexer.
_this.checkEnvVar()
const txid = req.body.txid
if (!txid || txid === '') {
res.status(400)
return res.json({
success: false,
error: 'txid can not be empty'
})
}
if (txid.length !== 64) {
res.status(400)
return res.json({
success: false,
error: 'This is not a txid'
})
}
const response = await _this.axios.post(
`${_this.psfSlpIndexerApi}slp/tx/`,
{ txid }
)
res.status(200)
return res.json(response.data)
} catch (err) {
// console.log('err', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {post} /psf/slp/tokenStats/ List stats for a single slp token.
* @apiName List stats for a single slp token.
* @apiGroup PSF SLP
* @apiDescription Return list stats for a single slp token.
*
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2" }' localhost:3000/v5/psf/slp/token
*
*
*/
async getTokenStats (req, res, next) {
try {
// Verify env var is set for interacting with the indexer.
_this.checkEnvVar()
const tokenId = req.body.tokenId
if (!tokenId || tokenId === '') {
res.status(400)
return res.json({
success: false,
error: 'tokenId can not be empty'
})
}
const response = await _this.axios.post(
`${_this.psfSlpIndexerApi}slp/token/`,
{ tokenId }
)
res.status(200)
return res.json(response.data)
} catch (err) {
return _this.errorHandler(err, res)
}
}
// Check the the environment variable is set correctly.
checkEnvVar () {
_this.psfSlpIndexerApi = process.env.SLP_INDEXER_API
if (!_this.psfSlpIndexerApi) {
throw new Error(
'SLP_INDEXER_API env var not set. Can not connect to PSF SLP indexer.'
)
}
}
// DRY error handler.
errorHandler (err, res) {
// Attempt to decode the error message.
const { msg, status } = _this.routeUtils.decodeError(err)
// console.log('errorHandler msg: ', msg)
// console.log('errorHandler status: ', status)
if (msg) {
res.status(status)
return res.json({ success: false, error: msg })
}
// Handle error patterns specific to this route.
if (err.message) {
res.status(400)
return res.json({ success: false, error: err.message })
}
// If error can be handled, return the stack trace
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
module.exports = PsfSlpIndexer
+2 -2
View File
@@ -105,8 +105,8 @@ class Slpdb {
const username = 'BITBOX'
const password = SLPDB_PASS
const combined = `${username}:${password}`
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
const base64Credential = Buffer.from(combined).toString('base64')
const readyCredential = `Basic ${base64Credential}`
const options = {
headers: {
+10 -11
View File
@@ -418,9 +418,8 @@ class Slp {
},
{
$match: {
'graphTxn.outputs.address': _this.bchjs.SLP.Address.toSLPAddress(
address
),
'graphTxn.outputs.address':
_this.bchjs.SLP.Address.toSLPAddress(address),
'graphTxn.outputs.status': 'UNSPENT',
'graphTxn.outputs.slpAmount': { $gte: 0 }
}
@@ -631,9 +630,8 @@ class Slp {
},
{
$match: {
'graphTxn.outputs.address': _this.bchjs.SLP.Address.toSLPAddress(
address
),
'graphTxn.outputs.address':
_this.bchjs.SLP.Address.toSLPAddress(address),
'graphTxn.outputs.status': 'UNSPENT',
'graphTxn.outputs.slpAmount': { $gte: 0 }
}
@@ -1753,14 +1751,14 @@ class Slp {
const password = SLPDB_PASS_GP
const combined = `${username}:${password}`
// console.log(`combined: ${combined}`)
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
const base64Credential = Buffer.from(combined).toString('base64')
const readyCredential = `Basic ${base64Credential}`
const options = {
headers: {
authorization: readyCredential
},
timeout: 15000
timeout: 45000
}
return options
@@ -1773,8 +1771,8 @@ class Slp {
const password = SLPDB_PASS_WL
const combined = `${username}:${password}`
// console.log(`combined: ${combined}`)
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
const base64Credential = Buffer.from(combined).toString('base64')
const readyCredential = `Basic ${base64Credential}`
const options = {
headers: {
@@ -1803,6 +1801,7 @@ class Slp {
tokenOutputs.map((x) => {
const string = parseFloat(x.amount) * 100000000
sendOutputs.push(string.toString())
return 1 // Making the linter happy
})
// Because you are not using Insight API indexer, you do not get the
+9 -7
View File
@@ -6,24 +6,26 @@
'use strict'
var winston = require('winston')
const winston = require('winston')
require('winston-daily-rotate-file')
var NETWORK = process.env.NETWORK
const NETWORK = process.env.NETWORK
// Default 1 Megabyte
var LOG_MAX_SIZE = process.env.LOG_MAX_SIZE ? process.env.LOG_MAX_SIZE : '1m'
const LOG_MAX_SIZE = process.env.LOG_MAX_SIZE ? process.env.LOG_MAX_SIZE : '1m'
// Default 5 days.
// This was causing a problem with popularity. Creating over a gigabyte of files.
// var LOG_MAX_FILES = process.env.LOG_MAX_FILES ? process.env.LOG_MAX_FILES : '5d'
// 250 files @ 1Meg each = 250 megs
var LOG_MAX_FILES = process.env.LOG_MAX_FILES ? process.env.LOG_MAX_FILES : '250'
const LOG_MAX_FILES = process.env.LOG_MAX_FILES
? process.env.LOG_MAX_FILES
: '250'
// Configure daily-rotation transport.
var transport = new winston.transports.DailyRotateFile({
filename: `${__dirname}/../../logs/rest-${NETWORK}-%DATE%.log`,
const transport = new winston.transports.DailyRotateFile({
filename: `${__dirname.toString()}/../../logs/rest-${NETWORK}-%DATE%.log`,
datePattern: 'YYYY-MM-DD',
zippedArchive: false,
maxSize: LOG_MAX_SIZE,
@@ -39,7 +41,7 @@ transport.on('rotate', function (oldFilename, newFilename) {
})
// This controls what goes into the log FILES
var wlogger = winston.createLogger({
const wlogger = winston.createLogger({
level: 'verbose',
format: winston.format.json(),
transports: [
+3
View File
@@ -73,4 +73,7 @@ export LOG_MAX_FILES=5d
# (Optional) if using a bcash node, set this variable. Otherwise leave it as-is.
export BCASH_SERVER=http://localhost
# PSF SLP indexer
export SLP_INDEXER_API=https://psf-slp-indexer.fullstack.cash/
npm start
+6 -6
View File
@@ -359,7 +359,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
@@ -596,7 +596,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if txid array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -843,7 +843,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if heights array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.heights = testArray
@@ -1206,7 +1206,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
@@ -1494,7 +1494,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
@@ -1781,7 +1781,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
+4 -4
View File
@@ -456,7 +456,7 @@ describe('#BlockchainRouter', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.hashes = testArray
@@ -1006,7 +1006,7 @@ describe('#BlockchainRouter', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -1525,7 +1525,7 @@ describe('#BlockchainRouter', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -1729,7 +1729,7 @@ describe('#BlockchainRouter', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.proofs = testArray
+4 -4
View File
@@ -216,7 +216,7 @@ describe('#Raw-Transactions', () => {
it('should throw 400 error if hexes array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.hexes = testArray
@@ -459,7 +459,7 @@ describe('#Raw-Transactions', () => {
it('should throw 400 error if hexes array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.hexes = testArray
@@ -599,7 +599,7 @@ describe('#Raw-Transactions', () => {
it('should throw 400 error if txids array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -885,7 +885,7 @@ describe('#Raw-Transactions', () => {
it('should throw 400 error if hexs array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.hexes = testArray
+13 -23
View File
@@ -36,7 +36,7 @@ if (process.env.TEST === 'unit') {
process.env.BITDB_URL = 'http://fakeurl/'
process.env.BITCOINCOM_BASEURL = 'http://fakeurl/'
process.env.SLPDB_URL = 'http://fakeurl/'
mockServerUrl = 'http://fakeurl'
// mockServerUrl = 'http://fakeurl'
}
// Prepare the slpRoute for stubbing dependcies on slpjs.
@@ -577,7 +577,7 @@ describe('#SLP', () => {
it('should throw 400 error if array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -787,7 +787,7 @@ describe('#SLP', () => {
it('should throw 400 error if array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -1406,8 +1406,7 @@ describe('#SLP', () => {
it('should throw error if Array is too long', async () => {
const utxo = {
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
@@ -1435,8 +1434,7 @@ describe('#SLP', () => {
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
@@ -1444,8 +1442,7 @@ describe('#SLP', () => {
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
@@ -1459,8 +1456,7 @@ describe('#SLP', () => {
// Mock the external network call.
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetails').resolves([
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
@@ -1469,8 +1465,7 @@ describe('#SLP', () => {
isValid: false
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
@@ -1581,8 +1576,7 @@ describe('#SLP', () => {
it('should throw error if Array is too long', async () => {
const utxo = {
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
@@ -1610,8 +1604,7 @@ describe('#SLP', () => {
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
@@ -1619,8 +1612,7 @@ describe('#SLP', () => {
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
@@ -1634,8 +1626,7 @@ describe('#SLP', () => {
// Mock the external network call.
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetailsWL').resolves([
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
@@ -1644,8 +1635,7 @@ describe('#SLP', () => {
isValid: false
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
+1 -1
View File
@@ -203,7 +203,7 @@ describe('#Util', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
+6 -6
View File
@@ -239,7 +239,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
@@ -485,7 +485,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
@@ -729,7 +729,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -1065,7 +1065,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if heights array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.heights = testArray
@@ -1261,7 +1261,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
@@ -1503,7 +1503,7 @@ describe('#Electrumx', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
+4 -4
View File
@@ -456,7 +456,7 @@ describe('#BlockchainRouter', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.hashes = testArray
@@ -1006,7 +1006,7 @@ describe('#BlockchainRouter', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -1525,7 +1525,7 @@ describe('#BlockchainRouter', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -1729,7 +1729,7 @@ describe('#BlockchainRouter', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.proofs = testArray
+130
View File
@@ -0,0 +1,130 @@
/*
This library contains mocking data for running unit tests on the psf-slp-indexer route.
*/
'use strict'
const tokenStats = {
tokenData: {
type: 1,
ticker: 'TP03',
name: 'Test Plugin 03',
tokenId: '13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
documentUri: 'fullstack.cash',
documentHash: 'i\u00043s\u0003tz}/PǚZ>T)',
decimals: 0,
mintBatonIsActive: false,
tokensInCirculationBN: '1',
tokensInCirculationStr: '1',
blockCreated: 722420,
totalBurned: '0',
totalMinted: '1',
txs: [
{
txid: '13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
height: 722420,
type: 'GENESIS',
qty: '1'
}
]
}
}
const txData = {
txData: {
txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
version: 2,
size: 339,
locktime: 0,
vin: [
{
txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
vout: 0,
scriptSig: {
asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
},
sequence: 4294967295,
address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
value: 0.00051303,
tokenQty: 0,
tokenQtyStr: '0',
tokenId: null
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm: 'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
hex: '6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
type: 'nulldata'
},
tokenQtyStr: '0',
tokenQty: 0
}
],
hex: '0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
blockhash: '0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
confirmations: 97398,
time: 1581773131,
blocktime: 1581773131,
blockheight: 622414,
isSlpTx: true,
tokenTxType: 'GENESIS',
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
tokenType: 1,
tokenTicker: 'TROUT',
tokenName: "Trout's test token",
tokenDecimals: 2,
tokenUri: 'troutsblog.com',
tokenDocHash: '',
isValidSlp: true
}
}
const balance = {
balance: {
utxos: [
{
txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
vout: 1,
type: 'token',
qty: '1800',
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
}
],
txs: [
{
txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
height: 717796
},
{
txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
height: 717832
}
],
balances: [
{
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
qty: '1800'
}
]
}
}
const status = {
status: {
startBlockHeight: 543376,
syncedBlockHeight: 722860,
chainBlockHeight: 722679
}
}
module.exports = {
tokenStats,
txData,
balance,
status
}
+508
View File
@@ -0,0 +1,508 @@
/*
TESTS FOR THE PSF-SLP-INDEXER.JS LIBRARY
*/
'use strict'
const chai = require('chai')
const assert = chai.assert
const PsfSlpIndexerRouter = require('../../src/routes/v5/psf-slp-indexer')
const uut = new PsfSlpIndexerRouter()
// const nock = require('nock') // HTTP mocking
const sinon = require('sinon')
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require('./mocks/express-mocks')
const mockData = require('./mocks/psf-slp-indexer-mocks')
// Used for debugging.
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
describe('#PsfSlpIndexer', () => {
let req, res
let sandbox
before(() => {
// Save existing environment variables.
originalEnvVars = {
SLP_INDEXER_API: process.env.SLP_INDEXER_API
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = 'unit'
if (process.env.TEST === 'unit') {
process.env.SLP_INDEXER_API = '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 = {}
sandbox = sinon.createSandbox()
})
afterEach(() => {
// Restore Sandbox
sandbox.restore()
})
after(() => {
// Restore any pre-existing environment variables.
process.env.SLP_INDEXER_API = originalEnvVars.SLP_INDEXER_API
})
describe('#root', async () => {
// root route handler.
it('should respond to GET for base route', async () => {
const result = uut.root(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(result.status, 'psf-slp-indexer', 'Returns static string')
})
})
describe('#getTokenStats', async () => {
it('should throw 400 error if tokenId is missing', async () => {
const result = await uut.getTokenStats(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error', 'success'])
assert.isFalse(result.success)
assert.include(result.error, 'tokenId can not be empty')
})
it('should throw 503 when network issues', async () => {
req.body.tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
// Save the existing RPC URL.
const savedUrl2 = process.env.SLP_INDEXER_API
// Manipulate the URL to cause a 500 network error.
process.env.SLP_INDEXER_API = 'http://fakeurl/api/'
await uut.getTokenStats(req, res)
// console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.SLP_INDEXER_API = savedUrl2
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// assert.include(result.error,"Network error: Could not communicate with full node","Error message expected")
})
it('returns proper error when downstream service stalls', async () => {
req.body.tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
// Mock the timeout error.
sandbox.stub(uut.axios, 'post').throws({ code: 'ECONNABORTED' })
const result = await uut.getTokenStats(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
req.body.tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
// Mock the timeout error.
sandbox.stub(uut.axios, 'post').throws({ code: 'ECONNREFUSED' })
const result = await uut.getTokenStats(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should GET tokens stats', async function () {
req.body.tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(uut.axios, 'post').resolves({ data: mockData.tokenStats })
} else {
return this.skip()
}
const result = await uut.getTokenStats(req, res)
const tokenData = result.tokenData
assert.property(tokenData, 'type')
assert.property(tokenData, 'ticker')
assert.property(tokenData, 'name')
assert.property(tokenData, 'tokenId')
assert.property(tokenData, 'documentUri')
assert.property(tokenData, 'documentHash')
assert.property(tokenData, 'decimals')
assert.property(tokenData, 'mintBatonIsActive')
assert.property(tokenData, 'tokensInCirculationBN')
assert.property(tokenData, 'tokensInCirculationStr')
assert.property(tokenData, 'blockCreated')
assert.property(tokenData, 'totalBurned')
assert.property(tokenData, 'totalMinted')
assert.property(tokenData, 'txs')
})
})
describe('#getTxid', async () => {
it('should throw 400 error if txid is missing', async () => {
const result = await uut.getTxid(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error', 'success'])
assert.isFalse(result.success)
assert.include(result.error, 'txid can not be empty')
})
it('should throw 400 error if txid is invalid format', async () => {
req.body.txid = 'txid'
const result = await uut.getTxid(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error', 'success'])
assert.isFalse(result.success)
assert.include(result.error, 'This is not a txid')
})
it('should throw 503 when network issues', async () => {
req.body.txid =
'f3e14cd871402a766e85045dc552f2c1e87857dd3ea1b15efab6334ccef5e315'
// Save the existing RPC URL.
const savedUrl2 = process.env.SLP_INDEXER_API
// Manipulate the URL to cause a 500 network error.
process.env.SLP_INDEXER_API = 'http://fakeurl/api/'
await uut.getTxid(req, res)
// console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.SLP_INDEXER_API = savedUrl2
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// assert.include(result.error,"Network error: Could not communicate with full node","Error message expected")
})
it('returns proper error when downstream service stalls', async () => {
req.body.txid =
'f3e14cd871402a766e85045dc552f2c1e87857dd3ea1b15efab6334ccef5e315'
// Mock the timeout error.
sandbox.stub(uut.axios, 'post').throws({ code: 'ECONNABORTED' })
const result = await uut.getTxid(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
req.body.txid =
'f3e14cd871402a766e85045dc552f2c1e87857dd3ea1b15efab6334ccef5e315'
// Mock the timeout error.
sandbox.stub(uut.axios, 'post').throws({ code: 'ECONNREFUSED' })
const result = await uut.getTxid(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should GET tx data', async function () {
req.body.txid =
'f3e14cd871402a766e85045dc552f2c1e87857dd3ea1b15efab6334ccef5e315'
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(uut.axios, 'post').resolves({ data: mockData.txData })
} else {
return this.skip()
}
const result = await uut.getTxid(req, res)
const txData = result.txData
assert.property(txData, 'txid')
assert.property(txData, 'hash')
assert.property(txData, 'version')
assert.property(txData, 'size')
assert.property(txData, 'locktime')
assert.property(txData, 'vin')
assert.property(txData, 'vout')
assert.property(txData, 'hex')
assert.property(txData, 'blockhash')
assert.property(txData, 'confirmations')
assert.property(txData, 'time')
assert.property(txData, 'blocktime')
assert.property(txData, 'blockheight')
assert.property(txData, 'isSlpTx')
assert.property(txData, 'tokenTxType')
assert.property(txData, 'tokenId')
assert.property(txData, 'tokenType')
assert.property(txData, 'tokenTicker')
assert.property(txData, 'tokenName')
assert.property(txData, 'tokenDecimals')
assert.property(txData, 'tokenUri')
assert.property(txData, 'tokenDocHash')
assert.property(txData, 'isValidSlp')
})
})
describe('#getAddress', async () => {
it('should throw 400 error if address is missing', async () => {
const result = await uut.getAddress(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error', 'success'])
assert.isFalse(result.success)
assert.include(result.error, 'address can not be empty')
})
it('should throw 400 error if address is invalid format', async () => {
req.body.address = 'address'
const result = await uut.getAddress(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error', 'success'])
assert.isFalse(result.success)
assert.include(
result.error,
'Invalid BCH address. Double check your address is valid'
)
})
it('should throw 400 error for invalid network address', async () => {
req.body.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'
const result = await uut.getAddress(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error', 'success'])
assert.isFalse(result.success)
assert.include(
result.error,
'Invalid network. Trying to use a testnet address on mainnet, or vice versa.'
)
})
it('should throw 503 when network issues', async () => {
req.body.address =
'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
// Save the existing RPC URL.
const savedUrl2 = process.env.SLP_INDEXER_API
// Manipulate the URL to cause a 500 network error.
process.env.SLP_INDEXER_API = 'http://fakeurl/api/'
await uut.getAddress(req, res)
// console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.SLP_INDEXER_API = savedUrl2
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// assert.include(result.error,"Network error: Could not communicate with full node","Error message expected")
})
it('returns proper error when downstream service stalls', async () => {
req.body.address =
'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
// Mock the timeout error.
sandbox.stub(uut.axios, 'post').throws({ code: 'ECONNABORTED' })
const result = await uut.getAddress(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
req.body.address =
'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
// Mock the timeout error.
sandbox.stub(uut.axios, 'post').throws({ code: 'ECONNREFUSED' })
const result = await uut.getAddress(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should GET address data', async function () {
req.body.address =
'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(uut.axios, 'post').resolves({ data: mockData.balance })
} else {
return this.skip()
}
const result = await uut.getAddress(req, res)
const balance = result.balance
assert.property(balance, 'utxos')
assert.property(balance, 'txs')
assert.property(balance, 'balances')
assert.isArray(balance.utxos)
assert.isArray(balance.txs)
assert.isArray(balance.balances)
})
})
describe('#getStatus', async () => {
it('should throw 503 when network issues', async () => {
// Save the existing RPC URL.
const savedUrl2 = process.env.SLP_INDEXER_API
// Manipulate the URL to cause a 500 network error.
process.env.SLP_INDEXER_API = 'http://fakeurl/api/'
await uut.getStatus(req, res)
// console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.SLP_INDEXER_API = savedUrl2
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// assert.include(result.error,"Network error: Could not communicate with full node","Error message expected")
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'get').throws({ code: 'ECONNABORTED' })
const result = await uut.getStatus(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'get').throws({ code: 'ECONNREFUSED' })
const result = await uut.getStatus(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should GET indexer status', async function () {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(uut.axios, 'get').resolves({ data: mockData.status })
} else {
return this.skip()
}
const result = await uut.getStatus(req, res)
const status = result.status
assert.property(status, 'startBlockHeight')
assert.property(status, 'syncedBlockHeight')
assert.property(status, 'chainBlockHeight')
})
})
describe('#errorHandler', () => {
it('should handle unexpected errors', () => {
sandbox.stub(uut.routeUtils, 'decodeError').returns({ msg: false })
const result = uut.errorHandler(new Error('test error'), res)
// console.log('result: ', result)
assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.')
assert.property(result, 'error')
})
it('should handle unknow errors', () => {
sandbox.stub(uut.routeUtils, 'decodeError').returns({ msg: false })
const result = uut.errorHandler({}, res)
// console.log('result: ', result)
assert.equal(res.statusCode, 500, 'HTTP status code 500 expected.')
assert.property(result, 'error')
})
})
describe('#checkEnvVar', () => {
it('should throw errors if SLP_INDEXER_API env var is not provided', () => {
const savedSlpIndexerUrl = process.env.SLP_INDEXER_API
try {
process.env.SLP_INDEXER_API = ''
uut.checkEnvVar()
assert.fail('unexpected code path')
} catch (error) {
assert.include(
error.message,
'SLP_INDEXER_API env var not set. Can not connect to PSF SLP indexer.',
'Error message expected'
)
}
process.env.SLP_INDEXER_API = savedSlpIndexerUrl
})
})
})
+4 -4
View File
@@ -216,7 +216,7 @@ describe('#Raw-Transactions', () => {
it('should throw 400 error if hexes array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.hexes = testArray
@@ -459,7 +459,7 @@ describe('#Raw-Transactions', () => {
it('should throw 400 error if hexes array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.hexes = testArray
@@ -599,7 +599,7 @@ describe('#Raw-Transactions', () => {
it('should throw 400 error if txids array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -885,7 +885,7 @@ describe('#Raw-Transactions', () => {
it('should throw 400 error if hexs array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.hexes = testArray
+13 -23
View File
@@ -36,7 +36,7 @@ if (process.env.TEST === 'unit') {
process.env.BITDB_URL = 'http://fakeurl/'
process.env.BITCOINCOM_BASEURL = 'http://fakeurl/'
process.env.SLPDB_URL = 'http://fakeurl/'
mockServerUrl = 'http://fakeurl'
// mockServerUrl = 'http://fakeurl'
}
// Prepare the slpRoute for stubbing dependcies on slpjs.
@@ -577,7 +577,7 @@ describe('#SLP', () => {
it('should throw 400 error if array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -787,7 +787,7 @@ describe('#SLP', () => {
it('should throw 400 error if array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
@@ -1406,8 +1406,7 @@ describe('#SLP', () => {
it('should throw error if Array is too long', async () => {
const utxo = {
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
@@ -1435,8 +1434,7 @@ describe('#SLP', () => {
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
@@ -1444,8 +1442,7 @@ describe('#SLP', () => {
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
@@ -1459,8 +1456,7 @@ describe('#SLP', () => {
// Mock the external network call.
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetails').resolves([
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
@@ -1469,8 +1465,7 @@ describe('#SLP', () => {
isValid: false
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
@@ -1581,8 +1576,7 @@ describe('#SLP', () => {
it('should throw error if Array is too long', async () => {
const utxo = {
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
txid: 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
@@ -1610,8 +1604,7 @@ describe('#SLP', () => {
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
@@ -1619,8 +1612,7 @@ describe('#SLP', () => {
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
@@ -1634,8 +1626,7 @@ describe('#SLP', () => {
// Mock the external network call.
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetailsWL').resolves([
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
@@ -1644,8 +1635,7 @@ describe('#SLP', () => {
isValid: false
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
txid: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
+1 -1
View File
@@ -203,7 +203,7 @@ describe('#Util', () => {
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
for (let i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray