mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-21 16:52:00 -07:00
Increased test coverage
This commit is contained in:
@@ -100,11 +100,11 @@ class OfferRESTControllerLib {
|
||||
try {
|
||||
console.log('REST API controller, body: ', ctx.request.body)
|
||||
|
||||
const offerCid = ctx.request.body.offerCid
|
||||
const nostrEventId = ctx.request.body.nostrEventId
|
||||
|
||||
const hash = await _this.useCases.offer.takeOffer(offerCid)
|
||||
const eventId = await _this.useCases.offer.takeOffer(nostrEventId)
|
||||
|
||||
ctx.body = { hash }
|
||||
ctx.body = { eventId }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in takeOffer() REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
|
||||
@@ -273,12 +273,12 @@ class OfferUseCases {
|
||||
// provide functionality to take less than the total amount of tokens offered
|
||||
// (offerInfo.numTokens). Taking less than the offered amount will be added
|
||||
// in the future.
|
||||
async takeOffer (offerCid) {
|
||||
async takeOffer (eventId) {
|
||||
try {
|
||||
console.log('offerCid: ', offerCid)
|
||||
if (!eventId || typeof eventId !== 'string') throw new Error('eventId must be a string')
|
||||
|
||||
// Get the Offer information
|
||||
const offerInfo = await this.findOfferByHash(offerCid)
|
||||
const offerInfo = await this.findOfferByEvent(eventId)
|
||||
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
||||
|
||||
// Ensure the offer is in a 'posted' state and not already 'taken'
|
||||
@@ -291,6 +291,8 @@ class OfferUseCases {
|
||||
tx_hash: offerInfo.utxoTxid,
|
||||
tx_pos: offerInfo.utxoVout
|
||||
}
|
||||
|
||||
// Note : should be added to retry-queue?
|
||||
const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
|
||||
console.log('utxoStatus: ', utxoStatus)
|
||||
if (!utxoStatus) {
|
||||
@@ -310,6 +312,7 @@ class OfferUseCases {
|
||||
|
||||
// Calculate amount of sats to generate a counter offer.
|
||||
let satsToMove = Math.ceil(offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit))
|
||||
console.log('satsToMove', satsToMove, offerInfo)
|
||||
if (isNaN(satsToMove)) {
|
||||
throw new Error('Could not calculate the amount of BCH to generate counter offer')
|
||||
}
|
||||
@@ -335,9 +338,9 @@ class OfferUseCases {
|
||||
// Create valid Offer object
|
||||
const takenOfferInfo = Object.assign({}, offerInfo)
|
||||
takenOfferInfo.partialTxHex = partialTxHex
|
||||
delete takenOfferInfo.p2wdbHash
|
||||
delete takenOfferInfo.nostrEventId
|
||||
delete takenOfferInfo._id
|
||||
takenOfferInfo.offerHash = offerInfo.p2wdbHash
|
||||
takenOfferInfo.offerHash = offerInfo.nostrEventId
|
||||
|
||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||
takenOfferInfo.dataType = 'counter-offer'
|
||||
@@ -345,19 +348,19 @@ class OfferUseCases {
|
||||
// Write offer info to the P2WDB
|
||||
// TODO: This will trigger the webhook. Find some way of triggering the
|
||||
// webhook on new offers, but not on counteroffers
|
||||
const p2wdbObj = {
|
||||
const nostrData = {
|
||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||
data: takenOfferInfo,
|
||||
appId: this.config.p2wdbAppId
|
||||
}
|
||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||
const resultEventId = await this.adapters.nostr.post(nostrData)
|
||||
|
||||
// Delete the Offer from the database, so that the user doesn't attempt
|
||||
// to take the offer more than once.
|
||||
offerInfo.remove()
|
||||
|
||||
// Return the P2WDB CID
|
||||
return hash
|
||||
return resultEventId
|
||||
|
||||
// return 'fake-hash'
|
||||
} catch (err) {
|
||||
@@ -380,6 +383,7 @@ class OfferUseCases {
|
||||
await this.adapters.wallet.bchWallet.initialize()
|
||||
|
||||
// Ensure the app wallet has enough funds to write to the P2WDB.
|
||||
// Note : this validation should be deprecated for nostr functionality?
|
||||
const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
|
||||
const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif)
|
||||
if (!canWriteToP2WDB) throw new Error('App wallet does not have funds for writing to the P2WDB.')
|
||||
@@ -420,28 +424,27 @@ class OfferUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
async findOfferByHash (p2wdbHash) {
|
||||
// try {
|
||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
||||
throw new Error('p2wdbHash must be a string')
|
||||
// Retrieve an Order model from the database. Find it by its event Id.
|
||||
async findOfferByEvent (nostrEventId) {
|
||||
try {
|
||||
if (typeof nostrEventId !== 'string' || !nostrEventId) {
|
||||
throw new Error('nostrEventId must be a string')
|
||||
}
|
||||
|
||||
const order = await this.OfferModel.findOne({ nostrEventId })
|
||||
|
||||
if (!order) {
|
||||
throw new Error('offer not found')
|
||||
}
|
||||
|
||||
const orderObject = order.toObject()
|
||||
// return this.offerEntity.validateFromModel(offerObject)
|
||||
|
||||
return orderObject
|
||||
} catch (err) {
|
||||
console.error('Error in findOfferByEvent()')
|
||||
throw err
|
||||
}
|
||||
|
||||
const offer = await this.OfferModel.findOne({ p2wdbHash })
|
||||
|
||||
if (!offer) {
|
||||
throw new Error('offer not found')
|
||||
}
|
||||
|
||||
return offer
|
||||
|
||||
// const offerObject = offer.toObject()
|
||||
// return this.offerEntity.validateFromModel(offerObject)
|
||||
|
||||
// return offerObject
|
||||
// } catch (err) {
|
||||
// // console.error('Error in findOffer(): ', err)
|
||||
// throw errByHash
|
||||
// }
|
||||
}
|
||||
|
||||
async findOfferByTxid (utxoTxid) {
|
||||
@@ -468,13 +471,15 @@ class OfferUseCases {
|
||||
// Counter Offer is passed to bch-dex by the P2WDB, the data is then passed
|
||||
// to this function. It does due dilligence on the Counter Offer, then signs
|
||||
// and broadcasts the transaction to accept the Counter Offer.
|
||||
async acceptCounterOffer (p2wdbData) {
|
||||
async acceptCounterOffer (offerData) {
|
||||
try {
|
||||
console.log(`acceptCounterOffer() p2wdbData: ${JSON.stringify(p2wdbData, null, 2)}`)
|
||||
console.log(`acceptCounterOffer() offerData: ${JSON.stringify(offerData, null, 2)}`)
|
||||
|
||||
// See if this instance of bch-dex is managing the Order associated with
|
||||
// the incoming Counter Offer.
|
||||
const orderHash = p2wdbData.data.offerHash
|
||||
|
||||
// Note : this should be handle by nostrEvent id or UtxoId?
|
||||
const orderHash = offerData.data.nostrEventId
|
||||
let orderData = {}
|
||||
try {
|
||||
orderData = await this.orderUseCase.findOrderByEvent(orderHash)
|
||||
@@ -485,12 +490,13 @@ class OfferUseCases {
|
||||
}
|
||||
|
||||
// Deserialize the partially signed transaction.
|
||||
const txHex = p2wdbData.data.partialTxHex
|
||||
const txHex = offerData.data.partialTxHex
|
||||
const txObj = await this.adapters.wallet.deseralizeTx(txHex)
|
||||
console.log(`txObj: ${JSON.stringify(txObj, null, 2)}`)
|
||||
|
||||
// Ensure the 3rd output (vout=2) contains the required amount of BCH.
|
||||
const satsToReceive = Math.ceil(orderData.numTokens * parseInt(orderData.rateInBaseUnit))
|
||||
console.log('Ceil', satsToReceive)
|
||||
if (isNaN(satsToReceive)) {
|
||||
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
|
||||
}
|
||||
@@ -623,32 +629,39 @@ class OfferUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
async flagOffer (flagData) {
|
||||
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
|
||||
async flagOffer (flagData = {}) {
|
||||
try {
|
||||
if (!flagData.data) throw new Error('"data" property is required')
|
||||
|
||||
const p2wdbHash = flagData.data.p2wdbHash
|
||||
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
|
||||
|
||||
// Get the offer from the database.
|
||||
const offer = await this.findOfferByHash(p2wdbHash)
|
||||
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
|
||||
const eventId = flagData.data.nostrEventId
|
||||
|
||||
if (!offer) {
|
||||
throw new Error(`Offer ${p2wdbHash} not found in the database.`)
|
||||
// Get the offer from the database.
|
||||
const offer = await this.findOfferByEvent(eventId)
|
||||
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
|
||||
|
||||
if (!offer) {
|
||||
throw new Error(`Offer ${eventId} not found in the database.`)
|
||||
}
|
||||
|
||||
// Add the raw flag data to the database model.
|
||||
offer.flags.push(flagData)
|
||||
|
||||
// If flag count is 3 or more, mark the Offer as NSFW
|
||||
const flagCnt = offer.flags.length
|
||||
if (flagCnt >= 3) {
|
||||
offer.nsfw = true
|
||||
}
|
||||
|
||||
// Save the updated offer data to the database.
|
||||
await offer.save()
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Error in flagOffer(): ', error)
|
||||
throw error
|
||||
}
|
||||
|
||||
// Add the raw flag data to the database model.
|
||||
offer.flags.push(flagData)
|
||||
|
||||
// If flag count is 3 or more, mark the Offer as NSFW
|
||||
const flagCnt = offer.flags.length
|
||||
if (flagCnt >= 3) {
|
||||
offer.nsfw = true
|
||||
}
|
||||
|
||||
// Save the updated offer data to the database.
|
||||
await offer.save()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Get offers data from nostr.
|
||||
|
||||
@@ -151,7 +151,11 @@ const wallet = {
|
||||
},
|
||||
bchWallet: new MockBchWallet(),
|
||||
moveTokens: async () => {},
|
||||
reclaimTokens: async ()=>{}
|
||||
moveBch: async () => {},
|
||||
reclaimTokens: async ()=>{},
|
||||
generatePartialTx: async ()=>{},
|
||||
deseralizeTx:async ()=>{},
|
||||
completeTx:async ()=>{ return ''},
|
||||
}
|
||||
|
||||
const p2wdb = {
|
||||
|
||||
@@ -24,6 +24,7 @@ class MockBchWallet {
|
||||
this.sendTokens = async () => {
|
||||
return 'fakeTxid';
|
||||
};
|
||||
this.utxoIsValid =async ()=>{}
|
||||
this.getUtxos = async () => { };
|
||||
this.getBalance = async () => { };
|
||||
this.listTokens = async () => { };
|
||||
|
||||
@@ -3,45 +3,45 @@
|
||||
*/
|
||||
|
||||
const nftOffer01 = {
|
||||
"messageType": 1,
|
||||
"messageClass": 1,
|
||||
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
|
||||
"buyOrSell": "sell",
|
||||
"rateInBaseUnit": 7672536,
|
||||
"minUnitsToExchange": 7672536,
|
||||
"numTokens": 1,
|
||||
"utxoTxid": "c736d73e274df20e9b069b4990d1a264fb80aa98d67cc6c7a39e42bff48e7c04",
|
||||
"utxoVout": 1,
|
||||
"timestamp": 1662930309998,
|
||||
"globaltimestamp": "2022-09-11T21:05:09.998Z",
|
||||
"localTimestamp": "9/11/2022, 9:05:09 PM",
|
||||
"txid": "4266862b8358664996038d8c29d49dc0f3d3058fd1ef3d567b084e6b16ceb5b2",
|
||||
"p2wdbHash": "zdpuArq7rCuVCGPyTnWpLjy9AYsh8yrbwjGdKwg2GDDALkM8t",
|
||||
"offerStatus": "posted",
|
||||
"makerAddr": "bitcoincash:qrqlz63cwmu0hcmsrfnd8jemn3atkpaqds6tf4ksrr",
|
||||
"ticker": "TV001",
|
||||
"tokenType": 65
|
||||
"messageType": 1,
|
||||
"messageClass": 1,
|
||||
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
|
||||
"buyOrSell": "sell",
|
||||
"rateInBaseUnit": 7672536,
|
||||
"minUnitsToExchange": 7672536,
|
||||
"numTokens": 1,
|
||||
"utxoTxid": "c736d73e274df20e9b069b4990d1a264fb80aa98d67cc6c7a39e42bff48e7c04",
|
||||
"utxoVout": 1,
|
||||
"timestamp": 1662930309998,
|
||||
"globaltimestamp": "2022-09-11T21:05:09.998Z",
|
||||
"localTimestamp": "9/11/2022, 9:05:09 PM",
|
||||
"txid": "4266862b8358664996038d8c29d49dc0f3d3058fd1ef3d567b084e6b16ceb5b2",
|
||||
"p2wdbHash": "zdpuArq7rCuVCGPyTnWpLjy9AYsh8yrbwjGdKwg2GDDALkM8t",
|
||||
"offerStatus": "posted",
|
||||
"makerAddr": "bitcoincash:qrqlz63cwmu0hcmsrfnd8jemn3atkpaqds6tf4ksrr",
|
||||
"ticker": "TV001",
|
||||
"tokenType": 65
|
||||
}
|
||||
|
||||
const nftTokenData01 = {
|
||||
"genesisData": {
|
||||
"type": 65,
|
||||
"ticker": "TV001",
|
||||
"name": "Introduction to NFTs on BCH",
|
||||
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
|
||||
"documentUri": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
|
||||
"documentHash": "c1731268f4873f1928438abdaf6ffc546d86a1817dc6f3c6bc73fbdfb4664f10",
|
||||
"decimals": 0,
|
||||
"mintBatonIsActive": false,
|
||||
"tokensInCirculationBN": "1",
|
||||
"tokensInCirculationStr": "1",
|
||||
"blockCreated": 740395,
|
||||
"totalBurned": "0",
|
||||
"totalMinted": "1",
|
||||
"parentGroupId": "030563ddd65772d8e9b79b825529ed53c7d27037507b57c528788612b4911107"
|
||||
},
|
||||
"immutableData": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
|
||||
"mutableData": "ipfs://bafybeifzhunfpodsztsj5x4ypopngkxuapbxwaxaxkahaybdktk6joqtlq"
|
||||
"genesisData": {
|
||||
"type": 65,
|
||||
"ticker": "TV001",
|
||||
"name": "Introduction to NFTs on BCH",
|
||||
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
|
||||
"documentUri": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
|
||||
"documentHash": "c1731268f4873f1928438abdaf6ffc546d86a1817dc6f3c6bc73fbdfb4664f10",
|
||||
"decimals": 0,
|
||||
"mintBatonIsActive": false,
|
||||
"tokensInCirculationBN": "1",
|
||||
"tokensInCirculationStr": "1",
|
||||
"blockCreated": 740395,
|
||||
"totalBurned": "0",
|
||||
"totalMinted": "1",
|
||||
"parentGroupId": "030563ddd65772d8e9b79b825529ed53c7d27037507b57c528788612b4911107"
|
||||
},
|
||||
"immutableData": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
|
||||
"mutableData": "ipfs://bafybeifzhunfpodsztsj5x4ypopngkxuapbxwaxaxkahaybdktk6joqtlq"
|
||||
}
|
||||
|
||||
const simpleNftOffer01 = {
|
||||
@@ -126,7 +126,7 @@ const fungibleTokenData01 = {
|
||||
"mutableData": ""
|
||||
}
|
||||
|
||||
const offerMockData ={
|
||||
const offerMockData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
@@ -144,6 +144,31 @@ const offerMockData ={
|
||||
}
|
||||
}
|
||||
|
||||
const deserealizeTxMock = {
|
||||
//...
|
||||
vout: [
|
||||
{
|
||||
value: 0,
|
||||
scriptPubKey: {
|
||||
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
scriptPubKey: {
|
||||
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
scriptPubKey: {
|
||||
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||
}
|
||||
},
|
||||
//...
|
||||
]
|
||||
}
|
||||
|
||||
export default {
|
||||
nftOffer01,
|
||||
nftTokenData01,
|
||||
@@ -151,5 +176,6 @@ export default {
|
||||
simpleNftTokenData01,
|
||||
fungibleOffer01,
|
||||
fungibleTokenData01,
|
||||
offerMockData
|
||||
offerMockData,
|
||||
deserealizeTxMock
|
||||
};
|
||||
|
||||
@@ -191,6 +191,69 @@ describe('#offer-use-case', () => {
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
// Mock dependencies and force desired code path.
|
||||
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
|
||||
// sandbox.stub(uut.OfferModel, 'remove').resolves()
|
||||
|
||||
await uut.removeDuplicateOffers()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#removeStaleOffers', () => {
|
||||
it('remove offer with wrong utxoState', async () => {
|
||||
// Mock dependencies and force desired code path.
|
||||
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(false)
|
||||
|
||||
await uut.removeStaleOffers()
|
||||
})
|
||||
it('remove offer with wrong txid', async () => {
|
||||
// Mock dependencies and force desired code path.
|
||||
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').throws(new Error('txid needs to be a proper transaction ID'))
|
||||
|
||||
await uut.removeStaleOffers()
|
||||
})
|
||||
it('remove expired offer ', async () => {
|
||||
const tsMock = new Date()
|
||||
tsMock.setMonth(tsMock.getMonth() - 3)
|
||||
|
||||
const timestamp = tsMock.getTime()
|
||||
// Mock dependencies and force desired code path.
|
||||
sandbox.stub(uut.OfferModel, 'find').resolves([{ timestamp, remove: async () => { } }])
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(true)
|
||||
|
||||
await uut.removeStaleOffers()
|
||||
})
|
||||
it('should handle axios error ', async () => {
|
||||
const testErr = new Error()
|
||||
testErr.isAxiosError = true
|
||||
|
||||
// Mock dependencies and force desired code path.
|
||||
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').throws(testErr)
|
||||
|
||||
await uut.removeStaleOffers()
|
||||
})
|
||||
it('should handle error ', async () => {
|
||||
try {
|
||||
const testErr = new Error('unknow error')
|
||||
|
||||
// Mock dependencies and force desired code path.
|
||||
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').throws(testErr)
|
||||
|
||||
await uut.removeStaleOffers()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'unknow error')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#findOfferByTxid', () => {
|
||||
it('should throw an error if input is not provided', async () => {
|
||||
@@ -256,4 +319,425 @@ describe('#offer-use-case', () => {
|
||||
assert.isFalse(result)
|
||||
})
|
||||
})
|
||||
describe('#listOffers', () => {
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
|
||||
|
||||
await uut.listOffers()
|
||||
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should list orders', async () => {
|
||||
const queryMock = {
|
||||
sort () {
|
||||
return this
|
||||
},
|
||||
skip () {
|
||||
return this
|
||||
},
|
||||
limit () { return [] }
|
||||
}
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
|
||||
|
||||
const result = await uut.listOffers()
|
||||
assert.isArray(result)
|
||||
})
|
||||
})
|
||||
describe('#listNftOffers', () => {
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
|
||||
|
||||
await uut.listNftOffers()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should list orders', async () => {
|
||||
const queryMock = {
|
||||
sort () {
|
||||
return this
|
||||
},
|
||||
skip () {
|
||||
return this
|
||||
},
|
||||
limit () { return [] }
|
||||
}
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
|
||||
|
||||
const result = await uut.listNftOffers(1, true)
|
||||
assert.isArray(result)
|
||||
})
|
||||
})
|
||||
describe('#listFungibleOffers', () => {
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
|
||||
|
||||
await uut.listFungibleOffers()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should list orders', async () => {
|
||||
const queryMock = {
|
||||
sort () {
|
||||
return this
|
||||
},
|
||||
skip () {
|
||||
return this
|
||||
},
|
||||
limit () { return [] }
|
||||
}
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
|
||||
|
||||
const result = await uut.listFungibleOffers()
|
||||
assert.isArray(result)
|
||||
})
|
||||
})
|
||||
describe('#takeOffer', () => {
|
||||
it('should handle error if input is not provided', async () => {
|
||||
try {
|
||||
await uut.takeOffer()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'eventId must be a string')
|
||||
}
|
||||
})
|
||||
it('should handle error for wrong offer status', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'completed' })
|
||||
|
||||
await uut.takeOffer('eventId')
|
||||
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'offer status is not "posted", so offer is dead and can not be countered.')
|
||||
}
|
||||
})
|
||||
it('should handle error for invalid utxo', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(false)
|
||||
|
||||
await uut.takeOffer('eventId')
|
||||
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'UTXO does not exist. Aborting.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle insufficient funds', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
|
||||
sandbox.stub(uut, 'ensureFunds').throws(new Error('test error'))
|
||||
|
||||
await uut.takeOffer('eventId')
|
||||
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
it('should handle error if counter offer cant be calculated', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves(true)
|
||||
|
||||
await uut.takeOffer('eventId')
|
||||
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Could not calculate the amount of BCH to generate counter offer')
|
||||
}
|
||||
})
|
||||
it('should handle error if counter offer cant be calculated', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves(true)
|
||||
|
||||
await uut.takeOffer('eventId')
|
||||
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Could not calculate the amount of BCH to generate counter offer')
|
||||
}
|
||||
})
|
||||
it('should take offer', async () => {
|
||||
// Mock data
|
||||
const offerMock = Object.assign({}, mockData.offerMockData.data)
|
||||
offerMock.remove = async () => { }
|
||||
offerMock.offerStatus = 'posted'
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'findOfferByEvent').returns(offerMock)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves(true)
|
||||
sandbox.stub(uut.adapters.wallet, 'moveBch').resolves({ sats: 1 })
|
||||
|
||||
await uut.takeOffer('eventId')
|
||||
})
|
||||
})
|
||||
describe('#ensureFunds', () => {
|
||||
it('should handle insufficient funds to use p2wdb', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(false)
|
||||
|
||||
await uut.ensureFunds(mockData.offerMockData.data)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'App wallet does not have funds for writing to the P2WDB')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw error if sats needed could no be able to calculated', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(true)
|
||||
|
||||
// Mock Input
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.rateInBaseUnit = null
|
||||
|
||||
await uut.ensureFunds(mock)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Could not calculate sats needed!')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw error if app wallet does not have enough bch', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(true)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'getBalance').resolves(0)
|
||||
|
||||
await uut.ensureFunds(mockData.offerMockData.data)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'App wallet does not control enough BCH to purchase the tokens.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle BUY offer', async () => {
|
||||
try {
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.buyOrSell = 'buy'
|
||||
|
||||
await uut.ensureFunds(mock)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Buy offers are not supported yet.')
|
||||
}
|
||||
})
|
||||
it('should return true', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(true)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'getBalance').resolves(10 * 10 ** 6)
|
||||
|
||||
const result = await uut.ensureFunds(mockData.offerMockData.data)
|
||||
assert.isTrue(result)
|
||||
})
|
||||
})
|
||||
describe('#findOrderByEvent', () => {
|
||||
it('should throw an error if hash is not provided', async () => {
|
||||
try {
|
||||
await uut.findOfferByEvent()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'nostrEventId must be a string')
|
||||
}
|
||||
})
|
||||
it('should throw an error if order is not found!', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'findOne').resolves(null)
|
||||
|
||||
await uut.findOfferByEvent('eventId')
|
||||
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'offer not found')
|
||||
}
|
||||
})
|
||||
it('should return offer by eventId', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'findOne').resolves({ toObject: () => { return { hash: 'hash' } } })
|
||||
|
||||
const result = await uut.findOfferByEvent('eventId')
|
||||
assert.isObject(result)
|
||||
})
|
||||
})
|
||||
describe('#flagOffer', () => {
|
||||
it('should throw an error if input is not provided', async () => {
|
||||
try {
|
||||
await uut.flagOffer()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, '"data" property is required')
|
||||
}
|
||||
})
|
||||
it('should throw an error if offer is not found!', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'findOfferByEvent').resolves(null)
|
||||
|
||||
const input = {
|
||||
data:
|
||||
{
|
||||
nostrEventId: 'eventId'
|
||||
}
|
||||
}
|
||||
await uut.flagOffer(input)
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'not found in the database')
|
||||
}
|
||||
})
|
||||
it('should flag offer', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'findOfferByEvent').resolves({ flags: ['a', 'b', 'c'], save: () => { } })
|
||||
|
||||
const input = {
|
||||
data:
|
||||
{
|
||||
nostrEventId: 'eventId'
|
||||
}
|
||||
}
|
||||
const result = await uut.flagOffer(input)
|
||||
assert.isTrue(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#loadOffers', () => {
|
||||
it('should handle nostr error', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.nostr, 'read').throws(new Error('test error'))
|
||||
|
||||
await uut.loadOffers()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
it('should skip internal function errors ', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.nostr, 'read').resolves([mockData.offerMockData])
|
||||
|
||||
await uut.loadOffers()
|
||||
})
|
||||
it('should review and load offers', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.nostr, 'read').resolves([JSON.stringify(mockData.offerMockData)])
|
||||
|
||||
await uut.loadOffers()
|
||||
})
|
||||
})
|
||||
describe('#acceptCounterOffer', () => {
|
||||
it('should return if order is not found!', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').throws(new Error('test error'))
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
it('should handle error if counter offer cant be calculated', async () => {
|
||||
try {
|
||||
// Mock Data
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.rateInBaseUnit = null
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||
|
||||
await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Could not calculate the amount of BCH offered in the Counter Offer')
|
||||
}
|
||||
})
|
||||
it('should handle error for wrong transaction output', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mockData.offerMockData.data)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'The Counter Offer has an output of ')
|
||||
assert.include(err.message, 'which does not match the required')
|
||||
}
|
||||
})
|
||||
it('should handle error for wrong transaction output address', async () => {
|
||||
try {
|
||||
// Mock data
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.makerAddr = 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477' // Unknow Adress
|
||||
mock.rateInBaseUnit = 0
|
||||
mock.numTokens = 0
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'The Counter Offer has an output address of')
|
||||
assert.include(err.message, 'which does not match the Maker address')
|
||||
}
|
||||
})
|
||||
it('should return tx id', async () => {
|
||||
// Mock data
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.makerAddr = mockData.deserealizeTxMock.vout[2].scriptPubKey.addresses[0] // Maker Address
|
||||
mock.rateInBaseUnit = 0
|
||||
mock.numTokens = 0
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
//
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
assert.isString(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user