mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-21 16:52:00 -07:00
fix(offer): Added entity biz logic and unit tests for offer status
This commit is contained in:
@@ -72,6 +72,10 @@ Offer entities have the following properties:
|
||||
- _minUnitsToExchange_ - The minimum order size accepted.
|
||||
- _p2wdbTxid_ - The TXID proof-of-burn used to add the order to the P2WDB.
|
||||
- _p2wdbHash_ - The CID used to identify the order entry in the P2WDB.
|
||||
- _offerStatus_ - The state of the offer. When the data is added to the P2WDB, it gets a value of 'posted', but the database model internal to bch-dex can have the following properties:
|
||||
- *posted* - The offer is posted and can be countered by a taker.
|
||||
- *taken* - The offer was countered and accepted.
|
||||
- *dead* - The UTXO was spent outside the trade, which automatically makes the offer dead.
|
||||
|
||||
- Authentication Data:
|
||||
- _signature_ - A message signed by the address which created the order.
|
||||
|
||||
@@ -14,6 +14,7 @@ const Offer = new mongoose.Schema({
|
||||
minUnitsToExchange: { type: String },
|
||||
p2wdbTxid: { type: String },
|
||||
p2wdbHash: { type: String },
|
||||
offerStatus: { type: String },
|
||||
|
||||
// Authentication data
|
||||
signature: { type: String },
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
REST API Controller library for the /offer route
|
||||
*/
|
||||
|
||||
// const { wlogger } = require('../../../adapters/wlogger')
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
@@ -65,19 +65,15 @@ class OfferRESTControllerLib {
|
||||
// listed in the offer.
|
||||
async takeOffer (ctx) {
|
||||
try {
|
||||
console.log('body: ', ctx.request.body)
|
||||
console.log('REST API controller, body: ', ctx.request.body)
|
||||
|
||||
const offerCid = ctx.request.body.offerCid
|
||||
|
||||
// Find the Offer.
|
||||
// const offerEntity = await _this.useCases.offer.findOffer(offerId)
|
||||
|
||||
// 'Take' the Offer.
|
||||
const hash = await _this.useCases.offer.takeOffer(offerCid)
|
||||
|
||||
ctx.body = { hash }
|
||||
} catch (err) {
|
||||
console.log('Error in takeOffer REST API handler.')
|
||||
wlogger.error('Error in takeOffer() REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
class OfferEntity {
|
||||
constructor () {
|
||||
this.orderStatus = ['posted', 'taken', 'completed']
|
||||
this.orderStatus = ['posted', 'taken', 'dead']
|
||||
}
|
||||
|
||||
validate (orderData = {}) {
|
||||
@@ -48,7 +48,7 @@ class OfferEntity {
|
||||
throw new Error("Property 'utxoVout' must be an integer number.")
|
||||
}
|
||||
if (orderStatus && !this.orderStatus.includes(orderStatus)) {
|
||||
throw new Error("Property 'orderStatus' must be a valid string")
|
||||
throw new Error("Property 'orderStatus' must be posted, taken, or dead")
|
||||
}
|
||||
|
||||
const validatedOfferData = {
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
/*
|
||||
Use Case library for Offers.
|
||||
|
||||
Offers are created by a webhook trigger from the P2WDB. Offers are a result of
|
||||
new data in P2WDB. They differ from Offers, which are generated by a local
|
||||
user.
|
||||
|
||||
An Offer is created to match a local Offer, but it's created indirectly, as
|
||||
a response to the webhook from the P2WDB. In this way, Offers generated from
|
||||
local Offers are no different than Offers generated by other peers.
|
||||
|
||||
A Counter Offer is created by calling the /take/:cid endpoint. It creates
|
||||
a partially signed transaction.
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
@@ -86,8 +91,8 @@ class OfferUseCases {
|
||||
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
||||
|
||||
// Ensure the offer is in a 'posted' state and not already 'taken'
|
||||
if (offerInfo.offerStatus && offerInfo.offerStatus !== 'posted') {
|
||||
throw new Error('offer already taken')
|
||||
if (!offerInfo.offerStatus || offerInfo.offerStatus !== 'posted') {
|
||||
throw new Error('offer status is not "posted", so offer is dead and can not be countered.')
|
||||
}
|
||||
|
||||
// Verify that UTXO for sale is unspent. Abort if it's been spent.
|
||||
|
||||
@@ -27,7 +27,7 @@ class OrderLib {
|
||||
// Create a new order model and add it to the Mongo database.
|
||||
async createOrder (entryObj) {
|
||||
try {
|
||||
// console.log('createOrder(entryObj): ', entryObj)
|
||||
console.log('createOrder(entryObj): ', entryObj)
|
||||
|
||||
// Input Validation
|
||||
const orderEntity = this.orderEntity.validate(entryObj)
|
||||
@@ -47,7 +47,6 @@ class OrderLib {
|
||||
// Update the order with the new UTXO information.
|
||||
orderEntity.utxoTxid = utxoInfo.txid
|
||||
orderEntity.utxoVout = utxoInfo.vout
|
||||
orderEntity.hdIndex = utxoInfo.hdIndex
|
||||
|
||||
// Add order to P2WDB.
|
||||
const p2wdbObj = {
|
||||
@@ -59,6 +58,7 @@ class OrderLib {
|
||||
// console.log('hash: ', hash)
|
||||
|
||||
// Create a MongoDB model to hold the Order
|
||||
orderEntity.hdIndex = utxoInfo.hdIndex
|
||||
orderEntity.p2wdbHash = hash
|
||||
console.log(`creating new order model: ${JSON.stringify(orderEntity, null, 2)}`)
|
||||
const order = new this.OrderModel(orderEntity)
|
||||
@@ -67,7 +67,8 @@ class OrderLib {
|
||||
return hash
|
||||
} catch (err) {
|
||||
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
|
||||
wlogger.error('Error in use-cases/entry.js/createOrder())')
|
||||
wlogger.error('Error in use-cases/createOrder())')
|
||||
console.log('error entryObj: ', entryObj)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Part 2 of 3: Take an order
|
||||
Part 2 of 3: Take an offer by submiting a counter-offer.
|
||||
*/
|
||||
|
||||
const axios = require('axios')
|
||||
@@ -10,9 +10,9 @@ async function start () {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/order/take`,
|
||||
url: `${LOCALHOST}/offer/take`,
|
||||
data: {
|
||||
orderCid: 'zdpuAkp98gTuivaNzGP31jTQi3ADXrFA6uANrceQcrQkTXy2j'
|
||||
offerCid: 'zdpuAppDJR57Hrn1mAhsFRssSBp5qNrDT6PcSeHL5ndQqiqJc'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ describe('#Offer-Entity', () => {
|
||||
it('should throw an error if data is not provided', () => {
|
||||
try {
|
||||
uut.validate()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -38,6 +40,8 @@ describe('#Offer-Entity', () => {
|
||||
try {
|
||||
const orderData = { data: {} }
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -51,6 +55,8 @@ describe('#Offer-Entity', () => {
|
||||
try {
|
||||
const orderData = { data: { messageType: 1 } }
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -64,6 +70,8 @@ describe('#Offer-Entity', () => {
|
||||
try {
|
||||
const orderData = { data: { messageType: 1, messageClass: 1 } }
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'tokenId' must be a string.")
|
||||
@@ -77,6 +85,8 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'buyOrSell' must be a string.")
|
||||
@@ -95,6 +105,8 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -117,6 +129,8 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -139,6 +153,8 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'numTokens' must be a number.")
|
||||
@@ -159,6 +175,8 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'utxoTxid' must be a string.")
|
||||
@@ -180,6 +198,8 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -189,6 +209,34 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if proper status is not applied', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350,
|
||||
numTokens: 1,
|
||||
utxoTxid: 'fakeTxid',
|
||||
utxoVout: 0,
|
||||
orderStatus: 'badStatus'
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
"Property 'orderStatus' must be posted, taken, or dead"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should validate a new order', () => {
|
||||
const orderObj = {
|
||||
appId: 'swapTest555',
|
||||
@@ -203,7 +251,8 @@ describe('#Offer-Entity', () => {
|
||||
numTokens: 0.02,
|
||||
utxoTxid:
|
||||
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
utxoVout: 0
|
||||
utxoVout: 0,
|
||||
orderStatus: 'posted'
|
||||
},
|
||||
timestamp: '2021-09-20T17:54:26.395Z',
|
||||
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
This script has not been customized for offers yet.
|
||||
*/
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
// Force test environment
|
||||
// make sure environment variable is set before this file gets called.
|
||||
// see test script in package.json.
|
||||
// process.env.KOA_ENV = 'test'
|
||||
const config = require('../../config')
|
||||
|
||||
const User = require('../../src/models/users')
|
||||
|
||||
async function deleteUsers () {
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
await mongoose.connect(config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
|
||||
// Get all the users in the DB.
|
||||
const users = await User.find({}, '-password')
|
||||
// console.log(`users: ${JSON.stringify(users, null, 2)}`)
|
||||
|
||||
// Delete each user.
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
const thisUser = users[i]
|
||||
await thisUser.remove()
|
||||
}
|
||||
|
||||
mongoose.connection.close()
|
||||
}
|
||||
|
||||
deleteUsers()
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Get all Offers in the database.
|
||||
*/
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
const Offer = require('../../src/adapters/localdb/models/offer')
|
||||
|
||||
async function getOffers () {
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
await mongoose.connect(config.database, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true
|
||||
})
|
||||
|
||||
const offers = await Offer.find({})
|
||||
console.log(`offers: ${JSON.stringify(offers, null, 2)}`)
|
||||
|
||||
mongoose.connection.close()
|
||||
}
|
||||
getOffers()
|
||||
Reference in New Issue
Block a user