Finished implementation

This commit is contained in:
Daniel Gonzalez
2021-07-31 13:33:23 -04:00
parent 8307c69030
commit ddb4316f13
11 changed files with 815 additions and 26610 deletions
+139 -26550
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -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",
+77
View File
@@ -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
+4 -1
View File
@@ -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
} }
+1 -1
View File
@@ -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
View File
@@ -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
} }
+151
View File
@@ -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')
}
})
})
})
+110
View File
@@ -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)
})
})
})
+38 -10
View File
@@ -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 }
+62
View File
@@ -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
}
+205
View File
@@ -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)
})
})
})