Increased test coverage

This commit is contained in:
Daniel Gonzalez
2024-12-04 06:51:28 -04:00
parent b3ba7f03e4
commit 9dbd18d0d6
6 changed files with 625 additions and 97 deletions
+3 -3
View File
@@ -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)
+68 -55
View File
@@ -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.
+5 -1
View File
@@ -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 = {
+1
View File
@@ -24,6 +24,7 @@ class MockBchWallet {
this.sendTokens = async () => {
return 'fakeTxid';
};
this.utxoIsValid =async ()=>{}
this.getUtxos = async () => { };
this.getBalance = async () => { };
this.listTokens = async () => { };
+64 -38
View File
@@ -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
};
+484
View File
@@ -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)
})
})
})