Compare commits

..
4 Commits
Author SHA1 Message Date
Chris Troutner c86512254d Merge pull request #104 from Permissionless-Software-Foundation/dh-test-coverage
feat(tests): Improved adapters test coverage
2025-08-06 09:42:17 -07:00
Daniel Gonzalez 7df9b9b8a4 feat(tests): Improved adapters test coverage 2025-08-05 16:45:37 -04:00
Chris Troutner 5f91395766 Merge pull request #103 from Permissionless-Software-Foundation/ct-unstable
fix(GET /offer/list/addr/:addr): Get offers by an address
2025-07-28 11:14:25 -07:00
Chris Troutner dda6cb2aa4 fix(GET /offer/list/addr/:addr): Get offers by an address 2025-07-28 11:12:04 -07:00
14 changed files with 682 additions and 77 deletions
+3
View File
@@ -66,6 +66,9 @@ class Bch {
// }
// }
/** DEV NOTE: This function are deprecated.?
* https://github.com/Permissionless-Software-Foundation/bch-message-lib/pull/41
*/
async getMerit (slpAddr) {
try {
if (!slpAddr || typeof slpAddr !== 'string') {
+28 -3
View File
@@ -34,6 +34,7 @@ class WalletAdapter {
this.config = config
this.bitcoinJs = bitcoinJs
this.BchWallet = BchWallet
this.BchTokenSweep = BchTokenSweep
this.bchWallet = {} // Will be replaced when initialized.
// this.advancedConfig = localConfig.advancedConfig
this.retryQueue = new RetryQueue({
@@ -205,7 +206,9 @@ class WalletAdapter {
// Generate a cryptographic signature, required to write to the P2WDB.
async generateSignature (message) {
try {
// TODO: Add input validation for message.
if (!message || typeof message !== 'string') {
throw new Error('message is required!')
}
const privKey = this.bchWallet.walletInfo.privateKey
@@ -370,6 +373,12 @@ class WalletAdapter {
async moveTokens (inObj = {}) {
try {
const { tokenId, qty } = inObj
if (!tokenId || typeof tokenId !== 'string') {
throw new Error('tokenId must be a string!')
}
if (!qty || typeof qty !== 'number') {
throw new Error('qty must be a number!')
}
const keyPair = await this.getKeyPair()
console.log('keyPair: ', keyPair)
@@ -392,6 +401,10 @@ class WalletAdapter {
x => x.tokenId === tokenId
)
if (tokenUtxos.length === 0) {
throw new Error('tokenId not found!')
}
const txid = await this.bchWallet.sendTokens(receiver, 3)
const utxoInfo = {
@@ -412,6 +425,10 @@ class WalletAdapter {
// generate a segregated UTXO.
async moveBch (amountSat) {
try {
if (!amountSat) {
throw new Error('amountSat is required!')
}
const keyPair = await this.getKeyPair()
console.log('keyPair: ', keyPair)
@@ -444,6 +461,10 @@ class WalletAdapter {
// representing the transaction.
async deseralizeTx (txHex) {
try {
// Input validation
if (!txHex || typeof txHex !== 'string') {
throw new Error('hex must be a string!')
}
// Ensure the URL points at FullStack.cash, since the web 3 infra does not
// yet support this call.
const oldUrl = this.bchWallet.bchjs.RawTransactions.restURL
@@ -555,8 +576,12 @@ class WalletAdapter {
// Given an Order database object, this function sends the tokens from the
// HD index where they are stored, back to the root address of the wallet.
async reclaimTokens (orderData) {
async reclaimTokens (orderData = {}) {
try {
if (!orderData.hdIndex) {
throw new Error('orderData.hdIndex is required!')
}
// Get the key pair from the Order.
const keyPair = await this.getKeyPair(orderData.hdIndex)
console.log('keyPair: ', keyPair)
@@ -576,7 +601,7 @@ class WalletAdapter {
// const txid = await tempWallet.sendTokens(receiver)
// Sweep the tokens to the root address.
const sweeper = new BchTokenSweep(
const sweeper = new this.BchTokenSweep(
keyPair.wif,
this.bchWallet.walletInfo.privateKey,
this.bchWallet,
+15 -13
View File
@@ -6,13 +6,15 @@ import config from '../../config/index.js'
import axios from 'axios'
let _this
class WebHook {
constructor () {
_this = this
_this.config = config
_this.axios = axios
this.config = config
this.axios = axios
this.sleepTime = 2000
this.createWebhook = this.createWebhook.bind(this)
this.deleteWebhook = this.deleteWebhook.bind(this)
this.waitUntilSuccess = this.waitUntilSuccess.bind(this)
this.sleep = this.sleep.bind(this)
}
// REST petition to create a webhook in p2wdb-service
@@ -22,14 +24,14 @@ class WebHook {
throw new Error('url must be a string')
}
const endpoint = _this.config.webhookService
const endpoint = this.config.webhookService
const obj = {
appId: this.config.p2wdbAppId,
url
}
const result = await axios.post(endpoint, obj)
const result = await this.axios.post(endpoint, obj)
return result.data
} catch (err) {
@@ -45,14 +47,14 @@ class WebHook {
throw new Error('url must be a string')
}
const endpoint = _this.config.webhookService
const endpoint = this.config.webhookService
const obj = {
appId: this.config.p2wdbAppId,
url
}
const result = await axios.delete(endpoint, { data: obj })
const result = await this.axios.delete(endpoint, { data: obj })
return result.data
} catch (err) {
@@ -86,7 +88,7 @@ class WebHook {
} catch (err) {
const now = new Date()
console.log(`${now.toLocaleString()}: Error trying to create webhook with P2WDB. Trying again...`)
await sleep(2000)
await this.sleep(this.sleepTime)
}
} while (!success)
@@ -96,10 +98,10 @@ class WebHook {
throw err
}
}
}
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
async sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
export default WebHook
@@ -1,5 +1,8 @@
/*
REST API Controller library for the /offer route
TODO:
- Add api-doc documentation for all endpoints in this file.
*/
import wlogger from '../../../adapters/wlogger.js'
@@ -30,6 +33,7 @@ class OfferRESTControllerLib {
this.listNftOffers = this.listNftOffers.bind(this)
this.listFungibleOffers = this.listFungibleOffers.bind(this)
this.takeOffer = this.takeOffer.bind(this)
this.listOffersByAddress = this.listOffersByAddress.bind(this)
this.handleError = this.handleError.bind(this)
}
@@ -115,6 +119,21 @@ class OfferRESTControllerLib {
}
}
// List all offers being made by a given address.
// curl -X GET http://localhost:5700/offer/list/addr/bitcoincash:qrpxtnrlhfz9wsuuse7z5k2mxmvw0r3pu5qepyhmq2
async listOffersByAddress (ctx) {
try {
const addr = ctx.params.addr
const offers = await this.useCases.offer.listOffersByAddress(addr)
ctx.body = offers
} catch (err) {
console.log('Error in listOffersByAddress REST API handler: ', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
console.log('err', err.message)
+10 -10
View File
@@ -8,8 +8,6 @@ import Router from 'koa-router'
// Local libraries.
import OfferRESTControllerLib from './controller.js'
let _this
class OfferRouter {
constructor (localConfig = {}) {
// Dependency Injection.
@@ -38,7 +36,8 @@ class OfferRouter {
const baseUrl = '/offer'
this.router = new Router({ prefix: baseUrl })
_this = this
// Bind 'this' object to all subfunctions.
this.attach = this.attach.bind(this)
}
attach (app) {
@@ -50,19 +49,20 @@ class OfferRouter {
// 12/3/24 CT:
// Note: The createOffer() path was used by a P2WDB webhook to generate an
// Offer from and Order. This has been deprecated and Offers are now created
// Offer from an Order. This has been deprecated and Offers are now created
// by a Timer Controller monitoring a Nostr topic.
// Define the routes and attach the controller.
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
this.router.post('/take', _this.offerRESTController.takeOffer)
this.router.get('/list/all/:page', _this.offerRESTController.listOffers)
this.router.get('/list/nft/:page', _this.offerRESTController.listNftOffers)
this.router.get('/list/fungible/:page', _this.offerRESTController.listFungibleOffers)
this.router.post('/take', this.offerRESTController.takeOffer)
this.router.get('/list/all/:page', this.offerRESTController.listOffers)
this.router.get('/list/nft/:page', this.offerRESTController.listNftOffers)
this.router.get('/list/fungible/:page', this.offerRESTController.listFungibleOffers)
this.router.get('/list/addr/:addr', this.offerRESTController.listOffersByAddress)
// Attach the Controller routes to the Koa app.
app.use(_this.router.routes())
app.use(_this.router.allowedMethods())
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
}
+12
View File
@@ -64,6 +64,7 @@ class OfferUseCases {
this.removeStaleOffers = this.removeStaleOffers.bind(this)
this.flagOffer = this.flagOffer.bind(this)
this.loadOffers = this.loadOffers.bind(this)
this.listOffersByAddress = this.listOffersByAddress.bind(this)
// State
this.seenOffers = []
@@ -808,6 +809,17 @@ class OfferUseCases {
throw error
}
}
// List all offers being made by a given address.
async listOffersByAddress (addr) {
try {
const offers = await this.OfferModel.find({ makerAddr: addr })
return offers
} catch (err) {
console.error('Error in listOffersByAddress(): ', err)
throw err
}
}
}
export default OfferUseCases
+10 -11
View File
@@ -24,19 +24,18 @@ describe('#adapters', () => {
})
describe('#start', () => {
// it('should start the async adapters', async () => {
// // Mock dependencies
// uut.config.getJwtAtStartup = true
// uut.config.useIpfs = true
// uut.config.env = 'not-a-test'
// sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
// sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
// sandbox.stub(uut.ipfs, 'start').resolves()
it('should start the async adapters', async () => {
// Mock dependencies
uut.config.getJwtAtStartup = true
uut.config.useIpfs = true
uut.config.env = 'not-a-test'
sandbox.stub(uut.wallet, 'instanceWallet').resolves()
sandbox.stub(uut.ipfs, 'start').resolves()
// const result = await uut.start()
const result = await uut.start()
// assert.equal(result, true)
// })
assert.equal(result, true)
})
it('should not start ipfs on test enviroment', async () => {
// Mock dependencies
+14 -13
View File
@@ -135,18 +135,19 @@ describe('bch', () => {
}
})
// it('should return the merit ', async () => {
// try {
// // Mock live network calls.
// sandbox.stub(uut.msgLib.merit, 'agMerit').resolves(100)
//
// const slpAddr =
// 'simpleledger:qqgnksc6zr4nzxrye69fq625wu2myxey6uh9kzjy96'
// const merit = await uut.getMerit(slpAddr)
// assert.isNumber(merit)
// } catch (err) {
// assert.fail('Unexpected result')
// }
// })
/* it('should return the merit ', async () => {
try {
// Mock live network calls.
sandbox.stub(uut.msgLib.merit, 'agMerit').resolves(100)
const slpAddr =
'simpleledger:qqgnksc6zr4nzxrye69fq625wu2myxey6uh9kzjy96'
const merit = await uut.getMerit(slpAddr)
assert.isNumber(merit)
} catch (err) {
console.log('err: ', err)
assert.fail('Unexpected result')
}
}) */
})
})
+96
View File
@@ -128,4 +128,100 @@ describe('#nostr.js', () => {
assert.equal(result.length, 1)
})
})
describe('#eventId2note', () => {
it('should handle encode error', async () => {
try {
await uut.eventId2note('invalid format')
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Invalid byte sequence')
}
})
it('should handle valid eventId', async () => {
const result = await uut.eventId2note('b1e4a3d5e40b6d0b3f1e2c234b6c9dbf4d6a80e9b3e8a2d765c7f9ed84c9a5ef')
assert.isString(result)
assert.include(result, 'note')
})
})
describe('#pubkey2npub', () => {
it('should handle encode error', async () => {
try {
await uut.pubkey2npub('invalid format')
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Invalid byte sequence')
}
})
it('should handle valid pubkey', async () => {
const result = await uut.pubkey2npub('f5a1b39d2cfae8e10e93b9b4d7ec8248eaf45fbe4a73409d8d4c67015e9729a3')
assert.isString(result)
assert.include(result, 'npub')
})
})
describe('#npub2pubkey', () => {
it('should handle decode error', async () => {
try {
await uut.npub2pubkey('npub17ksm88fvlt5wzr5nhx6d0myzfr40g')
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Invalid checksum')
}
})
it('should handle valid pubkey', async () => {
const result = await uut.npub2pubkey('npub17ksm88fvlt5wzr5nhx6d0myzfr40gha7ffe5p8vdf3nszh5h9x3sdwl7u2')
assert.isObject(result)
assert.property(result, 'type')
assert.property(result, 'data')
assert.equal(result.type, 'npub')
assert.equal(result.data.length, 64)
})
})
describe('#readGlobalFeed', () => {
it('should throw an error if provided limit is wrong', async () => {
try {
await uut.readGlobalFeed({ limit: -5 })
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Limit must be greater than 0')
}
})
it('should read message', async () => {
const result = await uut.readGlobalFeed()
assert.isArray(result)
})
it('should read message with custom limit', async () => {
const result = await uut.readGlobalFeed({ limit: 1 })
assert.isArray(result)
})
})
describe('#getFollowers', () => {
it('should throw an error if no pubkey is provided', async () => {
try {
await uut.getFollowers()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'pubkey must be a string!')
}
})
it('should get followers', async () => {
const result = await uut.getFollowers({ pubkey: 'f5a1b39d2cfae8e10e93b9b4d7ec8248eaf45fbe4a73409d8d4c67015e9729a3' })
assert.isArray(result)
})
})
})
+378 -26
View File
@@ -9,24 +9,56 @@ import sinon from 'sinon'
import WalletAdapter from '../../../src/adapters/wallet.js'
import { MockBchWallet } from '../mocks/adapters/wallet.js'
import { BchTokenSweepMock } from '../mocks/bch-token-sweep-mock.js'
import offerMockData from '../mocks/use-cases/offer-mock-data.js'
import fs from 'fs'
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const testWalletFile = `${__dirname.toString()}/../../../test-wallet.json`
describe('#wallet', () => {
let uut
let sandbox
let testWalletData
before(() => {
// try to remove the test wallet file
try {
fs.unlinkSync(testWalletFile)
} catch (error) {
console.log('error fsUnlinkSync: ', error)
// silently fail
}
})
after(() => {
// try to remove the test wallet file
try {
fs.unlinkSync(testWalletFile)
} catch (error) {
console.log('error fsUnlinkSync: ', error)
// silently fail
}
})
beforeEach(() => {
uut = new WalletAdapter()
uut.BchWallet = MockBchWallet
uut.bchWallet = new MockBchWallet()
uut.BchTokenSweep = BchTokenSweepMock
uut.WALLET_FILE = testWalletFile
uut.bitcoinJs = {
Transaction: {
fromHex: () => { return { } },
SIGHASH_ALL: () => { return { } }
fromHex: () => { return {} },
SIGHASH_ALL: () => { return {} }
},
TransactionBuilder: {
fromTransaction: () => { return { sign: () => { return { } }, build: () => { return { toHex: () => { return 'hex' } } } } }
fromTransaction: () => { return { sign: () => { return {} }, build: () => { return { toHex: () => { return 'hex' } } } } }
},
ECPair: {
fromWIF: () => { return { } }
fromWIF: () => { return {} }
}
}
sandbox = sinon.createSandbox()
@@ -34,6 +66,278 @@ describe('#wallet', () => {
afterEach(() => sandbox.restore())
describe('#openWallet', () => {
it('should create a new wallet when wallet file does not exist', async () => {
// Ensure we open the test file, not the production wallet file.
const result = await uut.openWallet()
testWalletData = result
// console.log('result: ', result)
assert.property(result, 'mnemonic')
assert.property(result, 'privateKey')
assert.property(result, 'publicKey')
assert.property(result, 'cashAddress')
assert.property(result, 'address')
assert.property(result, 'slpAddress')
assert.property(result, 'legacyAddress')
assert.property(result, 'hdPath')
})
it('should open existing wallet file', async () => {
// This test case uses the file created in the previous test case.
// Ensure we open the test file, not the production wallet file.
const result = await uut.openWallet()
// console.log('result: ', result)
assert.property(result, 'mnemonic')
assert.property(result, 'privateKey')
assert.property(result, 'publicKey')
assert.property(result, 'cashAddress')
assert.property(result, 'address')
assert.property(result, 'slpAddress')
assert.property(result, 'legacyAddress')
assert.property(result, 'hdPath')
})
it('should catch and throw an error', async () => {
try {
uut.WALLET_FILE = ''
// Force an error
uut.BchWallet = () => {
}
await uut.openWallet()
// console.log('result: ', result)
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'this.BchWallet is not a constructor')
}
})
})
describe('#instanceWallet', () => {
it('should create an instance of BchWallet with rest-api', async () => {
uut.config.useFullStackCash = true
const result = await uut.instanceWallet(testWalletData)
assert.equal(result.advancedOptions.interface, 'rest-api')
})
it('should create an instance of BchWallet with consumer-api', async () => {
uut.config.useFullStackCash = false
const result = await uut.instanceWallet(testWalletData)
assert.equal(result.advancedOptions.interface, 'consumer-api')
})
it('should catch and throw an error if no mnemonic is provided', async () => {
try {
// Force an error
await uut.instanceWallet({})
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Wallet data is not formatted correctly. Can not read mnemonic in wallet file!')
}
})
})
describe('#incrementNextAddress', () => {
it('should increment the nextAddress property', async () => {
const result = await uut.incrementNextAddress()
assert.equal(result, 2)
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut, 'openWallet').rejects(new Error('test error'))
await uut.incrementNextAddress()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('#getKeyPair', () => {
it('should return an object with a key pair', async () => {
// Ensure we open the test file, not the production wallet file.
// uut.WALLET_FILE = testWalletFile
uut.config.walletFile = testWalletFile
const result = await uut.getKeyPair()
// console.log('result: ', result)
assert.property(result, 'cashAddress')
assert.property(result, 'wif')
assert.property(result, 'hdIndex')
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox
.stub(uut, 'incrementNextAddress')
.rejects(new Error('test error'))
await uut.getKeyPair()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('#generateSignature', () => {
it('should sign message', async () => {
const result = await uut.generateSignature('unit test message')
// console.log('result: ', result)
assert.isString(result)
})
it('should catch and throw an error', async () => {
try {
await uut.generateSignature()
} catch (err) {
assert.include(err.message, 'message is required!')
}
})
})
describe('#generatePartialTx', () => {
it('should generate tx for fungible token', async () => {
const utxoInfo = {
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
vout: 0,
sats: 1000,
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
}
sandbox.stub(uut.bchWallet, 'getTxData').resolves([offerMockData.fungibleTokenData01.genesisData])
const result = await uut.generatePartialTx(offerMockData.fungibleOffer01, utxoInfo)
assert.isString(result)
})
it('should generate tx for nft', async () => {
const utxoInfo = {
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
vout: 0,
sats: 1000,
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
}
sandbox.stub(uut.bchWallet, 'getTxData').resolves([offerMockData.nftTokenData01.genesisData])
const result = await uut.generatePartialTx(offerMockData.nftOffer01, utxoInfo)
// console.log('result: ', result)
assert.isString(result)
})
it('should handle unknown token type', async () => {
try {
const utxoInfo = {
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
vout: 0,
sats: 1000,
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
}
const offerMock = Object.assign({}, offerMockData.nftOffer01)
offerMock.tokenType = 100
await uut.generatePartialTx(offerMock, utxoInfo)
} catch (err) {
assert.include(err.message, 'Unknown token type')
}
})
it('should handle multiple outputs', async () => {
const utxoInfo = {
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
vout: 0,
sats: 1000,
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
}
sandbox.stub(uut.bchWallet, 'getTxData').resolves([offerMockData.nftTokenData01.genesisData])
sandbox.stub(uut.bchWallet.bchjs.SLP.NFT1, 'generateNFTChildSendOpReturn').returns({ outputs: 5 })
const res = await uut.generatePartialTx(offerMockData.nftOffer01, utxoInfo)
assert.isUndefined(res)
})
})
describe('#moveTokens', () => {
it('should move tokens', async () => {
sandbox.stub(uut, 'getKeyPair').resolves({
cashAddress: 'cashAddress',
wif: 'wif',
hdIndex: 11
})
const input = {
tokenId: uut.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens[0].tokenId,
qty: 1
}
const res = await uut.moveTokens(input)
assert.isObject(res)
})
it('should throw an error if the tokenId is not found', async () => {
try {
sandbox.stub(uut, 'getKeyPair').resolves({
cashAddress: 'cashAddress',
wif: 'wif',
hdIndex: 11
})
const input = {
tokenId: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
qty: 1
}
await uut.moveTokens(input)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'tokenId not found!')
}
})
it('should throw an error if the tokenId is not provided', async () => {
try {
await uut.moveTokens({
qty: 1
})
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'tokenId must be a string!')
}
})
it('should throw an error if the qty is not provided', async () => {
try {
await uut.moveTokens({
tokenId: 'tokenId'
})
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'qty must be a number!')
}
})
})
describe('#moveBch', () => {
it('should move bch', async () => {
sandbox.stub(uut, 'getKeyPair').resolves({
cashAddress: 'cashAddress',
wif: 'wif',
hdIndex: 11
})
const res = await uut.moveBch(100)
assert.isObject(res)
})
it('should throw an error if the amountSat is not provided', async () => {
try {
sandbox.stub(uut, 'getKeyPair').resolves({
cashAddress: 'cashAddress',
wif: 'wif',
hdIndex: 11
})
await uut.moveBch()
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'amountSat is required!')
}
})
})
describe('#moveTokensFromCustomWallet', () => {
it('should move tokens from a custom wallet', async () => {
sandbox.stub(uut, 'getKeyPair').resolves({
@@ -100,28 +404,27 @@ describe('#wallet', () => {
}
})
})
describe('#deseralizeTx', () => {
it('should throw an error if the hex is not provided', async () => {
try {
await uut.deseralizeTx()
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'hex must be a string!')
}
})
it('should decode hex', async () => {
try {
sandbox.stub(uut.bchWallet.bchjs.RawTransactions, 'decodeRawTransaction').resolves({})
const hex = '020000000286b2959cccdb80163b68bb1360abac8e0228ba7b70c779443168cc65c7c42d820100000000ffffffff879b066729eda979f1f062c83b7eccdb9e77f49b417e4723864b3861bf061c24000000006b483045022100b99c6161e18e4e34c1e438b6b03e808703695f217b30da42c3f787798a9f40cc02206d26b4f6799735779d4fac823bc60d276fbb3cea91c66dbfa136cf5b1bd0d8324121028dcd38a58c8295dbd105ef524014f77af4cddfa19b26c50926b4aaeebd95c7ceffffffff030000000000000000376a04534c500001010453454e4420a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b208000000000000006422020000000000001976a914bef6c5180648165704f22e71a1b011ba56336aee88acf81d0000000000001976a914c1f16a3876f8fbe3701a66d3cb3b9c7abb07a06c88ac00000000'
const result = await uut.deseralizeTx(hex)
assert.isObject(result)
} catch (error) {
assert.fail('Unexpected code path')
}
})
})
describe('#completeTx', () => {
// TODO: This test needs to be refactored to use the new instanceWallet() method for loading the user wallet.
// it('should complete a transaction', async () => {
// sandbox.stub(uut, 'getKeyPair').resolves({
// cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
// wif: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
// hdIndex: 11
// })
// sandbox.stub(uut, 'deseralizeTx').resolves({
// txid: 'complete tx id result'
// })
// sandbox.stub(uut.retryQueue, 'addToQueue').resolves('complete tx id result')
// const hex = 'hex'
// const hdIndex = 11
// const mnemonic = 'course abstract aerobic deer try switch turtle diet fence affair butter top'
// const txid = await uut.completeTx(hex, hdIndex, mnemonic)
// assert.equal(txid, 'complete tx id result')
// })
it('should throw an error if the hex is not provided', async () => {
try {
const hdIndex = 11
@@ -134,11 +437,60 @@ describe('#wallet', () => {
it('should throw an error if the hdIndex is not provided', async () => {
try {
const hex = 'hex'
await uut.completeTx(hex, null)
await uut.completeTx(hex)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'hdIndex must be a non-negative number!')
}
})
it('should throw an error if the mnemonic is not provided', async () => {
try {
const hex = 'hex'
const hdIndex = 11
await uut.completeTx(hex, hdIndex)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'mnemonic must be a string!')
}
})
it('should complete a transaction', async () => {
sandbox.stub(uut, 'getKeyPair').resolves({
cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
wif: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
hdIndex: 11
})
sandbox.stub(uut, 'deseralizeTx').resolves({
txid: 'complete tx id result'
})
sandbox.stub(uut.retryQueue, 'addToQueue').resolves('complete tx id result')
const hex = 'hex'
const hdIndex = 11
const mnemonic = 'course abstract aerobic deer try switch turtle diet fence affair butter top'
const txid = await uut.completeTx(hex, hdIndex, mnemonic)
assert.equal(txid, 'complete tx id result')
})
})
describe('#reClaimTokens', () => {
it('should re-claim tokens', async () => {
const orderData = {
hdIndex: 11,
tokenId: 'tokenId',
numTokens: 1
}
const res = await uut.reclaimTokens(orderData)
assert.equal(res, 'fakeTxid')
})
it('should handle error if order hd index is not provided', async () => {
try {
const orderData = {}
await uut.reclaimTokens(orderData)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'orderData.hdIndex is required!')
}
})
})
})
@@ -65,4 +65,51 @@ describe('#Webhook-Adapter', () => {
assert.isString(result.id)
})
})
describe('#deleteWebhook', () => {
it('should throw error if input is not provided', async () => {
try {
await uut.deleteWebhook()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'url must be a string')
}
})
it('should delete webhook', async () => {
sandbox.stub(uut.axios, 'delete').resolves({ data: { success: true } })
const url = 'https://test.com/my-webhook'
const result = await uut.deleteWebhook(url)
assert.isObject(result)
assert.property(result, 'success')
})
})
describe('#waitUntilSuccess', () => {
it('should handle error', async () => {
try {
sandbox.stub(uut, 'sleep').throws(new Error('test error'))
sandbox.stub(uut, 'deleteWebhook')
.onCall(0).throws(new Error('test error'))
sandbox.stub(uut, 'createWebhook')
.onCall(0).throws(new Error('test error'))
.onCall(1).resolves({ data: { success: true } })
await uut.waitUntilSuccess()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should wait until success', async () => {
uut.sleepTime = 100
sandbox.stub(uut, 'deleteWebhook')
.onCall(0).throws(new Error('test error'))
sandbox.stub(uut, 'createWebhook')
.onCall(0).throws(new Error('test error'))
.onCall(1).resolves({ data: { success: true } })
const result = await uut.waitUntilSuccess()
assert.isTrue(result)
})
})
})
+22 -1
View File
@@ -12,8 +12,18 @@ const mockWallet = {
nextAddress: 1
};
class MockBchWallet {
class AdapterRoute {
constructor() {
this.sendTx = async () => {
return 'fakeTxid';
}
}
}
class MockBchWallet {
constructor(mnemonic, advancedOptions) {
this.advancedOptions = advancedOptions
this.walletInfoPromise = true;
this.walletInfo = mockWallet;
this.initialize = async () => {}
@@ -24,6 +34,9 @@ class MockBchWallet {
this.sendTokens = async () => {
return 'fakeTxid';
};
this.send = async () => {
return 'fakeTxid';
};
this.utxoIsValid =async ()=>{}
this.getUtxos = async () => { };
this.getBalance = async () => { };
@@ -33,7 +46,15 @@ class MockBchWallet {
tokenTicker: 'TROUT'
}];
};
this.getKeyPair = async () => {
return {
cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
wif: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
hdIndex: 11
}
};
this.optimize = async () => { };
this.ar = new AdapterRoute()
// Environment variable is used by wallet-balance.unit.js to force an error.
if (process.env.NO_UTXO) {
this.utxos = {};
+27
View File
@@ -0,0 +1,27 @@
class BchTokenSweepMock {
constructor(wif, privateKey, bchWallet, fee, cashAddress) {
this.wif = wif
this.privateKey = privateKey
this.bchWallet = bchWallet
this.fee = fee
this.cashAddress = cashAddress
}
populateObjectFromNetwork() {
return {
success: true,
txid: 'txid'
}
}
sweepTo() {
return {
success: true,
txid: 'txid'
}
}
}
export { BchTokenSweepMock }
export default {
BchTokenSweepMock
}
@@ -115,6 +115,7 @@ const fungibleTokenData01 = {
"documentUri": "troutsblog.com",
"documentHash": "",
"decimals": 2,
"tokenDecimals": 2,
"mintBatonIsActive": true,
"tokensInCirculationBN": "100097954686",
"tokensInCirculationStr": "100097954686",