mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-21 16:51:59 -07:00
fix(_hydrateUtxo): Added unit tests
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/chains/testnet",
|
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/chains/testnet",
|
||||||
"test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v4/ && mocha --timeout 30000 test/integration/",
|
"test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v4/ && mocha --timeout 30000 test/integration/",
|
||||||
"test:temp": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
|
"test:temp": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
|
||||||
"test:temp2": "mocha --timeout=30000 -g '#decodeOpReturn' test/unit/",
|
"test:temp2": "mocha --timeout=30000 -g '#_hydrateUtxo' test/unit/",
|
||||||
"test:temp3": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
|
"test:temp3": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
|
||||||
"test:temp4": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#decodeOpReturn' test/integration/",
|
"test:temp4": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#decodeOpReturn' test/integration/",
|
||||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
|
|||||||
+20
-1
@@ -1253,6 +1253,12 @@ class Utils {
|
|||||||
// token data. If that call throws an error due to hitting rate limits, this
|
// token data. If that call throws an error due to hitting rate limits, this
|
||||||
// function will not throw an error. Instead, it will mark the `isValid`
|
// function will not throw an error. Instead, it will mark the `isValid`
|
||||||
// property as `null`
|
// property as `null`
|
||||||
|
//
|
||||||
|
// Exception to the above: It *will* throw an error if decodeOpReturn() throws
|
||||||
|
// an error while trying to get the Genesis transaction for a Send or Mint
|
||||||
|
// transaction. However, that is a rare occurence since the cache of
|
||||||
|
// decodeOpReturn() will minimize API calls for this case. This behavior
|
||||||
|
// could be changed, but right now it's a corner case of a corner case.
|
||||||
async _hydrateUtxo (utxos, usrObj = null) {
|
async _hydrateUtxo (utxos, usrObj = null) {
|
||||||
try {
|
try {
|
||||||
const decodeOpReturnCache = {}
|
const decodeOpReturnCache = {}
|
||||||
@@ -1312,6 +1318,7 @@ class Utils {
|
|||||||
// Halt the execution of the loop and increase to the next index.
|
// Halt the execution of the loop and increase to the next index.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||||
|
|
||||||
const txType = slpData.txType.toLowerCase()
|
const txType = slpData.txType.toLowerCase()
|
||||||
|
|
||||||
@@ -1349,6 +1356,10 @@ class Utils {
|
|||||||
utxo.decimals = slpData.decimals
|
utxo.decimals = slpData.decimals
|
||||||
utxo.tokenType = slpData.tokenType
|
utxo.tokenType = slpData.tokenType
|
||||||
|
|
||||||
|
// Initial value is null until UTXO can be validated and confirmed
|
||||||
|
// to be valid (true) or not (false).
|
||||||
|
utxo.isValid = null
|
||||||
|
|
||||||
outAry[i] = utxo
|
outAry[i] = utxo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1398,6 +1409,10 @@ class Utils {
|
|||||||
|
|
||||||
utxo.mintBatonVout = slpData.mintBatonVout
|
utxo.mintBatonVout = slpData.mintBatonVout
|
||||||
|
|
||||||
|
// Initial value is null until UTXO can be validated and confirmed
|
||||||
|
// to be valid (true) or not (false).
|
||||||
|
utxo.isValid = null
|
||||||
|
|
||||||
outAry[i] = utxo
|
outAry[i] = utxo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1439,6 +1454,10 @@ class Utils {
|
|||||||
utxo.decimals = genesisData.decimals
|
utxo.decimals = genesisData.decimals
|
||||||
utxo.tokenType = slpData.tokenType
|
utxo.tokenType = slpData.tokenType
|
||||||
|
|
||||||
|
// Initial value is null until UTXO can be validated and confirmed
|
||||||
|
// to be valid (true) or not (false).
|
||||||
|
utxo.isValid = null
|
||||||
|
|
||||||
// Calculate the real token quantity.
|
// Calculate the real token quantity.
|
||||||
|
|
||||||
const tokenQtyBig = new BigNumber(tokenQty).div(
|
const tokenQtyBig = new BigNumber(tokenQty).div(
|
||||||
@@ -1457,7 +1476,7 @@ class Utils {
|
|||||||
return outAry
|
return outAry
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('_hydrateUtxo error: ', error)
|
console.log('_hydrateUtxo error: ', error)
|
||||||
if (error.response && error.response.data) throw error.response.data
|
// if (error.response && error.response.data) throw error.response.data
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -530,6 +530,253 @@ describe('#SLP Utils', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('#_hydrateUtxo', () => {
|
||||||
|
// // This captures an important corner-case. When an SLP token is created, the
|
||||||
|
// // change UTXO will contain the same SLP txid, but it is not an SLP UTXO.
|
||||||
|
it('should return details on minting baton from genesis transaction', async () => {
|
||||||
|
// Mock the call to REST API
|
||||||
|
// sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||||
|
|
||||||
|
// Stub the calls to decodeOpReturn.
|
||||||
|
sandbox.stub(uut.Utils, 'decodeOpReturn').resolves({
|
||||||
|
tokenType: 1,
|
||||||
|
txType: 'GENESIS',
|
||||||
|
ticker: 'SLPSDK',
|
||||||
|
name: 'SLP SDK example using BITBOX',
|
||||||
|
tokenId:
|
||||||
|
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||||
|
documentUri: 'developer.bitcoin.com',
|
||||||
|
documentHash: '',
|
||||||
|
decimals: 8,
|
||||||
|
mintBatonVout: 2,
|
||||||
|
qty: '50700000000'
|
||||||
|
})
|
||||||
|
|
||||||
|
const utxos = [
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||||
|
vout: 3,
|
||||||
|
amount: 0.00002015,
|
||||||
|
satoshis: 2015,
|
||||||
|
height: 594892,
|
||||||
|
confirmations: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||||
|
vout: 2,
|
||||||
|
amount: 0.00000546,
|
||||||
|
satoshis: 546,
|
||||||
|
height: 594892,
|
||||||
|
confirmations: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const data = await uut.Utils._hydrateUtxo(utxos)
|
||||||
|
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||||
|
|
||||||
|
// assert.equal(data[0], false, "Change UTXO marked as false.")
|
||||||
|
assert.property(data[0], 'txid')
|
||||||
|
assert.property(data[0], 'vout')
|
||||||
|
assert.property(data[0], 'amount')
|
||||||
|
assert.property(data[0], 'satoshis')
|
||||||
|
assert.property(data[0], 'height')
|
||||||
|
assert.property(data[0], 'confirmations')
|
||||||
|
assert.property(data[0], 'isValid')
|
||||||
|
assert.equal(data[0].isValid, false)
|
||||||
|
|
||||||
|
assert.property(data[1], 'txid')
|
||||||
|
assert.property(data[1], 'vout')
|
||||||
|
assert.property(data[1], 'amount')
|
||||||
|
assert.property(data[1], 'satoshis')
|
||||||
|
assert.property(data[1], 'height')
|
||||||
|
assert.property(data[1], 'confirmations')
|
||||||
|
assert.property(data[1], 'utxoType')
|
||||||
|
assert.property(data[1], 'tokenId')
|
||||||
|
assert.property(data[1], 'tokenTicker')
|
||||||
|
assert.property(data[1], 'tokenName')
|
||||||
|
assert.property(data[1], 'tokenDocumentUrl')
|
||||||
|
assert.property(data[1], 'tokenDocumentHash')
|
||||||
|
assert.property(data[1], 'decimals')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 429 means the user is exceeding the rate limits.
|
||||||
|
it('should return isValid=null for 429 rate limit error', async () => {
|
||||||
|
// Force decodeOpReturn() to throw a 429 error.
|
||||||
|
sandbox.stub(uut.Utils, 'decodeOpReturn').rejects(mockData.mock429Error)
|
||||||
|
|
||||||
|
const utxos = [
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||||
|
vout: 3,
|
||||||
|
amount: 0.00002015,
|
||||||
|
satoshis: 2015,
|
||||||
|
height: 594892,
|
||||||
|
confirmations: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||||
|
vout: 2,
|
||||||
|
amount: 0.00000546,
|
||||||
|
satoshis: 546,
|
||||||
|
height: 594892,
|
||||||
|
confirmations: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = await uut.Utils._hydrateUtxo(utxos)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.equal(result[0].isValid, null)
|
||||||
|
assert.equal(result[1].isValid, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 503 means the server is down or is not responding.
|
||||||
|
it('should return isValid=null for 503 rate limit error', async () => {
|
||||||
|
// Force decodeOpReturn() to throw a 429 error.
|
||||||
|
sandbox.stub(uut.Utils, 'decodeOpReturn').rejects(mockData.mock503Error)
|
||||||
|
|
||||||
|
const utxos = [
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||||
|
vout: 3,
|
||||||
|
amount: 0.00002015,
|
||||||
|
satoshis: 2015,
|
||||||
|
height: 594892,
|
||||||
|
confirmations: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||||
|
vout: 2,
|
||||||
|
amount: 0.00000546,
|
||||||
|
satoshis: 546,
|
||||||
|
height: 594892,
|
||||||
|
confirmations: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = await uut.Utils._hydrateUtxo(utxos)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.equal(result[0].isValid, null)
|
||||||
|
assert.equal(result[1].isValid, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return details for a simple SEND SLP token utxo', async () => {
|
||||||
|
// Mock the call to REST API
|
||||||
|
// Stub the calls to decodeOpReturn.
|
||||||
|
sandbox
|
||||||
|
.stub(uut.Utils, 'decodeOpReturn')
|
||||||
|
.onCall(0)
|
||||||
|
.resolves({
|
||||||
|
tokenType: 1,
|
||||||
|
txType: 'SEND',
|
||||||
|
tokenId:
|
||||||
|
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||||
|
amounts: ['200000000', '99887500000000']
|
||||||
|
})
|
||||||
|
.onCall(1)
|
||||||
|
.resolves({
|
||||||
|
tokenType: 1,
|
||||||
|
txType: 'GENESIS',
|
||||||
|
ticker: 'TOK-CH',
|
||||||
|
name: 'TokyoCash',
|
||||||
|
tokenId:
|
||||||
|
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||||
|
documentUri: '',
|
||||||
|
documentHash: '',
|
||||||
|
decimals: 8,
|
||||||
|
mintBatonVout: 0,
|
||||||
|
qty: '2100000000000000'
|
||||||
|
})
|
||||||
|
|
||||||
|
// sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||||
|
|
||||||
|
const utxos = [
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||||
|
vout: 1,
|
||||||
|
amount: 0.00000546,
|
||||||
|
satoshis: 546,
|
||||||
|
height: 596089,
|
||||||
|
confirmations: 748
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const data = await uut.Utils._hydrateUtxo(utxos)
|
||||||
|
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(data[0], 'txid')
|
||||||
|
assert.property(data[0], 'vout')
|
||||||
|
assert.property(data[0], 'amount')
|
||||||
|
assert.property(data[0], 'satoshis')
|
||||||
|
assert.property(data[0], 'height')
|
||||||
|
assert.property(data[0], 'confirmations')
|
||||||
|
assert.property(data[0], 'utxoType')
|
||||||
|
assert.property(data[0], 'tokenId')
|
||||||
|
assert.property(data[0], 'tokenTicker')
|
||||||
|
assert.property(data[0], 'tokenName')
|
||||||
|
assert.property(data[0], 'tokenDocumentUrl')
|
||||||
|
assert.property(data[0], 'tokenDocumentHash')
|
||||||
|
assert.property(data[0], 'decimals')
|
||||||
|
assert.property(data[0], 'tokenQty')
|
||||||
|
assert.property(data[0], 'isValid')
|
||||||
|
assert.equal(data[0].isValid, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
// I don't know if this is the ideal behavior, but this is the behavior
|
||||||
|
// that is in production, so I wanted to capture it in a test case. This
|
||||||
|
// behavior can always be changed in the future.
|
||||||
|
it('should throw error if 429 when querying Genesis transaction', async () => {
|
||||||
|
try {
|
||||||
|
// Mock the call to REST API
|
||||||
|
// Stub the calls to decodeOpReturn.
|
||||||
|
sandbox
|
||||||
|
.stub(uut.Utils, 'decodeOpReturn')
|
||||||
|
.onCall(0)
|
||||||
|
.resolves({
|
||||||
|
tokenType: 1,
|
||||||
|
txType: 'SEND',
|
||||||
|
tokenId:
|
||||||
|
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||||
|
amounts: ['200000000', '99887500000000']
|
||||||
|
})
|
||||||
|
.onCall(1)
|
||||||
|
.rejects(mockData.mock429Error)
|
||||||
|
|
||||||
|
const utxos = [
|
||||||
|
{
|
||||||
|
txid:
|
||||||
|
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||||
|
vout: 1,
|
||||||
|
amount: 0.00000546,
|
||||||
|
satoshis: 546,
|
||||||
|
height: 596089,
|
||||||
|
confirmations: 748
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
await uut.Utils._hydrateUtxo(utxos)
|
||||||
|
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('error: ', err)
|
||||||
|
|
||||||
|
assert.property(err, 'message')
|
||||||
|
assert.property(err, 'response')
|
||||||
|
assert.equal(err.response.status, 429)
|
||||||
|
assert.equal(err.response.statusText, 'Too Many Requests')
|
||||||
|
assert.include(err.response.data.error, 'Too many requests')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('#tokenUtxoDetails', () => {
|
describe('#tokenUtxoDetails', () => {
|
||||||
it('should throw error if input is not an array.', async () => {
|
it('should throw error if input is not an array.', async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user