fix(offer): Added entity biz logic and unit tests for offer status

This commit is contained in:
Chris Troutner
2022-03-21 11:39:23 -07:00
parent dcaa73a22f
commit 1563a740aa
10 changed files with 137 additions and 19 deletions
+4
View File
@@ -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.
+1
View File
@@ -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 },
+3 -7
View File
@@ -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)
}
}
+2 -2
View File
@@ -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 = {
+8 -3
View File
@@ -1,12 +1,17 @@
/*
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
const OfferEntity = require('../../entities/offer')
@@ -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.
+4 -3
View File
@@ -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
}
}
+3 -3
View File
@@ -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'
}
}
+50 -1
View File
@@ -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',
+37
View File
@@ -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()
+25
View File
@@ -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()