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 | |
|---|---|---|---|
|
|
0057007b4c | ||
|
|
8aad93e572 | ||
|
|
fa0ec3ece2 | ||
|
|
4f9f2f8370 | ||
|
|
0fb93be2cd | ||
|
|
26dcd6072e | ||
|
|
8be70baff2 | ||
|
|
7e5c314000 | ||
|
|
9669112d69 | ||
|
|
9a71763f79 | ||
|
|
1a96eac8c9 | ||
|
|
a2acdef539 | ||
|
|
58ddbbcc4e | ||
|
|
5ff33ff19f | ||
|
|
682f599261 | ||
|
|
7c26e33a8c | ||
|
|
2595ee8295 | ||
|
|
531685c0e4 | ||
|
|
476fdcd0aa | ||
|
|
b6762b215e | ||
|
|
3cdf49bb56 | ||
|
|
28615403b8 | ||
|
|
bb10bd819a | ||
|
|
3882217e6a | ||
|
|
8cd2896115 | ||
|
|
e570519ec8 | ||
|
|
2f74b74110 | ||
|
|
fddb5499f0 | ||
|
|
fed98d51be | ||
|
|
8e3ee3a0fb | ||
|
|
e57faf8f26 | ||
|
|
61fa6b0ed6 | ||
|
|
99843061ab | ||
|
|
3a23763800 | ||
|
|
3f4d49a228 | ||
|
|
88cfadbc17 | ||
|
|
6c1da41cb3 | ||
|
|
0e6a411012 | ||
|
|
55e9e35c1f | ||
|
|
84603356ef | ||
|
|
2f6b4dfc09 | ||
|
|
b19af253ae | ||
|
|
b6c6c186a8 | ||
|
|
ae6e708f51 | ||
|
|
442f4ed752 | ||
|
|
8617cf5fd0 | ||
|
|
2e00230f79 | ||
|
|
44dcce6c45 | ||
|
|
8d43192a1c | ||
|
|
59e2bb473b | ||
|
|
b4d2506e81 | ||
|
|
2af50ecf02 | ||
|
|
f88a24c1b5 | ||
|
|
a4b4ff13d2 | ||
|
|
2bf4f9df28 | ||
|
|
19ba38e8ce | ||
|
|
373a52ebf1 | ||
|
|
fe82478dc4 | ||
|
|
bed281c8c3 | ||
|
|
01484b4e03 | ||
|
|
c86512254d | ||
|
|
7df9b9b8a4 | ||
|
|
5f91395766 | ||
|
|
dda6cb2aa4 | ||
|
|
578aade39b | ||
|
|
b09890a527 | ||
|
|
a32bd840b9 | ||
|
|
0bdc232938 | ||
|
|
f543cfce73 | ||
|
|
0f5e694682 | ||
|
|
615638d705 | ||
|
|
9b46bbd7bb | ||
|
|
1b58bda5a7 | ||
|
|
c1f15a4079 | ||
|
|
18ea03a4d6 | ||
|
|
3d911d1062 | ||
|
|
cf88fadfdc | ||
|
|
4977ae7003 | ||
|
|
f0f6970a08 | ||
|
|
fcb4acb11d | ||
|
|
28e012feb6 | ||
|
|
958c637c24 | ||
|
|
34381e6e93 | ||
|
|
8843b3e2b0 | ||
|
|
3f91653d23 |
Vendored
+7
-1
@@ -48,6 +48,7 @@ export default {
|
||||
consumerUrl: process.env.CONSUMER_URL
|
||||
? process.env.CONSUMER_URL
|
||||
: 'https://free-bch.fullstack.cash',
|
||||
// : 'https://dev-consumer.psfoundation.info',
|
||||
// : 'https://wa-usa-bch-consumer.fullstackcash.nl',
|
||||
|
||||
// P2WDB URL that will accept API calls from the p2wdb npm library.
|
||||
@@ -184,10 +185,15 @@ export default {
|
||||
// Nostr
|
||||
nostrRelay: process.env.NOSTR_RELAY ? process.env.NOSTR_RELAY : 'wss://nostr-relay.psfoundation.info',
|
||||
nostrTopic: process.env.NOSTR_TOPIC ? process.env.NOSTR_TOPIC : 'bch-dex-test-topic-02',
|
||||
nostrGlobalFeed: process.env.NOSTR_GLOBAL_FEED ? process.env.NOSTR_GLOBAL_FEED : 'slpdex-socialmedia',
|
||||
|
||||
// Account Configuration
|
||||
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
|
||||
|
||||
// Admin password
|
||||
adminPassword: process.env.ADMIN_PASSWORD
|
||||
adminPassword: process.env.ADMIN_PASSWORD,
|
||||
|
||||
// The Operator of BCH DEX can elect to receive a percentage of each sale.
|
||||
operatorAddress: process.env.OPERATOR_ADDRESS ? process.env.OPERATOR_ADDRESS : 'bitcoincash:qqsrke9lh257tqen99dkyy2emh4uty0vky9y0z0lsr',
|
||||
operatorPercentage: process.env.OPERATOR_PERCENTAGE ? parseFloat(process.env.OPERATOR_PERCENTAGE) : 2.0
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -10,6 +10,7 @@
|
||||
"test:all": "export BCH_DEX=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:unit": "export BCH_DEX=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:e2e:auto": "export BCH_DEX=test && mocha --exit --timeout 30000 test/e2e/automated/",
|
||||
"start:e2e:server": "export BCH_DEX=test && node index.js",
|
||||
"test:integration": "export BCH_DEX=test && mocha --exit --timeout 45000 --recursive test/integration",
|
||||
"test:temp": "export BCH_DEX=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
|
||||
"lint": "standard --env mocha --fix",
|
||||
|
||||
@@ -66,6 +66,9 @@ class Bch {
|
||||
// }
|
||||
// }
|
||||
|
||||
/** DEV NOTE: This function are deprecated.?
|
||||
* https://github.com/Permissionless-Software-Foundation/bch-message-lib/pull/41
|
||||
*/
|
||||
async getMerit (slpAddr) {
|
||||
try {
|
||||
if (!slpAddr || typeof slpAddr !== 'string') {
|
||||
|
||||
@@ -7,6 +7,10 @@ import Users from './models/users.js'
|
||||
import Entries from './models/entry.js'
|
||||
import Order from './models/order.js'
|
||||
import Offer from './models/offer.js'
|
||||
import Usage from './models/usage.js'
|
||||
import SmAccount from './models/smAccount.js'
|
||||
import DeletedChat from './models/deletedChat.js'
|
||||
import DeletedPost from './models/deletedPost.js'
|
||||
|
||||
class LocalDB {
|
||||
constructor () {
|
||||
@@ -15,6 +19,10 @@ class LocalDB {
|
||||
this.Entry = Entries
|
||||
this.Order = Order
|
||||
this.Offer = Offer
|
||||
this.Usage = Usage
|
||||
this.SmAccount = SmAccount
|
||||
this.DeletedChat = DeletedChat
|
||||
this.DeletedPost = DeletedPost
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Database model for tracking deleted Nostr chat messages.
|
||||
*/
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
const DeletedChat = new mongoose.Schema({
|
||||
eventId: { type: String, required: true, default: null }, // Event ID of the deleted message.
|
||||
npub: { type: String, default: null }, // Nostr public key of the user who created the message.
|
||||
bchAddr: { type: String, default: null }, // bch address of the user who created the message.
|
||||
pubkey: { type: String, default: null }, // Nostr public key of the user who created the message.
|
||||
createdAt: { type: Date, default: Date.now },
|
||||
updatedAt: { type: Date, default: Date.now }
|
||||
})
|
||||
|
||||
export default mongoose.model('deletedChat', DeletedChat)
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Database model for tracking deleted Nostr posts.
|
||||
*/
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
const DeletedPost = new mongoose.Schema({
|
||||
eventId: { type: String, required: true, default: null }, // Event ID of the deleted post.
|
||||
npub: { type: String, default: null }, // Nostr public key of the user who created the post.
|
||||
bchAddr: { type: String, default: null }, // bch address of the user who created the post.
|
||||
pubkey: { type: String, default: null }, // Nostr public key of the user who created the post.
|
||||
createdAt: { type: Date, default: Date.now },
|
||||
updatedAt: { type: Date, default: Date.now }
|
||||
})
|
||||
|
||||
export default mongoose.model('deletedPost', DeletedPost)
|
||||
@@ -18,6 +18,7 @@ const Offer = new mongoose.Schema({
|
||||
p2wdbHash: { type: String },
|
||||
offerStatus: { type: String },
|
||||
makerAddr: { type: String },
|
||||
makerNpub: { type: String, default: null },
|
||||
|
||||
// Authentication data
|
||||
signature: { type: String },
|
||||
@@ -35,8 +36,20 @@ const Offer = new mongoose.Schema({
|
||||
lokadId: { type: String },
|
||||
messageType: { type: Number },
|
||||
messageClass: { type: Number },
|
||||
nostrEventId: { type: String } // Nostr Event Id.
|
||||
nostrEventId: { type: String }, // Nostr Event Id.
|
||||
|
||||
// Operator fee and address
|
||||
operatorAddress: { type: String },
|
||||
operatorPercentage: { type: Number },
|
||||
|
||||
// Token Data
|
||||
immutableDataCid: { type: String, default: null },
|
||||
mutableDataCid: { type: String, default: null },
|
||||
tokenIconUrl: { type: String, default: null },
|
||||
tokenCategories: { type: Array, default: [] },
|
||||
tokenTags: { type: Array, default: [] },
|
||||
userDataStr: { type: String }, // Token user data
|
||||
lastUpdatedTokenData: { type: String, default: null } // ISO timestamp of the last time token data was updated.
|
||||
})
|
||||
|
||||
export default mongoose.model('offer', Offer)
|
||||
|
||||
@@ -24,6 +24,7 @@ const Order = new mongoose.Schema({
|
||||
p2wdbTxid: { type: String },
|
||||
p2wdbHash: { type: String },
|
||||
makerAddr: { type: String, required: true },
|
||||
makerNpub: { type: String, required: true },
|
||||
|
||||
// Authentication data
|
||||
signature: { type: String },
|
||||
@@ -43,7 +44,11 @@ const Order = new mongoose.Schema({
|
||||
// Additional properties found in createOrder
|
||||
dataType: { type: String, required: true },
|
||||
|
||||
userId: { type: String, required: true }
|
||||
userId: { type: String, required: true },
|
||||
|
||||
// Operator fee and address
|
||||
operatorAddress: { type: String },
|
||||
operatorPercentage: { type: Number }
|
||||
})
|
||||
|
||||
export default mongoose.model('order', Order)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Social Media Account Model.
|
||||
|
||||
This model is used to track content creators using the DEX
|
||||
Nostr social media channel.
|
||||
*/
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
const SmAccount = new mongoose.Schema({
|
||||
npub: { type: String, required: true },
|
||||
bchAddr: { type: String, required: true, default: '' },
|
||||
pubkey: { type: String, required: true },
|
||||
pfp: { type: String, default: '' },
|
||||
createdAt: { type: Date, default: Date.now },
|
||||
updatedAt: { type: Date, default: Date.now },
|
||||
followerCnt: { type: Number, default: 0 },
|
||||
followingCnt: { type: Number, default: 0 },
|
||||
posts: { type: Number, default: 0 },
|
||||
likes: { type: Number, default: 0 },
|
||||
comments: { type: Number, default: 0 },
|
||||
reposts: { type: Number, default: 0 }
|
||||
})
|
||||
|
||||
export default mongoose.model('smAccount', SmAccount)
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
Usage data model
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
const Usage = new mongoose.Schema({
|
||||
ip: { type: String },
|
||||
url: { type: String },
|
||||
method: { type: String },
|
||||
timestamp: { type: Date }
|
||||
})
|
||||
|
||||
export default mongoose.model('usage', Usage)
|
||||
@@ -7,11 +7,14 @@ import BchNostr from 'bch-nostr'
|
||||
import { RelayPool } from 'nostr'
|
||||
import RetryQueue from '@chris.troutner/retry-queue'
|
||||
import * as nip19 from 'nostr-tools/nip19'
|
||||
import { base58_to_binary as base58Tobinary } from 'base58-js'
|
||||
import { getPublicKey } from 'nostr-tools/pure'
|
||||
|
||||
class NostrAdapter {
|
||||
constructor (localConfig = { nostrRelay: '', nostrTopic: '' }) {
|
||||
this.relayWs = localConfig.nostrRelay //
|
||||
this.topic = localConfig.nostrTopic
|
||||
this.globalFeed = localConfig.nostrGlobalFeed
|
||||
|
||||
if (!this.relayWs) {
|
||||
throw new Error(
|
||||
@@ -39,6 +42,11 @@ class NostrAdapter {
|
||||
this.post = this.post.bind(this)
|
||||
this.read = this.read.bind(this)
|
||||
this.eventId2note = this.eventId2note.bind(this)
|
||||
this.pubkey2npub = this.pubkey2npub.bind(this)
|
||||
this.npub2pubkey = this.npub2pubkey.bind(this)
|
||||
this.readGlobalFeed = this.readGlobalFeed.bind(this)
|
||||
this.getFollowers = this.getFollowers.bind(this)
|
||||
this.privKeyToNpub = this.privKeyToNpub.bind(this)
|
||||
}
|
||||
|
||||
// Create nostr keys.
|
||||
@@ -131,6 +139,142 @@ class NostrAdapter {
|
||||
eventId2note (eventId) {
|
||||
return nip19.noteEncode(eventId)
|
||||
}
|
||||
|
||||
// Convert a pubkey into a `npubabc..` syntax that Astral expects.
|
||||
pubkey2npub (pubkey) {
|
||||
return nip19.npubEncode(pubkey)
|
||||
}
|
||||
|
||||
npub2pubkey (npub) {
|
||||
return nip19.decode(npub)
|
||||
}
|
||||
|
||||
// Read the global feed.
|
||||
async readGlobalFeed (inObj = {}) {
|
||||
try {
|
||||
const { limit = 10 } = inObj
|
||||
|
||||
if (typeof limit !== 'number' || limit < 1) {
|
||||
throw new Error('Limit must be greater than 0')
|
||||
}
|
||||
|
||||
const relays = [this.relayWs]
|
||||
const pool = this.RelayPool(relays)
|
||||
|
||||
const nostrData = new Promise((resolve, reject) => {
|
||||
const messages = []
|
||||
|
||||
pool.on('open', (relay) => {
|
||||
// relay.subscribe('REQ', { ids: [eventId] })
|
||||
relay.subscribe('REQ', { limit, kinds: [1], '#t': [this.globalFeed] })
|
||||
})
|
||||
|
||||
pool.on('eose', (relay) => {
|
||||
relay.close()
|
||||
resolve(messages)
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
// console.log('ev: ', ev)
|
||||
|
||||
const { content, id, tags, pubkey } = ev
|
||||
|
||||
const msg = {
|
||||
content,
|
||||
id,
|
||||
tags,
|
||||
npub: this.pubkey2npub(pubkey),
|
||||
pubkey
|
||||
}
|
||||
|
||||
messages.push(msg)
|
||||
})
|
||||
})
|
||||
|
||||
const messages = await nostrData
|
||||
|
||||
return messages
|
||||
} catch (error) {
|
||||
console.log(`Error in nostr.js/readGlobalFeed() ${error.message} `)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async getFollowers (inObj = {}) {
|
||||
try {
|
||||
const { pubkey } = inObj
|
||||
|
||||
if (!pubkey || typeof pubkey !== 'string') {
|
||||
throw new Error('pubkey must be a string!')
|
||||
}
|
||||
|
||||
const relays = [this.relayWs]
|
||||
const pool = this.RelayPool(relays)
|
||||
|
||||
const nostrData = new Promise((resolve, reject) => {
|
||||
const followers = []
|
||||
|
||||
pool.on('open', (relay) => {
|
||||
// Query for kind 3 events where the target pubkey appears in tags
|
||||
// This finds all contact lists that include the target pubkey
|
||||
relay.subscribe('subid', {
|
||||
limit: 100,
|
||||
kinds: [3],
|
||||
'#p': [pubkey] // Filter by tag 'p' containing the target pubkey
|
||||
})
|
||||
})
|
||||
|
||||
pool.on('eose', relay => {
|
||||
relay.close()
|
||||
resolve(followers)
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
// Each event represents a contact list from someone who follows the target
|
||||
// The author of this event is a follower
|
||||
const follower = {
|
||||
pubkey: ev.pubkey,
|
||||
npub: this.pubkey2npub(ev.pubkey),
|
||||
contactList: ev.tags,
|
||||
createdAt: ev.created_at
|
||||
}
|
||||
|
||||
followers.push(follower)
|
||||
})
|
||||
})
|
||||
|
||||
const followers = await nostrData
|
||||
return followers
|
||||
} catch (error) {
|
||||
console.log(`Error in nostr.js/getFollowers() ${error.message} `)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a BCH private key (WIF) to a Nostr npub.
|
||||
privKeyToNpub (privKey) {
|
||||
try {
|
||||
if (!privKey || typeof privKey !== 'string') {
|
||||
throw new Error('privKey must be a string!')
|
||||
}
|
||||
// Extract the privaty key from the WIF, using this guide:
|
||||
// https://learnmeabitcoin.com/technical/keys/private-key/wif/
|
||||
const wifBuf = base58Tobinary(privKey)
|
||||
const privBuf = wifBuf.slice(1, 33)
|
||||
// console.log('privBuf: ', privBuf)
|
||||
|
||||
const nostrPubKey = getPublicKey(privBuf)
|
||||
// console.log('nostrPubKey: ', nostrPubKey)
|
||||
// Convert pubkey to npub
|
||||
const nostrNpub = nip19.npubEncode(nostrPubKey)
|
||||
// console.log('nostrNpub: ', nostrNpub)
|
||||
|
||||
return nostrNpub
|
||||
} catch (error) {
|
||||
console.log(`Error in nostr.js/privKeyToNpub() ${error.message} `)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NostrAdapter
|
||||
|
||||
+48
-3
@@ -34,6 +34,7 @@ class WalletAdapter {
|
||||
this.config = config
|
||||
this.bitcoinJs = bitcoinJs
|
||||
this.BchWallet = BchWallet
|
||||
this.BchTokenSweep = BchTokenSweep
|
||||
this.bchWallet = {} // Will be replaced when initialized.
|
||||
// this.advancedConfig = localConfig.advancedConfig
|
||||
this.retryQueue = new RetryQueue({
|
||||
@@ -55,6 +56,7 @@ class WalletAdapter {
|
||||
this.completeTx = this.completeTx.bind(this)
|
||||
this.reclaimTokens = this.reclaimTokens.bind(this)
|
||||
this.moveTokensFromCustomWallet = this.moveTokensFromCustomWallet.bind(this)
|
||||
this.cid2json = this.cid2json.bind(this)
|
||||
}
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
@@ -205,7 +207,9 @@ class WalletAdapter {
|
||||
// Generate a cryptographic signature, required to write to the P2WDB.
|
||||
async generateSignature (message) {
|
||||
try {
|
||||
// TODO: Add input validation for message.
|
||||
if (!message || typeof message !== 'string') {
|
||||
throw new Error('message is required!')
|
||||
}
|
||||
|
||||
const privKey = this.bchWallet.walletInfo.privateKey
|
||||
|
||||
@@ -370,6 +374,12 @@ class WalletAdapter {
|
||||
async moveTokens (inObj = {}) {
|
||||
try {
|
||||
const { tokenId, qty } = inObj
|
||||
if (!tokenId || typeof tokenId !== 'string') {
|
||||
throw new Error('tokenId must be a string!')
|
||||
}
|
||||
if (!qty || typeof qty !== 'number') {
|
||||
throw new Error('qty must be a number!')
|
||||
}
|
||||
|
||||
const keyPair = await this.getKeyPair()
|
||||
console.log('keyPair: ', keyPair)
|
||||
@@ -392,6 +402,10 @@ class WalletAdapter {
|
||||
x => x.tokenId === tokenId
|
||||
)
|
||||
|
||||
if (tokenUtxos.length === 0) {
|
||||
throw new Error('tokenId not found!')
|
||||
}
|
||||
|
||||
const txid = await this.bchWallet.sendTokens(receiver, 3)
|
||||
|
||||
const utxoInfo = {
|
||||
@@ -412,6 +426,10 @@ class WalletAdapter {
|
||||
// generate a segregated UTXO.
|
||||
async moveBch (amountSat) {
|
||||
try {
|
||||
if (!amountSat) {
|
||||
throw new Error('amountSat is required!')
|
||||
}
|
||||
|
||||
const keyPair = await this.getKeyPair()
|
||||
console.log('keyPair: ', keyPair)
|
||||
|
||||
@@ -444,6 +462,10 @@ class WalletAdapter {
|
||||
// representing the transaction.
|
||||
async deseralizeTx (txHex) {
|
||||
try {
|
||||
// Input validation
|
||||
if (!txHex || typeof txHex !== 'string') {
|
||||
throw new Error('hex must be a string!')
|
||||
}
|
||||
// Ensure the URL points at FullStack.cash, since the web 3 infra does not
|
||||
// yet support this call.
|
||||
const oldUrl = this.bchWallet.bchjs.RawTransactions.restURL
|
||||
@@ -555,8 +577,12 @@ class WalletAdapter {
|
||||
|
||||
// Given an Order database object, this function sends the tokens from the
|
||||
// HD index where they are stored, back to the root address of the wallet.
|
||||
async reclaimTokens (orderData) {
|
||||
async reclaimTokens (orderData = {}) {
|
||||
try {
|
||||
if (!orderData.hdIndex) {
|
||||
throw new Error('orderData.hdIndex is required!')
|
||||
}
|
||||
|
||||
// Get the key pair from the Order.
|
||||
const keyPair = await this.getKeyPair(orderData.hdIndex)
|
||||
console.log('keyPair: ', keyPair)
|
||||
@@ -576,7 +602,7 @@ class WalletAdapter {
|
||||
// const txid = await tempWallet.sendTokens(receiver)
|
||||
|
||||
// Sweep the tokens to the root address.
|
||||
const sweeper = new BchTokenSweep(
|
||||
const sweeper = new this.BchTokenSweep(
|
||||
keyPair.wif,
|
||||
this.bchWallet.walletInfo.privateKey,
|
||||
this.bchWallet,
|
||||
@@ -652,6 +678,25 @@ class WalletAdapter {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async cid2json (urlOrCid) {
|
||||
try {
|
||||
// Input validation
|
||||
if (!urlOrCid || typeof urlOrCid !== 'string') {
|
||||
throw new Error('urlOrCid must be a string!')
|
||||
}
|
||||
// Extract the cid from the url or cid.
|
||||
const cid = urlOrCid.split('/').pop()
|
||||
console.log('cid to json: ', cid)
|
||||
const jsonRes = await this.bchWallet.cid2json({ cid })
|
||||
const json = jsonRes.json
|
||||
// console.log('json: ', json)
|
||||
return json
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/cid2json()', err.message)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default WalletAdapter
|
||||
|
||||
+15
-13
@@ -6,13 +6,15 @@ import config from '../../config/index.js'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
let _this
|
||||
|
||||
class WebHook {
|
||||
constructor () {
|
||||
_this = this
|
||||
_this.config = config
|
||||
_this.axios = axios
|
||||
this.config = config
|
||||
this.axios = axios
|
||||
this.sleepTime = 2000
|
||||
this.createWebhook = this.createWebhook.bind(this)
|
||||
this.deleteWebhook = this.deleteWebhook.bind(this)
|
||||
this.waitUntilSuccess = this.waitUntilSuccess.bind(this)
|
||||
this.sleep = this.sleep.bind(this)
|
||||
}
|
||||
|
||||
// REST petition to create a webhook in p2wdb-service
|
||||
@@ -22,14 +24,14 @@ class WebHook {
|
||||
throw new Error('url must be a string')
|
||||
}
|
||||
|
||||
const endpoint = _this.config.webhookService
|
||||
const endpoint = this.config.webhookService
|
||||
|
||||
const obj = {
|
||||
appId: this.config.p2wdbAppId,
|
||||
url
|
||||
}
|
||||
|
||||
const result = await axios.post(endpoint, obj)
|
||||
const result = await this.axios.post(endpoint, obj)
|
||||
|
||||
return result.data
|
||||
} catch (err) {
|
||||
@@ -45,14 +47,14 @@ class WebHook {
|
||||
throw new Error('url must be a string')
|
||||
}
|
||||
|
||||
const endpoint = _this.config.webhookService
|
||||
const endpoint = this.config.webhookService
|
||||
|
||||
const obj = {
|
||||
appId: this.config.p2wdbAppId,
|
||||
url
|
||||
}
|
||||
|
||||
const result = await axios.delete(endpoint, { data: obj })
|
||||
const result = await this.axios.delete(endpoint, { data: obj })
|
||||
|
||||
return result.data
|
||||
} catch (err) {
|
||||
@@ -86,7 +88,7 @@ class WebHook {
|
||||
} catch (err) {
|
||||
const now = new Date()
|
||||
console.log(`${now.toLocaleString()}: Error trying to create webhook with P2WDB. Trying again...`)
|
||||
await sleep(2000)
|
||||
await this.sleep(this.sleepTime)
|
||||
}
|
||||
} while (!success)
|
||||
|
||||
@@ -96,10 +98,10 @@ class WebHook {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
async sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
}
|
||||
|
||||
export default WebHook
|
||||
|
||||
@@ -29,7 +29,36 @@ class EntryRESTControllerLib {
|
||||
_this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /entry Create an entry
|
||||
* @apiPermission public
|
||||
* @apiName CreateEntry
|
||||
* @apiGroup REST Entry
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{"entry": "1234567890", "description": "1234567890", "slpAddress": "1234567890", "signature": "1234567890", "category": "1234567890"}' localhost:5001/entry
|
||||
*
|
||||
* @apiSuccess {Object} entry Entry object
|
||||
* @apiSuccess {String} entry.id Entry ID
|
||||
* @apiSuccess {String} entry.description Entry description
|
||||
* @apiSuccess {String} entry.slpAddress Entry slp address
|
||||
* @apiSuccess {String} entry.signature Entry signature
|
||||
* @apiSuccess {String} entry.category Entry category
|
||||
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "entry": {
|
||||
* "id": "1234567890",
|
||||
* "description": "1234567890",
|
||||
* "slpAddress": "1234567890",
|
||||
* "signature": "1234567890",
|
||||
* "category": "1234567890"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
// No api-doc documentation because this wont be a public endpoint
|
||||
// Note: this is a private endpoint, needs to be authenticated
|
||||
async createEntry (ctx) {
|
||||
try {
|
||||
console.log('body: ', ctx.request.body)
|
||||
|
||||
@@ -18,6 +18,8 @@ import OfferRouter from './offer/index.js'
|
||||
import OrderRouter from './order/index.js'
|
||||
import P2WDBRouter from './p2wdb/index.js'
|
||||
import UsageRESTController from './usage/index.js'
|
||||
import SmAccountRouter from './smAccount/index.js'
|
||||
import NostrRouter from './nostr/index.js'
|
||||
|
||||
class RESTControllers {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -86,6 +88,13 @@ class RESTControllers {
|
||||
// Attach the REST API Controllers associated with the /usage route
|
||||
const usageRESTController = new UsageRESTController(dependencies)
|
||||
usageRESTController.attach(app)
|
||||
|
||||
const smAccountRouter = new SmAccountRouter(dependencies)
|
||||
smAccountRouter.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /nostr route
|
||||
const nostrRouter = new NostrRouter(dependencies)
|
||||
nostrRouter.attach(app)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,11 +40,23 @@ class IpfsRESTControllerLib {
|
||||
* @api {get} /ipfs Get status on IPFS infrastructure
|
||||
* @apiPermission public
|
||||
* @apiName GetIpfsStatus
|
||||
* @apiGroup REST BCH
|
||||
* @apiGroup REST IPFS
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs
|
||||
*
|
||||
* @apiSuccess {Object} status IPFS status object
|
||||
* @apiSuccess {String} status.ipfsId IPFS ID
|
||||
* @apiSuccess {String} status.multiaddrs IPFS multiaddrs
|
||||
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "status": {
|
||||
* "ipfsId": "1234567890",
|
||||
* "multiaddrs": ["/ip4/123.45.67.89/tcp/4001/p2p/1234567890"]
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
async getStatus (ctx) {
|
||||
try {
|
||||
@@ -58,7 +70,26 @@ class IpfsRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
// Return information on IPFS peers this node is connected to.
|
||||
/**
|
||||
* @api {post} /ipfs/peers Get information on IPFS peers this node is connected to
|
||||
* @apiPermission public
|
||||
* @apiName GetIpfsPeers
|
||||
* @apiGroup REST IPFS
|
||||
*
|
||||
* @apiParam {Boolean} [showAll=false] Whether to include detailed peer data
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/peers \
|
||||
* -d '{"showAll": false}'
|
||||
*
|
||||
* @apiSuccess {Object[]} peers Array of peer objects
|
||||
* @apiSuccess {String} peers[].peer Peer ID
|
||||
* @apiSuccess {String} peers[].name Peer name
|
||||
* @apiSuccess {String} peers[].protocol Protocol used by the peer
|
||||
* @apiSuccess {String} peers[].version Peer version
|
||||
* @apiSuccess {String} peers[].connectionAddr Connection address
|
||||
* @apiSuccess {Object} [peers[].peerData] Detailed peer data (when showAll=true)
|
||||
*/
|
||||
async getPeers (ctx) {
|
||||
try {
|
||||
const showAll = ctx.request.body.showAll
|
||||
@@ -73,6 +104,24 @@ class IpfsRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /ipfs/relays Get data about the known Circuit Relays
|
||||
* @apiPermission public
|
||||
* @apiName GetIpfsRelays
|
||||
* @apiGroup REST IPFS
|
||||
*
|
||||
* @apiDescription Returns information about Circuit Relays, both v1 and v2, that this node knows about. V2 relays are hydrated with peer data from the connected peers list.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/relays
|
||||
*
|
||||
* @apiSuccess {Object} relays Object containing relay information
|
||||
* @apiSuccess {Object[]} relays.v2Relays Array of v2 Circuit Relay objects
|
||||
* @apiSuccess {String} relays.v2Relays[].ipfsId IPFS ID of the relay
|
||||
* @apiSuccess {String} relays.v2Relays[].name Name of the relay (hydrated from peer data)
|
||||
* @apiSuccess {String} relays.v2Relays[].description Description of the relay (hydrated from peer data)
|
||||
* @apiSuccess {Object[]} relays.v1Relays Array of v1 Circuit Relay configurations
|
||||
*/
|
||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||
async getRelays (ctx) {
|
||||
try {
|
||||
@@ -86,6 +135,24 @@ class IpfsRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /ipfs/connect Connect to a specific IPFS peer
|
||||
* @apiPermission public
|
||||
* @apiName ConnectToIpfsPeer
|
||||
* @apiGroup REST IPFS
|
||||
*
|
||||
* @apiDescription Attempts to establish a connection to a specific IPFS peer using the provided multiaddr. Optionally returns detailed information about the connection.
|
||||
*
|
||||
* @apiParam {String} multiaddr Multiaddress of the peer to connect to (required)
|
||||
* @apiParam {Boolean} [getDetails=false] Whether to return detailed connection information
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/connect \
|
||||
* -d '{"multiaddr": "/ip4/161.35.99.207/tcp/4001/p2p/12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu", "getDetails": false}'
|
||||
*
|
||||
* @apiSuccess {Boolean} success Indicates whether the connection attempt was successful
|
||||
* @apiSuccess {Object} [details] Additional connection details (when getDetails=true)
|
||||
*/
|
||||
async connect (ctx) {
|
||||
try {
|
||||
const multiaddr = ctx.request.body.multiaddr
|
||||
@@ -107,7 +174,7 @@ class IpfsRESTControllerLib {
|
||||
* @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord
|
||||
* @apiPermission public
|
||||
* @apiName GetThisNode
|
||||
* @apiGroup REST BCH
|
||||
* @apiGroup REST IPFS
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
REST API Controller library for the /nostr route
|
||||
*/
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
class NostrRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /nostr REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /nostr REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
this.createDeletedChat = this.createDeletedChat.bind(this)
|
||||
this.getDeletedChats = this.getDeletedChats.bind(this)
|
||||
this.createDeletedPost = this.createDeletedPost.bind(this)
|
||||
this.getDeletedPosts = this.getDeletedPosts.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /nostr/deletedChat Create Deleted Chat.
|
||||
* @apiPermission admin
|
||||
* @apiName CreateDeletedChat
|
||||
* @apiGroup REST Nostr
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -H "Authorization: Bearer <token>" -X POST -d '{"eventId": "note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q" , "npub" : "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8" , "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a", "pubkey":"6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1"}' localhost:5700/nostr/deletedChat
|
||||
*
|
||||
* @apiSuccess {String} _id DB Model id
|
||||
* @apiSuccess {String} eventId Event id
|
||||
* @apiSuccess {String} bchAddr BCH Address
|
||||
* @apiSuccess {String} npub Nostr Npub
|
||||
* @apiSuccess {String} pubkey Nostr pubkey
|
||||
* @apiSuccess {Date} createdAt Created at date.
|
||||
* @apiSuccess {Date} updatedAt Updated at date.
|
||||
*
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "_id": "6900f87f9cafee38028ea27e",
|
||||
* "npub": "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8",
|
||||
* "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a",
|
||||
* "pubKey": "6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1",
|
||||
* "createdAt":"2025-10-28T17:08:15.409Z",
|
||||
* "updatedAt": "2025-10-28T17:08:15.409Z"
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
async createDeletedChat (ctx) {
|
||||
try {
|
||||
const chat = ctx.request.body
|
||||
|
||||
const result = await this.useCases.nostr.createDeletedChat(chat)
|
||||
|
||||
ctx.body = {
|
||||
deletedChat: result
|
||||
}
|
||||
} catch (err) {
|
||||
wlogger.error('Error in createDeletedChat() REST API handler.')
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /nostr/deletedChat Get Deleted Chats.
|
||||
* @apiPermission public
|
||||
* @apiName GetDeletedChats
|
||||
* @apiGroup REST Nostr
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5700/nostr/deletedChat
|
||||
*
|
||||
* @apiSuccess {String} _id DB Model id
|
||||
* @apiSuccess {String} eventId Event id
|
||||
* @apiSuccess {String} bchAddr BCH Address
|
||||
* @apiSuccess {String} npub Nostr Npub
|
||||
* @apiSuccess {String} pubkey Nostr pubkey
|
||||
* @apiSuccess {Date} createdAt Created at date.
|
||||
* @apiSuccess {Date} updatedAt Updated at date.
|
||||
*
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [{
|
||||
* "_id": "6900f87f9cafee38028ea27e",
|
||||
* "npub": "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8",
|
||||
* "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a",
|
||||
* "pubKey": "6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1",
|
||||
* "createdAt":"2025-10-28T17:08:15.409Z",
|
||||
* "updatedAt": "2025-10-28T17:08:15.409Z"
|
||||
* }]
|
||||
*
|
||||
* */
|
||||
async getDeletedChats (ctx) {
|
||||
try {
|
||||
const deletedChats = await this.useCases.nostr.getDeletedChats()
|
||||
|
||||
ctx.body = { deletedChats }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in getDeletedChats() REST API handler.')
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /nostr/deletedPost Create Deleted Post.
|
||||
* @apiPermission admin
|
||||
* @apiName CreateDeletedPost
|
||||
* @apiGroup REST Nostr
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -H "Authorization: Bearer <token>" -X POST -d '{"eventId": "note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q" , "npub" : "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8" , "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a", "pubkey":"6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1"}' localhost:5700/nostr/deletedPost
|
||||
*
|
||||
* @apiSuccess {String} _id DB Model id
|
||||
* @apiSuccess {String} eventId Event id
|
||||
* @apiSuccess {String} bchAddr BCH Address
|
||||
* @apiSuccess {String} npub Nostr Npub
|
||||
* @apiSuccess {String} pubkey Nostr pubkey
|
||||
* @apiSuccess {Date} createdAt Created at date.
|
||||
* @apiSuccess {Date} updatedAt Updated at date.
|
||||
*
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "_id": "6900f87f9cafee38028ea27e",
|
||||
* "npub": "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8",
|
||||
* "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a",
|
||||
* "pubKey": "6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1",
|
||||
* "createdAt":"2025-10-28T17:08:15.409Z",
|
||||
* "updatedAt": "2025-10-28T17:08:15.409Z"
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
async createDeletedPost (ctx) {
|
||||
try {
|
||||
const post = ctx.request.body
|
||||
|
||||
const result = await this.useCases.nostr.createDeletedPost(post)
|
||||
|
||||
ctx.body = {
|
||||
deletedPost: result
|
||||
}
|
||||
} catch (err) {
|
||||
wlogger.error('Error in createDeletedPost() REST API handler.')
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /nostr/deletedPost Get Deleted Posts.
|
||||
* @apiPermission public
|
||||
* @apiName GetDeletedPosts
|
||||
* @apiGroup REST Nostr
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5700/nostr/deletedPost
|
||||
*
|
||||
* @apiSuccess {String} _id DB Model id
|
||||
* @apiSuccess {String} eventId Event id
|
||||
* @apiSuccess {String} bchAddr BCH Address
|
||||
* @apiSuccess {String} npub Nostr Npub
|
||||
* @apiSuccess {String} pubkey Nostr pubkey
|
||||
* @apiSuccess {Date} createdAt Created at date.
|
||||
* @apiSuccess {Date} updatedAt Updated at date.
|
||||
*
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [{
|
||||
* "_id": "6900f87f9cafee38028ea27e",
|
||||
* "npub": "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8",
|
||||
* "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a",
|
||||
* "pubKey": "6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1",
|
||||
* "createdAt":"2025-10-28T17:08:15.409Z",
|
||||
* "updatedAt": "2025-10-28T17:08:15.409Z"
|
||||
* }]
|
||||
*
|
||||
* */
|
||||
async getDeletedPosts (ctx) {
|
||||
try {
|
||||
const deletedPosts = await this.useCases.nostr.getDeletedPosts()
|
||||
|
||||
ctx.body = { deletedPosts }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in getDeletedPosts() REST API handler.')
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
ctx.throw(err.status, err.message)
|
||||
} else {
|
||||
ctx.throw(err.status)
|
||||
}
|
||||
} else {
|
||||
// By default use a 422 error if the HTTP status is not specified.
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NostrRESTControllerLib
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
REST API library for /nostr route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
import NostrRESTControllerLib from './controller.js'
|
||||
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
class NostrRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating NostrRouter REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating NostrRouter REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.config = config
|
||||
this.nostrRESTController = new NostrRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/nostr'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
this.attach = this.attach.bind(this)
|
||||
this.createDeletedChat = this.createDeletedChat.bind(this)
|
||||
this.createDeletedPost = this.createDeletedPost.bind(this)
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error(
|
||||
'Must pass app object when attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/deletedChat', this.createDeletedChat)
|
||||
this.router.get('/deletedChat', this.nostrRESTController.getDeletedChats)
|
||||
this.router.post('/deletedPost', this.createDeletedPost)
|
||||
this.router.get('/deletedPost', this.nostrRESTController.getDeletedPosts)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
|
||||
async createDeletedChat (ctx, next) {
|
||||
await this.validators.ensureAdmin(ctx, next)
|
||||
await this.nostrRESTController.createDeletedChat(ctx, next)
|
||||
return true
|
||||
}
|
||||
|
||||
async createDeletedPost (ctx, next) {
|
||||
await this.validators.ensureAdmin(ctx, next)
|
||||
await this.nostrRESTController.createDeletedPost(ctx, next)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export default NostrRouter
|
||||
@@ -1,5 +1,8 @@
|
||||
/*
|
||||
REST API Controller library for the /offer route
|
||||
|
||||
TODO:
|
||||
- Add api-doc documentation for all endpoints in this file.
|
||||
*/
|
||||
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
@@ -30,10 +33,13 @@ class OfferRESTControllerLib {
|
||||
this.listNftOffers = this.listNftOffers.bind(this)
|
||||
this.listFungibleOffers = this.listFungibleOffers.bind(this)
|
||||
this.takeOffer = this.takeOffer.bind(this)
|
||||
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
||||
this.syncOfferMutableData = this.syncOfferMutableData.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
// No api-doc documentation because this wont be a public endpoint
|
||||
// This function has no endpoint
|
||||
async createOffer (ctx) {
|
||||
try {
|
||||
console.log('body: ', ctx.request.body)
|
||||
@@ -52,6 +58,65 @@ class OfferRESTControllerLib {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @api {get} /offer/list/all/:page Get all offers
|
||||
* @apiPermission public
|
||||
* @apiName ListOffers
|
||||
* @apiGroup REST Offer
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5000/offer/list/all/0
|
||||
*
|
||||
* @apiSuccess {Object[]} offers Array of offer objects
|
||||
* @apiSuccess {ObjectId} offers._id Offer id
|
||||
* @apiSuccess {String} offers.ticker Ticker
|
||||
* @apiSuccess {String} offers.tokenId Token id
|
||||
* @apiSuccess {String} offers.utxoTxid Utxo transaction id
|
||||
* @apiSuccess {Number} offers.utxoVout Utxo output index
|
||||
* @apiSuccess {String} offers.buyOrSell Buy or sell
|
||||
* @apiSuccess {Number} offers.numTokens Number of tokens
|
||||
* @apiSuccess {String} offers.rateInBaseUnit Rate in base unit
|
||||
* @apiSuccess {String} offers.minUnitsToExchange Minimum units to exchange
|
||||
* @apiSuccess {String} offers.offerStatus Offer status
|
||||
* @apiSuccess {String} offers.makerAddr Maker address
|
||||
* @apiSuccess {Number} offers.timestamp Timestamp
|
||||
* @apiSuccess {String} offers.displayCategory Display category
|
||||
* @apiSuccess {Boolean} offers.nsfw Nsfw
|
||||
* @apiSuccess {Array} offers.flags Flags
|
||||
* @apiSuccess {Number} offers.messageType Message type
|
||||
* @apiSuccess {Number} offers.messageClass Message class
|
||||
* @apiSuccess {String} offers.nostrEventId Nostr event id
|
||||
* @apiSuccess {String} offers.operatorAddress Operator address
|
||||
* @apiSuccess {Number} offers.operatorPercentage Operator percentage
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "offers": [{
|
||||
* "_id": "56bd1da600a526986cf65c80",
|
||||
* "tokenId": "1234567890",
|
||||
* "utxoTxid": "1234567890",
|
||||
* "utxoVout": 0,
|
||||
* "buyOrSell": "buy",
|
||||
* "numTokens": 1,
|
||||
* "rateInBaseUnit": "100000000",
|
||||
* "minUnitsToExchange": "100000000",
|
||||
* "offerStatus": "active",
|
||||
* "makerAddr": "1234567890",
|
||||
* "timestamp": 1716883200,
|
||||
* "displayCategory": "nft",
|
||||
* "nsfw": false,
|
||||
* "flags": [],
|
||||
* "messageType": 1,
|
||||
* "messageClass": 1,
|
||||
* "nostrEventId": "1234567890",
|
||||
* "operatorAddress": "1234567890",
|
||||
* "operatorPercentage": 100
|
||||
* }]
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
|
||||
// curl -X GET http://localhost:5700/offer/list/all/0
|
||||
async listOffers (ctx) {
|
||||
@@ -68,6 +133,66 @@ class OfferRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /offer/list/nft/:page Get all nft offers
|
||||
* @apiPermission public
|
||||
* @apiName ListNftOffers
|
||||
* @apiGroup REST Offer
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5000/offer/list/nft/0
|
||||
*
|
||||
* @apiSuccess {Object[]} offers Array of offer objects
|
||||
* @apiSuccess {ObjectId} offers._id Offer id
|
||||
* @apiSuccess {String} offers.ticker Ticker
|
||||
* @apiSuccess {String} offers.tokenId Token id
|
||||
* @apiSuccess {String} offers.utxoTxid Utxo transaction id
|
||||
* @apiSuccess {Number} offers.utxoVout Utxo output index
|
||||
* @apiSuccess {String} offers.buyOrSell Buy or sell
|
||||
* @apiSuccess {Number} offers.numTokens Number of tokens
|
||||
* @apiSuccess {String} offers.rateInBaseUnit Rate in base unit
|
||||
* @apiSuccess {String} offers.minUnitsToExchange Minimum units to exchange
|
||||
* @apiSuccess {String} offers.offerStatus Offer status
|
||||
* @apiSuccess {String} offers.makerAddr Maker address
|
||||
* @apiSuccess {Number} offers.timestamp Timestamp
|
||||
* @apiSuccess {String} offers.displayCategory Display category
|
||||
* @apiSuccess {Boolean} offers.nsfw Nsfw
|
||||
* @apiSuccess {Array} offers.flags Flags
|
||||
* @apiSuccess {Number} offers.messageType Message type
|
||||
* @apiSuccess {Number} offers.messageClass Message class
|
||||
* @apiSuccess {String} offers.nostrEventId Nostr event id
|
||||
* @apiSuccess {String} offers.operatorAddress Operator address
|
||||
* @apiSuccess {Number} offers.operatorPercentage Operator percentage
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "offers": [{
|
||||
* "_id": "56bd1da600a526986cf65c80",
|
||||
* "tokenId": "1234567890",
|
||||
* "utxoTxid": "1234567890",
|
||||
* "utxoVout": 0,
|
||||
* "buyOrSell": "buy",
|
||||
* "numTokens": 1,
|
||||
* "rateInBaseUnit": "100000000",
|
||||
* "minUnitsToExchange": "100000000",
|
||||
* "offerStatus": "active",
|
||||
* "makerAddr": "1234567890",
|
||||
* "timestamp": 1716883200,
|
||||
* "displayCategory": "nft",
|
||||
* "nsfw": false,
|
||||
* "flags": [],
|
||||
* "messageType": 1,
|
||||
* "messageClass": 1,
|
||||
* "nostrEventId": "1234567890",
|
||||
* "operatorAddress": "1234567890",
|
||||
* "operatorPercentage": 100
|
||||
* }]
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
|
||||
// curl -X GET http://localhost:5700/offer/list/nft/0
|
||||
async listNftOffers (ctx) {
|
||||
try {
|
||||
@@ -83,6 +208,65 @@ class OfferRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /offer/list/fungible/:page Get all fungible offers
|
||||
* @apiPermission public
|
||||
* @apiName ListFungibleOffers
|
||||
* @apiGroup REST Offer
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5000/offer/list/fungible/0
|
||||
*
|
||||
* @apiSuccess {Object[]} offers Array of offer objects
|
||||
* @apiSuccess {ObjectId} offers._id Offer id
|
||||
* @apiSuccess {String} offers.ticker Ticker
|
||||
* @apiSuccess {String} offers.tokenId Token id
|
||||
* @apiSuccess {String} offers.utxoTxid Utxo transaction id
|
||||
* @apiSuccess {Number} offers.utxoVout Utxo output index
|
||||
* @apiSuccess {String} offers.buyOrSell Buy or sell
|
||||
* @apiSuccess {Number} offers.numTokens Number of tokens
|
||||
* @apiSuccess {String} offers.rateInBaseUnit Rate in base unit
|
||||
* @apiSuccess {String} offers.minUnitsToExchange Minimum units to exchange
|
||||
* @apiSuccess {String} offers.offerStatus Offer status
|
||||
* @apiSuccess {String} offers.makerAddr Maker address
|
||||
* @apiSuccess {Number} offers.timestamp Timestamp
|
||||
* @apiSuccess {String} offers.displayCategory Display category
|
||||
* @apiSuccess {Boolean} offers.nsfw Nsfw
|
||||
* @apiSuccess {Array} offers.flags Flags
|
||||
* @apiSuccess {Number} offers.messageType Message type
|
||||
* @apiSuccess {Number} offers.messageClass Message class
|
||||
* @apiSuccess {String} offers.nostrEventId Nostr event id
|
||||
* @apiSuccess {String} offers.operatorAddress Operator address
|
||||
* @apiSuccess {Number} offers.operatorPercentage Operator percentage
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "offers": [{
|
||||
* "_id": "56bd1da600a526986cf65c80",
|
||||
* "tokenId": "1234567890",
|
||||
* "utxoTxid": "1234567890",
|
||||
* "utxoVout": 0,
|
||||
* "buyOrSell": "buy",
|
||||
* "numTokens": 1,
|
||||
* "rateInBaseUnit": "100000000",
|
||||
* "minUnitsToExchange": "100000000",
|
||||
* "offerStatus": "active",
|
||||
* "makerAddr": "1234567890",
|
||||
* "timestamp": 1716883200,
|
||||
* "displayCategory": "fungible",
|
||||
* "nsfw": false,
|
||||
* "flags": [],
|
||||
* "messageType": 1,
|
||||
* "messageClass": 1,
|
||||
* "nostrEventId": "1234567890",
|
||||
* "operatorAddress": "1234567890",
|
||||
* "operatorPercentage": 100
|
||||
* }]
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
// curl -X GET http://localhost:5700/offer/list/fungible/0
|
||||
async listFungibleOffers (ctx) {
|
||||
try {
|
||||
@@ -98,6 +282,27 @@ class OfferRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /offer/take Take an offer
|
||||
* @apiPermission public
|
||||
* @apiName TakeOffer
|
||||
* @apiGroup REST Offer
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "nostrEventId": "1234567890" }' localhost:5010/offer/take
|
||||
*
|
||||
* @apiSuccess {String} eventId Event id
|
||||
* @apiSuccess {String} noteId Note id
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "eventId": "1234567890",
|
||||
* "noteId": "1234567890"
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
// Currently only supports 'sell' offers, and will only buy the 'numTokens'
|
||||
// listed in the offer.
|
||||
async takeOffer (ctx) {
|
||||
@@ -115,6 +320,94 @@ class OfferRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /offer/list/addr/:addr List all offers being made by a given address
|
||||
* @apiPermission public
|
||||
* @apiName ListOffersByAddress
|
||||
* @apiGroup REST Offer
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5000/offer/list/addr/bitcoincash:qrpxtnrlhfz9wsuuse7z5k2mxmvw0r3pu5qepyhmq2
|
||||
*
|
||||
*
|
||||
* @apiSuccess {Object[]} offers Array of offer objects
|
||||
* @apiSuccess {ObjectId} offers._id Offer id
|
||||
* @apiSuccess {String} offers.ticker Ticker
|
||||
* @apiSuccess {String} offers.tokenId Token id
|
||||
* @apiSuccess {String} offers.utxoTxid Utxo transaction id
|
||||
* @apiSuccess {Number} offers.utxoVout Utxo output index
|
||||
* @apiSuccess {String} offers.buyOrSell Buy or sell
|
||||
* @apiSuccess {Number} offers.numTokens Number of tokens
|
||||
* @apiSuccess {String} offers.rateInBaseUnit Rate in base unit
|
||||
* @apiSuccess {String} offers.minUnitsToExchange Minimum units to exchange
|
||||
* @apiSuccess {String} offers.offerStatus Offer status
|
||||
* @apiSuccess {String} offers.makerAddr Maker address
|
||||
* @apiSuccess {Number} offers.timestamp Timestamp
|
||||
* @apiSuccess {String} offers.displayCategory Display category
|
||||
* @apiSuccess {Boolean} offers.nsfw Nsfw
|
||||
* @apiSuccess {Array} offers.flags Flags
|
||||
* @apiSuccess {Number} offers.messageType Message type
|
||||
* @apiSuccess {Number} offers.messageClass Message class
|
||||
* @apiSuccess {String} offers.nostrEventId Nostr event id
|
||||
* @apiSuccess {String} offers.operatorAddress Operator address
|
||||
* @apiSuccess {Number} offers.operatorPercentage Operator percentage
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "offers": [{
|
||||
* "_id": "56bd1da600a526986cf65c80",
|
||||
* "tokenId": "1234567890",
|
||||
* "utxoTxid": "1234567890",
|
||||
* "utxoVout": 0,
|
||||
* "buyOrSell": "buy",
|
||||
* "numTokens": 1,
|
||||
* "rateInBaseUnit": "100000000",
|
||||
* "minUnitsToExchange": "100000000",
|
||||
* "offerStatus": "active",
|
||||
* "makerAddr": "bitcoincash:qrpxtnrlhfz9wsuuse7z5k2mxmvw0r3pu5qepyhmq2",
|
||||
* "timestamp": 1716883200,
|
||||
* "displayCategory": "fungible",
|
||||
* "nsfw": false,
|
||||
* "flags": [],
|
||||
* "messageType": 1,
|
||||
* "messageClass": 1,
|
||||
* "nostrEventId": "1234567890",
|
||||
* "operatorAddress": "1234567890",
|
||||
* "operatorPercentage": 100
|
||||
* }]
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
|
||||
// List all offers being made by a given address.
|
||||
// curl -X GET http://localhost:5700/offer/list/addr/bitcoincash:qrpxtnrlhfz9wsuuse7z5k2mxmvw0r3pu5qepyhmq2
|
||||
async listOffersByAddress (ctx) {
|
||||
try {
|
||||
const addr = ctx.params.addr
|
||||
|
||||
const offers = await this.useCases.offer.listOffersByAddress(addr)
|
||||
|
||||
ctx.body = offers
|
||||
} catch (err) {
|
||||
console.log('Error in listOffersByAddress REST API handler: ', err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
async syncOfferMutableData (ctx) {
|
||||
try {
|
||||
const tokenId = ctx.request.body.tokenId
|
||||
const offer = await this.useCases.offer.syncOfferMutableData(tokenId)
|
||||
|
||||
ctx.body = offer
|
||||
} catch (err) {
|
||||
console.log('Error in syncOfferMutableData REST API handler: ', err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err.message)
|
||||
|
||||
@@ -8,8 +8,6 @@ import Router from 'koa-router'
|
||||
// Local libraries.
|
||||
import OfferRESTControllerLib from './controller.js'
|
||||
|
||||
let _this
|
||||
|
||||
class OfferRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
@@ -38,7 +36,8 @@ class OfferRouter {
|
||||
const baseUrl = '/offer'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
_this = this
|
||||
// Bind 'this' object to all subfunctions.
|
||||
this.attach = this.attach.bind(this)
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
@@ -50,19 +49,21 @@ class OfferRouter {
|
||||
|
||||
// 12/3/24 CT:
|
||||
// Note: The createOffer() path was used by a P2WDB webhook to generate an
|
||||
// Offer from and Order. This has been deprecated and Offers are now created
|
||||
// Offer from an Order. This has been deprecated and Offers are now created
|
||||
// by a Timer Controller monitoring a Nostr topic.
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
|
||||
this.router.post('/take', _this.offerRESTController.takeOffer)
|
||||
this.router.get('/list/all/:page', _this.offerRESTController.listOffers)
|
||||
this.router.get('/list/nft/:page', _this.offerRESTController.listNftOffers)
|
||||
this.router.get('/list/fungible/:page', _this.offerRESTController.listFungibleOffers)
|
||||
this.router.post('/take', this.offerRESTController.takeOffer)
|
||||
this.router.post('/mutable/sync/', this.offerRESTController.syncOfferMutableData)
|
||||
this.router.get('/list/all/:page', this.offerRESTController.listOffers)
|
||||
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)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
app.use(_this.router.allowedMethods())
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,28 @@ class OrderRESTControllerLib {
|
||||
_this = this
|
||||
}
|
||||
|
||||
// No api-doc documentation because this wont be a public endpoint
|
||||
/**
|
||||
* @api {post} /order Create an order
|
||||
* @apiPermission User
|
||||
* @apiName CreateOrder
|
||||
* @apiGroup REST Order
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{"messageType": "1", "messageClass": "1", "tokenId": "1234567890", "buyOrSell": "buy", "rateInBaseUnit": "100000000", "minUnitsToExchange": "100000000", "numTokens": "1", "makerAddr": "1234567890"}' localhost:5001/order
|
||||
*
|
||||
* @apiSuccess {String} eventId Event id
|
||||
* @apiSuccess {String} noteId Note id
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "eventId": "1234567890",
|
||||
* "noteId": "1234567890"
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
|
||||
async createOrder (ctx) {
|
||||
try {
|
||||
// console.log('body: ', ctx.request.body)
|
||||
@@ -49,6 +70,68 @@ class OrderRESTControllerLib {
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @api {get} /order/list/all/:page List all orders
|
||||
* @apiPermission user
|
||||
* @apiName ListOrders
|
||||
* @apiGroup REST Order
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5000/order/list/all/0
|
||||
*
|
||||
* @apiSuccess {Object[]} orders Array of order objects
|
||||
* @apiSuccess {ObjectId} orders._id Order id
|
||||
* @apiSuccess {String} orders.tokenId Token id
|
||||
* @apiSuccess {String} orders.ticker Ticker
|
||||
* @apiSuccess {String} orders.dataType Data type
|
||||
* @apiSuccess {String} orders.utxoTxid Utxo transaction id
|
||||
* @apiSuccess {Number} orders.utxoVout Utxo output index
|
||||
* @apiSuccess {String} orders.buyOrSell Buy or sell
|
||||
* @apiSuccess {Number} orders.numTokens Number of tokens
|
||||
* @apiSuccess {String} orders.rateInBaseUnit Rate in base unit
|
||||
* @apiSuccess {String} orders.minUnitsToExchange Minimum units to exchange
|
||||
* @apiSuccess {String} orders.offerStatus Offer status
|
||||
* @apiSuccess {String} orders.makerAddr Maker address
|
||||
* @apiSuccess {Number} orders.timestamp Timestamp
|
||||
* @apiSuccess {String} orders.displayCategory Display category
|
||||
* @apiSuccess {Boolean} orders.nsfw Nsfw
|
||||
* @apiSuccess {Array} orders.flags Flags
|
||||
* @apiSuccess {Number} orders.messageType Message type
|
||||
* @apiSuccess {Number} orders.messageClass Message class
|
||||
* @apiSuccess {String} orders.nostrEventId Nostr event id
|
||||
* @apiSuccess {String} orders.operatorAddress Operator address
|
||||
* @apiSuccess {Number} orders.operatorPercentage Operator percentage
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "orders": [{
|
||||
* "_id": "56bd1da600a526986cf65c80",
|
||||
* "tokenId": "1234567890",
|
||||
* "ticker": "BTC",
|
||||
* "dataType": "offer",
|
||||
* "utxoTxid": "1234567890",
|
||||
* "utxoVout": 0,
|
||||
* "buyOrSell": "buy",
|
||||
* "numTokens": 1,
|
||||
* "rateInBaseUnit": "100000000",
|
||||
* "minUnitsToExchange": "100000000",
|
||||
* "offerStatus": "active",
|
||||
* "makerAddr": "1234567890",
|
||||
* "timestamp": 1716883200,
|
||||
* "displayCategory": "nft",
|
||||
* "nsfw": false,
|
||||
* "flags": [],
|
||||
* "messageType": 1,
|
||||
* "messageClass": 1,
|
||||
* "nostrEventId": "1234567890",
|
||||
* "operatorAddress": "1234567890",
|
||||
* "operatorPercentage": 100
|
||||
* }]
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
|
||||
// curl -X GET http://localhost:5700/order/list/all/0
|
||||
async listOrders (ctx) {
|
||||
@@ -67,6 +150,28 @@ class OrderRESTControllerLib {
|
||||
|
||||
// Delete an existing order by returning the token to the root address of
|
||||
// the DEX wallet.
|
||||
/**
|
||||
* @api {post} /order/delete Delete an order
|
||||
* @apiPermission user
|
||||
* @apiName DeleteOrder
|
||||
* @apiGroup REST Order
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "nostrEventId": "1234567890" }' localhost:5000/order/delete
|
||||
*
|
||||
* @apiSuccess {String} txid Transaction id
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "txid": "1234567890"
|
||||
* }
|
||||
*
|
||||
* @apiUse TokenError
|
||||
*/
|
||||
|
||||
// curl -X POST http://localhost:5700/order/delete -d '{ "nostrEventId": "1234567890" }'
|
||||
|
||||
async deleteOrder (ctx) {
|
||||
try {
|
||||
// console.log('body: ', ctx.request.body)
|
||||
|
||||
@@ -54,7 +54,7 @@ class OrderRouter {
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', this.createOrder)
|
||||
this.router.get('/list/all/:page', _this.orderRESTController.listOrders)
|
||||
this.router.post('/delete', _this.orderRESTController.deleteOrder)
|
||||
this.router.post('/delete', this.deleteOrder)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
@@ -65,6 +65,11 @@ class OrderRouter {
|
||||
await _this.validators.ensureUser(ctx, next)
|
||||
await _this.orderRESTController.createOrder(ctx, next)
|
||||
}
|
||||
|
||||
async deleteOrder (ctx, next) {
|
||||
await _this.validators.ensureUser(ctx, next)
|
||||
await _this.orderRESTController.deleteOrder(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderRouter
|
||||
|
||||
@@ -31,7 +31,25 @@ class P2WDBRESTControllerLib {
|
||||
_this = this
|
||||
}
|
||||
|
||||
// No api-doc documentation because this wont be a public endpoint
|
||||
/**
|
||||
* @api {post} /p2wdb Route a P2WDB webhook
|
||||
* @apiPermission public
|
||||
* @apiName RouteWebhook
|
||||
* @apiGroup REST P2WDB
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{"data": {"dataType": "offer"} } ' localhost:5001/p2wdb
|
||||
*
|
||||
* @apiSuccess {String} success Success message
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "success": true
|
||||
* }
|
||||
*/
|
||||
|
||||
// Note: verify if this is a private endpoint, needs to be authenticated
|
||||
async routeWebhook (ctx) {
|
||||
try {
|
||||
console.log('p2wdb REST API handler: body: ', ctx.request.body)
|
||||
@@ -64,6 +82,7 @@ class P2WDBRESTControllerLib {
|
||||
await _this.useCases.offer.flagOffer(flagData)
|
||||
} else {
|
||||
console.log('Could not route P2WDB webhook data.')
|
||||
// NOTE: Should we throw an error here?
|
||||
}
|
||||
|
||||
// const orderObj = ctx.request.body.order
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
REST API Controller library for the /order route
|
||||
*/
|
||||
|
||||
// const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
class SmAccountRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /sm REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /sm REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.SmAccountModel = this.adapters.localdb.SmAccount
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /sm/list/all/{page} List all Social Media Accounts
|
||||
* @apiPermission public
|
||||
* @apiName ListAccounts
|
||||
* @apiGroup REST Social Media Accounts
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5700/sm/list/all/0
|
||||
*
|
||||
*/
|
||||
// curl -X GET http://localhost:5700/sm/list/all/0
|
||||
async listAccounts (ctx) {
|
||||
try {
|
||||
let page = ctx.params.page
|
||||
if (!page) page = 0
|
||||
|
||||
const offers = await _this.useCases.smAccount.listAccounts(page)
|
||||
|
||||
ctx.body = offers
|
||||
} catch (err) {
|
||||
console.log('Error in listAccounts REST API handler: ', err)
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /sm/npub/{npub} Get Social Media Account by Npub
|
||||
* @apiPermission public
|
||||
* @apiName GetAccountByNpub
|
||||
* @apiGroup REST Social Media Accounts
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5700/sm/npub/npub1y3xq402pu9aqms3khetnt5gzm54t63pzcla56wwfmwshde0ws77qkff5gc
|
||||
*
|
||||
*/
|
||||
// curl -X GET http://localhost:5700/sm/npub/npub1y3xq402pu9aqms3khetnt5gzm54t63pzcla56wwfmwshde0ws77qkff5gc
|
||||
async getAccountByNpub (ctx) {
|
||||
try {
|
||||
const npub = ctx.params.npub
|
||||
if (!npub) {
|
||||
ctx.throw(400, 'Npub is required')
|
||||
}
|
||||
|
||||
const account = await _this.useCases.smAccount.getAccountByNpub(npub)
|
||||
|
||||
if (!account) {
|
||||
ctx.throw(404, 'Account not found')
|
||||
}
|
||||
|
||||
ctx.body = account
|
||||
} catch (err) {
|
||||
// console.log('Error in getAccountByNpub REST API handler: ', err)
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /sm/bchAddr/{bchAddr} Get Social Media Account by BCH address
|
||||
* @apiPermission public
|
||||
* @apiName GetAccountByBchAddr
|
||||
* @apiGroup REST Social Media Accounts
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5700/sm/bchAddr/bitcoincash:qz6lf6gpmn3secx73g7ucfcgy8mrh3sz2y2ylk643x
|
||||
*
|
||||
*/
|
||||
// curl -X GET http://localhost:5700/sm/bchAddr/bitcoincash:qz6lf6gpmn3secx73g7ucfcgy8mrh3sz2y2ylk643x
|
||||
async getAccountByBchAddr (ctx) {
|
||||
try {
|
||||
const bchAddr = ctx.params.bchAddr
|
||||
if (!bchAddr) {
|
||||
ctx.throw(400, 'BCH address is required')
|
||||
}
|
||||
|
||||
const account = await _this.useCases.smAccount.getAccountByBchAddr(bchAddr)
|
||||
|
||||
if (!account) {
|
||||
ctx.throw(404, 'Account not found')
|
||||
}
|
||||
|
||||
ctx.body = account
|
||||
} catch (err) {
|
||||
// console.log('Error in getAccountByBchAddr REST API handler: ', err)
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err)
|
||||
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
ctx.throw(err.status, err.message)
|
||||
} else {
|
||||
ctx.throw(err.status)
|
||||
}
|
||||
} else {
|
||||
// By default use a 422 error if the HTTP status is not specified.
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SmAccountRESTControllerLib
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
REST API library for /order route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
import SmAccountRESTControllerLib from './controller.js'
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
let _this
|
||||
|
||||
class SmAccountRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /sm REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /sm REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.validators = new Validators()
|
||||
this.smAccountRESTController = new SmAccountRESTControllerLib(dependencies)
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/sm'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error(
|
||||
'Must pass app object when attached REST API controllers.'
|
||||
)
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.get('/list/all/:page', _this.smAccountRESTController.listAccounts)
|
||||
this.router.get('/npub/:npub', _this.smAccountRESTController.getAccountByNpub)
|
||||
this.router.get('/bchAddr/:bchAddr', _this.smAccountRESTController.getAccountByBchAddr)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
app.use(_this.router.allowedMethods())
|
||||
}
|
||||
}
|
||||
|
||||
export default SmAccountRouter
|
||||
@@ -22,6 +22,15 @@ class TimerControllers {
|
||||
}
|
||||
|
||||
this.debugLevel = localConfig.debugLevel
|
||||
|
||||
// Constants
|
||||
this.cleanUsageInterval = 60000 * 60 // 1 hour
|
||||
this.backupUsageInterval = 60000 * 10 // 10 minutes
|
||||
this.newSmAccountsInterval = 60000 * 60 // 60 minutes
|
||||
this.updateSmAccountsInterval = 60000 * 10 // 10 minutes
|
||||
this.removeOlderDeletedChatsAndPostsInterval = 60000 * 60 * 1 // 1 hour
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
@@ -30,15 +39,19 @@ class TimerControllers {
|
||||
this.gcOffers = this.gcOffers.bind(this)
|
||||
this.checkDupOffers = this.checkDupOffers.bind(this)
|
||||
this.loadOffers = this.loadOffers.bind(this)
|
||||
// Bind 'this' object to all subfunctions.
|
||||
// this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
|
||||
this.cleanUsage = this.cleanUsage.bind(this)
|
||||
|
||||
this.backupUsage = this.backupUsage.bind(this)
|
||||
this.newSmAccounts = this.newSmAccounts.bind(this)
|
||||
this.updateSmAccounts = this.updateSmAccounts.bind(this)
|
||||
this.removeOlderDeletedChatsAndPosts = this.removeOlderDeletedChatsAndPosts.bind(this)
|
||||
// State
|
||||
this.gcOrdersInt = null
|
||||
this.gcOffersInt = null
|
||||
this.checkDupOffersInt = null
|
||||
this.loadOffersInt = null
|
||||
this.newSmAccountsInt = null
|
||||
this.updateSmAccountsInt = null
|
||||
this.removeOlderDeletedChatsAndPostsInt = null
|
||||
}
|
||||
|
||||
// Start all the time-based controllers.
|
||||
@@ -47,11 +60,11 @@ class TimerControllers {
|
||||
this.gcOffersInt = setInterval(this.gcOffers, 60000 * 5)
|
||||
// this.checkDupOffersInt = setInterval(this.checkDupOffers, 60000 * 4.5)
|
||||
this.loadOffersInt = setInterval(this.loadOffers, 60000 * 2)
|
||||
// Any new timer control functions can be added here. They will be started
|
||||
// when the server starts.
|
||||
// this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 60)
|
||||
this.cleanUsageHandle = setInterval(this.cleanUsage, 60000 * 60) // 1 hour
|
||||
|
||||
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
|
||||
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
|
||||
this.newSmAccountsInt = setInterval(this.newSmAccounts, this.newSmAccountsInterval)
|
||||
this.updateSmAccountsInt = setInterval(this.updateSmAccounts, this.updateSmAccountsInterval)
|
||||
this.removeOlderDeletedChatsAndPostsInt = setInterval(this.removeOlderDeletedChatsAndPosts, this.removeOlderDeletedChatsAndPostsInterval)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -61,6 +74,10 @@ class TimerControllers {
|
||||
// clearInterval(this.checkDupOffers)
|
||||
clearInterval(this.optimizeWalletHandle)
|
||||
clearInterval(this.cleanUsageHandle)
|
||||
clearInterval(this.backupUsageHandle)
|
||||
clearInterval(this.newSmAccountsInt)
|
||||
clearInterval(this.updateSmAccountsInt)
|
||||
clearInterval(this.removeOlderDeletedChatsAndPostsInt)
|
||||
}
|
||||
|
||||
// Garbage Collect the Orders.
|
||||
@@ -75,6 +92,27 @@ class TimerControllers {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean the usage state so that stats reflect the last 24 hours.
|
||||
cleanUsage () {
|
||||
try {
|
||||
clearInterval(this.cleanUsageHandle)
|
||||
|
||||
const now = new Date()
|
||||
console.log(`cleanUsage() Timer Controller executing at ${now.toLocaleString()}`)
|
||||
|
||||
this.useCases.usage.cleanUsage()
|
||||
|
||||
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in time-controller.js/cleanUsage(): ', err)
|
||||
|
||||
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Garbage Collect the Offers.
|
||||
gcOffers () {
|
||||
try {
|
||||
@@ -111,22 +149,71 @@ class TimerControllers {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean the usage state so that stats reflect the last 24 hours.
|
||||
cleanUsage () {
|
||||
// Backup the usage stats to the database
|
||||
async backupUsage () {
|
||||
try {
|
||||
const now = new Date()
|
||||
console.log(`cleanUsage() Timer Controller executing at ${now.toLocaleString()}`)
|
||||
clearInterval(this.backupUsageHandle)
|
||||
|
||||
this.useCases.usage.cleanUsage()
|
||||
console.log('backupUsage() Timer Controller executing at ', new Date().toLocaleString())
|
||||
|
||||
// Clear the database of old usage data.
|
||||
await this.useCases.usage.clearUsage()
|
||||
|
||||
// Save the current usage snapshot to the database.
|
||||
await this.useCases.usage.saveUsage()
|
||||
|
||||
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in time-controller.js/cleanUsage(): ', err)
|
||||
console.error('Error in time-controller.js/backupUsage(): ', err)
|
||||
|
||||
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
|
||||
|
||||
// Note: Do not throw an error. This is a top-level function.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check for new Social Media Accounts.
|
||||
async newSmAccounts () {
|
||||
try {
|
||||
await this.useCases.smAccount.checkForNewSmAccounts()
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in time-controller.js/newSmAccounts(): ', err)
|
||||
|
||||
// Note: Do not throw an error. This is a top-level function.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Update the Social Media Accounts.
|
||||
async updateSmAccounts () {
|
||||
try {
|
||||
await this.useCases.smAccount.updateSmAccounts()
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in time-controller.js/updateSmAccounts(): ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Remove older deleted chats and posts.
|
||||
async removeOlderDeletedChatsAndPosts () {
|
||||
try {
|
||||
// console.log('removeOlderDeletedChatsAndPosts() Timer Controller executing at ', new Date().toLocaleString())
|
||||
clearInterval(this.removeOlderDeletedChatsAndPostsInt)
|
||||
await this.useCases.nostr.removeOlderDeletedChats()
|
||||
await this.useCases.nostr.removeOlderDeletedPosts()
|
||||
this.removeOlderDeletedChatsAndPostsInt = setInterval(this.removeOlderDeletedChatsAndPosts, this.removeOlderDeletedChatsAndPostsInterval)
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in time-controller.js/removeOlderDeletedChatsAndPosts(): ', err)
|
||||
this.removeOlderDeletedChatsAndPostsInt = setInterval(this.removeOlderDeletedChatsAndPosts, this.removeOlderDeletedChatsAndPostsInterval)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TimerControllers
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
DeletedChat Entity
|
||||
*/
|
||||
|
||||
class DeletedChat {
|
||||
validate (data = {}) {
|
||||
const { eventId } = data
|
||||
// Input Validation
|
||||
if (!eventId || typeof eventId !== 'string') {
|
||||
throw new Error("Property 'eventId' must be a string!")
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
export default DeletedChat
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
DeletedPost Entity
|
||||
*/
|
||||
|
||||
class DeletedPost {
|
||||
validate (data = {}) {
|
||||
const { eventId } = data
|
||||
// Input Validation
|
||||
if (!eventId || typeof eventId !== 'string') {
|
||||
throw new Error("Property 'eventId' must be a string!")
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
export default DeletedPost
|
||||
@@ -1,6 +1,9 @@
|
||||
/*
|
||||
Entry Entity
|
||||
|
||||
TODO: Explain what an entry is and how it works.
|
||||
*/
|
||||
|
||||
class Entry {
|
||||
validate ({
|
||||
entry,
|
||||
|
||||
+19
-3
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Offer Entity
|
||||
An ffer is created when a new Signal is detected via the P2WDB webhook.
|
||||
An offer is created when a new Signal is detected via the P2WDB webhook.
|
||||
It's destroyed when the UTXO described in the Signal has been detected as spent.
|
||||
*/
|
||||
|
||||
@@ -31,9 +31,12 @@ class OfferEntity {
|
||||
utxoVout,
|
||||
offerStatus,
|
||||
makerAddr,
|
||||
makerNpub,
|
||||
ticker,
|
||||
tokenType,
|
||||
nostrEventId
|
||||
nostrEventId,
|
||||
operatorAddress,
|
||||
operatorPercentage
|
||||
} = offerData.data
|
||||
|
||||
// Input Validation
|
||||
@@ -80,6 +83,18 @@ class OfferEntity {
|
||||
throw new Error("Property 'nostrEventId' must be a string.")
|
||||
}
|
||||
|
||||
// Add the operator address and percentage to the offer.
|
||||
if (!operatorAddress || typeof operatorAddress !== 'string') {
|
||||
throw new Error("Property 'operatorAddress' must be a string.")
|
||||
}
|
||||
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)
|
||||
timestamp = timestamp.getTime()
|
||||
@@ -103,7 +118,8 @@ class OfferEntity {
|
||||
makerAddr,
|
||||
ticker,
|
||||
tokenType,
|
||||
nostrEventId
|
||||
nostrEventId,
|
||||
makerNpub
|
||||
}
|
||||
// console.log('offer entity validatedOfferData: ', validatedOfferData)
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
Entry use-case library.
|
||||
|
||||
TODO: Explain what an entry is and how it works.
|
||||
*/
|
||||
|
||||
// local libraries
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
@@ -30,11 +35,12 @@ class EntryLib {
|
||||
if (!isValidSignature) {
|
||||
throw new Error('Invalid signature')
|
||||
}
|
||||
|
||||
// Verify psf tokens balance
|
||||
// const psfBalance = await this.bch.getPSFTokenBalance(
|
||||
// entryEntity.slpAddress
|
||||
// )
|
||||
// NOTE: this.bch.getPSFTokenBalance , does not exist , should be created?
|
||||
|
||||
const psfBalance = 1
|
||||
|
||||
if (psfBalance < 10) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import EntryUseCases from './entry.js'
|
||||
import OfferUseCases from './offer/index.js'
|
||||
import OrderUseCases from './order.js'
|
||||
import { UsageUseCases } from './usage-use-cases.js'
|
||||
import SmAccountUseCases from './smAccount-use-cases.js'
|
||||
import NostrUseCases from './nostr-use-cases.js'
|
||||
|
||||
class UseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -27,10 +29,15 @@ class UseCases {
|
||||
localConfig.order = this.order
|
||||
this.offer = new OfferUseCases(localConfig)
|
||||
this.usage = new UsageUseCases(localConfig)
|
||||
this.smAccount = new SmAccountUseCases(localConfig)
|
||||
this.nostr = new NostrUseCases(localConfig)
|
||||
}
|
||||
|
||||
// Run any startup Use Cases at the start of the app.
|
||||
async start () {
|
||||
// Load the usage stats from the database
|
||||
await this.usage.loadUsage()
|
||||
|
||||
console.log('Async Use Cases have been started.')
|
||||
|
||||
return true
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
This library contains business-logic for dealing with deleted chats. Most of these
|
||||
functions are called by the /nostr REST API endpoints.
|
||||
*/
|
||||
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
import DeletedChatEntity from '../entities/deletedChat.js'
|
||||
import DeletedPostEntity from '../entities/deletedPost.js'
|
||||
|
||||
class NostrUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of adapters must be passed in when instantiating Nostr Use Cases library.'
|
||||
)
|
||||
}
|
||||
// Encapsulate dependencies
|
||||
this.DeletedChatEntity = new DeletedChatEntity()
|
||||
this.DeletedPostEntity = new DeletedPostEntity()
|
||||
this.DeletedChatModel = this.adapters.localdb.DeletedChat
|
||||
this.DeletedPostModel = this.adapters.localdb.DeletedPost
|
||||
|
||||
this.createDeletedChat = this.createDeletedChat.bind(this)
|
||||
this.getDeletedChats = this.getDeletedChats.bind(this)
|
||||
this.createDeletedPost = this.createDeletedPost.bind(this)
|
||||
this.getDeletedPosts = this.getDeletedPosts.bind(this)
|
||||
this.removeOlderDeletedChats = this.removeOlderDeletedChats.bind(this)
|
||||
this.removeOlderDeletedPosts = this.removeOlderDeletedPosts.bind(this)
|
||||
}
|
||||
|
||||
// Create a new deleted chat model and add it to the Mongo database.
|
||||
async createDeletedChat (deletedChatObj) {
|
||||
try {
|
||||
// Input Validation
|
||||
|
||||
const deletedChatEntity = this.DeletedChatEntity.validate(deletedChatObj)
|
||||
const deletedChat = new this.DeletedChatModel(deletedChatEntity)
|
||||
|
||||
// Save the new model to the database.
|
||||
await deletedChat.save()
|
||||
return deletedChat
|
||||
} catch (err) {
|
||||
// console.log('createDeletedChat() error: ', err)
|
||||
wlogger.error('Error in lib/nostr.js/createDeletedChat()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an array of all deleted chats models in the Mongo database.
|
||||
async getDeletedChats () {
|
||||
try {
|
||||
// Get deleted chats models.
|
||||
const deletedChats = await this.DeletedChatModel.find({})
|
||||
|
||||
return deletedChats
|
||||
} catch (err) {
|
||||
wlogger.error('Error in lib/users.js/getAllDeletedChat()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new deleted post model and add it to the Mongo database.
|
||||
async createDeletedPost (deletedPostObj) {
|
||||
try {
|
||||
// Input Validation
|
||||
|
||||
const deletedPostEntity = this.DeletedPostEntity.validate(deletedPostObj)
|
||||
const deletedPost = new this.DeletedPostModel(deletedPostEntity)
|
||||
|
||||
// Save the new model to the database.
|
||||
await deletedPost.save()
|
||||
return deletedPost
|
||||
} catch (err) {
|
||||
wlogger.error('Error in lib/users.js/createDeletedPost()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an array of all deleted posts models in the Mongo database.
|
||||
async getDeletedPosts () {
|
||||
try {
|
||||
// Get deleted posts models.
|
||||
const deletedPosts = await this.DeletedPostModel.find({})
|
||||
|
||||
return deletedPosts
|
||||
} catch (err) {
|
||||
wlogger.error('Error in lib/users.js/getDeletedPosts()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove deleted chats that are older than 3 months.
|
||||
async removeOlderDeletedChats () {
|
||||
try {
|
||||
const deletedChats = await this.DeletedChatModel.find({})
|
||||
const threeMonthsAgo = new Date()
|
||||
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3)
|
||||
for (let i = 0; i < deletedChats.length; i++) {
|
||||
const deletedChat = deletedChats[i]
|
||||
if (deletedChat.createdAt.getTime() < threeMonthsAgo.getTime()) {
|
||||
await deletedChat.remove()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
wlogger.error('Error in lib/users.js/removeOlderDeletedChats()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove deleted posts that are older than 3 months.
|
||||
async removeOlderDeletedPosts () {
|
||||
try {
|
||||
const deletedPosts = await this.DeletedPostModel.find({})
|
||||
const threeMonthsAgo = new Date()
|
||||
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3)
|
||||
for (let i = 0; i < deletedPosts.length; i++) {
|
||||
const deletedPost = deletedPosts[i]
|
||||
if (deletedPost.createdAt.getTime() < threeMonthsAgo.getTime()) {
|
||||
await deletedPost.remove()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
wlogger.error('Error in lib/users.js/removeOlderDeletedPosts()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NostrUseCases
|
||||
+232
-20
@@ -64,6 +64,8 @@ class OfferUseCases {
|
||||
this.removeStaleOffers = this.removeStaleOffers.bind(this)
|
||||
this.flagOffer = this.flagOffer.bind(this)
|
||||
this.loadOffers = this.loadOffers.bind(this)
|
||||
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
||||
this.syncOfferMutableData = this.syncOfferMutableData.bind(this)
|
||||
|
||||
// State
|
||||
this.seenOffers = []
|
||||
@@ -110,7 +112,10 @@ class OfferUseCases {
|
||||
const utxoStatus = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.utxoIsValid, utxo)
|
||||
// console.log('utxoStatus: ', utxoStatus)
|
||||
// if (utxoStatus === null) return false
|
||||
if (!utxoStatus) return false
|
||||
if (!utxoStatus) {
|
||||
console.log(`UTXO txid: ${offerObj.data.utxoTxid}, vout: ${offerObj.data.utxoVout} has been spent. Skipping.`)
|
||||
return false
|
||||
}
|
||||
|
||||
// A new offer gets a status of 'posted'
|
||||
offerObj.data.offerStatus = 'posted'
|
||||
@@ -125,20 +130,57 @@ class OfferUseCases {
|
||||
|
||||
// Get data about the token.
|
||||
const tokenId = offerEntity.tokenId
|
||||
// const tokenData = await this.adapters.wallet.bchWallet.getTokenData(tokenId)
|
||||
const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId)
|
||||
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
|
||||
let tokenData = null
|
||||
try {
|
||||
tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId)
|
||||
} catch (err) {
|
||||
console.error('Error in OfferUseCases/createOffer() getting token data: ', err.message)
|
||||
}
|
||||
|
||||
// Generate a 'display category' for the token. This will allow the
|
||||
// front end UI to figure out how to display the token.
|
||||
const displayCategory = this.categorizeToken(offerEntity, tokenData)
|
||||
console.log('displayCategory: ', displayCategory)
|
||||
offerEntity.displayCategory = displayCategory
|
||||
// Do additional data analysis if the token data was successfully retrieved.
|
||||
if (tokenData) {
|
||||
// Store the mutable and immutable data cids.
|
||||
const mutableDataCid = tokenData.mutableData
|
||||
const immutableDataCid = tokenData.immutableData
|
||||
offerEntity.mutableDataCid = mutableDataCid
|
||||
offerEntity.immutableDataCid = immutableDataCid
|
||||
|
||||
// Detect if user set the NSFW flag.
|
||||
// const nsfw = false
|
||||
// nsfw = await this.retryQueue.addToQueue(this.detectNsfw, tokenData)
|
||||
// offerEntity.nsfw = nsfw
|
||||
// Get the mutable data from the cid if it exists.
|
||||
if (mutableDataCid && typeof mutableDataCid === 'string') {
|
||||
let mutableData = null
|
||||
try {
|
||||
mutableData = await this.retryQueue.addToQueue(this.adapters.wallet.cid2json, mutableDataCid)
|
||||
} catch (err) {
|
||||
console.error('Error in OfferUseCases/createOffer() getting mutable data: ', err.message)
|
||||
}
|
||||
console.log('mutableData: ', mutableData)
|
||||
|
||||
if (mutableData) {
|
||||
try {
|
||||
offerEntity.tokenIconUrl = mutableData.tokenIcon
|
||||
offerEntity.tokenCategories = mutableData.category
|
||||
offerEntity.tokenTags = mutableData.tags
|
||||
offerEntity.lastUpdatedTokenData = new Date().getTime()
|
||||
|
||||
offerEntity.userDataStr = JSON.stringify(mutableData.userData)
|
||||
} catch (error) {
|
||||
// skip error
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('offerEntity: ', offerEntity)
|
||||
|
||||
// Generate a 'display category' for the token. This will allow the
|
||||
// front end UI to figure out how to display the token.
|
||||
const displayCategory = this.categorizeToken(offerEntity, tokenData)
|
||||
console.log('displayCategory: ', displayCategory)
|
||||
offerEntity.displayCategory = displayCategory
|
||||
|
||||
// Detect if user set the NSFW flag.
|
||||
// const nsfw = false
|
||||
// nsfw = await this.retryQueue.addToQueue(this.detectNsfw, tokenData)
|
||||
// offerEntity.nsfw = nsfw
|
||||
}
|
||||
|
||||
// Add offer to the local database.
|
||||
const offerModel = new this.OfferModel(offerEntity)
|
||||
@@ -146,7 +188,7 @@ class OfferUseCases {
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in createOffer()', err.message)
|
||||
console.error('\n\nError in createOffer()', err.message, '\n\n', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -263,7 +305,7 @@ class OfferUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
async listNftOffers (page = 0, nsfw = false) {
|
||||
/* async listNftOffers (page = 0, nsfw = false) {
|
||||
try {
|
||||
const data = await this.OfferModel.find({
|
||||
displayCategory: { $ne: 'fungible' },
|
||||
@@ -283,6 +325,42 @@ class OfferUseCases {
|
||||
console.error('Error in use-cases/offer/listNftOffers()')
|
||||
throw error
|
||||
}
|
||||
} */
|
||||
|
||||
async listNftOffers (page = 0, nsfw = false) {
|
||||
try {
|
||||
const query = {
|
||||
displayCategory: { $ne: 'fungible' },
|
||||
nsfw
|
||||
}
|
||||
// Total query offers
|
||||
const totalOffers = await this.OfferModel.countDocuments(query)
|
||||
// Total pages
|
||||
const totalPages = Math.ceil(totalOffers / NFT_ENTRIES_PER_PAGE)
|
||||
|
||||
const data = await this.OfferModel.find(query)
|
||||
// Sort entries so newest entries show first.
|
||||
.sort('-timestamp')
|
||||
// Skip to the start of the selected page.
|
||||
.skip(page * NFT_ENTRIES_PER_PAGE)
|
||||
// Only return 20 results.
|
||||
.limit(NFT_ENTRIES_PER_PAGE)
|
||||
|
||||
// console.log('listNftOffers() returning this data: ', data)
|
||||
|
||||
return {
|
||||
data,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
totalPages,
|
||||
totalOffers,
|
||||
pageSize: NFT_ENTRIES_PER_PAGE
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in use-cases/offer/listNftOffers()')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async listFungibleOffers (page = 0) {
|
||||
@@ -512,9 +590,8 @@ class OfferUseCases {
|
||||
// }
|
||||
}
|
||||
|
||||
// This function is called by the P2WDB webhook REST API handler. When a
|
||||
// Counter Offer is passed to bch-dex by the P2WDB, the data is then passed
|
||||
// to this function. It does due diligence on the Counter Offer, then signs
|
||||
// This function is called by loadOffers().
|
||||
// It does due diligence on the Counter Offer, then signs
|
||||
// and broadcasts the transaction to accept the Counter Offer.
|
||||
async acceptCounterOffer (offerData) {
|
||||
try {
|
||||
@@ -527,6 +604,8 @@ class OfferUseCases {
|
||||
return false
|
||||
}
|
||||
|
||||
console.log(`New counter offer detected: https://astral.psfoundation.info/${this.adapters.nostr.eventId2note(offerData.data.nostrEventId)}`)
|
||||
|
||||
// See if this instance of bch-dex is managing the Order associated with
|
||||
// the incoming Counter Offer.
|
||||
|
||||
@@ -593,20 +672,57 @@ class OfferUseCases {
|
||||
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
|
||||
}
|
||||
const satsOut = this.adapters.wallet.bchWallet.bchjs.BitcoinCash.toSatoshi(txObj.vout[2].value)
|
||||
|
||||
const hasRequiredAmount = satsOut === satsToReceive
|
||||
|
||||
if (!hasRequiredAmount) {
|
||||
throw new Error(`The Counter Offer has an output of ${satsOut}, which does not match the required ${satsToReceive} in the Offer.`)
|
||||
}
|
||||
|
||||
// Ensure the Counter Offer has an output for the Operator of bch-dex.
|
||||
if (!txObj.vout[3]) {
|
||||
console.log('The Counter Offer does not have an output for the Operator.')
|
||||
|
||||
// Add order to list of seen orders, so that we don't spent time trying to validate it again.
|
||||
this.seenOffers.push(eventId)
|
||||
|
||||
return 'N/A'
|
||||
}
|
||||
|
||||
// Ensure the 3rd output (vout=2) is going to the maker address specified
|
||||
// in the Offer.
|
||||
|
||||
const addrInCounterOffer = txObj.vout[2].scriptPubKey.addresses[0]
|
||||
const makerAddr = orderData.makerAddr
|
||||
const hasCorrectAddr = makerAddr === addrInCounterOffer
|
||||
|
||||
if (!hasCorrectAddr) {
|
||||
throw new Error(`The Counter Offer has an output address of ${addrInCounterOffer}, which does not match the Maker address of ${makerAddr} in the Offer.`)
|
||||
}
|
||||
|
||||
// Ensure the 4th output (vout=3) is going to the operator address specified in the Offer.
|
||||
// NOTE: This is the operator of the bch-dex intance, NOT the seller of the token.
|
||||
const operatorAddr = orderData.operatorAddress
|
||||
|
||||
const hasCorrectOperatorAddr = operatorAddr === txObj.vout[3].scriptPubKey.addresses[0]
|
||||
if (!hasCorrectOperatorAddr) {
|
||||
throw new Error(`The Counter Offer has an output address of ${txObj.vout[3].scriptPubKey.addresses[0]}, which does not match the Operator address of ${operatorAddr} in the Offer.`)
|
||||
}
|
||||
|
||||
// Ensure the 4th output (vout=3) contains the required amount of BCH.
|
||||
const operatorSatsOut = this.adapters.wallet.bchWallet.bchjs.BitcoinCash.toSatoshi(txObj.vout[3].value)
|
||||
let estimatedOperatorFee = Math.floor(txObj.vout[3].value * orderData.operatorPercentage / 100)
|
||||
if (estimatedOperatorFee < 546) estimatedOperatorFee = 546
|
||||
console.log('operatorSatsOut: ', operatorSatsOut, 'estimatedOperatorFee: ', estimatedOperatorFee)
|
||||
if (operatorSatsOut < estimatedOperatorFee) {
|
||||
console.log(`Skipping: The Counter Offer has an output of ${operatorSatsOut}, which is less than the estimated operator fee of ${estimatedOperatorFee}.`)
|
||||
|
||||
// Add order to list of seen orders, so that we don't spent time trying to validate it again.
|
||||
this.seenOffers.push(eventId)
|
||||
|
||||
return 'N/A'
|
||||
}
|
||||
|
||||
// Get the User ID from the Order model.
|
||||
const userId = orderData.userId
|
||||
|
||||
@@ -717,7 +833,8 @@ class OfferUseCases {
|
||||
await thisOffer.remove()
|
||||
}
|
||||
|
||||
// If the Offer is older than 7 days, delete it.
|
||||
// TODO: Instead of deleting the offer, send the token back to the Makers wallet.
|
||||
// If the Offer is older than a threshold, delete it.
|
||||
const nowMs = now.getTime()
|
||||
const eightWeeks = 1000 * 60 * 60 * 24 * 7 * 8
|
||||
const eightWeeksAgo = nowMs - eightWeeks
|
||||
@@ -794,7 +911,7 @@ class OfferUseCases {
|
||||
if (offerObj.data.dataType === 'counter-offer') {
|
||||
// console.log('Counter offer detected: ', offerObj)
|
||||
// console.log('Counter offer detected: ', offerObj.data.nostrEventId)
|
||||
console.log(`Counter offer detected: https://astral.psfoundation.info/${this.adapters.nostr.eventId2note(offerObj.data.nostrEventId)}`)
|
||||
// console.log(`Counter offer detected: https://astral.psfoundation.info/${this.adapters.nostr.eventId2note(offerObj.data.nostrEventId)}`)
|
||||
await this.acceptCounterOffer(offerObj)
|
||||
}
|
||||
|
||||
@@ -808,6 +925,101 @@ class OfferUseCases {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// List all offers being made by a given address.
|
||||
async listOffersByAddress (addr) {
|
||||
try {
|
||||
const offers = await this.OfferModel.find({ makerAddr: addr })
|
||||
return offers
|
||||
} catch (err) {
|
||||
console.error('Error in listOffersByAddress(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// update offer model mutable data
|
||||
async syncOfferMutableData (tokenId) {
|
||||
try {
|
||||
// validate input
|
||||
if (!tokenId || typeof tokenId !== 'string') {
|
||||
throw new Error('tokenId must be a string!')
|
||||
}
|
||||
|
||||
// validate existing offer with the associated tokenId
|
||||
const offer = await this.OfferModel.findOne({ tokenId })
|
||||
if (!offer) throw new Error('Associated offer not found!')
|
||||
|
||||
// Verify last update timestamp. This prevents users from spamming the API with requests.
|
||||
// It only updates the mutable data if it has been more than 5 minutes since the last update.
|
||||
const lastUpdateTs = Number(offer.lastUpdatedTokenData)
|
||||
const now = new Date().getTime()
|
||||
const period = 5
|
||||
if (lastUpdateTs) {
|
||||
// add 5 minutes to the last update
|
||||
const lastUpdate = new Date(lastUpdateTs)
|
||||
console.log('lastUpdate', lastUpdate)
|
||||
lastUpdate.setMinutes(lastUpdate.getMinutes() + period)
|
||||
|
||||
console.log(new Date().getMinutes() + ' ' + lastUpdate.getMinutes())
|
||||
// if now is less than the lastUpdate + 5 minutos , them skip.
|
||||
if (now < lastUpdate.getTime()) {
|
||||
console.log('Skipping , token lastUpdate is less than 5 minutes.')
|
||||
return offer
|
||||
}
|
||||
}
|
||||
|
||||
let tokenData = null
|
||||
try {
|
||||
tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId)
|
||||
} catch (err) {
|
||||
// Dev Note: If getTokenData() fails, the code below will
|
||||
console.error('Error in OfferUseCases/createOffer() getting token data: ', err.message)
|
||||
}
|
||||
|
||||
// Do additional data analysis if the token data was successfully retrieved.
|
||||
if (tokenData) {
|
||||
// Store the mutable and immutable data cids.
|
||||
const mutableDataCid = tokenData.mutableData
|
||||
const immutableDataCid = tokenData.immutableData
|
||||
offer.mutableDataCid = mutableDataCid
|
||||
offer.immutableDataCid = immutableDataCid
|
||||
|
||||
// Get the mutable data from the cid if it exists.
|
||||
if (mutableDataCid && typeof mutableDataCid === 'string') {
|
||||
let mutableData = null
|
||||
try {
|
||||
mutableData = await this.retryQueue.addToQueue(this.adapters.wallet.cid2json, mutableDataCid)
|
||||
} catch (err) {
|
||||
console.error('Error in OfferUseCases/createOffer() getting mutable data: ', err.message)
|
||||
}
|
||||
console.log('mutableData: ', mutableData)
|
||||
|
||||
if (mutableData) {
|
||||
try {
|
||||
offer.tokenIconUrl = mutableData.tokenIcon
|
||||
offer.tokenCategories = mutableData.category
|
||||
offer.tokenTags = mutableData.tags
|
||||
|
||||
offer.userDataStr = JSON.stringify(mutableData.userData)
|
||||
} catch (error) {
|
||||
// skip error
|
||||
}
|
||||
}
|
||||
}
|
||||
// Save update time stamp
|
||||
offer.lastUpdatedTokenData = new Date().getTime()
|
||||
}
|
||||
|
||||
// Save the updated offer data to the database.
|
||||
await offer.save()
|
||||
|
||||
// Return the updated offer data.
|
||||
return offer
|
||||
} catch (err) {
|
||||
console.error('Error in syncOfferMutableData(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default OfferUseCases
|
||||
|
||||
+10
-7
@@ -44,6 +44,7 @@ class OrderLib {
|
||||
|
||||
if (!entryObj.tokenId) throw new Error('entry does not contain required properties')
|
||||
|
||||
// Get the user data from the database, for the user creating the new Order.
|
||||
const user = await this.UserModel.findById(entryObj.userId)
|
||||
if (!user) throw new Error('user not found')
|
||||
|
||||
@@ -81,13 +82,7 @@ class OrderLib {
|
||||
// await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.optimize, {})
|
||||
await userWallet.optimize()
|
||||
|
||||
// Ensure sufficient tokens exist to create the order.
|
||||
// await this.ensureFunds(orderEntity)
|
||||
// await this.retryQueue.addToQueue(this.ensureFunds, orderEntity)
|
||||
|
||||
// Get Ticker for token ID.
|
||||
// const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
|
||||
// const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTxData, [entryObj.tokenId])
|
||||
const tokenData = await userWallet.getTxData([entryObj.tokenId])
|
||||
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
|
||||
orderEntity.ticker = tokenData[0].tokenTicker
|
||||
@@ -107,15 +102,23 @@ class OrderLib {
|
||||
// await this.adapters.wallet.bchWallet.getUtxos()
|
||||
// await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.initialize, {})
|
||||
await userWallet.initialize()
|
||||
|
||||
// Update the order with the new UTXO information.
|
||||
orderEntity.utxoTxid = utxoInfo.txid
|
||||
orderEntity.utxoVout = utxoInfo.vout
|
||||
orderEntity.tokenType = utxoInfo.tokenType
|
||||
|
||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||
// Add flag to signal that this is a new offer.
|
||||
orderEntity.dataType = 'offer'
|
||||
orderEntity.userId = user._id
|
||||
|
||||
// Add the operator address and percentage to the order.
|
||||
orderEntity.operatorAddress = this.config.operatorAddress
|
||||
orderEntity.operatorPercentage = this.config.operatorPercentage
|
||||
|
||||
// Convert the Seller's BCH private key to a Nostr npub. This is used to identify the seller in Nostr.
|
||||
orderEntity.makerNpub = this.adapters.nostr.privKeyToNpub(userWallet.walletInfo.privateKey)
|
||||
|
||||
// Post the new Order information to Nostr under the topic set in the
|
||||
// config file.
|
||||
const postObj = {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
Social Media Account Use Cases.
|
||||
*/
|
||||
|
||||
class smAccountUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('User localConfig: ', localConfig)
|
||||
|
||||
// Dependency injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of adapters must be passed in when instantiating SM Account Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
// Bind 'this' object to all methods
|
||||
this.checkForNewSmAccounts = this.checkForNewSmAccounts.bind(this)
|
||||
this.listAccounts = this.listAccounts.bind(this)
|
||||
this.getAccountByNpub = this.getAccountByNpub.bind(this)
|
||||
this.getAccountByBchAddr = this.getAccountByBchAddr.bind(this)
|
||||
this.updateSmAccounts = this.updateSmAccounts.bind(this)
|
||||
}
|
||||
|
||||
// Check for new Social Media Accounts.
|
||||
async checkForNewSmAccounts () {
|
||||
try {
|
||||
console.log('Checking for new Social Media Accounts...')
|
||||
|
||||
const messages = await this.adapters.nostr.readGlobalFeed()
|
||||
console.log('messages: ', messages)
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i]
|
||||
// const { content, id, tags, npub, pubkey } = msg
|
||||
const { tags, npub, pubkey } = msg
|
||||
let bchAddr = null
|
||||
// console.log('msg: ', JSON.stringify(msg, null, 2))
|
||||
|
||||
// Find the 'u' tag if it exists
|
||||
const uTag = tags.find(tag => tag[0] === 'u')
|
||||
|
||||
// If 'u' tag exists and contains a BCH address
|
||||
if (uTag && uTag[1].includes('bitcoincash:')) {
|
||||
bchAddr = uTag[1]
|
||||
// console.log('BCH address found:', bchAddr)
|
||||
}
|
||||
|
||||
const SmAccount = await this.adapters.localdb.SmAccount
|
||||
|
||||
if (bchAddr) {
|
||||
const smAccount = await SmAccount.findOne({ npub })
|
||||
if (!smAccount) {
|
||||
console.log(`Creating new Social Media Account with npub: ${npub} and bchAddr: ${bchAddr}`)
|
||||
const newSmAccount = new SmAccount({ npub, bchAddr, pubkey })
|
||||
await newSmAccount.save()
|
||||
} else {
|
||||
console.log(`Social Media Account with npub: ${npub} already exists`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in smAccount-use-cases.js/checkForNewSmAccounts(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// List all Social Media Accounts.
|
||||
async listAccounts (page = 0) {
|
||||
try {
|
||||
const SmAccount = await this.adapters.localdb.SmAccount
|
||||
const accounts = await SmAccount.find({}).skip(page * 10).limit(10)
|
||||
return accounts
|
||||
} catch (err) {
|
||||
console.error('Error in smAccount-use-cases.js/listAccounts(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get a Social Media Account by Npub.
|
||||
async getAccountByNpub (npub) {
|
||||
try {
|
||||
const SmAccount = await this.adapters.localdb.SmAccount
|
||||
const account = await SmAccount.findOne({ npub })
|
||||
return account
|
||||
} catch (err) {
|
||||
console.error('Error in smAccount-use-cases.js/getAccountByNpub(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get a Social Media Account by BCH address.
|
||||
async getAccountByBchAddr (bchAddr) {
|
||||
try {
|
||||
const SmAccount = await this.adapters.localdb.SmAccount
|
||||
const account = await SmAccount.findOne({ bchAddr })
|
||||
console.log('Account found: ', account)
|
||||
|
||||
return account
|
||||
} catch (err) {
|
||||
console.error('Error in smAccount-use-cases.js/getAccountByBchAddr(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Update metadata about social media accounts being tracked.
|
||||
async updateSmAccounts () {
|
||||
try {
|
||||
console.log('Updating Social Media Accounts...')
|
||||
const SmAccount = await this.adapters.localdb.SmAccount
|
||||
const accounts = await SmAccount.find({})
|
||||
for (let i = 0; i < accounts.length; i++) {
|
||||
const account = accounts[i]
|
||||
// console.log(`Account ${i}: `, account)
|
||||
|
||||
// Get the number of accounts following this account
|
||||
const pubkey = account.pubkey
|
||||
const followList = await this.adapters.nostr.getFollowers({ pubkey })
|
||||
// console.log(`Follow list ${i}: `, JSON.stringify(followList, null, 2))
|
||||
|
||||
// Update the follower count for this account.
|
||||
const followerCnt = followList.length
|
||||
await SmAccount.updateOne({ _id: account._id }, { $set: { followerCnt } })
|
||||
}
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in smAccount-use-cases.js/updateSmAccounts(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default smAccountUseCases
|
||||
@@ -3,6 +3,10 @@
|
||||
for tracking the usage of REST API and JSON RPC calls. This library is used
|
||||
by admins to keep an eye on how many API calls were made in a 24-hour and
|
||||
1-hour time period.
|
||||
|
||||
Usage stats are held in memory. But they are periodically backed up to the
|
||||
Mongo database. On startup, the usage stats are loaded from the database.
|
||||
This allows the usage stats to be persisted across restarts.
|
||||
*/
|
||||
|
||||
// This global variable is used to share data between the REST middleware and
|
||||
@@ -19,11 +23,17 @@ class UsageUseCases {
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.UsageModel = this.adapters.localdb.Usage
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.cleanUsage = this.cleanUsage.bind(this)
|
||||
this.getRestSummary = this.getRestSummary.bind(this)
|
||||
this.getTopIps = this.getTopIps.bind(this)
|
||||
this.getTopEndpoints = this.getTopEndpoints.bind(this)
|
||||
this.clearUsage = this.clearUsage.bind(this)
|
||||
this.saveUsage = this.saveUsage.bind(this)
|
||||
this.loadUsage = this.loadUsage.bind(this)
|
||||
|
||||
// State
|
||||
}
|
||||
@@ -108,6 +118,70 @@ class UsageUseCases {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the usage database data
|
||||
async clearUsage () {
|
||||
try {
|
||||
await this.UsageModel.deleteMany({})
|
||||
|
||||
// Debugging: verify the database is empty
|
||||
// Delete this code after debugging
|
||||
const usage = await this.UsageModel.find({})
|
||||
console.log('clearUsage() usage: ', usage)
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in usage-use-cases.js/clearUsage()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Save the usage data to the database
|
||||
async saveUsage (inObj = {}) {
|
||||
try {
|
||||
for (let i = 0; i < restCalls.length; i++) {
|
||||
const thisRestCall = restCalls[i]
|
||||
|
||||
// Debugging: delete this code after debugging
|
||||
// if (i === 5) {
|
||||
// console.log('saveUsage() thisRestCall: ', thisRestCall)
|
||||
// }
|
||||
|
||||
const usageData = {
|
||||
ip: thisRestCall.ip,
|
||||
url: thisRestCall.url,
|
||||
method: thisRestCall.method,
|
||||
timestamp: thisRestCall.timestamp
|
||||
}
|
||||
|
||||
const usage = new this.UsageModel(usageData)
|
||||
await usage.save()
|
||||
}
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in usage-use-cases.js/saveUsage()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Load usage data from the database
|
||||
async loadUsage () {
|
||||
try {
|
||||
const usage = await this.UsageModel.find({})
|
||||
// console.log('usage: ', usage)
|
||||
|
||||
// Debugging: delete this code after debugging
|
||||
if (usage[5]) {
|
||||
console.log('loadUsage() usage[5]: ', usage[5])
|
||||
}
|
||||
|
||||
restCalls = usage
|
||||
return usage
|
||||
} catch (err) {
|
||||
console.error('Error in usage-use-cases.js/loadUsage(): ', err)
|
||||
return false
|
||||
// throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This Koa middleware is called any time there is a REST API. It logs the
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Liveness test runs before all other tests to ensure the server is running.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
import axios from 'axios'
|
||||
import testUtils from '../../utils/test-utils.js'
|
||||
|
||||
// const sinon = require('sinon')
|
||||
|
||||
// Local support libraries
|
||||
import config from '../../../config/index.js'
|
||||
// import Server from '../../../bin/server.js'
|
||||
// import testUtils from '../../utils/test-utils.js'
|
||||
// const adminLib = new AdminLib()
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
describe('#Check Server Liveness', () => {
|
||||
// before(async () => {
|
||||
it('should confirm the server is running', async () => {
|
||||
try {
|
||||
const response = await axios.get(`${LOCALHOST}/`)
|
||||
assert(response.status === 200, 'Server is running, continuing with E2E tests.')
|
||||
} catch (err) {
|
||||
console.log('\nServer is not running, exiting tests.')
|
||||
console.log('Start the server with `npm run start:e2e:server` before running E2E tests.\n')
|
||||
console.log('Ensure running npm run docs before running the test server')
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
it('should confirm the server is running over test enviroment', async () => {
|
||||
try {
|
||||
const res = await testUtils.loginAdminUser()
|
||||
assert.property(res, 'user')
|
||||
assert.property(res, 'token')
|
||||
assert.property(res, 'id')
|
||||
} catch (err) {
|
||||
console.log('\nServer is not running over test enviroment, exiting tests.')
|
||||
console.log('Start the server with `npm run start:e2e:server` before running E2E tests.\n')
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -13,10 +13,8 @@ import axios from 'axios'
|
||||
|
||||
// Local support libraries
|
||||
import config from '../../../config/index.js'
|
||||
import Server from '../../../bin/server.js'
|
||||
// import Server from '../../../bin/server.js'
|
||||
import testUtils from '../../utils/test-utils.js'
|
||||
import AdminLib from '../../../src/adapters/admin.js'
|
||||
const adminLib = new AdminLib()
|
||||
|
||||
// const request = supertest.agent(app.listen())
|
||||
const context = {}
|
||||
@@ -26,10 +24,12 @@ const LOCALHOST = `http://localhost:${config.port}`
|
||||
if (!config.noMongo) {
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
const app = new Server()
|
||||
// const app = new Server()
|
||||
|
||||
// This should be the first instruction. It starts the REST API server.
|
||||
await app.startServer()
|
||||
// await app.startServer()
|
||||
|
||||
// TODO:
|
||||
|
||||
// Stop the IPFS node for the rest of the e2e tests.
|
||||
// await app.controllers.adapters.ipfs.stop()
|
||||
@@ -37,9 +37,6 @@ if (!config.noMongo) {
|
||||
// Delete all previous users in the database.
|
||||
await testUtils.deleteAllUsers()
|
||||
|
||||
// Create a new admin user.
|
||||
await adminLib.createSystemUser()
|
||||
|
||||
const userObj = {
|
||||
email: 'test@test.com',
|
||||
password: 'pass',
|
||||
|
||||
@@ -175,59 +175,6 @@ if (!config.noMongo) {
|
||||
assert.equal(result.data.user.type, 'user')
|
||||
assert.property(result.data.user, 'mnemonic')
|
||||
})
|
||||
it('should reject signup when DISABLE_NEW_ACCOUNTS is true', async () => {
|
||||
try {
|
||||
process.env.DISABLE_NEW_ACCOUNTS = true
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/users`,
|
||||
data: {
|
||||
email: 'test2@test.com',
|
||||
password: 'supersecretpassword',
|
||||
name: 'test3'
|
||||
}
|
||||
}
|
||||
|
||||
await axios(options)
|
||||
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
it('admin can create a user when DISABLE_NEW_ACCOUNTS is true', async () => {
|
||||
process.env.DISABLE_NEW_ACCOUNTS = true
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/users`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.adminJWT}`
|
||||
},
|
||||
data: {
|
||||
user: {
|
||||
email: 'fromAdmin@test.com',
|
||||
password: 'supersecretpassword',
|
||||
name: 'test3'
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
|
||||
context.user = result.data.user
|
||||
context.token = result.data.token
|
||||
|
||||
assert(result.status === 200, 'Status Code 200 expected.')
|
||||
assert(
|
||||
result.data.user.email === 'fromAdmin@test.com',
|
||||
'Email of test expected'
|
||||
)
|
||||
assert(
|
||||
result.data.user.password === undefined,
|
||||
'Password expected to be omited'
|
||||
)
|
||||
assert.property(result.data, 'token', 'Token property exists.')
|
||||
assert.equal(result.data.user.type, 'user')
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users', () => {
|
||||
@@ -349,6 +296,9 @@ if (!config.noMongo) {
|
||||
// assert.equal(err.response.data, 'test error')
|
||||
// }
|
||||
// })
|
||||
// assert.hasAnyKeys(users[0], ['type', '_id', 'email'])
|
||||
// assert.isNumber(users.length)
|
||||
// })
|
||||
})
|
||||
|
||||
describe('GET /users/:id', () => {
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import testUtils from '../../utils/test-utils.js'
|
||||
import { assert } from 'chai'
|
||||
import config from '../../../config/index.js'
|
||||
import axios from 'axios'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const context = {}
|
||||
let sandbox
|
||||
|
||||
// const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
|
||||
if (!config.noMongo) {
|
||||
describe('Nostr', () => {
|
||||
before(async () => {
|
||||
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
|
||||
|
||||
// Create a second test user.
|
||||
const userObj = {
|
||||
email: 'test2@test.com',
|
||||
password: 'pass2',
|
||||
name: 'test2'
|
||||
}
|
||||
const testUser = await testUtils.createUser(userObj)
|
||||
// console.log(`testUser2: ${JSON.stringify(testUser, null, 2)}`)
|
||||
|
||||
context.user2 = testUser.user
|
||||
context.token2 = testUser.token
|
||||
context.id2 = testUser.user._id
|
||||
|
||||
// Get the JWT used to log in as the admin 'system' user.
|
||||
const adminJWT = await testUtils.getAdminJWT()
|
||||
console.log(`adminJWT: ${adminJWT}`)
|
||||
context.adminJWT = adminJWT
|
||||
|
||||
// const admin = await testUtils.loginAdminUser()
|
||||
// context.adminJWT = admin.token
|
||||
|
||||
// const admin = await adminLib.loginAdmin()
|
||||
// console.log(`admin: ${JSON.stringify(admin, null, 2)}`)
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// const useCases = new UseCases({ adapters })
|
||||
// uut = new UserController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('POST /nostr/deletedChat - Create Deleted Chat', () => {
|
||||
it('should not create deleted chat if the authorization header is missing', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/nostr/deletedChat`
|
||||
}
|
||||
await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
}
|
||||
})
|
||||
it('should not create deleted chat if the authorization header is invalid scheme', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/nostr/deletedChat`,
|
||||
headers: {
|
||||
Authorization: 'Bearer invalid'
|
||||
}
|
||||
}
|
||||
|
||||
await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
}
|
||||
})
|
||||
it('should not create deleted chat if the authorization header is invalid token', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/nostr/deletedChat`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.token}`
|
||||
}
|
||||
}
|
||||
|
||||
await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
}
|
||||
})
|
||||
it('should not create deleted chat if token is not admin', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/nostr/deletedChat`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.token}`
|
||||
}
|
||||
}
|
||||
|
||||
await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /nostr/deletedPost - Create Deleted Post', () => {
|
||||
it('should not create deleted post if the authorization header is missing', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/nostr/deletedPost`
|
||||
}
|
||||
await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
}
|
||||
})
|
||||
|
||||
it('should not create deleted post if the authorization header is invalid scheme', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/nostr/deletedPost`,
|
||||
headers: {
|
||||
Authorization: 'Bearer invalid'
|
||||
}
|
||||
}
|
||||
await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
}
|
||||
})
|
||||
it('should not create deleted post if the authorization header is invalid token', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/nostr/deletedPost`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.token}`
|
||||
}
|
||||
}
|
||||
await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
}
|
||||
})
|
||||
it('should not create deleted post if token is not admin', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/nostr/deletedPost`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.token}`
|
||||
}
|
||||
}
|
||||
await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -29,8 +29,7 @@ describe('#adapters', () => {
|
||||
uut.config.getJwtAtStartup = true
|
||||
uut.config.useIpfs = true
|
||||
uut.config.env = 'not-a-test'
|
||||
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
|
||||
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
|
||||
sandbox.stub(uut.wallet, 'instanceWallet').resolves()
|
||||
sandbox.stub(uut.ipfs, 'start').resolves()
|
||||
|
||||
const result = await uut.start()
|
||||
@@ -38,6 +37,22 @@ describe('#adapters', () => {
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should not start ipfs on test enviroment', async () => {
|
||||
// Mock dependencies
|
||||
uut.config.getJwtAtStartup = true
|
||||
uut.config.useIpfs = true
|
||||
uut.config.env = 'test'
|
||||
|
||||
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
|
||||
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
|
||||
const ipfsSpy = sandbox.stub(uut.ipfs, 'start').resolves(null)
|
||||
|
||||
const result = await uut.start()
|
||||
|
||||
assert.isTrue(ipfsSpy.notCalled)
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
|
||||
+7
-19
@@ -19,26 +19,13 @@ describe('Admin', () => {
|
||||
|
||||
if (!config.noMongo) {
|
||||
describe('loginAdmin()', () => {
|
||||
it('should logind admin', async () => {
|
||||
it('should login admin', async () => {
|
||||
try {
|
||||
const error = new Error('test error')
|
||||
error.response = {
|
||||
status: 422
|
||||
}
|
||||
// sandbox.stub(uut.axios, 'request').onFirstCall().throws(error)
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'pass' })
|
||||
sandbox.stub(uut.axios, 'request').resolves(true)
|
||||
|
||||
const result = await uut.loginAdmin()
|
||||
const user = result.data.user
|
||||
|
||||
assert.property(user, '_id')
|
||||
assert.property(user, 'email')
|
||||
assert.property(user, 'type')
|
||||
|
||||
assert.isString(user._id)
|
||||
assert.isString(user.email)
|
||||
assert.isString(user.type)
|
||||
|
||||
assert.equal(user.type, 'admin')
|
||||
assert.isTrue(result)
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
@@ -48,13 +35,13 @@ describe('Admin', () => {
|
||||
try {
|
||||
// Returns an erroneous password to force
|
||||
// an auth error
|
||||
sandbox.stub(uut.axios, 'request').throws(new Error('test error'))
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
|
||||
|
||||
await uut.loginAdmin()
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
assert.include(err.response.data, 'Unauthorized')
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -84,6 +71,7 @@ describe('Admin', () => {
|
||||
}
|
||||
|
||||
sandbox.stub(uut.User, 'findOne').resolves(fakeUser)
|
||||
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves(true)
|
||||
const result = await uut.createSystemUser()
|
||||
|
||||
assert.property(result, 'email')
|
||||
@@ -135,18 +135,19 @@ describe('bch', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// 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')
|
||||
// }
|
||||
// })
|
||||
/* 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) {
|
||||
console.log('err: ', err)
|
||||
assert.fail('Unexpected result')
|
||||
}
|
||||
}) */
|
||||
})
|
||||
})
|
||||
|
||||
@@ -128,4 +128,129 @@ describe('#nostr.js', () => {
|
||||
assert.equal(result.length, 1)
|
||||
})
|
||||
})
|
||||
describe('#eventId2note', () => {
|
||||
it('should handle encode error', async () => {
|
||||
try {
|
||||
await uut.eventId2note('invalid format')
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'Invalid byte sequence')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle valid eventId', async () => {
|
||||
const result = await uut.eventId2note('b1e4a3d5e40b6d0b3f1e2c234b6c9dbf4d6a80e9b3e8a2d765c7f9ed84c9a5ef')
|
||||
assert.isString(result)
|
||||
assert.include(result, 'note')
|
||||
})
|
||||
})
|
||||
describe('#pubkey2npub', () => {
|
||||
it('should handle encode error', async () => {
|
||||
try {
|
||||
await uut.pubkey2npub('invalid format')
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'Invalid byte sequence')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle valid pubkey', async () => {
|
||||
const result = await uut.pubkey2npub('f5a1b39d2cfae8e10e93b9b4d7ec8248eaf45fbe4a73409d8d4c67015e9729a3')
|
||||
assert.isString(result)
|
||||
assert.include(result, 'npub')
|
||||
})
|
||||
})
|
||||
describe('#npub2pubkey', () => {
|
||||
it('should handle decode error', async () => {
|
||||
try {
|
||||
await uut.npub2pubkey('npub17ksm88fvlt5wzr5nhx6d0myzfr40g')
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'Invalid checksum')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle valid pubkey', async () => {
|
||||
const result = await uut.npub2pubkey('npub17ksm88fvlt5wzr5nhx6d0myzfr40gha7ffe5p8vdf3nszh5h9x3sdwl7u2')
|
||||
assert.isObject(result)
|
||||
assert.property(result, 'type')
|
||||
assert.property(result, 'data')
|
||||
assert.equal(result.type, 'npub')
|
||||
assert.equal(result.data.length, 64)
|
||||
})
|
||||
})
|
||||
describe('#readGlobalFeed', () => {
|
||||
it('should throw an error if provided limit is wrong', async () => {
|
||||
try {
|
||||
await uut.readGlobalFeed({ limit: -5 })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'Limit must be greater than 0')
|
||||
}
|
||||
})
|
||||
|
||||
it('should read message', async () => {
|
||||
const result = await uut.readGlobalFeed()
|
||||
assert.isArray(result)
|
||||
})
|
||||
|
||||
it('should read message with custom limit', async () => {
|
||||
const result = await uut.readGlobalFeed({ limit: 1 })
|
||||
assert.isArray(result)
|
||||
})
|
||||
})
|
||||
describe('#getFollowers', () => {
|
||||
it('should throw an error if no pubkey is provided', async () => {
|
||||
try {
|
||||
await uut.getFollowers()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'pubkey must be a string!')
|
||||
}
|
||||
})
|
||||
|
||||
it('should get followers', async () => {
|
||||
const result = await uut.getFollowers({ pubkey: 'f5a1b39d2cfae8e10e93b9b4d7ec8248eaf45fbe4a73409d8d4c67015e9729a3' })
|
||||
assert.isArray(result)
|
||||
})
|
||||
})
|
||||
describe('#privKeyToNpub', () => {
|
||||
it('should handle error if providedprivKey is not a string', async () => {
|
||||
try {
|
||||
uut.privKeyToNpub()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'privKey must be a string!')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return npub if privKey is valid', async () => {
|
||||
const result = uut.privKeyToNpub('L2kh34VinQNRq1ad8nnXnGoFrb9pT7j8GJXiT6Scsg18KMxsUVDr')
|
||||
// console.log('result: ', result)
|
||||
assert.isString(result)
|
||||
assert.include(result, 'npub')
|
||||
})
|
||||
it('should handle unexpected error', async () => {
|
||||
try {
|
||||
uut.privKeyToNpub('invalid format')
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'Invalid base58 character')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,6 +52,7 @@ describe('#OrderPagination', () => {
|
||||
nostrEventId: 'nostrEventId',
|
||||
dataType: 'dataType',
|
||||
makerAddr: 'makerAddr',
|
||||
makerNpub: 'makerNpub',
|
||||
rateInBaseUnit: 'rateInBaseUnit',
|
||||
minUnitsToExchange: 'minUnitsToExchange',
|
||||
ticker: 'ticker',
|
||||
|
||||
@@ -41,5 +41,17 @@ describe('#passport.js', () => {
|
||||
assert.include(err.message, 'cant auth user')
|
||||
}
|
||||
})
|
||||
it('should authenticate user', async () => {
|
||||
const ctx = {}
|
||||
const errMock = null
|
||||
const userMock = {
|
||||
_id: '123',
|
||||
email: 'test@test.com'
|
||||
}
|
||||
sandbox.stub(uut.passport, 'authenticate').yields(errMock, userMock)
|
||||
const user = await uut.authUser(ctx)
|
||||
assert.equal(user._id, userMock._id)
|
||||
assert.equal(user.email, userMock.email)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -55,6 +55,13 @@ describe('#User-Adapter', () => {
|
||||
|
||||
assert.notEqual(testuser.password, 'password')
|
||||
})
|
||||
it('should ignore password encrption if password property is not provided', async () => {
|
||||
const lastPassword = testuser.password
|
||||
await testuser.save()
|
||||
// console.log('testuser: ', testuser)
|
||||
|
||||
assert.equal(testuser.password, lastPassword)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validatePassword', () => {
|
||||
|
||||
@@ -9,24 +9,56 @@ import sinon from 'sinon'
|
||||
|
||||
import WalletAdapter from '../../../src/adapters/wallet.js'
|
||||
import { MockBchWallet } from '../mocks/adapters/wallet.js'
|
||||
import { BchTokenSweepMock } from '../mocks/bch-token-sweep-mock.js'
|
||||
import offerMockData from '../mocks/use-cases/offer-mock-data.js'
|
||||
import fs from 'fs'
|
||||
import * as url from 'url'
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const testWalletFile = `${__dirname.toString()}/../../../test-wallet.json`
|
||||
|
||||
describe('#wallet', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let testWalletData
|
||||
|
||||
before(() => {
|
||||
// try to remove the test wallet file
|
||||
try {
|
||||
fs.unlinkSync(testWalletFile)
|
||||
} catch (error) {
|
||||
console.log('error fsUnlinkSync: ', error)
|
||||
|
||||
// silently fail
|
||||
}
|
||||
})
|
||||
after(() => {
|
||||
// try to remove the test wallet file
|
||||
try {
|
||||
fs.unlinkSync(testWalletFile)
|
||||
} catch (error) {
|
||||
console.log('error fsUnlinkSync: ', error)
|
||||
// silently fail
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new WalletAdapter()
|
||||
uut.BchWallet = MockBchWallet
|
||||
uut.bchWallet = new MockBchWallet()
|
||||
uut.BchTokenSweep = BchTokenSweepMock
|
||||
uut.WALLET_FILE = testWalletFile
|
||||
uut.bitcoinJs = {
|
||||
Transaction: {
|
||||
fromHex: () => { return { } },
|
||||
SIGHASH_ALL: () => { return { } }
|
||||
fromHex: () => { return {} },
|
||||
SIGHASH_ALL: () => { return {} }
|
||||
},
|
||||
TransactionBuilder: {
|
||||
fromTransaction: () => { return { sign: () => { return { } }, build: () => { return { toHex: () => { return 'hex' } } } } }
|
||||
fromTransaction: () => { return { sign: () => { return {} }, build: () => { return { toHex: () => { return 'hex' } } } } }
|
||||
},
|
||||
ECPair: {
|
||||
fromWIF: () => { return { } }
|
||||
fromWIF: () => { return {} }
|
||||
}
|
||||
}
|
||||
sandbox = sinon.createSandbox()
|
||||
@@ -34,6 +66,278 @@ describe('#wallet', () => {
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#openWallet', () => {
|
||||
it('should create a new wallet when wallet file does not exist', async () => {
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
const result = await uut.openWallet()
|
||||
testWalletData = result
|
||||
// console.log('result: ', result)
|
||||
assert.property(result, 'mnemonic')
|
||||
assert.property(result, 'privateKey')
|
||||
assert.property(result, 'publicKey')
|
||||
assert.property(result, 'cashAddress')
|
||||
assert.property(result, 'address')
|
||||
assert.property(result, 'slpAddress')
|
||||
assert.property(result, 'legacyAddress')
|
||||
assert.property(result, 'hdPath')
|
||||
})
|
||||
|
||||
it('should open existing wallet file', async () => {
|
||||
// This test case uses the file created in the previous test case.
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
const result = await uut.openWallet()
|
||||
// console.log('result: ', result)
|
||||
assert.property(result, 'mnemonic')
|
||||
assert.property(result, 'privateKey')
|
||||
assert.property(result, 'publicKey')
|
||||
assert.property(result, 'cashAddress')
|
||||
assert.property(result, 'address')
|
||||
assert.property(result, 'slpAddress')
|
||||
assert.property(result, 'legacyAddress')
|
||||
assert.property(result, 'hdPath')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
uut.WALLET_FILE = ''
|
||||
// Force an error
|
||||
uut.BchWallet = () => {
|
||||
}
|
||||
await uut.openWallet()
|
||||
// console.log('result: ', result)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'this.BchWallet is not a constructor')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#instanceWallet', () => {
|
||||
it('should create an instance of BchWallet with rest-api', async () => {
|
||||
uut.config.useFullStackCash = true
|
||||
const result = await uut.instanceWallet(testWalletData)
|
||||
assert.equal(result.advancedOptions.interface, 'rest-api')
|
||||
})
|
||||
|
||||
it('should create an instance of BchWallet with consumer-api', async () => {
|
||||
uut.config.useFullStackCash = false
|
||||
const result = await uut.instanceWallet(testWalletData)
|
||||
assert.equal(result.advancedOptions.interface, 'consumer-api')
|
||||
})
|
||||
it('should catch and throw an error if no mnemonic is provided', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
await uut.instanceWallet({})
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'Wallet data is not formatted correctly. Can not read mnemonic in wallet file!')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#incrementNextAddress', () => {
|
||||
it('should increment the nextAddress property', async () => {
|
||||
const result = await uut.incrementNextAddress()
|
||||
assert.equal(result, 2)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut, 'openWallet').rejects(new Error('test error'))
|
||||
await uut.incrementNextAddress()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getKeyPair', () => {
|
||||
it('should return an object with a key pair', async () => {
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
// uut.WALLET_FILE = testWalletFile
|
||||
uut.config.walletFile = testWalletFile
|
||||
|
||||
const result = await uut.getKeyPair()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'cashAddress')
|
||||
assert.property(result, 'wif')
|
||||
assert.property(result, 'hdIndex')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut, 'incrementNextAddress')
|
||||
.rejects(new Error('test error'))
|
||||
await uut.getKeyPair()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#generateSignature', () => {
|
||||
it('should sign message', async () => {
|
||||
const result = await uut.generateSignature('unit test message')
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.generateSignature()
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'message is required!')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#generatePartialTx', () => {
|
||||
it('should generate tx for fungible token', async () => {
|
||||
const utxoInfo = {
|
||||
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
vout: 0,
|
||||
sats: 1000,
|
||||
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
|
||||
}
|
||||
|
||||
sandbox.stub(uut.bchWallet, 'getTxData').resolves([offerMockData.fungibleTokenData01.genesisData])
|
||||
const result = await uut.generatePartialTx(offerMockData.fungibleOffer01, utxoInfo)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
it('should generate tx for nft', async () => {
|
||||
const utxoInfo = {
|
||||
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
vout: 0,
|
||||
sats: 1000,
|
||||
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
|
||||
}
|
||||
|
||||
sandbox.stub(uut.bchWallet, 'getTxData').resolves([offerMockData.nftTokenData01.genesisData])
|
||||
const result = await uut.generatePartialTx(offerMockData.nftOffer01, utxoInfo)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should handle unknown token type', async () => {
|
||||
try {
|
||||
const utxoInfo = {
|
||||
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
vout: 0,
|
||||
sats: 1000,
|
||||
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
|
||||
}
|
||||
|
||||
const offerMock = Object.assign({}, offerMockData.nftOffer01)
|
||||
offerMock.tokenType = 100
|
||||
await uut.generatePartialTx(offerMock, utxoInfo)
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Unknown token type')
|
||||
}
|
||||
})
|
||||
it('should handle multiple outputs', async () => {
|
||||
const utxoInfo = {
|
||||
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
vout: 0,
|
||||
sats: 1000,
|
||||
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
|
||||
}
|
||||
|
||||
sandbox.stub(uut.bchWallet, 'getTxData').resolves([offerMockData.nftTokenData01.genesisData])
|
||||
sandbox.stub(uut.bchWallet.bchjs.SLP.NFT1, 'generateNFTChildSendOpReturn').returns({ outputs: 5 })
|
||||
const res = await uut.generatePartialTx(offerMockData.nftOffer01, utxoInfo)
|
||||
assert.isUndefined(res)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#moveTokens', () => {
|
||||
it('should move tokens', async () => {
|
||||
sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
cashAddress: 'cashAddress',
|
||||
wif: 'wif',
|
||||
hdIndex: 11
|
||||
})
|
||||
const input = {
|
||||
tokenId: uut.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens[0].tokenId,
|
||||
qty: 1
|
||||
}
|
||||
const res = await uut.moveTokens(input)
|
||||
assert.isObject(res)
|
||||
})
|
||||
|
||||
it('should throw an error if the tokenId is not found', async () => {
|
||||
try {
|
||||
sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
cashAddress: 'cashAddress',
|
||||
wif: 'wif',
|
||||
hdIndex: 11
|
||||
})
|
||||
const input = {
|
||||
tokenId: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
qty: 1
|
||||
}
|
||||
await uut.moveTokens(input)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'tokenId not found!')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if the tokenId is not provided', async () => {
|
||||
try {
|
||||
await uut.moveTokens({
|
||||
qty: 1
|
||||
})
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'tokenId must be a string!')
|
||||
}
|
||||
})
|
||||
it('should throw an error if the qty is not provided', async () => {
|
||||
try {
|
||||
await uut.moveTokens({
|
||||
tokenId: 'tokenId'
|
||||
})
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'qty must be a number!')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#moveBch', () => {
|
||||
it('should move bch', async () => {
|
||||
sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
cashAddress: 'cashAddress',
|
||||
wif: 'wif',
|
||||
hdIndex: 11
|
||||
})
|
||||
|
||||
const res = await uut.moveBch(100)
|
||||
assert.isObject(res)
|
||||
})
|
||||
|
||||
it('should throw an error if the amountSat is not provided', async () => {
|
||||
try {
|
||||
sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
cashAddress: 'cashAddress',
|
||||
wif: 'wif',
|
||||
hdIndex: 11
|
||||
})
|
||||
|
||||
await uut.moveBch()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'amountSat is required!')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#moveTokensFromCustomWallet', () => {
|
||||
it('should move tokens from a custom wallet', async () => {
|
||||
sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
@@ -100,28 +404,27 @@ describe('#wallet', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#deseralizeTx', () => {
|
||||
it('should throw an error if the hex is not provided', async () => {
|
||||
try {
|
||||
await uut.deseralizeTx()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'hex must be a string!')
|
||||
}
|
||||
})
|
||||
it('should decode hex', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.bchWallet.bchjs.RawTransactions, 'decodeRawTransaction').resolves({})
|
||||
const hex = '020000000286b2959cccdb80163b68bb1360abac8e0228ba7b70c779443168cc65c7c42d820100000000ffffffff879b066729eda979f1f062c83b7eccdb9e77f49b417e4723864b3861bf061c24000000006b483045022100b99c6161e18e4e34c1e438b6b03e808703695f217b30da42c3f787798a9f40cc02206d26b4f6799735779d4fac823bc60d276fbb3cea91c66dbfa136cf5b1bd0d8324121028dcd38a58c8295dbd105ef524014f77af4cddfa19b26c50926b4aaeebd95c7ceffffffff030000000000000000376a04534c500001010453454e4420a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b208000000000000006422020000000000001976a914bef6c5180648165704f22e71a1b011ba56336aee88acf81d0000000000001976a914c1f16a3876f8fbe3701a66d3cb3b9c7abb07a06c88ac00000000'
|
||||
const result = await uut.deseralizeTx(hex)
|
||||
assert.isObject(result)
|
||||
} catch (error) {
|
||||
assert.fail('Unexpected code path')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#completeTx', () => {
|
||||
// TODO: This test needs to be refactored to use the new instanceWallet() method for loading the user wallet.
|
||||
|
||||
// it('should complete a transaction', async () => {
|
||||
// sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
// cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
// wif: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
|
||||
// hdIndex: 11
|
||||
// })
|
||||
// sandbox.stub(uut, 'deseralizeTx').resolves({
|
||||
// txid: 'complete tx id result'
|
||||
// })
|
||||
// sandbox.stub(uut.retryQueue, 'addToQueue').resolves('complete tx id result')
|
||||
|
||||
// const hex = 'hex'
|
||||
// const hdIndex = 11
|
||||
// const mnemonic = 'course abstract aerobic deer try switch turtle diet fence affair butter top'
|
||||
|
||||
// const txid = await uut.completeTx(hex, hdIndex, mnemonic)
|
||||
// assert.equal(txid, 'complete tx id result')
|
||||
// })
|
||||
|
||||
it('should throw an error if the hex is not provided', async () => {
|
||||
try {
|
||||
const hdIndex = 11
|
||||
@@ -134,11 +437,91 @@ describe('#wallet', () => {
|
||||
it('should throw an error if the hdIndex is not provided', async () => {
|
||||
try {
|
||||
const hex = 'hex'
|
||||
await uut.completeTx(hex, null)
|
||||
await uut.completeTx(hex)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'hdIndex must be a non-negative number!')
|
||||
}
|
||||
})
|
||||
it('should throw an error if the mnemonic is not provided', async () => {
|
||||
try {
|
||||
const hex = 'hex'
|
||||
const hdIndex = 11
|
||||
await uut.completeTx(hex, hdIndex)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'mnemonic must be a string!')
|
||||
}
|
||||
})
|
||||
it('should complete a transaction', async () => {
|
||||
sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
wif: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
|
||||
hdIndex: 11
|
||||
})
|
||||
sandbox.stub(uut, 'deseralizeTx').resolves({
|
||||
txid: 'complete tx id result'
|
||||
})
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').resolves('complete tx id result')
|
||||
|
||||
const hex = 'hex'
|
||||
const hdIndex = 11
|
||||
const mnemonic = 'course abstract aerobic deer try switch turtle diet fence affair butter top'
|
||||
|
||||
const txid = await uut.completeTx(hex, hdIndex, mnemonic)
|
||||
assert.equal(txid, 'complete tx id result')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#reClaimTokens', () => {
|
||||
it('should re-claim tokens', async () => {
|
||||
const orderData = {
|
||||
hdIndex: 11,
|
||||
tokenId: 'tokenId',
|
||||
numTokens: 1
|
||||
}
|
||||
const res = await uut.reclaimTokens(orderData)
|
||||
assert.equal(res, 'fakeTxid')
|
||||
})
|
||||
it('should handle error if order hd index is not provided', async () => {
|
||||
try {
|
||||
const orderData = {}
|
||||
await uut.reclaimTokens(orderData)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'orderData.hdIndex is required!')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#cid2json', () => {
|
||||
it('should throw error if cid is not provided', async () => {
|
||||
try {
|
||||
await uut.cid2json()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'urlOrCid must be a string!')
|
||||
}
|
||||
})
|
||||
|
||||
it('should convert cid to json', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.bchWallet, 'cid2json').resolves({ json: offerMockData.mutableDataMock })
|
||||
const cid = 'bafkreidr6wfd6mcmwpea7abm5uk5rrprc2wfbcvo5wdcmtlolrpjab5oqm'
|
||||
const result = await uut.cid2json(cid)
|
||||
assert.isObject(result)
|
||||
} catch (error) {
|
||||
assert.fail('Unexpected code path')
|
||||
}
|
||||
})
|
||||
it('should convert ipfs url to json', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.bchWallet, 'cid2json').resolves({ json: offerMockData.mutableDataMock })
|
||||
const url = 'https://ipfs.io/ipfs/bafkreidr6wfd6mcmwpea7abm5uk5rrprc2wfbcvo5wdcmtlolrpjab5oqm'
|
||||
const result = await uut.cid2json(url)
|
||||
assert.isObject(result)
|
||||
} catch (error) {
|
||||
assert.fail('Unexpected code path')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -65,4 +65,51 @@ describe('#Webhook-Adapter', () => {
|
||||
assert.isString(result.id)
|
||||
})
|
||||
})
|
||||
describe('#deleteWebhook', () => {
|
||||
it('should throw error if input is not provided', async () => {
|
||||
try {
|
||||
await uut.deleteWebhook()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'url must be a string')
|
||||
}
|
||||
})
|
||||
it('should delete webhook', async () => {
|
||||
sandbox.stub(uut.axios, 'delete').resolves({ data: { success: true } })
|
||||
const url = 'https://test.com/my-webhook'
|
||||
const result = await uut.deleteWebhook(url)
|
||||
assert.isObject(result)
|
||||
assert.property(result, 'success')
|
||||
})
|
||||
})
|
||||
describe('#waitUntilSuccess', () => {
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut, 'sleep').throws(new Error('test error'))
|
||||
sandbox.stub(uut, 'deleteWebhook')
|
||||
.onCall(0).throws(new Error('test error'))
|
||||
|
||||
sandbox.stub(uut, 'createWebhook')
|
||||
.onCall(0).throws(new Error('test error'))
|
||||
.onCall(1).resolves({ data: { success: true } })
|
||||
await uut.waitUntilSuccess()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
it('should wait until success', async () => {
|
||||
uut.sleepTime = 100
|
||||
|
||||
sandbox.stub(uut, 'deleteWebhook')
|
||||
.onCall(0).throws(new Error('test error'))
|
||||
|
||||
sandbox.stub(uut, 'createWebhook')
|
||||
.onCall(0).throws(new Error('test error'))
|
||||
.onCall(1).resolves({ data: { success: true } })
|
||||
|
||||
const result = await uut.waitUntilSuccess()
|
||||
assert.isTrue(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,4 +38,25 @@ describe('#Controllers', () => {
|
||||
await uut.attachControllers(app)
|
||||
})
|
||||
})
|
||||
describe('#attachRESTControllers', () => {
|
||||
it('should attach the controllers', async () => {
|
||||
const app = {
|
||||
use: () => {}
|
||||
}
|
||||
|
||||
await uut.attachRESTControllers(app)
|
||||
})
|
||||
})
|
||||
describe('#initAdapters', () => {
|
||||
it('should attach the controllers', async () => {
|
||||
sandbox.stub(uut.adapters, 'start').resolves({})
|
||||
await uut.initAdapters()
|
||||
})
|
||||
})
|
||||
describe('#initUseCases', () => {
|
||||
it('should attach the controllers', async () => {
|
||||
sandbox.stub(uut.useCases, 'start').resolves({})
|
||||
await uut.initUseCases()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -79,11 +79,22 @@ describe('#Auth-REST-Router', () => {
|
||||
await uut.authUser(ctx)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
it('should catch and throw passport error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.passport, 'authUser').rejects('test error')
|
||||
|
||||
await uut.authUser(ctx)
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'Unauthorized')
|
||||
}
|
||||
})
|
||||
it('should handle error if user is not found!', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.passport, 'authUser').resolves(null)
|
||||
|
||||
await uut.authUser(ctx)
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /nostr endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local support libraries
|
||||
import adapters from '../../../mocks/adapters/index.js'
|
||||
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||
import NostrController from '../../../../../src/controllers/rest-api/nostr/controller.js'
|
||||
|
||||
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
|
||||
let uut
|
||||
let sandbox
|
||||
let ctx
|
||||
|
||||
describe('#Nostr-REST-Controller', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new NostrController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
ctx = mockContext()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new NostrController()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating /nostr REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new NostrController({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating /nostr REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#POST /nostr/deletedChat', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.useCases.nostr, 'createDeletedChat').rejects(new Error('test error'))
|
||||
await uut.createDeletedChat(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
ctx.request.body = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||
}
|
||||
|
||||
await uut.createDeletedChat(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
|
||||
assert.isObject(ctx.response.body)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#GET /nostr/deletedChat', () => {
|
||||
it('should return 422 status on arbitrary biz logic error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.useCases.nostr, 'getDeletedChats')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await uut.getDeletedChats(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
await uut.getDeletedChats(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
|
||||
// Assert that expected properties exist in the returned data.
|
||||
assert.isObject(ctx.response.body)
|
||||
assert.property(ctx.response.body, 'deletedChats')
|
||||
assert.isArray(ctx.response.body.deletedChats)
|
||||
})
|
||||
})
|
||||
describe('#POST /nostr/deletedPost', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.useCases.nostr, 'createDeletedPost').rejects(new Error('test error'))
|
||||
await uut.createDeletedPost(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
it('should return 200 status on success', async () => {
|
||||
ctx.request.body = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||
}
|
||||
|
||||
await uut.createDeletedPost(ctx)
|
||||
|
||||
assert.equal(ctx.status, 200)
|
||||
|
||||
assert.isObject(ctx.response.body)
|
||||
assert.property(ctx.response.body, 'deletedPost')
|
||||
assert.isObject(ctx.response.body.deletedPost)
|
||||
})
|
||||
})
|
||||
describe('#GET /nostr/deletedPost', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.useCases.nostr, 'getDeletedPosts').rejects(new Error('test error'))
|
||||
await uut.getDeletedPosts(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
it('should return 200 status on success', async () => {
|
||||
await uut.getDeletedPosts(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
|
||||
assert.isObject(ctx.response.body)
|
||||
assert.property(ctx.response.body, 'deletedPosts')
|
||||
assert.isArray(ctx.response.body.deletedPosts)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#handleError', () => {
|
||||
it('should still throw error if there is no message', () => {
|
||||
try {
|
||||
const err = {
|
||||
status: 404
|
||||
}
|
||||
|
||||
uut.handleError(ctx, err)
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Not Found')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw error with message', () => {
|
||||
try {
|
||||
const err = {
|
||||
status: 422,
|
||||
message: 'test error'
|
||||
}
|
||||
|
||||
uut.handleError(ctx, err)
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /nostr endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local support libraries
|
||||
import adapters from '../../../mocks/adapters/index.js'
|
||||
|
||||
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||
|
||||
// const app = require('../../../mocks/app-mock')
|
||||
|
||||
import NostrRouter from '../../../../../src/controllers/rest-api/nostr/index.js'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
// let ctx
|
||||
|
||||
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('#Nostr-REST-Router', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new NostrRouter({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
// ctx = mockContext()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new NostrRouter()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating NostrRouter REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new NostrRouter({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating NostrRouter REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#attach', () => {
|
||||
it('should throw an error if app is not passed in.', () => {
|
||||
try {
|
||||
uut.attach()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass app object when attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createDeletedChat', () => {
|
||||
it('should route to the createDeletedChat function', async () => {
|
||||
// Stub functions
|
||||
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
|
||||
sandbox.stub(uut.nostrRESTController, 'createDeletedChat').resolves(true)
|
||||
|
||||
// Call function
|
||||
const res = await uut.createDeletedChat()
|
||||
assert.isTrue(validationSpy.calledOnce, 'Admin validator should be called')
|
||||
assert.isTrue(res)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createDeletedPost', () => {
|
||||
it('should route to the createDeletedPost function', async () => {
|
||||
// Stub functions
|
||||
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
|
||||
sandbox.stub(uut.nostrRESTController, 'createDeletedPost').resolves(true)
|
||||
|
||||
// Call function
|
||||
const res = await uut.createDeletedPost()
|
||||
assert.isTrue(validationSpy.calledOnce, 'Admin validator should be called')
|
||||
assert.isTrue(res)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -91,25 +91,164 @@ describe('#Offer-REST-Router', () => {
|
||||
// assert.equal(ctx.body.hash, 'testHash')
|
||||
})
|
||||
|
||||
// it('should catch and throw an error', async () => {
|
||||
// try {
|
||||
// ctx.request.body = {
|
||||
// offer: {}
|
||||
// }
|
||||
//
|
||||
// // Force an error
|
||||
// sandbox
|
||||
// .stub(uut.useCases.offer, 'createOffer')
|
||||
// .rejects(new Error('test error'))
|
||||
//
|
||||
// await uut.createOffer(ctx)
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// // console.log('err: ', err)
|
||||
// assert.include(err.message, 'test error')
|
||||
// }
|
||||
// })
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.request.body = {
|
||||
offer: {}
|
||||
}
|
||||
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.useCases.offer, 'createOffer')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await uut.createOffer(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#listOffers', () => {
|
||||
it('should list offers', async () => {
|
||||
ctx.params = { page: 0 }
|
||||
sandbox.stub(uut.useCases.offer, 'listOffers').resolves([])
|
||||
await uut.listOffers(ctx)
|
||||
assert.isArray(ctx.body)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.params = { }
|
||||
sandbox
|
||||
.stub(uut.useCases.offer, 'listOffers')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.listOffers(ctx)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#listNftOffers', () => {
|
||||
it('should list nft offers', async () => {
|
||||
ctx.params = { page: 0 }
|
||||
sandbox.stub(uut.useCases.offer, 'listNftOffers').resolves([])
|
||||
await uut.listNftOffers(ctx)
|
||||
assert.isArray(ctx.body)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.params = { page: 0 }
|
||||
sandbox
|
||||
.stub(uut.useCases.offer, 'listNftOffers')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.listNftOffers(ctx)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#listFungibleOffers', () => {
|
||||
it('should list fungible offers', async () => {
|
||||
ctx.params = { page: 0 }
|
||||
sandbox.stub(uut.useCases.offer, 'listFungibleOffers').resolves([])
|
||||
await uut.listFungibleOffers(ctx)
|
||||
assert.isArray(ctx.body)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.params = { page: 0 }
|
||||
sandbox
|
||||
.stub(uut.useCases.offer, 'listFungibleOffers')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.listFungibleOffers(ctx)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#takeOffer', () => {
|
||||
it('should take an offer', async () => {
|
||||
ctx.request.body = {
|
||||
nostrEventId: 'testEventId'
|
||||
}
|
||||
|
||||
sandbox.stub(uut.useCases.offer, 'takeOffer').resolves({ eventId: 'testEventId', noteId: 'testNoteId' })
|
||||
await uut.takeOffer(ctx)
|
||||
assert.isObject(ctx.body)
|
||||
assert.property(ctx.body, 'eventId')
|
||||
assert.property(ctx.body, 'noteId')
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.request.body = {
|
||||
nostrEventId: 'testEventId'
|
||||
}
|
||||
|
||||
sandbox
|
||||
.stub(uut.useCases.offer, 'takeOffer')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.takeOffer(ctx)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#listOffersByAddress', () => {
|
||||
it('should list offers by address', async () => {
|
||||
ctx.params = { addr: 'testAddress' }
|
||||
sandbox.stub(uut.useCases.offer, 'listOffersByAddress').resolves([])
|
||||
await uut.listOffersByAddress(ctx)
|
||||
assert.isArray(ctx.body)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.params = { addr: 'testAddress' }
|
||||
sandbox
|
||||
.stub(uut.useCases.offer, 'listOffersByAddress')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.listOffersByAddress(ctx)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#syncOfferMutableData', () => {
|
||||
it('should sync mutable data', async () => {
|
||||
ctx.request.body = { tokenId: 'tokenId' }
|
||||
sandbox.stub(uut.useCases.offer, 'syncOfferMutableData').resolves({})
|
||||
await uut.syncOfferMutableData(ctx)
|
||||
assert.isObject(ctx.body)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.request.body = { tokenId: 'tokenId' }
|
||||
sandbox
|
||||
.stub(uut.useCases.offer, 'syncOfferMutableData')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.syncOfferMutableData(ctx)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#handleError', () => {
|
||||
|
||||
@@ -101,7 +101,47 @@ describe('#Order-REST-Router', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#listOrders', () => {
|
||||
it('should list orders', async () => {
|
||||
ctx.params = { page: 0 }
|
||||
sandbox.stub(uut.useCases.order, 'listOrders').resolves([])
|
||||
await uut.listOrders(ctx)
|
||||
assert.isArray(ctx.body)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.params = { page: 0 }
|
||||
sandbox.stub(uut.useCases.order, 'listOrders').throws(new Error('test error'))
|
||||
await uut.listOrders(ctx)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#deleteOrder', () => {
|
||||
it('should delete an order', async () => {
|
||||
ctx.request.body = {
|
||||
nostrEventId: 'testEventId'
|
||||
}
|
||||
sandbox.stub(uut.useCases.order, 'deleteOrder').resolves('testTxid')
|
||||
await uut.deleteOrder(ctx)
|
||||
assert.equal(ctx.body.txid, 'testTxid')
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.request.body = {
|
||||
nostrEventId: 'testEventId'
|
||||
}
|
||||
sandbox.stub(uut.useCases.order, 'deleteOrder').throws(new Error('test error'))
|
||||
await uut.deleteOrder(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 {
|
||||
|
||||
@@ -74,4 +74,22 @@ describe('#Order-REST-Router', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#createOrder', () => {
|
||||
it('should route to controller', async () => {
|
||||
sandbox.stub(uut.validators, 'ensureUser').resolves(true)
|
||||
const spy = sandbox.stub(uut.orderRESTController, 'createOrder').resolves(true)
|
||||
|
||||
await uut.createOrder()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#deleteOrder', () => {
|
||||
it('should route to controller', async () => {
|
||||
sandbox.stub(uut.validators, 'ensureUser').resolves(true)
|
||||
const spy = sandbox.stub(uut.orderRESTController, 'deleteOrder').resolves(true)
|
||||
|
||||
await uut.deleteOrder()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /sm endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local support libraries
|
||||
import adapters from '../../../mocks/adapters/index.js'
|
||||
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||
import P2WDBRESTController from '../../../../../src/controllers/rest-api/p2wdb/controller.js'
|
||||
|
||||
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
let ctx
|
||||
|
||||
describe('#P2WDB-REST-Router', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new P2WDBRESTController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
ctx = mockContext()
|
||||
ctx.params = { }
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new P2WDBRESTController()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new P2WDBRESTController({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#routeWebhook', () => {
|
||||
it('should route the webhook for a new counter offer', async () => {
|
||||
ctx.request = {
|
||||
body: {
|
||||
data: {
|
||||
dataType: 'counter-offer'
|
||||
}
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.useCases.offer, 'acceptCounterOffer').resolves()
|
||||
|
||||
await uut.routeWebhook(ctx)
|
||||
|
||||
assert.isTrue(true)
|
||||
})
|
||||
it('should route the webhook for a new offer', async () => {
|
||||
ctx.request = {
|
||||
body: {
|
||||
data: {
|
||||
dataType: 'offer'
|
||||
}
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.useCases.offer, 'createOffer').resolves()
|
||||
|
||||
await uut.routeWebhook(ctx)
|
||||
|
||||
assert.isTrue(true)
|
||||
})
|
||||
it('should route the webhook flag', async () => {
|
||||
ctx.request = {
|
||||
body: {
|
||||
data: {
|
||||
dataType: 'flag'
|
||||
}
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.useCases.offer, 'flagOffer').resolves()
|
||||
|
||||
await uut.routeWebhook(ctx)
|
||||
|
||||
assert.isTrue(true)
|
||||
})
|
||||
it('should handle unknown data type', async () => {
|
||||
ctx.request = {
|
||||
body: {
|
||||
data: {
|
||||
dataType: 'unknown'
|
||||
}
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.useCases.offer, 'flagOffer').resolves()
|
||||
|
||||
await uut.routeWebhook(ctx)
|
||||
|
||||
assert.isTrue(true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.routeWebhook(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'Cannot read properties of undefined')
|
||||
}
|
||||
})
|
||||
describe('#handleError', () => {
|
||||
it('should still throw error if there is no message', () => {
|
||||
try {
|
||||
const err = {
|
||||
status: 404
|
||||
}
|
||||
|
||||
uut.handleError(ctx, err)
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Not Found')
|
||||
}
|
||||
})
|
||||
it('should catch error if message is provided', () => {
|
||||
try {
|
||||
const err = {
|
||||
status: 422,
|
||||
message: 'Unprocessable Entity'
|
||||
}
|
||||
|
||||
uut.handleError(ctx, err)
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Unprocessable Entity')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /p2wdb endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local support libraries
|
||||
import adapters from '../../../mocks/adapters/index.js'
|
||||
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||
import P2WDBRouter from '../../../../../src/controllers/rest-api/p2wdb/index.js'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
// let ctx
|
||||
|
||||
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('#P2WDB-REST-Router', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new P2WDBRouter({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
// ctx = mockContext()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new P2WDBRouter()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new P2WDBRouter({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#attach', () => {
|
||||
it('should throw an error if app is not passed in.', () => {
|
||||
try {
|
||||
uut.attach()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass app object when attached REST API controllers.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -20,7 +20,7 @@ describe('#RESTControllers', () => {
|
||||
let sandbox
|
||||
// let ctx
|
||||
|
||||
before(async () => {})
|
||||
before(async () => { })
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
@@ -64,4 +64,19 @@ describe('#RESTControllers', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#attachRESTControllers', () => {
|
||||
it('should attach controllers without mongo service', () => {
|
||||
uut.config.noMongo = true
|
||||
|
||||
const app = { use: () => {} }
|
||||
uut.attachRESTControllers(app)
|
||||
})
|
||||
it('should attach controllers with mongo service', () => {
|
||||
uut.config.noMongo = false
|
||||
|
||||
const app = { use: () => {} }
|
||||
uut.attachRESTControllers(app)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /sm endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local support libraries
|
||||
import adapters from '../../../mocks/adapters/index.js'
|
||||
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||
import SmAccountRESTController from '../../../../../src/controllers/rest-api/smAccount/controller.js'
|
||||
|
||||
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
let ctx
|
||||
|
||||
describe('#SmAccount-REST-Router', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new SmAccountRESTController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
ctx = mockContext()
|
||||
ctx.params = { }
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new SmAccountRESTController()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating /sm REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new SmAccountRESTController({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating /sm REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#listAccounts', () => {
|
||||
it('should list all social media accounts', async () => {
|
||||
ctx.params.page = 0
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.useCases.smAccount, 'listAccounts').resolves([])
|
||||
|
||||
await uut.listAccounts(ctx)
|
||||
|
||||
assert.isArray(ctx.body)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.params.page = 0
|
||||
|
||||
// Force an error
|
||||
sandbox.stub(uut.useCases.smAccount, 'listAccounts')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.listAccounts(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getAccountByNpub', () => {
|
||||
it('should get a social media account by npub', async () => {
|
||||
sandbox.stub(uut.useCases.smAccount, 'getAccountByNpub').resolves({ npub: 'testNpub' })
|
||||
ctx.params.npub = 'testNpub'
|
||||
|
||||
await uut.getAccountByNpub(ctx)
|
||||
})
|
||||
it('should throw 400 if npub is not provided', async () => {
|
||||
try {
|
||||
await uut.getAccountByNpub(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Npub is required')
|
||||
}
|
||||
})
|
||||
it('should throw 400 if npub is not found', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.useCases.smAccount, 'getAccountByNpub').resolves(null)
|
||||
ctx.params.npub = 'testNpub'
|
||||
await uut.getAccountByNpub(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Account not found')
|
||||
}
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.useCases.smAccount, 'getAccountByNpub').throws(new Error('test error'))
|
||||
ctx.params.npub = 'testNpub'
|
||||
|
||||
await uut.getAccountByNpub(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getAccountByBchAddr', () => {
|
||||
it('should get a social media account by npub', async () => {
|
||||
sandbox.stub(uut.useCases.smAccount, 'getAccountByBchAddr').resolves({ bchAddr: 'testBchAddr' })
|
||||
ctx.params.bchAddr = 'testBchAddr'
|
||||
|
||||
await uut.getAccountByBchAddr(ctx)
|
||||
})
|
||||
|
||||
it('should throw 400 if bchAddr is not provided', async () => {
|
||||
try {
|
||||
await uut.getAccountByBchAddr(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'BCH address is required')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw 400 if bchAddr is not found', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.useCases.smAccount, 'getAccountByBchAddr').resolves(null)
|
||||
ctx.params.bchAddr = 'testBchAddr'
|
||||
await uut.getAccountByBchAddr(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Account not found')
|
||||
}
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.useCases.smAccount, 'getAccountByBchAddr').throws(new Error('test error'))
|
||||
ctx.params.bchAddr = 'testBchAddr'
|
||||
|
||||
await uut.getAccountByBchAddr(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 {
|
||||
const err = {
|
||||
status: 404
|
||||
}
|
||||
|
||||
uut.handleError(ctx, err)
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Not Found')
|
||||
}
|
||||
})
|
||||
it('should catch error if message is provided', () => {
|
||||
try {
|
||||
const err = {
|
||||
status: 422,
|
||||
message: 'Unprocessable Entity'
|
||||
}
|
||||
|
||||
uut.handleError(ctx, err)
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Unprocessable Entity')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /sm endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local support libraries
|
||||
import adapters from '../../../mocks/adapters/index.js'
|
||||
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||
import SmAccountRouter from '../../../../../src/controllers/rest-api/smAccount/index.js'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
// let ctx
|
||||
|
||||
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('#SmAccount-REST-Router', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new SmAccountRouter({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
// ctx = mockContext()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new SmAccountRouter()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating /sm REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new SmAccountRouter({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating /sm REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#attach', () => {
|
||||
it('should throw an error if app is not passed in.', () => {
|
||||
try {
|
||||
uut.attach()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass app object when attached REST API controllers.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -153,6 +153,20 @@ describe('#Users-REST-Controller', () => {
|
||||
// Assert that expected properties exist in the returned data.
|
||||
assert.property(ctx.response.body, 'user')
|
||||
})
|
||||
it('should run next function if it exists', async () => {
|
||||
// Mock dependencies
|
||||
const nextSpy = sandbox.spy()
|
||||
sandbox.stub(uut.useCases.user, 'getUser').resolves({ _id: '123' })
|
||||
|
||||
await uut.getUser(ctx, nextSpy)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
|
||||
// Assert that expected properties exist in the returned data.
|
||||
assert.property(ctx.response.body, 'user')
|
||||
assert.isTrue(nextSpy.calledOnce)
|
||||
})
|
||||
|
||||
it('should return other error status passed by biz logic', async () => {
|
||||
try {
|
||||
|
||||
@@ -79,4 +79,73 @@ describe('#Users-REST-Router', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createUser', () => {
|
||||
it('should ignore admin validator when DISABLE_NEW_ACCOUNTS is not defined', async () => {
|
||||
// Stub functions
|
||||
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
|
||||
sandbox.stub(uut.userRESTController, 'createUser').resolves(true)
|
||||
|
||||
// Call function
|
||||
await uut.createUser()
|
||||
|
||||
// Assertions
|
||||
assert.isTrue(validationSpy.notCalled, 'Admin validator should not be called')
|
||||
})
|
||||
it('should ensure admin when DISABLE_NEW_ACCOUNTS is defined', async () => {
|
||||
// Set environment variable
|
||||
process.env.DISABLE_NEW_ACCOUNTS = true
|
||||
|
||||
// Stub functions
|
||||
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
|
||||
sandbox.stub(uut.userRESTController, 'createUser').resolves(true)
|
||||
|
||||
// Call function
|
||||
await uut.createUser()
|
||||
|
||||
// Assertions
|
||||
assert.isTrue(validationSpy.calledOnce, 'Admin validator should be called')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getAll', () => {
|
||||
it('should route to controller', async () => {
|
||||
sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
|
||||
const spy = sandbox.stub(uut.userRESTController, 'getUsers').resolves(true)
|
||||
|
||||
await uut.getAll()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getById', () => {
|
||||
it('should route to controller', async () => {
|
||||
sandbox.stub(uut.validators, 'ensureTargetUserOrAdmin').resolves(true)
|
||||
const spy = sandbox.stub(uut.userRESTController, 'getUser').resolves(true)
|
||||
|
||||
await uut.getById()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#updateUser', () => {
|
||||
it('should route to controller', async () => {
|
||||
sandbox.stub(uut.validators, 'ensureTargetUserOrAdmin').resolves(true)
|
||||
sandbox.stub(uut.userRESTController, 'getUser').resolves(true)
|
||||
const spy = sandbox.stub(uut.userRESTController, 'updateUser').resolves(true)
|
||||
|
||||
await uut.updateUser()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#deleteUser', () => {
|
||||
it('should route to controller', async () => {
|
||||
sandbox.stub(uut.validators, 'ensureTargetUserOrAdmin').resolves(true)
|
||||
sandbox.stub(uut.userRESTController, 'getUser').resolves(true)
|
||||
const spy = sandbox.stub(uut.userRESTController, 'deleteUser').resolves(true)
|
||||
|
||||
await uut.deleteUser()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -137,4 +137,70 @@ describe('#Timer-Controllers', () => {
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#backupUsage', () => {
|
||||
it('should kick off the Use Case', async () => {
|
||||
const result = await uut.backupUsage()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return false on error', async () => {
|
||||
sandbox.stub(uut.useCases.usage, 'clearUsage').throws(new Error('test error'))
|
||||
// sandbox.stub(uut.useCases.usage, 'saveUsage').throws(new Error('test error'))
|
||||
|
||||
const result = await uut.backupUsage()
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#newSmAccounts', () => {
|
||||
it('should kick off the Use Case', async () => {
|
||||
const result = await uut.newSmAccounts()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return false on error', async () => {
|
||||
sandbox.stub(uut.useCases.smAccount, 'checkForNewSmAccounts').throws(new Error('test error'))
|
||||
// sandbox.stub(uut.useCases.usage, 'saveUsage').throws(new Error('test error'))
|
||||
|
||||
const result = await uut.newSmAccounts()
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
describe('#updateSmAccounts', () => {
|
||||
it('should kick off the Use Case', async () => {
|
||||
const result = await uut.updateSmAccounts()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return false on error', async () => {
|
||||
sandbox.stub(uut.useCases.smAccount, 'updateSmAccounts').throws(new Error('test error'))
|
||||
// sandbox.stub(uut.useCases.usage, 'saveUsage').throws(new Error('test error'))
|
||||
|
||||
const result = await uut.updateSmAccounts()
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
describe('#removeOlderDeletedChatsAndPosts', () => {
|
||||
it('should kick off the Use Cases and return true', async () => {
|
||||
sandbox.stub(uut.useCases.nostr, 'removeOlderDeletedChats').resolves()
|
||||
sandbox.stub(uut.useCases.nostr, 'removeOlderDeletedPosts').resolves()
|
||||
const result = await uut.removeOlderDeletedChatsAndPosts()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
it('should kick off the Use Cases and return false on error', async () => {
|
||||
sandbox.stub(uut.useCases.nostr, 'removeOlderDeletedChats').throws(new Error('test error'))
|
||||
sandbox.stub(uut.useCases.nostr, 'removeOlderDeletedPosts').throws(new Error('test error'))
|
||||
const result = await uut.removeOlderDeletedChatsAndPosts()
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Unit tests for the DeletedChat entity library.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import DeletedChat from '../../../src/entities/deletedChat.js'
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
|
||||
describe('#DeletedChat-Entity', () => {
|
||||
before(async () => {})
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new DeletedChat()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#validate', () => {
|
||||
it('should throw an error if eventId is not provided', () => {
|
||||
try {
|
||||
uut.validate()
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should return a DeletedChat object', () => {
|
||||
const inputData = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||
}
|
||||
|
||||
const deletedChat = uut.validate(inputData)
|
||||
// console.log('entry: ', entry)
|
||||
|
||||
assert.property(deletedChat, 'eventId')
|
||||
assert.equal(deletedChat.eventId, inputData.eventId)
|
||||
|
||||
assert.property(deletedChat, 'npub')
|
||||
assert.equal(deletedChat.npub, inputData.npub)
|
||||
|
||||
assert.property(deletedChat, 'bchAddr')
|
||||
assert.equal(deletedChat.bchAddr, inputData.bchAddr)
|
||||
|
||||
assert.property(deletedChat, 'pubkey')
|
||||
assert.equal(deletedChat.pubkey, inputData.pubkey)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Unit tests for the DeletedPost entity library.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import DeletedPost from '../../../src/entities/deletedPost.js'
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
|
||||
describe('#DeletedPost-Entity', () => {
|
||||
before(async () => { })
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new DeletedPost()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#validate', () => {
|
||||
it('should throw an error if eventId is not provided', () => {
|
||||
try {
|
||||
uut.validate()
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should return a DeletedPost object', () => {
|
||||
const inputData = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||
}
|
||||
|
||||
const deletedPost = uut.validate(inputData)
|
||||
// console.log('entry: ', entry)
|
||||
|
||||
assert.property(deletedPost, 'eventId')
|
||||
assert.equal(deletedPost.eventId, inputData.eventId)
|
||||
|
||||
assert.property(deletedPost, 'npub')
|
||||
assert.equal(deletedPost.npub, inputData.npub)
|
||||
|
||||
assert.property(deletedPost, 'bchAddr')
|
||||
assert.equal(deletedPost.bchAddr, inputData.bchAddr)
|
||||
|
||||
assert.property(deletedPost, 'pubkey')
|
||||
assert.equal(deletedPost.pubkey, inputData.pubkey)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -237,6 +237,113 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if makerAddr is not included', () => {
|
||||
try {
|
||||
const offerData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350,
|
||||
numTokens: 1,
|
||||
utxoTxid: 'fakeTxid',
|
||||
utxoVout: 0,
|
||||
offerStatus: 'posted',
|
||||
operatorAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
operatorPercentage: 10
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'makerAddr' must be a string.")
|
||||
}
|
||||
})
|
||||
it('should throw an error if makerAddr 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: 1234567890
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'makerAddr' must be a string.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if tokenType is not included', () => {
|
||||
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',
|
||||
operatorAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
operatorPercentage: 10
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'tokenType' must be a number.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if tokenType is not a number', () => {
|
||||
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: 'notANumber'
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'tokenType' must be a number.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if nostrEventId is not included', () => {
|
||||
try {
|
||||
const offerData = {
|
||||
@@ -267,6 +374,154 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if operatorAddress is not included', () => {
|
||||
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',
|
||||
operatorPercentage: 10
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'operatorAddress' must be a string.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if operatorAddress 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: 1234567890,
|
||||
operatorPercentage: 10
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'operatorAddress' must be a string.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if operatorPercentage is not included', () => {
|
||||
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'
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'operatorPercentage' must be a number.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if operatorPercentage is not a number', () => {
|
||||
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: 'notANumber'
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
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 = {
|
||||
appId: 'swapTest555',
|
||||
@@ -286,7 +541,10 @@ describe('#Offer-Entity', () => {
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
ticker: 'TROUT',
|
||||
tokenType: 1,
|
||||
nostrEventId: 'test'
|
||||
nostrEventId: 'test',
|
||||
operatorAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
operatorPercentage: 10,
|
||||
makerNpub: 'npub1dtkf954h5k2yuv04xp44g5hnmk5k0ppv8mekxafm2gc0vveaxh6s0297qg'
|
||||
},
|
||||
timestamp: '2021-09-20T17:54:26.395Z',
|
||||
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
||||
@@ -312,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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -130,5 +130,71 @@ describe('#Order-Entity', () => {
|
||||
assert.include(err.message, "Property 'numTokens' must be a number.")
|
||||
}
|
||||
})
|
||||
it('should throw an error if numTokens is not a number', () => {
|
||||
try {
|
||||
const data = {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350,
|
||||
numTokens: 'notANumber'
|
||||
}
|
||||
uut.inputValidate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'numTokens' must be a number.")
|
||||
}
|
||||
})
|
||||
it('should throw an error if makerAddress is not included', () => {
|
||||
try {
|
||||
const data = {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350,
|
||||
numTokens: 1000
|
||||
}
|
||||
uut.inputValidate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'makerAddr' must be a string.")
|
||||
}
|
||||
})
|
||||
it('should throw an error if makerAddress is not a string', () => {
|
||||
try {
|
||||
const data = {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350,
|
||||
numTokens: 1000,
|
||||
makerAddr: 1234567890
|
||||
}
|
||||
uut.inputValidate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'makerAddr' must be a string.")
|
||||
}
|
||||
})
|
||||
it('should return the correct data', () => {
|
||||
const data = {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350,
|
||||
numTokens: 1000,
|
||||
makerAddr: 'fakeAddr'
|
||||
}
|
||||
const result = uut.inputValidate(data)
|
||||
assert.deepEqual(result, data)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -34,6 +34,12 @@ describe('#passport', () => {
|
||||
|
||||
passportCallback(id, 'password', done)
|
||||
})
|
||||
it('should handle not found user', () => {
|
||||
// Mock Users model.
|
||||
sandbox.stub(User, 'findOne').resolves(null)
|
||||
|
||||
passportCallback(id, 'password', done)
|
||||
})
|
||||
|
||||
it('should return if password is validated', () => {
|
||||
// Mock Users model.
|
||||
@@ -41,6 +47,15 @@ describe('#passport', () => {
|
||||
|
||||
passportCallback(id, 'password', done)
|
||||
})
|
||||
it('should handle error on password validation', () => {
|
||||
// Mock Users model.
|
||||
const userMock = {
|
||||
validatePassword: () => false
|
||||
}
|
||||
sandbox.stub(User, 'findOne').resolves(userMock)
|
||||
|
||||
passportCallback(id, 'password', done)
|
||||
})
|
||||
|
||||
it('should catch a high-level error', () => {
|
||||
// Force an error
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('#server', () => {
|
||||
sandbox.stub(uut.controllers, 'attachControllers').resolves()
|
||||
sandbox.stub(uut, 'sleep').resolves()
|
||||
uut.config.env = 'dev'
|
||||
|
||||
uut.config.port = 5040
|
||||
const result = await uut.startServer()
|
||||
// console.log('result: ', result)
|
||||
|
||||
|
||||
@@ -74,6 +74,39 @@ const localdb = {
|
||||
}
|
||||
},
|
||||
|
||||
Usage: class Usage {
|
||||
static findById () {}
|
||||
static find () {}
|
||||
static findOne () {
|
||||
return {
|
||||
validatePassword: localdb.validatePassword
|
||||
}
|
||||
}
|
||||
|
||||
async save () {
|
||||
return {}
|
||||
}
|
||||
|
||||
generateToken () {
|
||||
return '123'
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async remove () {
|
||||
return true
|
||||
}
|
||||
|
||||
async validatePassword () {
|
||||
return true
|
||||
}
|
||||
static async deleteMany(){
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
validatePassword: () => {
|
||||
return true
|
||||
},
|
||||
@@ -116,11 +149,47 @@ const localdb = {
|
||||
static findById () {}
|
||||
static find () {}
|
||||
static findOne () {}
|
||||
static countDocuments(){}
|
||||
async save () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
SmAccount: class SmAccount {
|
||||
constructor (obj) {}
|
||||
|
||||
static findById () {}
|
||||
static find () {}
|
||||
static findOne () {}
|
||||
static updateOne () {}
|
||||
|
||||
async save () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
DeletedChat: class DeletedChat {
|
||||
constructor (obj) {}
|
||||
|
||||
static findById () {}
|
||||
static find () {}
|
||||
static findOne () {}
|
||||
static updateOne () {}
|
||||
|
||||
async save () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
DeletedPost: class DeletedPost {
|
||||
constructor (obj) {}
|
||||
|
||||
static findById () {}
|
||||
static find () {}
|
||||
static findOne () {}
|
||||
static updateOne () {}
|
||||
|
||||
async save () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bch = {
|
||||
@@ -169,7 +238,10 @@ const p2wdb = {
|
||||
const nostr = {
|
||||
post: async () => {return true },
|
||||
read: async () => { return true },
|
||||
eventId2note: () => { return 'testNoteId' }
|
||||
eventId2note: () => { return 'testNoteId' },
|
||||
readGlobalFeed: async () => { return [] },
|
||||
getFollowers: async () => { return [] },
|
||||
privKeyToNpub: async () => { return 'fakeNpub' }
|
||||
}
|
||||
|
||||
export default { ipfs, localdb, bch, wallet, p2wdb, bchjs, nostr}
|
||||
|
||||
@@ -12,8 +12,18 @@ const mockWallet = {
|
||||
nextAddress: 1
|
||||
};
|
||||
|
||||
class MockBchWallet {
|
||||
|
||||
class AdapterRoute {
|
||||
constructor() {
|
||||
this.sendTx = async () => {
|
||||
return 'fakeTxid';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MockBchWallet {
|
||||
constructor(mnemonic, advancedOptions) {
|
||||
this.advancedOptions = advancedOptions
|
||||
this.walletInfoPromise = true;
|
||||
this.walletInfo = mockWallet;
|
||||
this.initialize = async () => {}
|
||||
@@ -24,6 +34,9 @@ class MockBchWallet {
|
||||
this.sendTokens = async () => {
|
||||
return 'fakeTxid';
|
||||
};
|
||||
this.send = async () => {
|
||||
return 'fakeTxid';
|
||||
};
|
||||
this.utxoIsValid =async ()=>{}
|
||||
this.getUtxos = async () => { };
|
||||
this.getBalance = async () => { };
|
||||
@@ -33,7 +46,16 @@ class MockBchWallet {
|
||||
tokenTicker: 'TROUT'
|
||||
}];
|
||||
};
|
||||
this.getKeyPair = async () => {
|
||||
return {
|
||||
cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
wif: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
|
||||
hdIndex: 11
|
||||
}
|
||||
};
|
||||
this.optimize = async () => { };
|
||||
this.cid2json = async () => {};
|
||||
this.ar = new AdapterRoute()
|
||||
// Environment variable is used by wallet-balance.unit.js to force an error.
|
||||
if (process.env.NO_UTXO) {
|
||||
this.utxos = {};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
class BchTokenSweepMock {
|
||||
constructor(wif, privateKey, bchWallet, fee, cashAddress) {
|
||||
this.wif = wif
|
||||
this.privateKey = privateKey
|
||||
this.bchWallet = bchWallet
|
||||
this.fee = fee
|
||||
this.cashAddress = cashAddress
|
||||
}
|
||||
populateObjectFromNetwork() {
|
||||
return {
|
||||
success: true,
|
||||
txid: 'txid'
|
||||
}
|
||||
}
|
||||
sweepTo() {
|
||||
return {
|
||||
success: true,
|
||||
txid: 'txid'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { BchTokenSweepMock }
|
||||
|
||||
export default {
|
||||
BchTokenSweepMock
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
|
||||
const messagesMock = [
|
||||
{
|
||||
content: JSON.stringify("I'm testing so hard!!!"),
|
||||
id: '1',
|
||||
tags: [['u', 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477']],
|
||||
npub: 'npub1234567890',
|
||||
pubkey: '1234567890',
|
||||
}
|
||||
]
|
||||
const smAccountsMock = [
|
||||
{
|
||||
npub: 'npub1234567890',
|
||||
bchAddr: 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477',
|
||||
pubkey: '1234567890',
|
||||
pfp: '',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
followerCnt: 0,
|
||||
followingCnt: 0,
|
||||
posts: 0,
|
||||
likes: 0,
|
||||
comments: 0,
|
||||
reposts: 0
|
||||
},
|
||||
{
|
||||
npub: 'npub1234567891',
|
||||
bchAddr: 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478',
|
||||
pubkey: '1234567891',
|
||||
pfp: '',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
followerCnt: 0,
|
||||
followingCnt: 0,
|
||||
posts: 0,
|
||||
likes: 0,
|
||||
comments: 0,
|
||||
reposts: 0
|
||||
}
|
||||
]
|
||||
export default { messagesMock, smAccountsMock }
|
||||
@@ -50,6 +50,30 @@ class Offer {
|
||||
async loadOffers(){
|
||||
|
||||
}
|
||||
async listOffers() {
|
||||
return []
|
||||
}
|
||||
async listNftOffers() {
|
||||
return []
|
||||
}
|
||||
async listFungibleOffers() {
|
||||
return []
|
||||
}
|
||||
async takeOffer() {
|
||||
return []
|
||||
}
|
||||
async listOffersByAddress() {
|
||||
return []
|
||||
}
|
||||
async flagOffer() {
|
||||
return {}
|
||||
}
|
||||
async acceptCounterOffer() {
|
||||
return {}
|
||||
}
|
||||
async syncOfferMutableData(){
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
class Order {
|
||||
@@ -59,6 +83,12 @@ class Order {
|
||||
async removeStaleOrders(){
|
||||
return {}
|
||||
}
|
||||
async listOrders() {
|
||||
return []
|
||||
}
|
||||
async deleteOrder() {
|
||||
return 'testTxid'
|
||||
}
|
||||
}
|
||||
|
||||
class UsageUseCaseMock {
|
||||
@@ -77,6 +107,53 @@ class UsageUseCaseMock {
|
||||
async getTopEndpoints(existingUser, newData) {
|
||||
return true
|
||||
}
|
||||
|
||||
async clearUsage() {
|
||||
return true
|
||||
}
|
||||
|
||||
async saveUsage() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class SmAccountUseCaseMock {
|
||||
async listAccounts() {
|
||||
return []
|
||||
}
|
||||
async getAccountByNpub() {
|
||||
return {}
|
||||
}
|
||||
async getAccountByBchAddr() {
|
||||
return {}
|
||||
}
|
||||
async checkForNewSmAccounts() {
|
||||
return true
|
||||
}
|
||||
async updateSmAccounts() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class NostrUseCasesMock {
|
||||
async createDeletedChat() {
|
||||
return {}
|
||||
}
|
||||
async getDeletedChats() {
|
||||
return []
|
||||
}
|
||||
async createDeletedPost() {
|
||||
return {}
|
||||
}
|
||||
async getDeletedPosts() {
|
||||
return []
|
||||
}
|
||||
async removeOlderDeletedChats() {
|
||||
return true
|
||||
}
|
||||
async removeOlderDeletedPosts() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class UseCasesMock {
|
||||
@@ -89,6 +166,8 @@ class UseCasesMock {
|
||||
offer = new Offer()
|
||||
order = new Order()
|
||||
usage = new UsageUseCaseMock()
|
||||
smAccount = new SmAccountUseCaseMock()
|
||||
nostr = new NostrUseCasesMock()
|
||||
}
|
||||
|
||||
export default UseCasesMock;
|
||||
|
||||
@@ -115,6 +115,7 @@ const fungibleTokenData01 = {
|
||||
"documentUri": "troutsblog.com",
|
||||
"documentHash": "",
|
||||
"decimals": 2,
|
||||
"tokenDecimals": 2,
|
||||
"mintBatonIsActive": true,
|
||||
"tokensInCirculationBN": "100097954686",
|
||||
"tokensInCirculationStr": "100097954686",
|
||||
@@ -125,6 +126,20 @@ const fungibleTokenData01 = {
|
||||
"immutableData": "troutsblog.com",
|
||||
"mutableData": ""
|
||||
}
|
||||
const mutableDataMock = {
|
||||
schema: 'ps007-v1.0.3',
|
||||
schemaDocs: 'https://github.com/Permissionless-Software-Foundation/specifications/blob/master/ps007-token-data-schema.md',
|
||||
tokenIcon: 'https://files.tokentiger.com/ipfs/view/bafkreidr6wfd6mcmwpea7abm5uk5rrprc2wfbcvo5wdcmtlolrpjab5oqm',
|
||||
fullSizedUrl: '',
|
||||
nsfw: false,
|
||||
userData: { currentUrl: 'https://tokentiger.com' },
|
||||
jsonLd: {},
|
||||
about: 'This is AI generated art.',
|
||||
category: '',
|
||||
tags: [],
|
||||
mediaType: 'image',
|
||||
currentOwner: {},
|
||||
}
|
||||
|
||||
const offerMockData = {
|
||||
data: {
|
||||
@@ -141,10 +156,38 @@ const offerMockData = {
|
||||
utxoVout: 0,
|
||||
makerAddr: 'address',
|
||||
tokenType: 1,
|
||||
nostrEventId: 'test'
|
||||
nostrEventId: 'test',
|
||||
operatorAddress: 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478',
|
||||
operatorPercentage: 10
|
||||
}
|
||||
}
|
||||
|
||||
const deserealizeTxMockNoOperatorOut = {
|
||||
//...
|
||||
vout: [
|
||||
{
|
||||
value: 0,
|
||||
scriptPubKey: {
|
||||
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
scriptPubKey: {
|
||||
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
scriptPubKey: {
|
||||
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||
}
|
||||
},
|
||||
|
||||
//...
|
||||
]
|
||||
}
|
||||
|
||||
const deserealizeTxMock = {
|
||||
//...
|
||||
vout: [
|
||||
@@ -166,6 +209,12 @@ const deserealizeTxMock = {
|
||||
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
scriptPubKey: {
|
||||
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||
}
|
||||
},
|
||||
//...
|
||||
]
|
||||
}
|
||||
@@ -178,5 +227,7 @@ export default {
|
||||
fungibleOffer01,
|
||||
fungibleTokenData01,
|
||||
offerMockData,
|
||||
deserealizeTxMock
|
||||
deserealizeTxMockNoOperatorOut,
|
||||
deserealizeTxMock,
|
||||
mutableDataMock
|
||||
};
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
Unit tests for the Nostr Use Case library.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Unit under test (uut)
|
||||
import NostrUseCases from '../../../src/use-cases/nostr-use-cases.js'
|
||||
// Local support libraries
|
||||
import adapters from '../mocks/adapters/index.js'
|
||||
|
||||
describe('#nostr-use-cases', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
before(async () => {
|
||||
// Delete all previous users in the database.
|
||||
// await testUtils.deleteAllUsers()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
uut = new NostrUseCases({ adapters })
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new NostrUseCases()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // linter
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of adapters must be passed in when instantiating Nostr Use Cases library.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createDeletedChat', () => {
|
||||
it('should throw an error if no input is given', async () => {
|
||||
try {
|
||||
await uut.createDeletedChat()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
// assert.equal(err.status, 422)
|
||||
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if eventId is not provided', async () => {
|
||||
try {
|
||||
await uut.createDeletedChat({})
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should catch and throw DB errors', async () => {
|
||||
try {
|
||||
// Force an error with the database.
|
||||
sandbox.stub(uut, 'DeletedChatModel').throws(new Error('test error'))
|
||||
|
||||
const inObj = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q'
|
||||
}
|
||||
|
||||
await uut.createDeletedChat(inObj)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should create a new deleted chat model with minimum inputs', async () => {
|
||||
const inObj = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q'
|
||||
}
|
||||
|
||||
const res = await uut.createDeletedChat(inObj)
|
||||
|
||||
assert.isObject(res)
|
||||
})
|
||||
it('should create a new deleted chat model with maximum inputs', async () => {
|
||||
const inObj = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||
}
|
||||
|
||||
const res = await uut.createDeletedChat(inObj)
|
||||
|
||||
assert.isObject(res)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getDeletedChats', () => {
|
||||
it('should return all deleted chats from the database', async () => {
|
||||
sandbox.stub(uut.DeletedChatModel, 'find').resolves([])
|
||||
const res = await uut.getDeletedChats()
|
||||
|
||||
assert.isArray(res)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error.
|
||||
sandbox.stub(uut.DeletedChatModel, 'find').rejects(new Error('test error'))
|
||||
|
||||
await uut.getDeletedChats()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createDeletedPost', () => {
|
||||
it('should throw an error if no input is given', async () => {
|
||||
try {
|
||||
await uut.createDeletedPost()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if eventId is not provided', async () => {
|
||||
try {
|
||||
await uut.createDeletedPost({})
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should catch and throw DB errors', async () => {
|
||||
try {
|
||||
// Force an error with the database.
|
||||
sandbox.stub(uut, 'DeletedPostModel').throws(new Error('test error'))
|
||||
|
||||
const inObj = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q'
|
||||
}
|
||||
|
||||
await uut.createDeletedPost(inObj)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
it('should create a new deleted post model with minimum inputs', async () => {
|
||||
const inObj = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q'
|
||||
}
|
||||
|
||||
const res = await uut.createDeletedPost(inObj)
|
||||
|
||||
assert.isObject(res)
|
||||
})
|
||||
it('should create a new deleted post model with maximum inputs', async () => {
|
||||
const inObj = {
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||
}
|
||||
|
||||
const res = await uut.createDeletedPost(inObj)
|
||||
|
||||
assert.isObject(res)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getDeletedPosts', () => {
|
||||
it('should return all deleted posts from the database', async () => {
|
||||
sandbox.stub(uut.DeletedPostModel, 'find').resolves([])
|
||||
const res = await uut.getDeletedPosts()
|
||||
|
||||
assert.isArray(res)
|
||||
})
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error.
|
||||
sandbox.stub(uut.DeletedPostModel, 'find').rejects(new Error('test error'))
|
||||
|
||||
await uut.getDeletedPosts()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
describe('#removeOlderDeletedChats', () => {
|
||||
it('should remove older deleted chats from the database', async () => {
|
||||
const threeMonthsAgo = new Date()
|
||||
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3) // 3 months ago
|
||||
threeMonthsAgo.setDate(threeMonthsAgo.getDate() - 1) // 1 day ago
|
||||
const spy = sinon.spy()
|
||||
const deletedChatsMock = [
|
||||
// Newer deleted chat
|
||||
{
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
createdAt: new Date(),
|
||||
remove: spy
|
||||
},
|
||||
// Older deleted chat
|
||||
{
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
createdAt: threeMonthsAgo,
|
||||
remove: spy
|
||||
}
|
||||
]
|
||||
sandbox.stub(uut.DeletedChatModel, 'find').resolves(deletedChatsMock)
|
||||
await uut.removeOlderDeletedChats()
|
||||
assert.equal(spy.callCount, 1, 'Expected to be called once.')
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
// Force an error.
|
||||
sandbox.stub(uut.DeletedChatModel, 'find').throws(new Error('test error'))
|
||||
|
||||
await uut.removeOlderDeletedChats()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#removeOlderDeletedPosts', () => {
|
||||
it('should remove older deleted posts from the database', async () => {
|
||||
const threeMonthsAgo = new Date()
|
||||
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3) // 3 months ago
|
||||
threeMonthsAgo.setDate(threeMonthsAgo.getDate() - 1) // 1 day ago
|
||||
const spy = sinon.spy()
|
||||
const deletedPostsMock = [
|
||||
// Newer deleted post
|
||||
{
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
createdAt: new Date(),
|
||||
remove: spy
|
||||
},
|
||||
// Older deleted post
|
||||
{
|
||||
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||
createdAt: threeMonthsAgo,
|
||||
remove: spy
|
||||
}
|
||||
]
|
||||
sandbox.stub(uut.DeletedPostModel, 'find').resolves(deletedPostsMock)
|
||||
await uut.removeOlderDeletedPosts()
|
||||
assert.equal(spy.callCount, 1, 'Expected to be called once.')
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
// Force an error.
|
||||
sandbox.stub(uut.DeletedPostModel, 'find').throws(new Error('test error'))
|
||||
|
||||
await uut.removeOlderDeletedPosts()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,6 +5,7 @@
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import RetryQueue from '@chris.troutner/retry-queue'
|
||||
|
||||
// Local support libraries
|
||||
// const testUtils = require('../../utils/test-utils')
|
||||
@@ -30,6 +31,7 @@ describe('#offer-use-case', () => {
|
||||
|
||||
const order = new OrderUseCase({ adapters })
|
||||
uut = new OfferLib({ adapters, order })
|
||||
uut.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 1 })
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
@@ -85,6 +87,15 @@ describe('#offer-use-case', () => {
|
||||
assert.isFalse(result)
|
||||
})
|
||||
|
||||
it('should return false if offer already processed', async () => {
|
||||
const offerObj = mockData.offerMockData
|
||||
uut.seenOffers.push(offerObj.data.nostrEventId)
|
||||
// Mock dependencies
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
assert.isFalse(result)
|
||||
})
|
||||
|
||||
it('should return false for invalid utxo', async () => {
|
||||
const offerObj = mockData.offerMockData
|
||||
|
||||
@@ -97,6 +108,73 @@ describe('#offer-use-case', () => {
|
||||
assert.isFalse(result)
|
||||
})
|
||||
|
||||
it('should create offer with mutable data', async () => {
|
||||
const tokenDataMock = mockData.nftTokenData01
|
||||
const offerObj = mockData.offerMockData
|
||||
const mutableDataMock = mockData.mutableDataMock
|
||||
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||
.onCall(0).resolves({}) // Utxo Status call
|
||||
.onCall(1).resolves(tokenDataMock) // Token Data call
|
||||
.onCall(2).resolves(mutableDataMock)
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
assert.isTrue(result)
|
||||
})
|
||||
|
||||
it('should skip userData stringify error', async () => {
|
||||
const tokenDataMock = mockData.nftTokenData01
|
||||
const offerObj = mockData.offerMockData
|
||||
const mutableDataMock = mockData.mutableDataMock
|
||||
mutableDataMock.userData = { n: 10n }
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||
.onCall(0).resolves({}) // Utxo Status call
|
||||
.onCall(1).resolves(tokenDataMock) // Token Data call
|
||||
.onCall(2).resolves(mutableDataMock)
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
assert.isTrue(result)
|
||||
})
|
||||
|
||||
it('should handle error getting token data', async () => {
|
||||
// const tokenDataMock = mockData.nftTokenData01
|
||||
const offerObj = mockData.offerMockData
|
||||
const mutableDataMock = mockData.mutableDataMock
|
||||
mutableDataMock.userData = { n: 10n }
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||
.onCall(0).resolves({}) // Utxo Status call
|
||||
.onCall(1).throws(new Error('test error'))
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
assert.isTrue(result)
|
||||
})
|
||||
|
||||
it('should handle error getting mutable data', async () => {
|
||||
const tokenDataMock = mockData.nftTokenData01
|
||||
const offerObj = mockData.offerMockData
|
||||
const mutableDataMock = mockData.mutableDataMock
|
||||
mutableDataMock.userData = { n: 10n }
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||
.onCall(0).resolves({}) // Utxo Status call
|
||||
.onCall(1).resolves(tokenDataMock) // Token Data call
|
||||
.onCall(2).throws(new Error('test error')) // cid2json call
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
assert.isTrue(result)
|
||||
})
|
||||
|
||||
it('should create offer', async () => {
|
||||
const tokenDataMock = mockData.simpleNftTokenData01
|
||||
const offerObj = mockData.offerMockData
|
||||
@@ -334,6 +412,14 @@ describe('#offer-use-case', () => {
|
||||
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
|
||||
assert.isFalse(result)
|
||||
})
|
||||
it('should fetch alternative url', async () => {
|
||||
// Mock dependencies and force desired code path.
|
||||
sandbox.stub(uut.axios, 'get').onCall(0).throws(new Error())
|
||||
.onCall(1).resolves({ data: { nsfw: true } })
|
||||
|
||||
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
|
||||
assert.isTrue(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#listOffers', () => {
|
||||
@@ -381,7 +467,7 @@ describe('#offer-use-case', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should list orders', async () => {
|
||||
it('should list offers', async () => {
|
||||
const queryMock = {
|
||||
sort () {
|
||||
return this
|
||||
@@ -395,7 +481,11 @@ describe('#offer-use-case', () => {
|
||||
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
|
||||
|
||||
const result = await uut.listNftOffers(1, true)
|
||||
assert.isArray(result)
|
||||
assert.isObject(result)
|
||||
assert.property(result, 'data')
|
||||
assert.property(result, 'pagination')
|
||||
assert.isArray(result.data)
|
||||
assert.isObject(result.pagination)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -597,7 +687,7 @@ describe('#offer-use-case', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#findOrderByEvent', () => {
|
||||
describe('#findOfferByEvent', () => {
|
||||
it('should throw an error if hash is not provided', async () => {
|
||||
try {
|
||||
await uut.findOfferByEvent()
|
||||
@@ -629,6 +719,29 @@ describe('#offer-use-case', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#listOffersByAddress', () => {
|
||||
it('should throw an error if order is not found!', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
|
||||
|
||||
await uut.listOffersByAddress('eventId')
|
||||
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return offer by eventId', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'find').resolves([])
|
||||
|
||||
const result = await uut.listOffersByAddress()
|
||||
assert.isArray(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#flagOffer', () => {
|
||||
it('should throw an error if input is not provided', async () => {
|
||||
try {
|
||||
@@ -687,23 +800,86 @@ describe('#offer-use-case', () => {
|
||||
|
||||
it('should skip internal function errors ', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.nostr, 'read').resolves([mockData.offerMockData])
|
||||
const nostrReadMock = [{ content: JSON.stringify(mockData.offerMockData) }]
|
||||
sandbox.stub(uut.adapters.nostr, 'read').resolves(nostrReadMock)
|
||||
|
||||
await uut.loadOffers()
|
||||
})
|
||||
|
||||
it('should review and load offers', async () => {
|
||||
it('should createOffer ', async () => {
|
||||
mockData.offerMockData.data.dataType = 'offer'
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.nostr, 'read').resolves([JSON.stringify(mockData.offerMockData)])
|
||||
const nostrReadMock = [{ content: JSON.stringify(mockData.offerMockData) }]
|
||||
sandbox.stub(uut.adapters.nostr, 'read').resolves(nostrReadMock)
|
||||
const spy = sandbox.stub(uut, 'createOffer').resolves(true)
|
||||
|
||||
await uut.loadOffers()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
it('should accept counter Offer ', async () => {
|
||||
mockData.offerMockData.data.dataType = 'counter-offer'
|
||||
// Mock dependencies
|
||||
const nostrReadMock = [{ content: JSON.stringify(mockData.offerMockData) }]
|
||||
sandbox.stub(uut.adapters.nostr, 'read').resolves(nostrReadMock)
|
||||
const spy = sandbox.stub(uut, 'acceptCounterOffer').resolves(true)
|
||||
|
||||
await uut.loadOffers()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
|
||||
it('should skip error inside loop', async () => {
|
||||
// Mock dependencies
|
||||
const nostrReadMock = [{ content: '' }]
|
||||
sandbox.stub(uut.adapters.nostr, 'read').resolves(nostrReadMock)
|
||||
await uut.loadOffers()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#acceptCounterOffer', () => {
|
||||
it('should return false if order already processed', async () => {
|
||||
const offerObj = mockData.offerMockData
|
||||
uut.seenOffers.push(offerObj.data.nostrEventId)
|
||||
// Mock dependencies
|
||||
|
||||
const result = await uut.acceptCounterOffer(offerObj)
|
||||
assert.isFalse(result)
|
||||
})
|
||||
|
||||
it('should return if order is not found!', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').throws(new Error('test error'))
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').throws(new Error('test error'))
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
|
||||
it('should return N/A on axios error', async () => {
|
||||
// Mock dependencies
|
||||
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'))
|
||||
const axiosErr = new Error('axios err')
|
||||
axiosErr.isAxiosError = true
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').throws(axiosErr)
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
it('should return if utxo can not be validated', async () => {
|
||||
// Mock dependencies
|
||||
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: { /** .... */ } })
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
|
||||
it('should return if utxo is invalid', async () => {
|
||||
// Mock dependencies
|
||||
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: { /** .... */ } })
|
||||
assert.equal(result, 'N/A')
|
||||
@@ -719,7 +895,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: { /** .... */ } })
|
||||
const result = await uut.acceptCounterOffer({ data: {} })
|
||||
console.log('result: ', result)
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
@@ -734,7 +910,6 @@ describe('#offer-use-case', () => {
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mockData.offerMockData.data)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
@@ -746,6 +921,44 @@ 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
|
||||
mock.numTokens = 0
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMockNoOperatorOut)
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
it('should skip transactions if operator address does not match', async () => {
|
||||
try {
|
||||
// Mock data
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.makerAddr = mockData.deserealizeTxMockNoOperatorOut.vout[2].scriptPubKey.addresses[0]
|
||||
mock.operatorAddress = 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477'
|
||||
mock.rateInBaseUnit = 0
|
||||
mock.numTokens = 0
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
await uut.acceptCounterOffer({ data: {} })
|
||||
assert.fail('unexpected code path')
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'The Counter Offer has an output address of')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle error for wrong transaction output address', async () => {
|
||||
try {
|
||||
// Mock data
|
||||
@@ -761,7 +974,7 @@ describe('#offer-use-case', () => {
|
||||
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
await uut.acceptCounterOffer({ data: {} })
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'The Counter Offer has an output address of')
|
||||
@@ -769,21 +982,152 @@ describe('#offer-use-case', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should return tx id', async () => {
|
||||
it('should handle error if operator sats to receive cant be calculated', async () => {
|
||||
// Mock data
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.makerAddr = mockData.deserealizeTxMock.vout[2].scriptPubKey.addresses[0] // Maker Address
|
||||
mock.makerAddr = mockData.deserealizeTxMock.vout[2].scriptPubKey.addresses[0]
|
||||
mock.operatorAddress = mockData.deserealizeTxMock.vout[3].scriptPubKey.addresses[0]
|
||||
mock.rateInBaseUnit = 0
|
||||
mock.numTokens = 0
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
const result = await uut.acceptCounterOffer({ data: {} })
|
||||
assert.equal(result, 'N/A')
|
||||
})
|
||||
|
||||
it('should return tx id', async () => {
|
||||
// Mock data
|
||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||
mock.makerAddr = mockData.deserealizeTxMock.vout[2].scriptPubKey.addresses[0]
|
||||
mock.operatorAddress = mockData.deserealizeTxMock.vout[3].scriptPubKey.addresses[0]
|
||||
mock.rateInBaseUnit = 0
|
||||
mock.numTokens = 0
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').onCall(0).returns(0).onCall(1).returns(1000)
|
||||
sandbox.stub(uut.adapters.localdb.Users, 'findById').resolves({ mnemonic: 'test' })
|
||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||
|
||||
//
|
||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||
const result = await uut.acceptCounterOffer({ data: {} })
|
||||
assert.isString(result)
|
||||
assert.notEqual('N/A')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#syncOfferMutableData', () => {
|
||||
it('should throw error if tokenId is not provided!', async () => {
|
||||
try {
|
||||
await uut.syncOfferMutableData()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'tokenId must be a string!')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw error if tokenId is not found!', async () => {
|
||||
try {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.OfferModel, 'findOne').resolves(null)
|
||||
|
||||
await uut.syncOfferMutableData('tokenId')
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Associated offer not found!')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return current data if lastUpdatedTokenData is less than 5 minutes', async () => {
|
||||
// Mock dependencies
|
||||
const lastUpdatedTokenData = new Date().getTime()
|
||||
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||
offerMock.lastUpdatedTokenData = lastUpdatedTokenData
|
||||
// stub
|
||||
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||
const spy = sandbox.stub(uut.retryQueue, 'addToQueue').resolves(true)
|
||||
|
||||
const offer = await uut.syncOfferMutableData('tokenId')
|
||||
|
||||
assert.isObject(offer)
|
||||
assert.isTrue(spy.notCalled, 'it should not call retryQueue functions.')
|
||||
})
|
||||
|
||||
it('should sync offer', async () => {
|
||||
// create a timestamp 6 minutes in the past
|
||||
|
||||
// Create offer mock
|
||||
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||
offerMock.save = () => { }
|
||||
|
||||
// Stub functions
|
||||
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||
.onCall(0).resolves(mockData.simpleNftTokenData01) // Token Data call
|
||||
.onCall(1).resolves(mockData.mutableDataMock)
|
||||
|
||||
const offer = await uut.syncOfferMutableData('tokenId')
|
||||
assert.isObject(offer)
|
||||
assert.isNumber(offer.lastUpdatedTokenData)
|
||||
})
|
||||
|
||||
it('should skip userData stringify error', async () => {
|
||||
// create mutable data mock
|
||||
const mutableDataMock = mockData.mutableDataMock
|
||||
mutableDataMock.userData = { n: 10n }
|
||||
|
||||
// create offer mock
|
||||
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||
offerMock.save = () => { }
|
||||
|
||||
// Stub functions
|
||||
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||
.onCall(0).resolves(mockData.simpleNftTokenData01) // Token Data call
|
||||
.onCall(1).resolves(mutableDataMock)
|
||||
|
||||
const offer = await uut.syncOfferMutableData('tokenId')
|
||||
|
||||
assert.isObject(offer)
|
||||
assert.isNumber(offer.lastUpdatedTokenData)
|
||||
})
|
||||
|
||||
it('should handle error getting token data', async () => {
|
||||
// create offer mock
|
||||
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||
offerMock.save = () => { }
|
||||
|
||||
// Stub functions
|
||||
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||
.onCall(0).throws(new Error('tokendata error')) // Token Data call
|
||||
.onCall(1).resolves(mockData.mutableDataMock)
|
||||
|
||||
const offer = await uut.syncOfferMutableData('tokenId')
|
||||
assert.isObject(offer)
|
||||
})
|
||||
|
||||
it('should handle error getting mutable data', async () => {
|
||||
// create offer mock
|
||||
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||
offerMock.save = () => { }
|
||||
|
||||
// Stub functions
|
||||
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||
.onCall(0).resolves(mockData.simpleNftTokenData01) // Token Data call
|
||||
.onCall(1).throws(new Error('mutable data error'))
|
||||
|
||||
const offer = await uut.syncOfferMutableData('tokenId')
|
||||
assert.isObject(offer)
|
||||
assert.isNumber(offer.lastUpdatedTokenData)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -158,6 +158,7 @@ describe('#order-use-case', () => {
|
||||
|
||||
describe('#createOrder', () => {
|
||||
it('should create an order and return the hash', async () => {
|
||||
uut.config.useFullStackCash = true
|
||||
const entryObj = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
@@ -185,6 +186,7 @@ describe('#order-use-case', () => {
|
||||
assert.property(result, 'eventId')
|
||||
assert.property(result, 'noteId')
|
||||
})
|
||||
|
||||
it('should create an order with consumer-api', async () => {
|
||||
uut.config.useFullStackCash = false
|
||||
const entryObj = {
|
||||
@@ -214,6 +216,7 @@ describe('#order-use-case', () => {
|
||||
assert.property(result, 'eventId')
|
||||
assert.property(result, 'noteId')
|
||||
})
|
||||
|
||||
it('should throw error if user is not found', async () => {
|
||||
try {
|
||||
const entryObj = {
|
||||
@@ -384,4 +387,55 @@ describe('#order-use-case', () => {
|
||||
assert.isString(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#findOrderByUtxo', () => {
|
||||
it('should throw an error if data is not provided', async () => {
|
||||
try {
|
||||
await uut.findOrderByUtxo({})
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read properties of undefined')
|
||||
}
|
||||
})
|
||||
it('should throw an error if order is not found', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.OrderModel, 'findOne').resolves(null)
|
||||
const offerData = {
|
||||
data: {
|
||||
utxoTxid: 'utxoTxid',
|
||||
utxoVout: 'utxoVout'
|
||||
}
|
||||
}
|
||||
await uut.findOrderByUtxo(offerData)
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'order not found')
|
||||
}
|
||||
})
|
||||
it('should throw an error if order event id is not found', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.OrderModel, 'findOne').resolves({ nostrEventId: null })
|
||||
const offerData = {
|
||||
data: {
|
||||
utxoTxid: 'utxoTxid',
|
||||
utxoVout: 'utxoVout'
|
||||
}
|
||||
}
|
||||
await uut.findOrderByUtxo(offerData)
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'order not found')
|
||||
}
|
||||
})
|
||||
it('should return order by utxo', async () => {
|
||||
sandbox.stub(uut.OrderModel, 'findOne').resolves({ toObject: () => { return {} }, nostrEventId: 'nostrEventId' })
|
||||
const offerData = {
|
||||
data: {
|
||||
utxoTxid: 'utxoTxid',
|
||||
utxoVout: 'utxoVout'
|
||||
}
|
||||
}
|
||||
const result = await uut.findOrderByUtxo(offerData)
|
||||
assert.isObject(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
Unit tests for the SM Account Use Case library.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Unit under test (uut)
|
||||
import SMAccountLib from '../../../src/use-cases/smAccount-use-cases.js'
|
||||
import SMMock from '../mocks/social-media-mocks.js'
|
||||
// Local support libraries
|
||||
import adapters from '../mocks/adapters/index.js'
|
||||
|
||||
describe('#sm-account-use-case', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
before(async () => {
|
||||
// Delete all previous users in the database.
|
||||
// await testUtils.deleteAllUsers()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
uut = new SMAccountLib({ adapters })
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new SMAccountLib()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // linter
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of adapters must be passed in when instantiating SM Account Use Cases library.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#checkForNewSmAccounts', () => {
|
||||
it('check for new SM accounts', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.nostr, 'readGlobalFeed').resolves(SMMock.messagesMock)
|
||||
|
||||
const result = await uut.checkForNewSmAccounts()
|
||||
assert.isTrue(result)
|
||||
})
|
||||
it('should skip existing SM accounts', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.localdb.SmAccount, 'findOne').resolves({ npub: SMMock.messagesMock[0].npub })
|
||||
sandbox.stub(uut.adapters.nostr, 'readGlobalFeed').resolves(SMMock.messagesMock)
|
||||
|
||||
const result = await uut.checkForNewSmAccounts()
|
||||
assert.isTrue(result)
|
||||
})
|
||||
it('should throw an error if the readGlobalFeed fails', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.nostr, 'readGlobalFeed').rejects(new Error('Failed to read global feed'))
|
||||
|
||||
try {
|
||||
await uut.checkForNewSmAccounts()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Failed to read global feed')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#listAccounts', () => {
|
||||
it('should return a list of SM accounts', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.localdb.SmAccount, 'find').callsFake(() => {
|
||||
return {
|
||||
skip: () => {
|
||||
return {
|
||||
limit: () => {
|
||||
return SMMock.messagesMock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = await uut.listAccounts()
|
||||
assert.isArray(result)
|
||||
})
|
||||
it('should throw an error if the find fails', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.localdb.SmAccount, 'find').throws(new Error('Failed to find SM accounts'))
|
||||
|
||||
try {
|
||||
await uut.listAccounts()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Failed to find SM accounts')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getAccountByNpub', () => {
|
||||
it('should return a SM account by npub', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.localdb.SmAccount, 'findOne').resolves(SMMock.messagesMock[0])
|
||||
|
||||
const result = await uut.getAccountByNpub(SMMock.messagesMock[0].npub)
|
||||
assert.isObject(result)
|
||||
})
|
||||
it('should catch error', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.localdb.SmAccount, 'findOne').throws(new Error('Failed to find SM account by npub'))
|
||||
|
||||
try {
|
||||
await uut.getAccountByNpub(SMMock.messagesMock[0].npub)
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Failed to find SM account by npub')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getAccountByBchAddr', () => {
|
||||
it('should return a SM account by bch addr', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.localdb.SmAccount, 'findOne').resolves(SMMock.messagesMock[0])
|
||||
|
||||
const result = await uut.getAccountByBchAddr('bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477')
|
||||
assert.isObject(result)
|
||||
})
|
||||
it('should catch error', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.localdb.SmAccount, 'findOne').throws(new Error('Failed to find SM account by bch addr'))
|
||||
|
||||
try {
|
||||
await uut.getAccountByBchAddr('bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477')
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Failed to find SM account by bch addr')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#updateSmAccounts', () => {
|
||||
it('should update the SM accounts', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.localdb.SmAccount, 'find').resolves(SMMock.smAccountsMock)
|
||||
|
||||
const result = await uut.updateSmAccounts()
|
||||
assert.isTrue(result)
|
||||
})
|
||||
it('should catch error', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.localdb.SmAccount, 'find').throws(new Error('Failed to find SM accounts'))
|
||||
|
||||
try {
|
||||
await uut.updateSmAccounts()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Failed to find SM accounts')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -255,4 +255,67 @@ describe('#usage-use-case', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#clearUsage', () => {
|
||||
it('should clear the usage database data', async () => {
|
||||
const res = await uut.clearUsage()
|
||||
assert.isTrue(res)
|
||||
})
|
||||
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.UsageModel, 'deleteMany').throws(new Error('uut error'))
|
||||
await uut.clearUsage()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'uut error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#saveUsage', () => {
|
||||
it('should save usage', async () => {
|
||||
// Set mock data
|
||||
restCalls.push({
|
||||
timestamp: new Date().getTime(),
|
||||
ip: 'localhost',
|
||||
url: 'fakeUrl',
|
||||
method: 'unit test'
|
||||
})
|
||||
const res = await uut.saveUsage()
|
||||
assert.isTrue(res)
|
||||
})
|
||||
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
restCalls.push(null)
|
||||
await uut.saveUsage()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
assert.include(error.message, 'Cannot read properties')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#loadUsage', () => {
|
||||
it('should load usage', async () => {
|
||||
// Set mock data
|
||||
const mockObj = {
|
||||
timestamp: new Date().getTime(),
|
||||
ip: 'localhost',
|
||||
url: 'fakeUrl',
|
||||
method: 'unit test'
|
||||
}
|
||||
sandbox.stub(uut.UsageModel, 'find').returns(new Array(10).fill(null).map((_, i) => (mockObj)))
|
||||
const res = await uut.loadUsage()
|
||||
assert.equal(restCalls.length, 10)
|
||||
assert.equal(res.length, 10)
|
||||
})
|
||||
|
||||
it('should skip error', async () => {
|
||||
sandbox.stub(uut.UsageModel, 'find').throws(new Error('uut error'))
|
||||
const res = await uut.loadUsage()
|
||||
assert.isFalse(res)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+45
-20
@@ -8,7 +8,6 @@ import axios from 'axios'
|
||||
|
||||
// Local libraries
|
||||
import config from '../../config/index.js'
|
||||
import User from '../../src/adapters/localdb/models/users.js'
|
||||
import JsonFiles from '../../src/adapters/json-files.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
@@ -33,24 +32,6 @@ async function cleanDb () {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all users in the database. This ensures there is no previous state
|
||||
// to confuse tests.
|
||||
async function deleteAllUsers () {
|
||||
try {
|
||||
// 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()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in test-utils.js/deleteAllUsers()')
|
||||
}
|
||||
}
|
||||
|
||||
// This function is used to create new users.
|
||||
// userObj = {
|
||||
// username,
|
||||
@@ -170,12 +151,56 @@ async function getAdminJWT () {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// Fetches all users from the database.
|
||||
async function getAllUsers () {
|
||||
try {
|
||||
const adminJWT = await getAdminJWT()
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url: `${LOCALHOST}/users`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${adminJWT}`
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
return result.data.users
|
||||
} catch (err) {
|
||||
console.error('Error in test/utils.js/getAllUsers()', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes all users from the database.
|
||||
async function deleteAllUsers () {
|
||||
try {
|
||||
const allUsers = await getAllUsers()
|
||||
const adminJWT = await getAdminJWT()
|
||||
for (let i = 0; i < allUsers.length; i++) {
|
||||
const user = allUsers[i]
|
||||
// Skip the admin user.
|
||||
if (user.type === 'admin') {
|
||||
continue
|
||||
}
|
||||
const options = {
|
||||
method: 'DELETE',
|
||||
url: `${LOCALHOST}/users/${user._id}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${adminJWT}`
|
||||
}
|
||||
}
|
||||
await axios(options)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in test/utils.js/deleteAllUsers()', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
export default {
|
||||
cleanDb,
|
||||
createUser,
|
||||
loginTestUser,
|
||||
loginAdminUser,
|
||||
getAdminJWT,
|
||||
deleteAllUsers
|
||||
deleteAllUsers,
|
||||
getAllUsers
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import mongoose from 'mongoose'
|
||||
import config from '../../../config/index.js'
|
||||
import User from '../../../src/adapters/localdb/models/users.js'
|
||||
|
||||
async function getUsers () {
|
||||
// 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 }
|
||||
)
|
||||
|
||||
// Find the user by email.
|
||||
const user = await User.findOne({
|
||||
email: 'test@test.com'
|
||||
}, '-password')
|
||||
// console.log('user: ', user)
|
||||
|
||||
// Update the users password
|
||||
user.password = 'test'
|
||||
|
||||
// Change the user to an admin
|
||||
// user.type = 'admin'
|
||||
|
||||
// Save the changes to the database.
|
||||
await user.save()
|
||||
|
||||
mongoose.connection.close()
|
||||
}
|
||||
getUsers()
|
||||
@@ -0,0 +1,32 @@
|
||||
import mongoose from 'mongoose'
|
||||
// import config from '../../config/index.js'
|
||||
import User from '../../src/adapters/localdb/models/users.js'
|
||||
|
||||
const mongooseConnectStr = 'mongodb://172.17.0.1:5666/bch-swap-service-prod'
|
||||
|
||||
async function getUsers () {
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
await mongoose.connect(
|
||||
mongooseConnectStr,
|
||||
{ useNewUrlParser: true, useUnifiedTopology: true }
|
||||
)
|
||||
|
||||
// Find the user by email.
|
||||
const user = await User.findOne({
|
||||
email: 'test@test.com'
|
||||
}, '-password')
|
||||
|
||||
// Update the users password
|
||||
// user.password = 'newpassword'
|
||||
|
||||
// Change the user to an admin
|
||||
// user.type = 'admin'
|
||||
|
||||
// Save the changes to the database.
|
||||
await user.save()
|
||||
|
||||
mongoose.connection.close()
|
||||
}
|
||||
getUsers()
|
||||
Reference in New Issue
Block a user