mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-21 16:52:00 -07:00
Finished implementation
This commit is contained in:
Generated
+139
-26550
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@psf/bch-js": "^4.20.1",
|
"@psf/bch-js": "^4.20.1",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
|
"bch-message-lib": "^1.13.9",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
"ipfs": "^0.54.4",
|
"ipfs": "^0.54.4",
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
This library contains methods for working with the BCHN and BCHA blockchains.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
const BCHJS = require('@psf/bch-js')
|
||||||
|
const MsgLib = require('bch-message-lib')
|
||||||
|
|
||||||
|
class Bch {
|
||||||
|
constructor () {
|
||||||
|
// Encapsulate dependencies
|
||||||
|
this.bchjs = new BCHJS()
|
||||||
|
this.PSF_TOKEN_ID = '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
|
||||||
|
this.msgLib = new MsgLib({ bchjs: this.bchjs })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the entry was signed by a specific BCH address.
|
||||||
|
_verifySignature (verifyObj) {
|
||||||
|
try {
|
||||||
|
// Expand the input object.
|
||||||
|
const { slpAddress, signature, entry } = verifyObj
|
||||||
|
|
||||||
|
// Convert to BCH address.
|
||||||
|
const scrubbedAddr = this.bchjs.SLP.Address.toCashAddress(slpAddress)
|
||||||
|
|
||||||
|
const isValid = this.bchjs.BitcoinCash.verifyMessage(
|
||||||
|
scrubbedAddr,
|
||||||
|
signature,
|
||||||
|
entry
|
||||||
|
)
|
||||||
|
|
||||||
|
return isValid
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in bch.js/_verifySignature()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the total psf token balance
|
||||||
|
async getPSFTokenBalance (slpAddress) {
|
||||||
|
try {
|
||||||
|
if (!slpAddress || typeof slpAddress !== 'string') {
|
||||||
|
throw new Error('slpAddress must be a string')
|
||||||
|
}
|
||||||
|
let psfBalance = 0
|
||||||
|
const balances = await this.bchjs.SLP.Utils.balancesForAddress(slpAddress)
|
||||||
|
|
||||||
|
// Sums all the balances of all tokens
|
||||||
|
// that match the psf token ID
|
||||||
|
for (let i = 0; i < balances.length; i++) {
|
||||||
|
if (balances[i].tokenId === this.PSF_TOKEN_ID) {
|
||||||
|
psfBalance += balances[i].balance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return psfBalance
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in bch.js/getPSFTokenBalance()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMerit (slpAddr) {
|
||||||
|
try {
|
||||||
|
if (!slpAddr || typeof slpAddr !== 'string') {
|
||||||
|
throw new Error('slpAddr must be a string')
|
||||||
|
}
|
||||||
|
const merit = await this.msgLib.merit.agMerit(slpAddr, this.PSF_TOKEN_ID)
|
||||||
|
return merit
|
||||||
|
} catch (error) {
|
||||||
|
console.error('error in bch.js/getMerit()')
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Bch
|
||||||
@@ -12,6 +12,7 @@ const Passport = require('./passport')
|
|||||||
const Nodemailer = require('./nodemailer')
|
const Nodemailer = require('./nodemailer')
|
||||||
const { wlogger } = require('./wlogger')
|
const { wlogger } = require('./wlogger')
|
||||||
const JSONFiles = require('./json-files')
|
const JSONFiles = require('./json-files')
|
||||||
|
const BCHJSAdapter = require('./bch')
|
||||||
|
|
||||||
// Instantiate adapter libraries.
|
// Instantiate adapter libraries.
|
||||||
const ipfs = new IPFSAdapter()
|
const ipfs = new IPFSAdapter()
|
||||||
@@ -20,6 +21,7 @@ const logapi = new LogsAPI()
|
|||||||
const passport = new Passport()
|
const passport = new Passport()
|
||||||
const nodemailer = new Nodemailer()
|
const nodemailer = new Nodemailer()
|
||||||
const jsonFiles = new JSONFiles()
|
const jsonFiles = new JSONFiles()
|
||||||
|
const bchjs = new BCHJSAdapter()
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ipfs,
|
ipfs,
|
||||||
@@ -28,5 +30,6 @@ module.exports = {
|
|||||||
passport,
|
passport,
|
||||||
nodemailer,
|
nodemailer,
|
||||||
wlogger,
|
wlogger,
|
||||||
jsonFiles
|
jsonFiles,
|
||||||
|
bchjs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
// Load Mongoose models.
|
// Load Mongoose models.
|
||||||
const Users = require('./models/users')
|
const Users = require('./models/users')
|
||||||
const Entry = require('./models/entry')
|
const Entries = require('./models/entry')
|
||||||
class LocalDB {
|
class LocalDB {
|
||||||
constructor () {
|
constructor () {
|
||||||
// Encapsulate dependencies
|
// Encapsulate dependencies
|
||||||
|
|||||||
+27
-48
@@ -11,83 +11,62 @@ class EntryLib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Encapsulate dependencies
|
// Encapsulate dependencies
|
||||||
this.EntryModel = this.adapters.localdb.Entries
|
this.EntryModel = this.adapters.localdb.Entry
|
||||||
|
this.bchjs = this.adapters.bchjs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new entry model and add it to the Mongo database.
|
// Create a new entry model and add it to the Mongo database.
|
||||||
async createEntry (entryObj) {
|
async createEntry (entryObj) {
|
||||||
try {
|
try {
|
||||||
const {
|
|
||||||
entry,
|
|
||||||
description,
|
|
||||||
slpAddress,
|
|
||||||
signature,
|
|
||||||
category
|
|
||||||
} = entryObj
|
|
||||||
|
|
||||||
// Input Validation
|
// Input Validation
|
||||||
if (!entry || typeof entry !== 'string') {
|
if (!entryObj.entry || typeof entryObj.entry !== 'string') {
|
||||||
throw new Error("Property 'entry' must be a string!")
|
throw new Error("Property 'entry' must be a string!")
|
||||||
}
|
}
|
||||||
if (!description || typeof description !== 'string') {
|
if (!entryObj.description || typeof entryObj.description !== 'string') {
|
||||||
throw new Error("Property 'description' must be a string!")
|
throw new Error("Property 'description' must be a string!")
|
||||||
}
|
}
|
||||||
if (!slpAddress || typeof slpAddress !== 'string') {
|
if (!entryObj.slpAddress || typeof entryObj.slpAddress !== 'string') {
|
||||||
throw new Error("Property 'slpAddress' must be a string!")
|
throw new Error("Property 'slpAddress' must be a string!")
|
||||||
}
|
}
|
||||||
if (!signature || typeof signature !== 'string') {
|
if (!entryObj.signature || typeof entryObj.signature !== 'string') {
|
||||||
throw new Error("Property 'signature' must be a string!")
|
throw new Error("Property 'signature' must be a string!")
|
||||||
}
|
}
|
||||||
if (!category || typeof category !== 'string') {
|
if (!entryObj.category || typeof entryObj.category !== 'string') {
|
||||||
throw new Error("Property 'category' must be a string!")
|
throw new Error("Property 'category' must be a string!")
|
||||||
}
|
}
|
||||||
|
|
||||||
let psfBalance
|
// Verify that the entry was signed by a specific BCH address.
|
||||||
|
const isValidSignature = this.bchjs._verifySignature(entryObj.slpAddress)
|
||||||
try {
|
if (!isValidSignature) {
|
||||||
// Verify that the entry was signed by a specific BCH address.
|
throw new Error('Invalid signature')
|
||||||
const isValidSignature = _this.bchjs._verifySignature(body)
|
|
||||||
if (!isValidSignature) {
|
|
||||||
throw new Error('Invalid signature')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify psf tokens balance
|
|
||||||
if (_this.env === 'test') {
|
|
||||||
psfBalance = 100
|
|
||||||
} else {
|
|
||||||
psfBalance = 100// await _this.bchjs.getPSFTokenBalance(slpAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (psfBalance < 10) {
|
|
||||||
throw new Error('Insufficient psf balance')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
ctx.throw(406, err.message)
|
|
||||||
}
|
}
|
||||||
let merit
|
|
||||||
if (_this.env === 'test') {
|
// Verify psf tokens balance
|
||||||
merit = 100
|
|
||||||
} else {
|
const psfBalance = await this.bchjs.getPSFTokenBalance(entryObj.slpAddress)
|
||||||
merit = 100// await _this.bchjs.getMerit(slpAddress)
|
|
||||||
|
if (psfBalance < 10) {
|
||||||
|
throw new Error('Insufficient psf balance')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const merit = await this.bchjs.getMerit(entryObj.slpAddress)
|
||||||
|
|
||||||
const updatedEntry = {
|
const updatedEntry = {
|
||||||
entry: entry.trim(),
|
entry: entryObj.entry.trim(),
|
||||||
slpAddress: slpAddress.trim(),
|
slpAddress: entryObj.slpAddress.trim(),
|
||||||
description: description.trim(),
|
description: entryObj.description.trim(),
|
||||||
signature: signature.trim(),
|
signature: entryObj.signature.trim(),
|
||||||
category: category.trim(),
|
category: entryObj.category.trim(),
|
||||||
balance: psfBalance,
|
balance: psfBalance,
|
||||||
merit
|
merit
|
||||||
}
|
}
|
||||||
|
|
||||||
const entryModel = new this.EntryModel(updatedEntry)
|
const entryModel = new this.EntryModel(updatedEntry)
|
||||||
|
|
||||||
await entryModel.save()
|
await entryModel.save()
|
||||||
|
|
||||||
return { id: entry._id.toString() }
|
return entryModel
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log('createUser() error: ', err)
|
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
|
||||||
wlogger.error('Error in use-cases/entry.js/createEntry()')
|
wlogger.error('Error in use-cases/entry.js/createEntry()')
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
const assert = require('chai').assert
|
||||||
|
|
||||||
|
const BCHJS = require('../../../src/adapters/bch')
|
||||||
|
|
||||||
|
const sinon = require('sinon')
|
||||||
|
|
||||||
|
const util = require('util')
|
||||||
|
util.inspect.defaultOptions = { depth: 1 }
|
||||||
|
|
||||||
|
const mockData = require('../mocks/bchjs-mock')
|
||||||
|
|
||||||
|
let sandbox
|
||||||
|
let uut
|
||||||
|
describe('bch', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
uut = new BCHJS()
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#_verifySignature', () => {
|
||||||
|
it('should return true for valid signature', () => {
|
||||||
|
const slpAddress = 'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||||
|
const signature =
|
||||||
|
'H1Bv2xUBGZBTuNsUghix03Yp8n8YPPkfsPq6LktwDpC2e1estOfYx96NH3/eaHJpQpPSHSb6pQYaiR3KZ6Z9lRc='
|
||||||
|
const entry = 'example.com'
|
||||||
|
const verifyObj = { slpAddress, signature, entry }
|
||||||
|
|
||||||
|
const result = uut._verifySignature(verifyObj)
|
||||||
|
|
||||||
|
assert.equal(result, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false for invalid signature', () => {
|
||||||
|
const slpAddress = 'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||||
|
const signature =
|
||||||
|
'ICcj+ShSRIllp0iTqQK49Ltnycg1upaT7dK5CPAwNIBqEtegn305dPBf5IMdx/ScuyOBWPEfOqab2V73TbuK6Us='
|
||||||
|
const entry = 'example.com'
|
||||||
|
|
||||||
|
const verifyObj = { slpAddress, signature, entry }
|
||||||
|
|
||||||
|
const result = uut._verifySignature(verifyObj)
|
||||||
|
|
||||||
|
assert.equal(result, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw errors', () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox
|
||||||
|
.stub(uut.bchjs.BitcoinCash, 'verifyMessage')
|
||||||
|
.throws(new Error('test error'))
|
||||||
|
|
||||||
|
const slpAddress = 'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||||
|
const signature =
|
||||||
|
'ICcj+ShSRIllp0iTqQK49Ltnycg1upaT7dK5CPAwNIBqEtegn305dPBf5IMdx/ScuyOBWPEfOqab2V73TbuK6Us='
|
||||||
|
const entry = 'example.com'
|
||||||
|
const verifyObj = { slpAddress, signature, entry }
|
||||||
|
uut._verifySignature(verifyObj)
|
||||||
|
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getPSFTokenBalance', () => {
|
||||||
|
it('should throw error if slpAddress is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.getPSFTokenBalance()
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'slpAddress must be a string')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return psf tokens balance', async () => {
|
||||||
|
// Mock live network calls.
|
||||||
|
sandbox
|
||||||
|
.stub(uut.bchjs.SLP.Utils, 'balancesForAddress')
|
||||||
|
.resolves(mockData.psfBalances)
|
||||||
|
|
||||||
|
const slpAddress = 'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||||
|
const result = await uut.getPSFTokenBalance(slpAddress)
|
||||||
|
|
||||||
|
assert.isNumber(result)
|
||||||
|
assert.equal(result, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 0 if the slp address does not has Psf tokens', async () => {
|
||||||
|
// Mock live network calls.
|
||||||
|
sandbox
|
||||||
|
.stub(uut.bchjs.SLP.Utils, 'balancesForAddress')
|
||||||
|
.resolves(mockData.noPsfBalances)
|
||||||
|
|
||||||
|
const slpAddress = 'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||||
|
const result = await uut.getPSFTokenBalance(slpAddress)
|
||||||
|
|
||||||
|
assert.isNumber(result)
|
||||||
|
assert.equal(result, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 0 for empty balances', async () => {
|
||||||
|
// Mock live network calls.
|
||||||
|
sandbox
|
||||||
|
.stub(uut.bchjs.SLP.Utils, 'balancesForAddress')
|
||||||
|
.resolves([])
|
||||||
|
|
||||||
|
const slpAddress = 'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||||
|
const result = await uut.getPSFTokenBalance(slpAddress)
|
||||||
|
|
||||||
|
assert.isNumber(result)
|
||||||
|
assert.equal(result, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#getPSFTokenBalance', () => {
|
||||||
|
it('should throw error if slpAddr is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.getMerit()
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'slpAddr must be a string')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw error if slpAddr provided is invalid type', async () => {
|
||||||
|
try {
|
||||||
|
await uut.getMerit(1)
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'slpAddr must be a string')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should return the merit ', async () => {
|
||||||
|
try {
|
||||||
|
// Mock live network calls.
|
||||||
|
sandbox
|
||||||
|
.stub(uut.msgLib.merit, 'agMerit')
|
||||||
|
.resolves(100)
|
||||||
|
|
||||||
|
const slpAddr = 'simpleledger:qqgnksc6zr4nzxrye69fq625wu2myxey6uh9kzjy96'
|
||||||
|
const merit = await uut.getMerit(slpAddr)
|
||||||
|
assert.isNumber(merit)
|
||||||
|
} catch (err) {
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the User entity library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const assert = require('chai').assert
|
||||||
|
const sinon = require('sinon')
|
||||||
|
|
||||||
|
const Entry = require('../../../src/entities/entry')
|
||||||
|
|
||||||
|
let sandbox
|
||||||
|
let uut
|
||||||
|
|
||||||
|
describe('#User-Entity', () => {
|
||||||
|
before(async () => { })
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
uut = new Entry()
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#validate', () => {
|
||||||
|
it('should throw an error if entry is not provided', () => {
|
||||||
|
try {
|
||||||
|
uut.validate()
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'entry' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if description is not provided', () => {
|
||||||
|
try {
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry'
|
||||||
|
}
|
||||||
|
uut.validate(inputData)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'description' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if slpAddress is not provided', () => {
|
||||||
|
try {
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test'
|
||||||
|
}
|
||||||
|
uut.validate(inputData)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'slpAddress' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if signature is not provided', () => {
|
||||||
|
try {
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test',
|
||||||
|
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg'
|
||||||
|
}
|
||||||
|
uut.validate(inputData)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'signature' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if category is not provided', () => {
|
||||||
|
try {
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test',
|
||||||
|
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||||
|
signature: 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw='
|
||||||
|
}
|
||||||
|
uut.validate(inputData)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'category' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return a Entry object', () => {
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test',
|
||||||
|
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||||
|
signature: 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||||
|
category: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = uut.validate(inputData)
|
||||||
|
// console.log('entry: ', entry)
|
||||||
|
|
||||||
|
assert.property(entry, 'entry')
|
||||||
|
assert.equal(entry.entry, inputData.entry)
|
||||||
|
|
||||||
|
assert.property(entry, 'description')
|
||||||
|
assert.equal(entry.description, inputData.description)
|
||||||
|
|
||||||
|
assert.property(entry, 'slpAddress')
|
||||||
|
assert.equal(entry.slpAddress, inputData.slpAddress)
|
||||||
|
|
||||||
|
assert.property(entry, 'signature')
|
||||||
|
assert.equal(entry.signature, inputData.signature)
|
||||||
|
|
||||||
|
assert.property(entry, 'slpAddress')
|
||||||
|
assert.equal(entry.category, inputData.category)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Mocks for the Adapter library.
|
Mocks for the Adapter library.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const ipfs = {
|
const ipfs = {
|
||||||
ipfsAdapter: {
|
ipfsAdapter: {
|
||||||
ipfs: {}
|
ipfs: {}
|
||||||
@@ -13,38 +12,67 @@ const ipfs = {
|
|||||||
|
|
||||||
const localdb = {
|
const localdb = {
|
||||||
Users: class Users {
|
Users: class Users {
|
||||||
static findById () {}
|
static findById() { }
|
||||||
static find () {}
|
static find() { }
|
||||||
static findOne () {
|
static findOne() {
|
||||||
return {
|
return {
|
||||||
validatePassword: localdb.validatePassword
|
validatePassword: localdb.validatePassword
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async save () {
|
async save() {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
generateToken () {
|
generateToken() {
|
||||||
return '123'
|
return '123'
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON () {
|
toJSON() {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove () {
|
async remove() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async validatePassword () {
|
async validatePassword() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
validatePassword: () => {
|
validatePassword: () => {
|
||||||
return true
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
Entry: class Entry {
|
||||||
|
constructor(obj){
|
||||||
|
this._id = 'id',
|
||||||
|
this.entry = obj.entry
|
||||||
|
this.slpAddress = obj.slpAddress
|
||||||
|
this.description = obj.description
|
||||||
|
this.signature = obj.signature
|
||||||
|
this.category = obj.category
|
||||||
|
this.balance = obj.balance
|
||||||
|
this.merit = obj.merit
|
||||||
|
}
|
||||||
|
|
||||||
|
static findById() { }
|
||||||
|
static find() { }
|
||||||
|
static findOne() {}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { ipfs, localdb }
|
const bchjs = {
|
||||||
|
getMerit: async () => { return 100 },
|
||||||
|
getPSFTokenBalance: async () => { return 100 },
|
||||||
|
_verifySignature: () => { return true }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { ipfs, localdb, bchjs }
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Mocks for bch unit test
|
||||||
|
|
||||||
|
// Tokens array containing some psf tokens
|
||||||
|
const psfBalances = [
|
||||||
|
{
|
||||||
|
tokenId:
|
||||||
|
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||||
|
balance: 1,
|
||||||
|
slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
||||||
|
decimalCount: 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenId:
|
||||||
|
'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
||||||
|
balance: 617,
|
||||||
|
slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
||||||
|
decimalCount: 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenId:
|
||||||
|
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||||
|
balance: 1,
|
||||||
|
slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
||||||
|
decimalCount: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenId:
|
||||||
|
'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
|
||||||
|
balance: 776,
|
||||||
|
slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
||||||
|
decimalCount: 7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Token array without psf tokens
|
||||||
|
const noPsfBalances = [
|
||||||
|
{
|
||||||
|
tokenId:
|
||||||
|
'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
||||||
|
balance: 617,
|
||||||
|
slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
||||||
|
decimalCount: 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenId:
|
||||||
|
'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
|
||||||
|
balance: 776,
|
||||||
|
slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
||||||
|
decimalCount: 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tokenId:
|
||||||
|
'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
||||||
|
balance: 617,
|
||||||
|
slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk',
|
||||||
|
decimalCount: 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
module.exports = {
|
||||||
|
psfBalances,
|
||||||
|
noPsfBalances
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// Public npm libraries
|
||||||
|
const assert = require('chai').assert
|
||||||
|
const sinon = require('sinon')
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
// const testUtils = require('../../utils/test-utils')
|
||||||
|
|
||||||
|
// Unit under test (uut)
|
||||||
|
const EntryLib = require('../../../src/use-cases/entry')
|
||||||
|
const adapters = require('../mocks/adapters')
|
||||||
|
|
||||||
|
describe('#users-use-case', () => {
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
// Delete all previous users in the database.
|
||||||
|
// await testUtils.deleteAllUsers()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
uut = new EntryLib({ adapters })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new EntryLib()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of adapters must be passed in when instantiating User Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#createEntry', () => {
|
||||||
|
it('should throw an error if entry is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.createEntry({})
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'entry' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if description is not provided', async () => {
|
||||||
|
try {
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry'
|
||||||
|
}
|
||||||
|
await uut.createEntry(inputData)
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'description' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if slpAddress is not provided', async () => {
|
||||||
|
try {
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test'
|
||||||
|
}
|
||||||
|
await uut.createEntry(inputData)
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'slpAddress' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if signature is not provided', async () => {
|
||||||
|
try {
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test',
|
||||||
|
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg'
|
||||||
|
}
|
||||||
|
await uut.createEntry(inputData)
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'signature' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if category is not provided', async () => {
|
||||||
|
try {
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test',
|
||||||
|
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||||
|
signature: 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw='
|
||||||
|
}
|
||||||
|
await uut.createEntry(inputData)
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'category' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should catch and throw DB errors', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error with the database.
|
||||||
|
sandbox.stub(uut, 'EntryModel').throws(new Error('test error'))
|
||||||
|
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test',
|
||||||
|
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||||
|
signature: 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||||
|
category: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
await uut.createEntry(inputData)
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw error if signature is invalid', async () => {
|
||||||
|
try {
|
||||||
|
// Mocking bchjs functions
|
||||||
|
sandbox.stub(uut.bchjs, '_verifySignature').callsFake(() => { return false })
|
||||||
|
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test',
|
||||||
|
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||||
|
signature: 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||||
|
category: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
await uut.createEntry(inputData)
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Invalid signature')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw error for insufficient psf balance', async () => {
|
||||||
|
try {
|
||||||
|
// Mocking bchjs functions
|
||||||
|
sandbox.stub(uut.bchjs, 'getPSFTokenBalance').resolves(0)
|
||||||
|
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test',
|
||||||
|
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||||
|
signature: 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||||
|
category: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
await uut.createEntry(inputData)
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Insufficient psf balance')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a new entry in the DB', async () => {
|
||||||
|
// Mocking bchjs functions
|
||||||
|
// sandbox.stub(uut.bchjs, '_verifySignature').resolves(true)
|
||||||
|
// sandbox.stub(uut.bchjs, 'getPSFTokenBalance').resolves(100)
|
||||||
|
// sandbox.stub(uut.bchjs, 'getMerit').resolves(100)
|
||||||
|
|
||||||
|
const inputData = {
|
||||||
|
entry: 'entry',
|
||||||
|
description: 'test',
|
||||||
|
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||||
|
signature: 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||||
|
category: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await uut.createEntry(inputData)
|
||||||
|
|
||||||
|
assert.property(result, 'entry')
|
||||||
|
assert.isString(result.entry)
|
||||||
|
|
||||||
|
assert.property(result, 'slpAddress')
|
||||||
|
assert.isString(result.slpAddress)
|
||||||
|
|
||||||
|
assert.property(result, 'description')
|
||||||
|
assert.isString(result.description)
|
||||||
|
|
||||||
|
assert.property(result, 'signature')
|
||||||
|
assert.isString(result.signature)
|
||||||
|
|
||||||
|
assert.property(result, 'category')
|
||||||
|
assert.isString(result.category)
|
||||||
|
|
||||||
|
assert.property(result, 'balance')
|
||||||
|
assert.isNumber(result.balance)
|
||||||
|
|
||||||
|
assert.property(result, 'merit')
|
||||||
|
assert.isNumber(result.merit)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user