mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-22 01:02:00 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dddfbdcb6 | ||
|
|
fc2d7b4b9b | ||
|
|
c877898ae7 | ||
|
|
88e27f47f5 | ||
|
|
0057007b4c | ||
|
|
8aad93e572 |
@@ -11,6 +11,7 @@ import Usage from './models/usage.js'
|
||||
import SmAccount from './models/smAccount.js'
|
||||
import DeletedChat from './models/deletedChat.js'
|
||||
import DeletedPost from './models/deletedPost.js'
|
||||
import CounterOffer from './models/counter-offer.js'
|
||||
|
||||
class LocalDB {
|
||||
constructor () {
|
||||
@@ -23,6 +24,7 @@ class LocalDB {
|
||||
this.SmAccount = SmAccount
|
||||
this.DeletedChat = DeletedChat
|
||||
this.DeletedPost = DeletedPost
|
||||
this.CounterOffer = CounterOffer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
const CounterOffer = new mongoose.Schema({
|
||||
nostrEventId: { type: String, required: true }, // Nostr Event Id.
|
||||
takerAddr: { type: String, default: null },
|
||||
takerNpub: { type: String, default: null },
|
||||
makerNpub: { type: String, default: null },
|
||||
counterOfferAddr: { type: String, default: null },
|
||||
counterOfferUtxo: { type: String, default: null },
|
||||
takerOfferUtxo: { type: String, default: null },
|
||||
tokenId: { type: String, default: null }
|
||||
})
|
||||
|
||||
export default mongoose.model('counterOffer', CounterOffer)
|
||||
@@ -18,7 +18,7 @@ const Offer = new mongoose.Schema({
|
||||
p2wdbHash: { type: String },
|
||||
offerStatus: { type: String },
|
||||
makerAddr: { type: String },
|
||||
makerNpub: { type: String },
|
||||
makerNpub: { type: String, default: null },
|
||||
|
||||
// Authentication data
|
||||
signature: { type: String },
|
||||
|
||||
@@ -35,6 +35,7 @@ class OfferRESTControllerLib {
|
||||
this.takeOffer = this.takeOffer.bind(this)
|
||||
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
||||
this.syncOfferMutableData = this.syncOfferMutableData.bind(this)
|
||||
this.listCounterOffersByAddress = this.listCounterOffersByAddress.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
@@ -408,6 +409,52 @@ class OfferRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /offer/list/counter-offer/:addr List all counter offers being made by a given address
|
||||
* @apiPermission public
|
||||
* @apiName listCounterOffersByAddress
|
||||
* @apiGroup REST Offer
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5000/offer/list/counter-offer/bitcoincash:qq54a3xyyptty63ztlcavzzh64gy4d247qs4ueaupe
|
||||
*
|
||||
*
|
||||
|
||||
* @apiSuccess {String} offers.nostrEventId Nostr event id
|
||||
* @apiSuccess {String} offers.takerNpub Nostr Npub.
|
||||
* @apiSuccess {String} offers.takerAddr BCH Address.
|
||||
* @apiSuccess {String} offers.counterOfferAddr BCH Address
|
||||
* @apiSuccess {String} offers.counterOfferUtxo Utxo txid.
|
||||
*
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "counterOffers": [{
|
||||
* "_id": "56bd1da600a526986cf65c80",
|
||||
* "nostrEventId": "1234567890",
|
||||
* "takerAddr": "bitcoincash:qq54a3xyyptty63ztlcavzzh64gy4d247qs4ueaupe",
|
||||
* "takerNpub": "npub1nhc47z2tf42a4ps7plzcyax2j76gdausve763tqw3xuw7jjd7ceqppquly",
|
||||
* "counterOfferAddr": "bitcoincash:qzltx40ldxr53edchprppj5lpg4q7vl20usx25ga44",
|
||||
* "counterOfferUtxo": "71dab5b81cf06f3495aeebcc70fb351109e5e88bc85da67d0d144c0e5ca0bd6a",
|
||||
* }]
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
async listCounterOffersByAddress (ctx) {
|
||||
try {
|
||||
const addr = ctx.params.addr
|
||||
|
||||
const counterOffers = await this.useCases.offer.listCounterOffersByAddress(addr)
|
||||
|
||||
ctx.body = { counterOffers }
|
||||
} catch (err) {
|
||||
console.log('Error in listCounterOffersByAddress REST API handler: ', err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err.message)
|
||||
|
||||
@@ -60,6 +60,7 @@ class OfferRouter {
|
||||
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)
|
||||
this.router.get('/list/counter-offer/:addr', this.offerRESTController.listCounterOffersByAddress)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Counter Offer Entity
|
||||
*/
|
||||
|
||||
class CounterOfferEntity {
|
||||
validate (offerData = {}) {
|
||||
console.log('counter offer data input validation ', offerData)
|
||||
// Throw an error if input object does not have a data property
|
||||
if (!offerData.data) {
|
||||
throw new Error(
|
||||
'Input to counterOffer.validate() must be an object with a data property.'
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
nostrEventId,
|
||||
takerAddr,
|
||||
takerNpub,
|
||||
makerNpub,
|
||||
counterOfferAddr,
|
||||
counterOfferUtxo,
|
||||
takerOfferUtxo,
|
||||
tokenId
|
||||
} = offerData.data
|
||||
|
||||
// Input Validation
|
||||
if (!nostrEventId || typeof nostrEventId !== 'string') {
|
||||
throw new Error("Property 'nostrEventId' must be a string.")
|
||||
}
|
||||
// Next validations are optional, to allow old counter offers without the data.
|
||||
if (takerAddr && typeof takerAddr !== 'string') {
|
||||
throw new Error("Property 'takerAddr' must be a string.")
|
||||
}
|
||||
if (takerNpub && typeof takerNpub !== 'string') {
|
||||
throw new Error("Property 'takerNpub' must be a string.")
|
||||
}
|
||||
if (counterOfferAddr && typeof counterOfferAddr !== 'string') {
|
||||
throw new Error("Property 'counterOfferAddr' must be a string.")
|
||||
}
|
||||
if (counterOfferUtxo && typeof counterOfferUtxo !== 'string') {
|
||||
throw new Error("Property 'counterOfferUtxo' must be a string.")
|
||||
}
|
||||
if (takerOfferUtxo && typeof takerOfferUtxo !== 'string') {
|
||||
throw new Error("Property 'takerOfferUtxo' must be a string.")
|
||||
}
|
||||
if (tokenId && typeof tokenId !== 'string') {
|
||||
throw new Error("Property 'tokenId' must be a string.")
|
||||
}
|
||||
if (makerNpub && typeof makerNpub !== 'string') {
|
||||
throw new Error("Property 'makerNpub' must be a string.")
|
||||
}
|
||||
|
||||
const validatedCounterOfferData = {
|
||||
nostrEventId,
|
||||
takerAddr,
|
||||
takerNpub,
|
||||
counterOfferAddr,
|
||||
counterOfferUtxo,
|
||||
takerOfferUtxo,
|
||||
tokenId,
|
||||
makerNpub
|
||||
}
|
||||
|
||||
return validatedCounterOfferData
|
||||
}
|
||||
}
|
||||
|
||||
export default CounterOfferEntity
|
||||
@@ -31,6 +31,7 @@ class OfferEntity {
|
||||
utxoVout,
|
||||
offerStatus,
|
||||
makerAddr,
|
||||
makerNpub,
|
||||
ticker,
|
||||
tokenType,
|
||||
nostrEventId,
|
||||
@@ -89,6 +90,10 @@ class OfferEntity {
|
||||
if (!operatorPercentage || typeof operatorPercentage !== 'number') {
|
||||
throw new Error("Property 'operatorPercentage' must be a number.")
|
||||
}
|
||||
// Validate npub type if it exist
|
||||
if (makerNpub && typeof makerNpub !== 'string') {
|
||||
throw new Error("Property 'makerNpub' must be a string.")
|
||||
}
|
||||
|
||||
// Convert the timestamp to a number.
|
||||
let timestamp = new Date(offerData.timestamp)
|
||||
@@ -113,7 +118,8 @@ class OfferEntity {
|
||||
makerAddr,
|
||||
ticker,
|
||||
tokenType,
|
||||
nostrEventId
|
||||
nostrEventId,
|
||||
makerNpub
|
||||
}
|
||||
// console.log('offer entity validatedOfferData: ', validatedOfferData)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import RetryQueue from '@chris.troutner/retry-queue'
|
||||
|
||||
// Local libraries
|
||||
import OfferEntity from '../../entities/offer.js'
|
||||
import CounterOfferEntity from '../../entities/counterOffer.js'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
const DEFAULT_ENTRIES_PER_PAGE = 20
|
||||
@@ -45,7 +46,9 @@ class OfferUseCases {
|
||||
this.config = config
|
||||
this.axios = axios
|
||||
this.offerEntity = new OfferEntity()
|
||||
this.counterOfferEntity = new CounterOfferEntity()
|
||||
this.OfferModel = this.adapters.localdb.Offer
|
||||
this.CounterOfferModel = this.adapters.localdb.CounterOffer
|
||||
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
|
||||
|
||||
// Bind 'this' object to functions
|
||||
@@ -66,6 +69,7 @@ class OfferUseCases {
|
||||
this.loadOffers = this.loadOffers.bind(this)
|
||||
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
||||
this.syncOfferMutableData = this.syncOfferMutableData.bind(this)
|
||||
this.listCounterOffersByAddress = this.listCounterOffersByAddress.bind(this)
|
||||
|
||||
// State
|
||||
this.seenOffers = []
|
||||
@@ -595,10 +599,23 @@ class OfferUseCases {
|
||||
// and broadcasts the transaction to accept the Counter Offer.
|
||||
async acceptCounterOffer (offerData) {
|
||||
try {
|
||||
console.log('offerData', offerData)
|
||||
// console.log(`acceptCounterOffer() offerData: ${JSON.stringify(offerData, null, 2)}`)
|
||||
|
||||
// Quickly skip over offers that have already been processed.
|
||||
const eventId = offerData.data.nostrEventId
|
||||
|
||||
// Create counter offer entity for nostr event id if it not exist
|
||||
const existingCounterOffer = await this.CounterOfferModel.findOne(({ nostrEventId: eventId }))
|
||||
if (!existingCounterOffer) {
|
||||
// Add taker offer utxo to counter offer data.
|
||||
offerData.data.takerOfferUtxo = offerData.data.utxoTxid
|
||||
const counterOEntity = this.counterOfferEntity.validate(offerData)
|
||||
// Create new counter offfer model and save it.
|
||||
const counterOffer = new this.CounterOfferModel(counterOEntity)
|
||||
await counterOffer.save()
|
||||
}
|
||||
|
||||
if (this.seenOffers.includes(eventId)) {
|
||||
// console.log(`Offer with event ID ${eventId} already processed. Skipping.`)
|
||||
return false
|
||||
@@ -1020,6 +1037,20 @@ class OfferUseCases {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async listCounterOffersByAddress (takerAddr) {
|
||||
try {
|
||||
if (!takerAddr || typeof takerAddr !== 'string') {
|
||||
throw new Error('takerAddr must be a string')
|
||||
}
|
||||
|
||||
const counterOffers = await this.CounterOfferModel.find({ takerAddr })
|
||||
return counterOffers
|
||||
} catch (err) {
|
||||
console.error('Error in listCounterOffersByAddress(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default OfferUseCases
|
||||
|
||||
@@ -251,6 +251,28 @@ describe('#Offer-REST-Router', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#listCounterOffersByAddress', () => {
|
||||
it('should list counter offers by address', async () => {
|
||||
ctx.params = { addr: 'testAddress' }
|
||||
sandbox.stub(uut.useCases.offer, 'listCounterOffersByAddress').resolves([])
|
||||
await uut.listCounterOffersByAddress(ctx)
|
||||
assert.isArray(ctx.body.counterOffers)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.params = { addr: 'testAddress' }
|
||||
sandbox
|
||||
.stub(uut.useCases.offer, 'listCounterOffersByAddress')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.listCounterOffersByAddress(ctx)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#handleError', () => {
|
||||
it('should still throw error if there is no message', () => {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Unit tests for the CounterOffer entity library.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import CounterOfferEntity from '../../../src/entities/counterOffer.js'
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
|
||||
describe('#CounterOffer-Entity', () => {
|
||||
before(async () => { })
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new CounterOfferEntity()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#validate', () => {
|
||||
it('should throw an error if data is not provided', () => {
|
||||
try {
|
||||
uut.validate({})
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Input to counterOffer.validate() must be an object with a data property.')
|
||||
}
|
||||
})
|
||||
it('should throw an error if nostrEventId is not provided', () => {
|
||||
try {
|
||||
uut.validate({ data: {} })
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'nostrEventId' must be a string")
|
||||
}
|
||||
})
|
||||
it('should throw an error if provided takerAddr is not string', () => {
|
||||
try {
|
||||
const inObj = {
|
||||
nostrEventId: 'nostr event id',
|
||||
takerAddr: 1234
|
||||
}
|
||||
uut.validate({ data: inObj })
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'takerAddr' must be a string")
|
||||
}
|
||||
})
|
||||
it('should throw an error if provided takerNpub is not string', () => {
|
||||
try {
|
||||
const inObj = {
|
||||
nostrEventId: 'nostr event id',
|
||||
takerAddr: 'taker address',
|
||||
takerNpub: 1234
|
||||
}
|
||||
uut.validate({ data: inObj })
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'takerNpub' must be a string")
|
||||
}
|
||||
})
|
||||
it('should throw an error if provided makerNpub is not string', () => {
|
||||
try {
|
||||
const inObj = {
|
||||
nostrEventId: 'nostr event id',
|
||||
takerAddr: 'taker address',
|
||||
takerNpub: 'npub',
|
||||
makerNpub: 1234
|
||||
}
|
||||
uut.validate({ data: inObj })
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'makerNpub' must be a string")
|
||||
}
|
||||
})
|
||||
it('should throw an error if provided counterOfferAddr is not string', () => {
|
||||
try {
|
||||
const inObj = {
|
||||
nostrEventId: 'nostr event id',
|
||||
takerAddr: 'taker address',
|
||||
takerNpub: 'npub',
|
||||
makerNpub: 'npub',
|
||||
counterOfferAddr: 1234
|
||||
}
|
||||
uut.validate({ data: inObj })
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'counterOfferAddr' must be a string")
|
||||
}
|
||||
})
|
||||
it('should throw an error if provided counterOfferUtxo is not string', () => {
|
||||
try {
|
||||
const inObj = {
|
||||
nostrEventId: 'nostr event id',
|
||||
takerAddr: 'taker address',
|
||||
takerNpub: 'npub',
|
||||
makerNpub: 'npub',
|
||||
counterOfferAddr: 'counter offer address',
|
||||
counterOfferUtxo: 1234
|
||||
}
|
||||
uut.validate({ data: inObj })
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'counterOfferUtxo' must be a string")
|
||||
}
|
||||
})
|
||||
it('should throw an error if provided takerOfferUtxo is not string', () => {
|
||||
try {
|
||||
const inObj = {
|
||||
nostrEventId: 'nostr event id',
|
||||
takerAddr: 'taker address',
|
||||
takerNpub: 'npub',
|
||||
makerNpub: 'npub',
|
||||
counterOfferAddr: 'counter offer address',
|
||||
counterOfferUtxo: 'utxo txid',
|
||||
takerOfferUtxo: 1234
|
||||
}
|
||||
uut.validate({ data: inObj })
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'takerOfferUtxo' must be a string")
|
||||
}
|
||||
})
|
||||
it('should throw an error if provided tokenId is not string', () => {
|
||||
try {
|
||||
const inObj = {
|
||||
nostrEventId: 'nostr event id',
|
||||
takerAddr: 'taker address',
|
||||
takerNpub: 'npub',
|
||||
makerNpub: 'npub',
|
||||
counterOfferAddr: 'counter offer address',
|
||||
counterOfferUtxo: 'utxo txid',
|
||||
takerOfferUtxo: 'utxo txid',
|
||||
tokenId: 1234
|
||||
}
|
||||
uut.validate({ data: inObj })
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'tokenId' must be a string")
|
||||
}
|
||||
})
|
||||
|
||||
it('should return a counter offer object', () => {
|
||||
const inObj = {
|
||||
nostrEventId: 'nostr event id',
|
||||
takerAddr: 'taker address',
|
||||
takerNpub: 'npub',
|
||||
makerNpub: 'npub',
|
||||
counterOfferAddr: 'counter offer address',
|
||||
counterOfferUtxo: 'utxo txid',
|
||||
takerOfferUtxo: 'utxo txid',
|
||||
tokenId: 'token id'
|
||||
}
|
||||
|
||||
const counterOfferEntity = uut.validate({ data: inObj })
|
||||
|
||||
assert.property(counterOfferEntity, 'nostrEventId')
|
||||
assert.equal(counterOfferEntity.nostrEventId, inObj.nostrEventId)
|
||||
|
||||
assert.property(counterOfferEntity, 'takerAddr')
|
||||
assert.equal(counterOfferEntity.takerAddr, inObj.takerAddr)
|
||||
|
||||
assert.property(counterOfferEntity, 'takerNpub')
|
||||
assert.equal(counterOfferEntity.takerNpub, inObj.takerNpub)
|
||||
|
||||
assert.property(counterOfferEntity, 'makerNpub')
|
||||
assert.equal(counterOfferEntity.makerNpub, inObj.makerNpub)
|
||||
|
||||
assert.property(counterOfferEntity, 'counterOfferAddr')
|
||||
assert.equal(counterOfferEntity.counterOfferAddr, inObj.counterOfferAddr)
|
||||
|
||||
assert.property(counterOfferEntity, 'counterOfferUtxo')
|
||||
assert.equal(counterOfferEntity.counterOfferUtxo, inObj.counterOfferUtxo)
|
||||
|
||||
assert.property(counterOfferEntity, 'takerOfferUtxo')
|
||||
assert.equal(counterOfferEntity.takerOfferUtxo, inObj.takerOfferUtxo)
|
||||
|
||||
assert.property(counterOfferEntity, 'tokenId')
|
||||
assert.equal(counterOfferEntity.tokenId, inObj.tokenId)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -491,6 +491,36 @@ describe('#Offer-Entity', () => {
|
||||
assert.include(err.message, "Property 'operatorPercentage' must be a number.")
|
||||
}
|
||||
})
|
||||
it('should throw an error if makerNpub is not a string', () => {
|
||||
try {
|
||||
const offerData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350,
|
||||
numTokens: 1,
|
||||
utxoTxid: 'fakeTxid',
|
||||
utxoVout: 0,
|
||||
offerStatus: 'posted',
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
tokenType: 1,
|
||||
nostrEventId: 'test',
|
||||
operatorAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
operatorPercentage: 10,
|
||||
makerNpub: 1234
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'makerNpub' must be a string.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should validate a new offer', () => {
|
||||
const offerObj = {
|
||||
@@ -513,7 +543,8 @@ describe('#Offer-Entity', () => {
|
||||
tokenType: 1,
|
||||
nostrEventId: 'test',
|
||||
operatorAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
operatorPercentage: 10
|
||||
operatorPercentage: 10,
|
||||
makerNpub: 'npub1dtkf954h5k2yuv04xp44g5hnmk5k0ppv8mekxafm2gc0vveaxh6s0297qg'
|
||||
},
|
||||
timestamp: '2021-09-20T17:54:26.395Z',
|
||||
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
||||
@@ -539,6 +570,8 @@ describe('#Offer-Entity', () => {
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'p2wdbHash')
|
||||
assert.property(result, 'tokenType')
|
||||
assert.property(result, 'makerAddr')
|
||||
assert.property(result, 'makerNpub')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -154,6 +154,17 @@ const localdb = {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
CounterOffer: class CounterOffer {
|
||||
constructor (obj) {}
|
||||
|
||||
static findById () {}
|
||||
static find () {}
|
||||
static findOne () {}
|
||||
static countDocuments(){}
|
||||
async save () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
SmAccount: class SmAccount {
|
||||
constructor (obj) {}
|
||||
|
||||
|
||||
@@ -74,6 +74,9 @@ class Offer {
|
||||
async syncOfferMutableData(){
|
||||
return {}
|
||||
}
|
||||
async listCounterOffersByAddress() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
class Order {
|
||||
|
||||
@@ -835,6 +835,25 @@ describe('#offer-use-case', () => {
|
||||
})
|
||||
|
||||
describe('#acceptCounterOffer', () => {
|
||||
it('should capture counter offer if does not exist', async () => {
|
||||
const offerObj = mockData.offerMockData
|
||||
sandbox.stub(uut.CounterOfferModel, 'findOne').resolves(null)
|
||||
const spy = sandbox.stub(uut.CounterOfferModel.prototype, 'save')
|
||||
// Mock dependencies
|
||||
|
||||
await uut.acceptCounterOffer(offerObj)
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
it('should not save counter offer if already exist', async () => {
|
||||
const offerObj = mockData.offerMockData
|
||||
sandbox.stub(uut.CounterOfferModel, 'findOne').resolves(offerObj.data)
|
||||
const spy = sandbox.stub(uut.CounterOfferModel.prototype, 'save')
|
||||
// Mock dependencies
|
||||
|
||||
await uut.acceptCounterOffer(offerObj)
|
||||
assert.isFalse(spy.calledOnce)
|
||||
})
|
||||
|
||||
it('should return false if order already processed', async () => {
|
||||
const offerObj = mockData.offerMockData
|
||||
uut.seenOffers.push(offerObj.data.nostrEventId)
|
||||
@@ -846,14 +865,18 @@ describe('#offer-use-case', () => {
|
||||
|
||||
it('should return if order is not found!', async () => {
|
||||
// Mock dependencies
|
||||
const offerObj = mockData.offerMockData
|
||||
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').throws(new Error('test error'))
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
const result = await uut.acceptCounterOffer(offerObj)
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
|
||||
it('should return N/A on axios error', async () => {
|
||||
// Mock dependencies
|
||||
const offerObj = mockData.offerMockData
|
||||
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').throws(new Error('test error'))
|
||||
@@ -861,32 +884,38 @@ describe('#offer-use-case', () => {
|
||||
axiosErr.isAxiosError = true
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').throws(axiosErr)
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
const result = await uut.acceptCounterOffer(offerObj)
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
it('should return if utxo can not be validated', async () => {
|
||||
// Mock dependencies
|
||||
const offerObj = mockData.offerMockData
|
||||
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').throws(new Error('test error'))
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').throws(new Error('test error'))
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
const result = await uut.acceptCounterOffer(offerObj)
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
|
||||
it('should return if utxo is invalid', async () => {
|
||||
// Mock dependencies
|
||||
const offerObj = mockData.offerMockData
|
||||
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
const result = await uut.acceptCounterOffer(offerObj)
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
|
||||
it('should handle error if counter offer cant be calculated', async () => {
|
||||
try {
|
||||
const offerObj = mockData.offerMockData
|
||||
|
||||
// Mock Data
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.rateInBaseUnit = null
|
||||
@@ -895,8 +924,7 @@ describe('#offer-use-case', () => {
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: {} })
|
||||
console.log('result: ', result)
|
||||
await uut.acceptCounterOffer(offerObj)
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Could not calculate the amount of BCH offered in the Counter Offer')
|
||||
@@ -906,6 +934,7 @@ describe('#offer-use-case', () => {
|
||||
it('should handle error for wrong transaction output', async () => {
|
||||
try {
|
||||
// Mock Data
|
||||
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
|
||||
// Mock dependencies
|
||||
@@ -923,6 +952,7 @@ describe('#offer-use-case', () => {
|
||||
|
||||
it('should skip transactions that do not have an output for the operator', async () => {
|
||||
// Mock data
|
||||
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.makerAddr = 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477' // Unknow Adress
|
||||
mock.rateInBaseUnit = 0
|
||||
@@ -934,7 +964,7 @@ describe('#offer-use-case', () => {
|
||||
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMockNoOperatorOut)
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
const result = await uut.acceptCounterOffer({ data: mock })
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
it('should skip transactions if operator address does not match', async () => {
|
||||
@@ -952,7 +982,7 @@ describe('#offer-use-case', () => {
|
||||
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
await uut.acceptCounterOffer({ data: {} })
|
||||
await uut.acceptCounterOffer({ data: mock })
|
||||
assert.fail('unexpected code path')
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'The Counter Offer has an output address of')
|
||||
@@ -974,7 +1004,7 @@ describe('#offer-use-case', () => {
|
||||
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
await uut.acceptCounterOffer({ data: {} })
|
||||
await uut.acceptCounterOffer({ data: mock })
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'The Counter Offer has an output address of')
|
||||
@@ -997,7 +1027,7 @@ describe('#offer-use-case', () => {
|
||||
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: {} })
|
||||
const result = await uut.acceptCounterOffer({ data: mock })
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
|
||||
@@ -1017,7 +1047,7 @@ describe('#offer-use-case', () => {
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
//
|
||||
const result = await uut.acceptCounterOffer({ data: {} })
|
||||
const result = await uut.acceptCounterOffer({ data: mock })
|
||||
assert.isString(result)
|
||||
assert.notEqual('N/A')
|
||||
})
|
||||
@@ -1130,4 +1160,23 @@ describe('#offer-use-case', () => {
|
||||
assert.isNumber(offer.lastUpdatedTokenData)
|
||||
})
|
||||
})
|
||||
describe('#listCounterOffersByAddress', () => {
|
||||
it('should throw an error if takerAddr is not provided!', async () => {
|
||||
try {
|
||||
await uut.listCounterOffersByAddress()
|
||||
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'takerAddr must be a string')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return counter offers by address', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.CounterOfferModel, 'find').resolves([])
|
||||
|
||||
const result = await uut.listCounterOffersByAddress('bitcoincash:qzltx40ldxr53edchprppj5lpg4q7vl20usx25ga44')
|
||||
assert.isArray(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user