mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-service.git
synced 2026-09-21 16:52:03 -07:00
Syncing with upstream ipfs-service-provider. Fixing merge conflicts
This commit is contained in:
+5
-1
@@ -25,6 +25,7 @@ import config from '../config/index.js' // this first.
|
|||||||
|
|
||||||
import AdminLib from '../src/adapters/admin.js'
|
import AdminLib from '../src/adapters/admin.js'
|
||||||
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
|
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
|
||||||
|
import { usageMiddleware } from '../src/use-cases/usage-use-cases.js'
|
||||||
import wlogger from '../src/adapters/wlogger.js'
|
import wlogger from '../src/adapters/wlogger.js'
|
||||||
import Controllers from '../src/controllers/index.js'
|
import Controllers from '../src/controllers/index.js'
|
||||||
import { applyPassportMods } from '../config/passport.js'
|
import { applyPassportMods } from '../config/passport.js'
|
||||||
@@ -67,6 +68,7 @@ class Server {
|
|||||||
app.use(bodyParser())
|
app.use(bodyParser())
|
||||||
app.use(session())
|
app.use(session())
|
||||||
app.use(errorMiddleware())
|
app.use(errorMiddleware())
|
||||||
|
app.use(usageMiddleware())
|
||||||
|
|
||||||
// Used to generate the docs.
|
// Used to generate the docs.
|
||||||
app.use(mount('/', serve(`${process.cwd()}/docs`)))
|
app.use(mount('/', serve(`${process.cwd()}/docs`)))
|
||||||
@@ -124,7 +126,9 @@ class Server {
|
|||||||
console.log('\nConfiguration:')
|
console.log('\nConfiguration:')
|
||||||
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
|
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
|
||||||
console.log(`IPFS TCP port: ${this.config.ipfsTcpPort}`)
|
console.log(`IPFS TCP port: ${this.config.ipfsTcpPort}`)
|
||||||
console.log(`IPFS WS port: ${this.config.ipfsWsPort}\n`)
|
console.log(`IPFS WS port: ${this.config.ipfsWsPort}`)
|
||||||
|
console.log(`IPFS WebRTC port: ${this.config.ipfsWebRtcPort}`)
|
||||||
|
console.log(`Connection preference: ${this.config.connectPref}\n`)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Vendored
+2
@@ -134,6 +134,8 @@ export default {
|
|||||||
// IPFS Ports
|
// IPFS Ports
|
||||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 5668,
|
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 5668,
|
||||||
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 5669,
|
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 5669,
|
||||||
|
ipfsWebRtcPort: process.env.IPFS_WEB_RTC_PORT ? process.env.IPFS_WEB_RTC_PORT : 5670,
|
||||||
|
connectPref: process.env.CONNECT_PREF, // Used in helia-coord to select connection preference.
|
||||||
|
|
||||||
// Settings for production, using external go-ipfs node.
|
// Settings for production, using external go-ipfs node.
|
||||||
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
|
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import Server from './bin/server.js'
|
import Server from './bin/server.js'
|
||||||
const server = new Server()
|
const server = new Server()
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.log(`Handling ${reason.code} error. stack: `, reason)
|
||||||
|
})
|
||||||
|
|
||||||
server.startServer()
|
server.startServer()
|
||||||
|
|||||||
Generated
+4100
-5698
File diff suppressed because it is too large
Load Diff
+21
-21
@@ -25,28 +25,28 @@
|
|||||||
},
|
},
|
||||||
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-service",
|
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-service",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chainsafe/libp2p-gossipsub": "13.1.1",
|
"@chainsafe/libp2p-gossipsub": "14.1.0",
|
||||||
"@chainsafe/libp2p-noise": "15.1.1",
|
"@chainsafe/libp2p-noise": "16.0.1",
|
||||||
"@chainsafe/libp2p-yamux": "6.0.2",
|
"@chainsafe/libp2p-yamux": "7.0.1",
|
||||||
"@helia/unixfs": "3.0.7",
|
"@helia/unixfs": "4.0.2",
|
||||||
"@libp2p/bootstrap": "10.1.5",
|
"@libp2p/bootstrap": "11.0.26",
|
||||||
"@libp2p/circuit-relay-v2": "1.1.5",
|
"@libp2p/circuit-relay-v2": "3.2.2",
|
||||||
"@libp2p/identify": "2.1.5",
|
"@libp2p/config": "1.1.0",
|
||||||
"@libp2p/keychain": "4.1.5",
|
"@libp2p/identify": "3.0.22",
|
||||||
"@libp2p/logger": "4.0.20",
|
"@libp2p/keychain": "5.0.14",
|
||||||
"@libp2p/mplex": "10.0.16",
|
"@libp2p/logger": "5.1.8",
|
||||||
"@libp2p/tcp": "9.1.5",
|
"@libp2p/ping": "2.0.22",
|
||||||
"@libp2p/webrtc": "4.1.7",
|
"@libp2p/tcp": "10.1.2",
|
||||||
"@libp2p/websockets": "8.2.0",
|
"@libp2p/webrtc": "5.2.2",
|
||||||
"@multiformats/multiaddr": "12.3.0",
|
"@libp2p/websockets": "9.2.2",
|
||||||
"@psf/bch-js": "6.7.3",
|
"@multiformats/multiaddr": "12.3.5",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blockstore-fs": "2.0.0",
|
"blockstore-fs": "2.0.2",
|
||||||
"datastore-fs": "9.1.7",
|
"datastore-fs": "10.0.2",
|
||||||
"glob": "7.1.6",
|
"glob": "7.1.6",
|
||||||
"helia": "4.2.5",
|
"helia": "5.2.1",
|
||||||
"helia-coord": "1.5.14",
|
"helia-coord": "1.7.2",
|
||||||
"jsonrpc-lite": "2.2.0",
|
"jsonrpc-lite": "2.2.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"jwt-bch-lib": "1.3.0",
|
"jwt-bch-lib": "1.3.0",
|
||||||
@@ -61,9 +61,9 @@
|
|||||||
"koa-router": "10.0.0",
|
"koa-router": "10.0.0",
|
||||||
"koa-static": "5.0.0",
|
"koa-static": "5.0.0",
|
||||||
"koa2-ratelimit": "0.9.1",
|
"koa2-ratelimit": "0.9.1",
|
||||||
"libp2p": "1.9.1",
|
"libp2p": "2.7.2",
|
||||||
"line-reader": "0.4.0",
|
"line-reader": "0.4.0",
|
||||||
"minimal-slp-wallet": "5.12.0",
|
"minimal-slp-wallet": "5.13.2",
|
||||||
"mongoose": "5.13.14",
|
"mongoose": "5.13.14",
|
||||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||||
"nodemailer": "6.7.5",
|
"nodemailer": "6.7.5",
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wal
|
|||||||
WORKDIR /home/safeuser/ipfs-bch-wallet-service
|
WORKDIR /home/safeuser/ipfs-bch-wallet-service
|
||||||
|
|
||||||
# For development: switch to unstable branch
|
# For development: switch to unstable branch
|
||||||
RUN git checkout ct-unstable
|
RUN git checkout master
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
#RUN npm install -g @mapbox/node-pre-gyp
|
#RUN npm install -g @mapbox/node-pre-gyp
|
||||||
|
|||||||
@@ -26,9 +26,12 @@ services:
|
|||||||
links:
|
links:
|
||||||
- mongo-bch-service
|
- mongo-bch-service
|
||||||
ports:
|
ports:
|
||||||
- '5032:5032' # <host port>:<container port>
|
# <host port>:<container port>
|
||||||
- '5668:5668'
|
- '5032:5032' # REST API
|
||||||
- '5669:5669'
|
- '5668:5668' # TCP
|
||||||
|
- '5669:5669' # Websockets
|
||||||
|
- '5670:5670' # WebRTC
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-service/.ipfsdata
|
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-service/.ipfsdata
|
||||||
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-service/start-production.sh
|
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-service/start-production.sh
|
||||||
|
|||||||
@@ -40,9 +40,15 @@ export IPFS_HOST=172.17.0.1
|
|||||||
export IPFS_API_PORT=5001
|
export IPFS_API_PORT=5001
|
||||||
export IPFS_TCP_PORT=5668
|
export IPFS_TCP_PORT=5668
|
||||||
export IPFS_WS_PORT=5669
|
export IPFS_WS_PORT=5669
|
||||||
|
export IPFS_WEB_RTC_PORT=5670
|
||||||
|
|
||||||
# Set the debug level for helia-coord. 0-3.
|
# Set the debug level for helia-coord. 0-3.
|
||||||
# 0 = no debug logs. 3 = maximum debug logs.
|
# 0 = no debug logs. 3 = maximum debug logs.
|
||||||
export DEBUG_LEVEL=0
|
export DEBUG_LEVEL=0
|
||||||
|
|
||||||
|
# Use this if the IPFS node has a publically accessible IP address.
|
||||||
|
#export CONNECT_PREF=direct
|
||||||
|
# Use this if the IPFS node is behind a NAT or Firewall and cannot be accessed directly.
|
||||||
|
export CONNECT_PREF=cr
|
||||||
|
|
||||||
npm start
|
npm start
|
||||||
|
|||||||
+80
-49
@@ -19,17 +19,20 @@ import { noise } from '@chainsafe/libp2p-noise'
|
|||||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||||
// import { bootstrap } from '@libp2p/bootstrap'
|
// import { bootstrap } from '@libp2p/bootstrap'
|
||||||
// import { identifyService } from 'libp2p/identify'
|
// import { identifyService } from 'libp2p/identify'
|
||||||
import { identify } from '@libp2p/identify'
|
import { identify, identifyPush } from '@libp2p/identify'
|
||||||
// import { circuitRelayServer, circuitRelayTransport } from 'libp2p/circuit-relay'
|
import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
||||||
import { circuitRelayServer } from '@libp2p/circuit-relay-v2'
|
// import { circuitRelayServer } from '@libp2p/circuit-relay-v2'
|
||||||
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
|
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
|
||||||
import { webSockets } from '@libp2p/websockets'
|
import { webSockets } from '@libp2p/websockets'
|
||||||
import { publicIpv4 } from 'public-ip'
|
import { publicIpv4 } from 'public-ip'
|
||||||
import { multiaddr } from '@multiformats/multiaddr'
|
import { multiaddr } from '@multiformats/multiaddr'
|
||||||
// import { webRTC } from '@libp2p/webrtc'
|
import { webRTC, webRTCDirect } from '@libp2p/webrtc'
|
||||||
import { keychain } from '@libp2p/keychain'
|
import { keychain } from '@libp2p/keychain'
|
||||||
import { defaultLogger } from '@libp2p/logger'
|
|
||||||
import { unixfs } from '@helia/unixfs'
|
import { unixfs } from '@helia/unixfs'
|
||||||
|
// import { generateKeyPairFromSeed } from '@libp2p/crypto/keys'
|
||||||
|
// import crypto from 'crypto'
|
||||||
|
import { ping } from '@libp2p/ping'
|
||||||
|
import { loadOrCreateSelfKey } from '@libp2p/config'
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import config from '../../../config/index.js'
|
import config from '../../../config/index.js'
|
||||||
@@ -64,8 +67,8 @@ class IpfsAdapter {
|
|||||||
this.createNode = this.createNode.bind(this)
|
this.createNode = this.createNode.bind(this)
|
||||||
this.stop = this.stop.bind(this)
|
this.stop = this.stop.bind(this)
|
||||||
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
|
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
|
||||||
|
// this.getPrivateKey = this.getPrivateKey.bind(this)
|
||||||
this.getSeed = this.getSeed.bind(this)
|
this.getSeed = this.getSeed.bind(this)
|
||||||
this.getKeychain = this.getKeychain.bind(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start an IPFS node.
|
// Start an IPFS node.
|
||||||
@@ -111,19 +114,6 @@ class IpfsAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getKeychain (datastore) {
|
|
||||||
const keychainInit = {
|
|
||||||
pass: await this.getSeed()
|
|
||||||
}
|
|
||||||
|
|
||||||
const chain = this.keychain(keychainInit)({
|
|
||||||
datastore,
|
|
||||||
logger: defaultLogger()
|
|
||||||
})
|
|
||||||
|
|
||||||
return chain
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function creates an IPFS node using Helia.
|
// This function creates an IPFS node using Helia.
|
||||||
// It returns the node as an object.
|
// It returns the node as an object.
|
||||||
async createNode () {
|
async createNode () {
|
||||||
@@ -132,44 +122,38 @@ class IpfsAdapter {
|
|||||||
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
|
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
|
||||||
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
|
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
|
||||||
|
|
||||||
// const keychainInit = {
|
// Get the private key.
|
||||||
// pass: await this.getSeed()
|
// const privateKey = await this.getPrivateKey()
|
||||||
// }
|
// console.log('privateKey: ', privateKey)
|
||||||
|
|
||||||
// Create an identity
|
// Create an identity
|
||||||
let peerId
|
const keychainInit = {
|
||||||
// console.log('this.keychain: ', this.keychain)
|
selfKey: 'myKey',
|
||||||
// const chain = this.keychain(keychainInit)({
|
pass: await this.getSeed()
|
||||||
// datastore,
|
|
||||||
// logger: defaultLogger()
|
|
||||||
// })
|
|
||||||
const chain = await this.getKeychain(datastore)
|
|
||||||
try {
|
|
||||||
peerId = await chain.exportPeerId('myKey')
|
|
||||||
} catch (err) {
|
|
||||||
await chain.createKey('myKey', 'Ed25519', 4096)
|
|
||||||
peerId = await chain.exportPeerId('myKey')
|
|
||||||
}
|
}
|
||||||
|
const privateKey = await loadOrCreateSelfKey(datastore, keychainInit)
|
||||||
|
|
||||||
// Configure services
|
// Configure services
|
||||||
const services = {
|
const services = {
|
||||||
identify: identify(),
|
identify: identify(),
|
||||||
pubsub: gossipsub({ allowPublishToZeroTopicPeers: true })
|
identifyPush: identifyPush(),
|
||||||
|
pubsub: gossipsub({ allowPublishToZeroTopicPeers: true }),
|
||||||
|
ping: ping(),
|
||||||
|
keychain: keychain(keychainInit)
|
||||||
}
|
}
|
||||||
if (this.config.isCircuitRelay) {
|
if (this.config.isCircuitRelay) {
|
||||||
console.log('Helia (IPFS) node IS configured as Circuit Relay')
|
console.log('Helia (IPFS) node IS configured as Circuit Relay')
|
||||||
services.relay = circuitRelayServer({ // makes the node function as a relay server
|
services.relay = circuitRelayServer({ // makes the node function as a relay server
|
||||||
hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit
|
hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit
|
||||||
advertise: true,
|
|
||||||
reservations: {
|
reservations: {
|
||||||
maxReservations: 15, // how many peers are allowed to reserve relay slots on this server
|
maxReservations: 15, // how many peers are allowed to reserve relay slots on this server
|
||||||
reservationClearInterval: 300 * 1000, // how often to reclaim stale reservations
|
reservationClearInterval: 300 * 1000, // how often to reclaim stale reservations
|
||||||
applyDefaultLimit: true, // whether to apply default data/duration limits to each relayed connection
|
applyDefaultLimit: true, // whether to apply default data/duration limits to each relayed connection
|
||||||
defaultDurationLimit: 2 * 60 * 1000, // the default maximum amount of time a relayed connection can be open for
|
defaultDurationLimit: 2 * 60 * 1000, // the default maximum amount of time a relayed connection can be open for
|
||||||
defaultDataLimit: BigInt(2 << 7), // the default maximum number of bytes that can be transferred over a relayed connection
|
defaultDataLimit: BigInt(2 << 7) // the default maximum number of bytes that can be transferred over a relayed connection
|
||||||
maxInboundHopStreams: 32, // how many inbound HOP streams are allow simultaneously
|
},
|
||||||
maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously
|
maxInboundHopStreams: 32, // how many inbound HOP streams are allow simultaneously
|
||||||
}
|
maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
|
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
|
||||||
@@ -177,28 +161,31 @@ class IpfsAdapter {
|
|||||||
|
|
||||||
const transports = [
|
const transports = [
|
||||||
tcp(),
|
tcp(),
|
||||||
webSockets()
|
webSockets(),
|
||||||
// circuitRelayTransport({
|
circuitRelayTransport({
|
||||||
// discoverRelays: 3,
|
discoverRelays: 3,
|
||||||
// reservationConcurrency: 3
|
reservationConcurrency: 3
|
||||||
// }),
|
}),
|
||||||
// webRTC()
|
webRTC(),
|
||||||
|
webRTCDirect()
|
||||||
]
|
]
|
||||||
|
|
||||||
// libp2p is the networking layer that underpins Helia
|
// libp2p is the networking layer that underpins Helia
|
||||||
const libp2p = await this.createLibp2p({
|
const libp2p = await this.createLibp2p({
|
||||||
peerId,
|
// peerId,
|
||||||
|
privateKey,
|
||||||
datastore,
|
datastore,
|
||||||
addresses: {
|
addresses: {
|
||||||
listen: [
|
listen: [
|
||||||
'/ip4/127.0.0.1/tcp/0',
|
'/ip4/127.0.0.1/tcp/0',
|
||||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
||||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`,
|
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`,
|
||||||
'/webrtc'
|
'/webrtc',
|
||||||
|
'/p2p-circuit'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
transports,
|
transports,
|
||||||
connectionEncryption: [
|
connectionEncrypters: [
|
||||||
noise()
|
noise()
|
||||||
],
|
],
|
||||||
streamMuxers: [
|
streamMuxers: [
|
||||||
@@ -244,6 +231,8 @@ class IpfsAdapter {
|
|||||||
|
|
||||||
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
|
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
|
||||||
|
|
||||||
|
!this.fs.existsSync(`${IPFS_DIR}/datastore/pkcs8`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore/pkcs8`)
|
||||||
|
|
||||||
// !fs.existsSync(`${IPFS_DIR}/datastore/peers`) && fs.mkdirSync(`${IPFS_DIR}/datastore/peers`)
|
// !fs.existsSync(`${IPFS_DIR}/datastore/peers`) && fs.mkdirSync(`${IPFS_DIR}/datastore/peers`)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -280,6 +269,48 @@ class IpfsAdapter {
|
|||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the private key from disk, or generate a new one and save it,
|
||||||
|
// if it doesn't exist.
|
||||||
|
// async getPrivateKey () {
|
||||||
|
// try {
|
||||||
|
// const filename = `${IPFS_DIR}/privkey.json`
|
||||||
|
|
||||||
|
// let privKeyHex
|
||||||
|
// try {
|
||||||
|
// // Try to read the JSON file containing the seed.
|
||||||
|
// privKeyHex = await this.jsonFiles.readJSON(filename)
|
||||||
|
// // console.log('saved privKeyHex: ', privKeyHex)
|
||||||
|
// } catch (err) {
|
||||||
|
// // Generate a new private key and save it to disk.
|
||||||
|
|
||||||
|
// // Generate a new private key and save it to disk.
|
||||||
|
// const randomBuffer = crypto.randomBytes(32)
|
||||||
|
// // console.log('randomBuffer: ', randomBuffer)
|
||||||
|
|
||||||
|
// privKeyHex = randomBuffer.toString('hex')
|
||||||
|
// // console.log('new privKeyHex: ', privKeyHex)
|
||||||
|
|
||||||
|
// // Save the newly generated seed
|
||||||
|
// await this.jsonFiles.writeJSON(privKeyHex, filename)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Convert the saved hex string to a buffer.
|
||||||
|
// const privKeyBuf = Buffer.from(privKeyHex, 'hex')
|
||||||
|
|
||||||
|
// // Convert the buffer to a Uint8Array 'seed'
|
||||||
|
// const seed = new Uint8Array(privKeyBuf.buffer, privKeyBuf.byteOffset, privKeyBuf.byteLength)
|
||||||
|
// // console.log('seed: ', seed)
|
||||||
|
|
||||||
|
// // Generate a ED25519 key pair.
|
||||||
|
// const privKey = await generateKeyPairFromSeed('Ed25519', seed)
|
||||||
|
|
||||||
|
// return privKey
|
||||||
|
// } catch (err) {
|
||||||
|
// console.error('Error in adapters/ipfs/ipfs.js/getPrivateKey(): ', err)
|
||||||
|
// throw err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IpfsAdapter
|
export default IpfsAdapter
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ class Controllers {
|
|||||||
this.useCases = new UseCases({ adapters: this.adapters })
|
this.useCases = new UseCases({ adapters: this.adapters })
|
||||||
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|
||||||
|
// Bind 'this' object to all subfunction
|
||||||
|
this.initAdapters = this.initAdapters.bind(this)
|
||||||
|
this.initUseCases = this.initUseCases.bind(this)
|
||||||
|
this.attachRESTControllers = this.attachRESTControllers.bind(this)
|
||||||
|
this.attachControllers = this.attachControllers.bind(this)
|
||||||
|
this.attachRPCControllers = this.attachRPCControllers.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spin up any adapter libraries that have async startup needs.
|
// Spin up any adapter libraries that have async startup needs.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import LogsRESTController from './logs/index.js'
|
|||||||
import IpfsRESTController from './ipfs/index.js'
|
import IpfsRESTController from './ipfs/index.js'
|
||||||
import BCHRESTController from './bch/index.js'
|
import BCHRESTController from './bch/index.js'
|
||||||
import config from '../../../config/index.js'
|
import config from '../../../config/index.js'
|
||||||
|
import UsageRESTController from './usage/index.js'
|
||||||
|
|
||||||
class RESTControllers {
|
class RESTControllers {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -31,6 +32,9 @@ class RESTControllers {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind 'this' object to all subfunctions.
|
||||||
|
this.attachRESTControllers = this.attachRESTControllers.bind(this)
|
||||||
|
|
||||||
// Encapsulate dependencies
|
// Encapsulate dependencies
|
||||||
this.config = config
|
this.config = config
|
||||||
}
|
}
|
||||||
@@ -66,6 +70,10 @@ class RESTControllers {
|
|||||||
// Attach the REST API Controllers associated with the /fulcrum route
|
// Attach the REST API Controllers associated with the /fulcrum route
|
||||||
const bchRESTController = new BCHRESTController(dependencies)
|
const bchRESTController = new BCHRESTController(dependencies)
|
||||||
bchRESTController.attach(app)
|
bchRESTController.attach(app)
|
||||||
|
|
||||||
|
// Attach the REST API Controllers associated with the /usage route
|
||||||
|
const usageRESTController = new UsageRESTController(dependencies)
|
||||||
|
usageRESTController.attach(app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
REST API Controller library for the /usage route
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global npm libraries
|
||||||
|
|
||||||
|
// Local libraries
|
||||||
|
import wlogger from '../../../adapters/wlogger.js'
|
||||||
|
|
||||||
|
class UsageRESTControllerLib {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating /usage REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantiating /usage REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encapsulate dependencies
|
||||||
|
|
||||||
|
// Bind 'this' object to all subfunctions
|
||||||
|
this.getStatus = this.getStatus.bind(this)
|
||||||
|
this.getTopIps = this.getTopIps.bind(this)
|
||||||
|
this.getTopEndpoints = this.getTopEndpoints.bind(this)
|
||||||
|
this.handleError = this.handleError.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /usage Get status on IPFS infrastructure
|
||||||
|
* @apiPermission public
|
||||||
|
* @apiName GetUsageStatus
|
||||||
|
* @apiGroup REST Usage
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getStatus (ctx) {
|
||||||
|
try {
|
||||||
|
// const status = await this.adapters.ipfs.getStatus()
|
||||||
|
const status = this.useCases.usage.getRestSummary()
|
||||||
|
|
||||||
|
ctx.body = { status }
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in usage/controller.js/getStatus(): ')
|
||||||
|
// ctx.throw(422, err.message)
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /usage/ips Get top IP addresses consuming the REST API
|
||||||
|
* @apiPermission public
|
||||||
|
* @apiName GetUsageIPs
|
||||||
|
* @apiGroup REST Usage
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/ips
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getTopIps (ctx) {
|
||||||
|
try {
|
||||||
|
const ips = this.useCases.usage.getTopIps()
|
||||||
|
|
||||||
|
ctx.body = { ips }
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in usage/controller.js/getTopIps(): ')
|
||||||
|
// ctx.throw(422, err.message)
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /usage/endpoints Get top endpoints consumed from the REST API
|
||||||
|
* @apiPermission public
|
||||||
|
* @apiName GetUsageEndpoints
|
||||||
|
* @apiGroup REST Usage
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/endpoints
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getTopEndpoints (ctx) {
|
||||||
|
try {
|
||||||
|
const endpoints = this.useCases.usage.getTopEndpoints()
|
||||||
|
|
||||||
|
ctx.body = { endpoints }
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in usage/controller.js/getTopEndpoints(): ')
|
||||||
|
// ctx.throw(422, err.message)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// module.exports = IpfsRESTControllerLib
|
||||||
|
export default UsageRESTControllerLib
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
REST API library for the /usage route.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries.
|
||||||
|
import Router from 'koa-router'
|
||||||
|
|
||||||
|
// Local libraries.
|
||||||
|
import UsageRESTControllerLib from './controller.js'
|
||||||
|
import Validators from '../middleware/validators.js'
|
||||||
|
|
||||||
|
// let _this
|
||||||
|
|
||||||
|
class UsageRouter {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating IPFS REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies = {
|
||||||
|
adapters: this.adapters,
|
||||||
|
useCases: this.useCases
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encapsulate dependencies.
|
||||||
|
this.usageRESTController = new UsageRESTControllerLib(dependencies)
|
||||||
|
this.validators = new Validators()
|
||||||
|
|
||||||
|
// Instantiate the router and set the base route.
|
||||||
|
const baseUrl = '/usage'
|
||||||
|
this.router = new Router({ prefix: baseUrl })
|
||||||
|
|
||||||
|
// _this = 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.get('/', this.usageRESTController.getStatus)
|
||||||
|
this.router.get('/ips', this.usageRESTController.getTopIps)
|
||||||
|
this.router.get('/endpoints', this.usageRESTController.getTopEndpoints)
|
||||||
|
|
||||||
|
// Attach the Controller routes to the Koa app.
|
||||||
|
app.use(this.router.routes())
|
||||||
|
app.use(this.router.allowedMethods())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// module.exports = BchRouter
|
||||||
|
export default UsageRouter
|
||||||
@@ -28,6 +28,7 @@ class TimerControllers {
|
|||||||
|
|
||||||
// Bind 'this' object to all subfunctions.
|
// Bind 'this' object to all subfunctions.
|
||||||
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
|
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
|
||||||
|
this.cleanUsage = this.cleanUsage.bind(this)
|
||||||
|
|
||||||
// this.startTimers()
|
// this.startTimers()
|
||||||
}
|
}
|
||||||
@@ -36,13 +37,15 @@ class TimerControllers {
|
|||||||
startTimers () {
|
startTimers () {
|
||||||
// Any new timer control functions can be added here. They will be started
|
// Any new timer control functions can be added here. They will be started
|
||||||
// when the server starts.
|
// when the server starts.
|
||||||
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 10)
|
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 60)
|
||||||
|
this.cleanUsageHandle = setInterval(this.cleanUsage, 60000 * 60) // 1 hour
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
stopTimers () {
|
stopTimers () {
|
||||||
clearInterval(this.optimizeWalletHandle)
|
clearInterval(this.optimizeWalletHandle)
|
||||||
|
clearInterval(this.cleanusageHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace this example function with your own timer handler.
|
// Replace this example function with your own timer handler.
|
||||||
@@ -60,6 +63,20 @@ class TimerControllers {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean the usage state so that stats reflect the last 24 hours.
|
||||||
|
cleanUsage () {
|
||||||
|
try {
|
||||||
|
this.useCases.usage.cleanUsage()
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in time-controller.js/cleanUsage(): ', err)
|
||||||
|
|
||||||
|
// Note: Do not throw an error. This is a top-level function.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TimerControllers
|
export default TimerControllers
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// Local libraries
|
// Local libraries
|
||||||
import UserUseCases from './user.js'
|
import UserUseCases from './user.js'
|
||||||
import BCHUseCases from './bch/index.js'
|
import BCHUseCases from './bch/index.js'
|
||||||
|
import { UsageUseCases } from './usage-use-cases.js'
|
||||||
|
|
||||||
class UseCases {
|
class UseCases {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -20,6 +21,7 @@ class UseCases {
|
|||||||
// console.log('use-cases/index.js localConfig: ', localConfig)
|
// console.log('use-cases/index.js localConfig: ', localConfig)
|
||||||
this.user = new UserUseCases(localConfig)
|
this.user = new UserUseCases(localConfig)
|
||||||
this.bch = new BCHUseCases(localConfig)
|
this.bch = new BCHUseCases(localConfig)
|
||||||
|
this.usage = new UsageUseCases(localConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run any startup Use Cases at the start of the app.
|
// Run any startup Use Cases at the start of the app.
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
Use Case library for tracking usage. This library contains business logic
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This global variable is used to share data between the REST middleware and
|
||||||
|
// the Usage Use Case class instance.
|
||||||
|
let restCalls = []
|
||||||
|
|
||||||
|
class UsageUseCases {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// console.log('User localConfig: ', localConfig)
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of adapters must be passed in when instantiating Usage Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// State
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the state by removing entries that are older than 24 hours. This
|
||||||
|
// ensures stats reflect only the last 24 hours.
|
||||||
|
// This function is called by a Timer Controller.
|
||||||
|
cleanUsage () {
|
||||||
|
try {
|
||||||
|
const now = new Date()
|
||||||
|
const twentyFourHoursAgo = now.getTime() - (60000 * 60 * 24)
|
||||||
|
|
||||||
|
restCalls = restCalls.filter(x => x.timestamp > twentyFourHoursAgo)
|
||||||
|
return restCalls
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in usage-use-cases.js/cleanUsage()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the calls to a REST API
|
||||||
|
getRestSummary (inObj = {}) {
|
||||||
|
try {
|
||||||
|
console.log(`getRestSummary(): There have been ${restCalls.length} REST calls`)
|
||||||
|
|
||||||
|
return restCalls.length
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in usage-use-cases.js/getRestSummary()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the top 20 IP addresses from the stats.
|
||||||
|
getTopIps () {
|
||||||
|
try {
|
||||||
|
const ips = restCalls.map(x => x.ip)
|
||||||
|
// Create a Map to count occurrences of each IP address string
|
||||||
|
const countMap = new Map()
|
||||||
|
ips.forEach(ip => {
|
||||||
|
countMap.set(ip, (countMap.get(ip) || 0) + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Convert the Map into an array of objects with `str` and `cnt` properties
|
||||||
|
const result = Array.from(countMap, ([ip, cnt]) => ({ ip, cnt }))
|
||||||
|
|
||||||
|
// Sort the results by the `cnt` property in descending order
|
||||||
|
result.sort((a, b) => b.cnt - a.cnt)
|
||||||
|
|
||||||
|
// Ensure the result has at most 20 elements
|
||||||
|
return result.slice(0, 20)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in usage-use-cases.js/getTopIps()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the top 20 most consumed endpoints.
|
||||||
|
getTopEndpoints () {
|
||||||
|
try {
|
||||||
|
const endpoints = restCalls.map(x => `${x.method} ${x.url}`)
|
||||||
|
|
||||||
|
// Create a Map to count occurrences of each IP address string
|
||||||
|
const countMap = new Map()
|
||||||
|
endpoints.forEach(endpoint => {
|
||||||
|
countMap.set(endpoint, (countMap.get(endpoint) || 0) + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Convert the Map into an array of objects with `str` and `cnt` properties
|
||||||
|
const result = Array.from(countMap, ([endpoint, cnt]) => ({ endpoint, cnt }))
|
||||||
|
|
||||||
|
// Sort the results by the `cnt` property in descending order
|
||||||
|
result.sort((a, b) => b.cnt - a.cnt)
|
||||||
|
|
||||||
|
// Ensure the result has at most 20 elements
|
||||||
|
return result.slice(0, 20)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in usage-use-cases.js/getTopEndpoints()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This Koa middleware is called any time there is a REST API. It logs the
|
||||||
|
// details from the request object.
|
||||||
|
function usageMiddleware () {
|
||||||
|
return async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
await next()
|
||||||
|
|
||||||
|
// console.log('ctx.request: ', ctx.request)
|
||||||
|
const now = new Date()
|
||||||
|
|
||||||
|
const reqObj = {
|
||||||
|
ip: ctx.request.ip,
|
||||||
|
url: ctx.request.url,
|
||||||
|
method: ctx.request.method,
|
||||||
|
timestamp: now.getTime()
|
||||||
|
}
|
||||||
|
// console.log('reqObj: ', reqObj)
|
||||||
|
|
||||||
|
restCalls.push(reqObj)
|
||||||
|
} catch (err) {
|
||||||
|
ctx.status = err.status || 500
|
||||||
|
ctx.body = err.message
|
||||||
|
ctx.app.emit('error', err, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { UsageUseCases, usageMiddleware, restCalls }
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
End-to-end tests for /usage endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import config from '../../../config/index.js'
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import axios from 'axios'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import util from 'util'
|
||||||
|
|
||||||
|
util.inspect.defaultOptions = { depth: 1 }
|
||||||
|
|
||||||
|
const LOCALHOST = `http://localhost:${config.port}`
|
||||||
|
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
describe('Usage', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('GET /usage', () => {
|
||||||
|
it('should return usage status', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'get',
|
||||||
|
url: `${LOCALHOST}/usage`
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await axios(options)
|
||||||
|
|
||||||
|
assert.property(result.data, 'status')
|
||||||
|
} catch (err) {
|
||||||
|
assert(false, 'Unexpected result')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /usage/ips', () => {
|
||||||
|
it('should return ips', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'get',
|
||||||
|
url: `${LOCALHOST}/usage/ips`
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await axios(options)
|
||||||
|
|
||||||
|
assert.property(result.data, 'ips')
|
||||||
|
} catch (err) {
|
||||||
|
assert(false, 'Unexpected result')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /usage/endpoints', () => {
|
||||||
|
it('should return ips', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'get',
|
||||||
|
url: `${LOCALHOST}/usage/endpoints`
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await axios(options)
|
||||||
|
|
||||||
|
assert.property(result.data, 'endpoints')
|
||||||
|
} catch (err) {
|
||||||
|
assert(false, 'Unexpected result')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
import { assert } from 'chai'
|
import { assert } from 'chai'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import cloneDeep from 'lodash.clonedeep'
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
import { peerIdFromString } from '@libp2p/peer-id'
|
// import { peerIdFromString } from '@libp2p/peer-id'
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import IPFSLib from '../../../src/adapters/ipfs/ipfs.js'
|
import IPFSLib from '../../../src/adapters/ipfs/ipfs.js'
|
||||||
@@ -165,17 +165,17 @@ describe('#IPFS-adapter', () => {
|
|||||||
uut.config.isCircuitRelay = false
|
uut.config.isCircuitRelay = false
|
||||||
|
|
||||||
// Mock dependencies and force desired code path.
|
// Mock dependencies and force desired code path.
|
||||||
let beenCalled = false
|
// const beenCalled = false
|
||||||
sandbox.stub(uut, 'getKeychain').resolves({
|
// sandbox.stub(uut, 'getKeychain').resolves({
|
||||||
exportPeerId: async () => {
|
// exportPeerId: async () => {
|
||||||
if (!beenCalled) {
|
// if (!beenCalled) {
|
||||||
beenCalled = true
|
// beenCalled = true
|
||||||
throw new Error('test error')
|
// throw new Error('test error')
|
||||||
}
|
// }
|
||||||
return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP')
|
// return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP')
|
||||||
},
|
// },
|
||||||
createKey: async () => {}
|
// createKey: async () => {}
|
||||||
})
|
// })
|
||||||
sandbox.stub(uut, 'createLibp2p').resolves()
|
sandbox.stub(uut, 'createLibp2p').resolves()
|
||||||
sandbox.stub(uut, 'createHelia').resolves({})
|
sandbox.stub(uut, 'createHelia').resolves({})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API middleware that handle response errors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local libraries
|
||||||
|
import errorMiddleware from '../../../../../src/controllers/rest-api/middleware/error.js'
|
||||||
|
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
|
||||||
|
|
||||||
|
describe('#Validators', () => {
|
||||||
|
let ctx
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock the context object.
|
||||||
|
ctx = mockContext()
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#errorMiddleware', () => {
|
||||||
|
it('should run next function', async () => {
|
||||||
|
// Spy on next
|
||||||
|
const next = sinon.spy(() => { })
|
||||||
|
errorMiddleware()(ctx, next)
|
||||||
|
|
||||||
|
assert.isTrue(next.calledOnce)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle unknown status error', async () => {
|
||||||
|
try {
|
||||||
|
const next = async () => {
|
||||||
|
const e = new Error('test error')
|
||||||
|
e.status = null
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
await errorMiddleware()(ctx, next)
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(ctx.status, 500)
|
||||||
|
assert.equal(ctx.body, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should handle known status error', async () => {
|
||||||
|
try {
|
||||||
|
const next = async () => {
|
||||||
|
const e = new Error('test error')
|
||||||
|
e.status = 422
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
await errorMiddleware()(ctx, next)
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(ctx.status, 422)
|
||||||
|
assert.equal(ctx.body, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /usage 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 UsageController from '../../../../../src/controllers/rest-api/usage/controller.js'
|
||||||
|
|
||||||
|
import { context as mockContext } from '../../../mocks/ctx-mock.js'
|
||||||
|
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
let ctx
|
||||||
|
|
||||||
|
describe('#Usage-REST-Controller', () => {
|
||||||
|
// const testUser = {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new UsageController({ 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 UsageController()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating /usage REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new UsageController({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantiating /usage REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#Get /usage', () => {
|
||||||
|
it('should return 422 status on biz logic error', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.useCases.usage, 'getRestSummary').throws(new Error('test error'))
|
||||||
|
await uut.getStatus(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 () => {
|
||||||
|
await uut.getStatus(ctx)
|
||||||
|
|
||||||
|
// Assert the expected HTTP response
|
||||||
|
assert.equal(ctx.status, 200)
|
||||||
|
|
||||||
|
// Assert that expected properties exist in the returned data.
|
||||||
|
assert.property(ctx.response.body, 'status')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#Get /ips', () => {
|
||||||
|
it('should return 422 status on biz logic error', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.useCases.usage, 'getTopIps').throws(new Error('test error'))
|
||||||
|
await uut.getTopIps(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 () => {
|
||||||
|
await uut.getTopIps(ctx)
|
||||||
|
|
||||||
|
// Assert the expected HTTP response
|
||||||
|
assert.equal(ctx.status, 200)
|
||||||
|
|
||||||
|
// Assert that expected properties exist in the returned data.
|
||||||
|
assert.property(ctx.response.body, 'ips')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#Get /endpoints', () => {
|
||||||
|
it('should return 422 status on biz logic error', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.useCases.usage, 'getTopEndpoints').throws(new Error('test error'))
|
||||||
|
await uut.getTopEndpoints(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 () => {
|
||||||
|
await uut.getTopEndpoints(ctx)
|
||||||
|
|
||||||
|
// Assert the expected HTTP response
|
||||||
|
assert.equal(ctx.status, 200)
|
||||||
|
|
||||||
|
// Assert that expected properties exist in the returned data.
|
||||||
|
assert.property(ctx.response.body, 'endpoints')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#handleError', () => {
|
||||||
|
it('should pass an error message', () => {
|
||||||
|
try {
|
||||||
|
const err = {
|
||||||
|
status: 422,
|
||||||
|
message: 'Unprocessable Entity'
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.handleError(ctx, err)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Unprocessable Entity')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /usage 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 UsageRouter from '../../../../../src/controllers/rest-api/usage/index.js'
|
||||||
|
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
// let ctx
|
||||||
|
|
||||||
|
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||||
|
|
||||||
|
describe('#Usage-REST-Router', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new UsageRouter({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new UsageRouter()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating IPFS REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new UsageRouter({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantiating IPFS 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.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -79,4 +79,19 @@ describe('#Timer-Controllers', () => {
|
|||||||
assert.equal(result, false)
|
assert.equal(result, false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('#cleanUsage', () => {
|
||||||
|
it('should kick off the Use Case', async () => {
|
||||||
|
const result = await uut.cleanUsage()
|
||||||
|
|
||||||
|
assert.equal(result, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false on error', async () => {
|
||||||
|
sandbox.stub(uut.useCases.usage, 'cleanUsage').throws(new Error('test error'))
|
||||||
|
const result = await uut.cleanUsage()
|
||||||
|
|
||||||
|
assert.equal(result, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,6 +47,24 @@ class BCH {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UsageUseCaseMock {
|
||||||
|
async cleanUsage() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRestSummary() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTopIps(params) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTopEndpoints(existingUser, newData) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class UseCasesMock {
|
class UseCasesMock {
|
||||||
constuctor(localConfig = {}) {
|
constuctor(localConfig = {}) {
|
||||||
// this.user = new UserUseCaseMock(localConfig)
|
// this.user = new UserUseCaseMock(localConfig)
|
||||||
@@ -54,6 +72,7 @@ class UseCasesMock {
|
|||||||
|
|
||||||
user = new UserUseCaseMock()
|
user = new UserUseCaseMock()
|
||||||
bch = new BCH()
|
bch = new BCH()
|
||||||
|
usage = new UsageUseCaseMock()
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UseCasesMock;
|
export default UseCasesMock;
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the use-cases/usage-use-cases.js business logic library.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../mocks/adapters/index.js'
|
||||||
|
|
||||||
|
// Mock
|
||||||
|
import { context as mockContext } from '../mocks/ctx-mock.js'
|
||||||
|
|
||||||
|
// Unit under test (uut)
|
||||||
|
import { UsageUseCases, restCalls, usageMiddleware } from '../../../src/use-cases/usage-use-cases.js'
|
||||||
|
|
||||||
|
describe('#usage-use-case', () => {
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
let ctx
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
uut = new UsageUseCases({ adapters })
|
||||||
|
|
||||||
|
// Set as empty array
|
||||||
|
restCalls.splice(0, restCalls.length)
|
||||||
|
|
||||||
|
ctx = mockContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new UsageUseCases()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of adapters must be passed in when instantiating Usage Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#cleanUsage', () => {
|
||||||
|
it('should delete older data than 24 hours', () => {
|
||||||
|
const now = new Date() // Mock date
|
||||||
|
|
||||||
|
// set older mock data
|
||||||
|
restCalls.push({
|
||||||
|
timestamp: now.getTime() - (60000 * 60 * 24),
|
||||||
|
ip: '127.0.0.1'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set recently mock data
|
||||||
|
restCalls.push({
|
||||||
|
timestamp: now.getTime(),
|
||||||
|
ip: 'localhost'
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = uut.cleanUsage()
|
||||||
|
|
||||||
|
assert.isArray(result)
|
||||||
|
assert.equal(result.length, 1)
|
||||||
|
assert.equal(result[0].ip, 'localhost')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle error', () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox.stub(restCalls, 'filter').throws(new Error('uut error'))
|
||||||
|
|
||||||
|
uut.cleanUsage()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'uut error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#getRestSummary', () => {
|
||||||
|
it('should get the number of rest calls', () => {
|
||||||
|
// Set mock data
|
||||||
|
restCalls.push({
|
||||||
|
ip: 'localhost'
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = uut.getRestSummary()
|
||||||
|
|
||||||
|
assert.isNumber(result)
|
||||||
|
assert.equal(result, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle error', () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox.stub(console, 'log').throws(new Error('uut error'))
|
||||||
|
|
||||||
|
uut.getRestSummary()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'uut error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getTopIps', () => {
|
||||||
|
it('should get top IPs', () => {
|
||||||
|
// Set mock data
|
||||||
|
restCalls.push({
|
||||||
|
ip: 'localhost'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set mock data
|
||||||
|
restCalls.push({
|
||||||
|
ip: 'localhost'
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = uut.getTopIps()
|
||||||
|
|
||||||
|
assert.isArray(result)
|
||||||
|
|
||||||
|
assert.property(result[0], 'ip')
|
||||||
|
assert.property(result[0], 'cnt')
|
||||||
|
|
||||||
|
assert.equal(result[0].ip, 'localhost')
|
||||||
|
assert.equal(result[0].cnt, '2')
|
||||||
|
})
|
||||||
|
it('should return a maximum of 20 values', () => {
|
||||||
|
// Fill Array with 21 values
|
||||||
|
for (let i = 0; i < 21; i++) {
|
||||||
|
restCalls.push({
|
||||||
|
ip: `localhost-${i}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = uut.getTopIps()
|
||||||
|
|
||||||
|
assert.isArray(result)
|
||||||
|
|
||||||
|
assert.property(result[0], 'ip')
|
||||||
|
assert.property(result[0], 'cnt')
|
||||||
|
|
||||||
|
assert.equal(result.length, 20)
|
||||||
|
})
|
||||||
|
it('should handle error', () => {
|
||||||
|
try {
|
||||||
|
// Set mock data
|
||||||
|
restCalls.push(null)
|
||||||
|
|
||||||
|
uut.getTopIps()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'Cannot read properties')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getTopEndpoints', () => {
|
||||||
|
it('should get top Endpoints', () => {
|
||||||
|
// Set mock data
|
||||||
|
restCalls.push({
|
||||||
|
ip: 'localhost',
|
||||||
|
url: '/api/v1/users',
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set mock data
|
||||||
|
restCalls.push({
|
||||||
|
ip: 'localhost',
|
||||||
|
url: '/api/v1/users',
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = uut.getTopEndpoints()
|
||||||
|
|
||||||
|
assert.isArray(result)
|
||||||
|
|
||||||
|
assert.property(result[0], 'endpoint')
|
||||||
|
assert.property(result[0], 'cnt')
|
||||||
|
|
||||||
|
assert.equal(result[0].endpoint, 'GET /api/v1/users')
|
||||||
|
assert.equal(result[0].cnt, '2')
|
||||||
|
})
|
||||||
|
it('should return a maximum of 20 values', () => {
|
||||||
|
// Fill Array with 21 values
|
||||||
|
for (let i = 0; i < 21; i++) {
|
||||||
|
restCalls.push({
|
||||||
|
ip: 'localhost',
|
||||||
|
url: `/api/v1/users-${i}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = uut.getTopEndpoints()
|
||||||
|
|
||||||
|
assert.isArray(result)
|
||||||
|
|
||||||
|
assert.property(result[0], 'endpoint')
|
||||||
|
assert.property(result[0], 'cnt')
|
||||||
|
|
||||||
|
assert.equal(result.length, 20)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle error', () => {
|
||||||
|
try {
|
||||||
|
// Set mock data
|
||||||
|
restCalls.push(null)
|
||||||
|
|
||||||
|
uut.getTopEndpoints()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'Cannot read properties')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#usageMiddleware', () => {
|
||||||
|
it('should update restCalls state', async () => {
|
||||||
|
// Spy on next
|
||||||
|
const next = sinon.spy(() => { })
|
||||||
|
|
||||||
|
await usageMiddleware()(ctx, next)
|
||||||
|
|
||||||
|
assert.equal(restCalls.length, 1)
|
||||||
|
assert.isTrue(next.called)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle error', async () => {
|
||||||
|
try {
|
||||||
|
const next = () => { throw new Error('uut error') }
|
||||||
|
|
||||||
|
await usageMiddleware()(ctx, next)
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'uut error')
|
||||||
|
assert.equal(ctx.status, 500)
|
||||||
|
assert.equal(restCalls.length, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user