mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer.git
synced 2026-09-22 01:02:04 -07:00
Compare commits
109
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d4b678447 | ||
|
|
e9606ecfe8 | ||
|
|
bfc0a5be9f | ||
|
|
af68e411bd | ||
|
|
b9b79c2142 | ||
|
|
58f24954ca | ||
|
|
6f4f21b373 | ||
|
|
b0e42bb956 | ||
|
|
efb02a4381 | ||
|
|
d894c1e596 | ||
|
|
821cf4abe8 | ||
|
|
74696cfb9e | ||
|
|
0bfb84e21b | ||
|
|
35b4e7079d | ||
|
|
7d369859a5 | ||
|
|
75ea6ecdde | ||
|
|
d4c9665c16 | ||
|
|
768bfdb4e7 | ||
|
|
764fe928d2 | ||
|
|
2de61458d3 | ||
|
|
d38e371a05 | ||
|
|
a68ca92468 | ||
|
|
7c26e33a8c | ||
|
|
2595ee8295 | ||
|
|
476fdcd0aa | ||
|
|
b6762b215e | ||
|
|
8faadfc5c1 | ||
|
|
fe0ad56395 | ||
|
|
0e6a411012 | ||
|
|
55e9e35c1f | ||
|
|
5a05a95876 | ||
|
|
163e5f5f10 | ||
|
|
ff371a79b9 | ||
|
|
487b72bfa2 | ||
|
|
7d5715be39 | ||
|
|
fc99cafb1d | ||
|
|
0e51dc7f15 | ||
|
|
c0e0e42fc6 | ||
|
|
932110698c | ||
|
|
59ed8d9e7a | ||
|
|
883f9aca67 | ||
|
|
9b88d7f40c | ||
|
|
18ea03a4d6 | ||
|
|
3d911d1062 | ||
|
|
6ceec71dd6 | ||
|
|
f1296fafd6 | ||
|
|
479299d747 | ||
|
|
cf88fadfdc | ||
|
|
4977ae7003 | ||
|
|
f0f6970a08 | ||
|
|
fcb4acb11d | ||
|
|
28e012feb6 | ||
|
|
958c637c24 | ||
|
|
34381e6e93 | ||
|
|
8843b3e2b0 | ||
|
|
3f91653d23 | ||
|
|
70da4d8000 | ||
|
|
3a4569c33f | ||
|
|
05a0620663 | ||
|
|
b8c1156de7 | ||
|
|
5146155555 | ||
|
|
af83c06b43 | ||
|
|
e6317c3282 | ||
|
|
354321c956 | ||
|
|
af2572a493 | ||
|
|
ac451fd601 | ||
|
|
6857fe0166 | ||
|
|
89a03e561a | ||
|
|
22e32a6fc1 | ||
|
|
fcf5db7fa4 | ||
|
|
f8cf903fb9 | ||
|
|
c7986d1986 | ||
|
|
87ca75425b | ||
|
|
587114e827 | ||
|
|
c3a694546d | ||
|
|
591bd618e4 | ||
|
|
166f2de20e | ||
|
|
35fbc23fe0 | ||
|
|
c65d662630 | ||
|
|
1b99f8afa1 | ||
|
|
1bba636e43 | ||
|
|
c6f0a1317c | ||
|
|
fad5f8e23d | ||
|
|
ec6e0f5e46 | ||
|
|
f1caef2c0d | ||
|
|
55bf9424f9 | ||
|
|
aa100c97ed | ||
|
|
9fa5eafe95 | ||
|
|
4d9e948e30 | ||
|
|
e5cdf28e34 | ||
|
|
1d82b36105 | ||
|
|
b910cd1e6d | ||
|
|
536861ba99 | ||
|
|
ef030e3dc6 | ||
|
|
3da46c3d6f | ||
|
|
82f5552836 | ||
|
|
4d25681c51 | ||
|
|
8fe78037c1 | ||
|
|
33a447cd61 | ||
|
|
8a70d6a169 | ||
|
|
9ae5e890ae | ||
|
|
1bf28cbc21 | ||
|
|
d7bb6e4cb2 | ||
|
|
98a1631498 | ||
|
|
8dce8cbf8d | ||
|
|
f097ee024a | ||
|
|
7d21fe02f8 | ||
|
|
0b3049138b | ||
|
|
d7136583b8 |
+3
-1
@@ -125,7 +125,9 @@ class Server {
|
||||
console.log('\nConfiguration:')
|
||||
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
|
||||
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
|
||||
} catch (err) {
|
||||
|
||||
Vendored
+8
-1
@@ -131,6 +131,8 @@ export default {
|
||||
// IPFS Ports
|
||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4101,
|
||||
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4103,
|
||||
ipfsWebRtcPort: process.env.IPFS_WEB_RTC_PORT ? process.env.IPFS_WEB_RTC_PORT : 4105,
|
||||
connectPref: process.env.CONNECT_PREF, // Used in helia-coord to select connection preference.
|
||||
|
||||
// Settings for production, using external go-ipfs node.
|
||||
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
|
||||
@@ -170,6 +172,11 @@ export default {
|
||||
// Preferred P2WDB provider
|
||||
preferredIpfsFileProvider: process.env.PREFERRED_IPFS_FILE_PROVIDER
|
||||
? process.env.PREFERRED_IPFS_FILE_PROVIDER
|
||||
: ''
|
||||
: '',
|
||||
|
||||
// Account Configuration
|
||||
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
|
||||
|
||||
// Admin password
|
||||
adminPassword: process.env.ADMIN_PASSWORD
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import development from './env/development.js'
|
||||
import production from './env/production.js'
|
||||
import test from './env/test.js'
|
||||
|
||||
const env = process.env.SVC_ENV || 'development'
|
||||
const env = process.env.CONSUMER_ENV || 'development'
|
||||
console.log(`Loading config for this environment: ${env}`)
|
||||
|
||||
let config = development
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"password": "RPWjFwYWyJ9S8ByjRDbi",
|
||||
"email": "system@system.com",
|
||||
"id": "678459df8ec91cd6c3973b63",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY3ODQ1OWRmOGVjOTFjZDZjMzk3M2I2MyIsImlhdCI6MTczNjcyNzAwN30.AHLNjmznprA5GzQ_z8GbhrYHOJUIlMzVC64SGRy-Xvs"
|
||||
"name": "admin",
|
||||
"password": "c9JjJxZmTs5bWmWC6adQ",
|
||||
"type": "admin",
|
||||
"id": "68d47521e6060ba7332301b1",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4ZDQ3NTIxZTYwNjBiYTczMzIzMDFiMSIsImlhdCI6MTc2MDY0MzI5NH0.SgoWnrP0YZZtPZ2V_C7HsfKoRHphfoIODN3-pltsGbo"
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"password": "RCJqq5FjgYzvTXOyS1vo",
|
||||
"email": "system@system.com",
|
||||
"id": "645e59c19d49119bbb8329e7",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0NWU1OWMxOWQ0OTExOWJiYjgzMjllNyIsImlhdCI6MTY4MzkwNDk2MX0.2yi-hkY31nkgMm_S1QrPs4dIm5DBnJ4GsJEPiSPYHDA"
|
||||
"name": "admin",
|
||||
"password": "NR0S1fPGLOAGUwPfKpJe",
|
||||
"type": "admin",
|
||||
"id": "698277538440afae85603e83",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY5ODI3NzUzODQ0MGFmYWU4NTYwM2U4MyIsImlhdCI6MTc3MDE1NzkwN30.rBILkWIXFtj0OkGBQyJyf89i9S2SYhm4ETPhvevVwaQ"
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import Server from './bin/server.js'
|
||||
const server = new Server()
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.log(`Handling ${reason.code} error. stack: `, reason)
|
||||
})
|
||||
|
||||
server.startServer()
|
||||
|
||||
Generated
+6862
-9233
File diff suppressed because it is too large
Load Diff
+16
-23
@@ -6,11 +6,12 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "npm run test:all",
|
||||
"test": "npm run test:unit",
|
||||
"test:all": "export CONSUMER_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:unit": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:unit": "export CONSUMER_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:e2e:auto": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
|
||||
"test:temp": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
|
||||
"start:e2e:server": "export CONSUMER_ENV=test && node index.js",
|
||||
"test:temp": "export SVC_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
|
||||
"lint": "standard --env mocha --fix",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||
"coverage": "c8 report --reporter=text-lcov | coveralls",
|
||||
@@ -24,26 +25,10 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
|
||||
"dependencies": {
|
||||
"@chainsafe/libp2p-gossipsub": "13.1.1",
|
||||
"@chainsafe/libp2p-noise": "15.1.1",
|
||||
"@chainsafe/libp2p-yamux": "6.0.2",
|
||||
"@helia/unixfs": "3.0.7",
|
||||
"@libp2p/bootstrap": "10.1.5",
|
||||
"@libp2p/circuit-relay-v2": "1.1.5",
|
||||
"@libp2p/identify": "2.1.5",
|
||||
"@libp2p/keychain": "4.1.5",
|
||||
"@libp2p/logger": "4.0.20",
|
||||
"@libp2p/tcp": "9.1.5",
|
||||
"@libp2p/webrtc": "4.1.7",
|
||||
"@libp2p/websockets": "8.2.0",
|
||||
"@multiformats/multiaddr": "12.3.0",
|
||||
"axios": "0.27.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blockstore-fs": "2.0.0",
|
||||
"datastore-fs": "9.1.7",
|
||||
"glob": "7.1.6",
|
||||
"helia": "4.2.5",
|
||||
"helia-coord": "1.5.15",
|
||||
"helia-coord": "2.0.3",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-bch-lib": "1.3.0",
|
||||
@@ -58,15 +43,14 @@
|
||||
"koa-router": "10.0.0",
|
||||
"koa-static": "5.0.0",
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "1.9.1",
|
||||
"line-reader": "0.4.0",
|
||||
"mime-types": "2.1.35",
|
||||
"minimal-slp-wallet": "5.12.0",
|
||||
"minimal-slp-wallet": "7.1.4",
|
||||
"mongoose": "5.13.14",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.7.5",
|
||||
"passport-local": "1.0.0",
|
||||
"psffpp": "1.1.9",
|
||||
"psffpp": "1.2.0",
|
||||
"public-ip": "6.0.1",
|
||||
"semver": "7.3.5",
|
||||
"winston": "3.3.3",
|
||||
@@ -102,5 +86,14 @@
|
||||
"ignore": [
|
||||
"/test/unit/mocks/**/*.js"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"@multiformats/multiaddr-to-uri": "10.1.2",
|
||||
"@multiformats/multiaddr-matcher": "1.6.0",
|
||||
"@libp2p/utils": "6.7.1",
|
||||
"helia": {
|
||||
"libp2p": "2.7.2",
|
||||
"@libp2p/upnp-nat": "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#IMAGE BUILD COMMANDS
|
||||
# ct-base-ubuntu = ubuntu 18.04 + nodejs v10 LTS
|
||||
#FROM christroutner/ct-base-ubuntu
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:24.04
|
||||
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
|
||||
|
||||
#Update the OS and install any OS packages needed.
|
||||
@@ -15,7 +15,7 @@ RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
RUN sudo apt-get install -y ca-certificates curl gnupg
|
||||
RUN sudo mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN sudo apt-get update
|
||||
RUN sudo apt-get install -y nodejs build-essential
|
||||
|
||||
@@ -66,9 +66,6 @@ RUN npm install
|
||||
# Generate the API docs
|
||||
RUN npm run docs
|
||||
|
||||
# Expose the port the API will be served on.
|
||||
#EXPOSE 5010
|
||||
|
||||
# Start the application.
|
||||
#COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
@@ -26,9 +26,12 @@ services:
|
||||
links:
|
||||
- mongo-bch-consumer
|
||||
ports:
|
||||
- '5015:5015' # <host port>:<container port>
|
||||
- '4101:4101'
|
||||
- '4103:4103'
|
||||
# <host port>:<container port>
|
||||
- '5015:5015' # REST API
|
||||
- '4101:4101' # TCP
|
||||
- '4103:4103' # Websockets
|
||||
- '4105:4105' # WebRTC
|
||||
|
||||
volumes:
|
||||
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-consumer/.ipfsdata
|
||||
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-consumer/start-production.sh
|
||||
|
||||
@@ -26,14 +26,20 @@ export DBURL=mongodb://172.17.0.1:5558/ipfs-bch-consumer-prod
|
||||
export PORT=5015
|
||||
|
||||
# Production settings using external go-ipfs node.
|
||||
export SVC_ENV=prod
|
||||
export CONSUMER_ENV=prod
|
||||
export IPFS_HOST=172.17.0.1
|
||||
export IPFS_API_PORT=5001
|
||||
export IPFS_TCP_PORT=4101
|
||||
export IPFS_WS_PORT=4103
|
||||
export IPFS_WEB_RTC_PORT=4105
|
||||
|
||||
# Set the debug level for helia-coord. 0-3.
|
||||
# 0 = no debug logs. 3 = maximum debug logs.
|
||||
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
|
||||
|
||||
+26
-58
@@ -51,70 +51,37 @@ class Admin {
|
||||
// used by the Listing Manager and test scripts, in order access private API
|
||||
// functions.
|
||||
async createSystemUser () {
|
||||
// Create the system user.
|
||||
try {
|
||||
context.password = _this._randomString(20)
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/users`,
|
||||
data: {
|
||||
user: {
|
||||
email: 'system@system.com',
|
||||
password: context.password,
|
||||
name: 'admin'
|
||||
}
|
||||
}
|
||||
const context = {
|
||||
email: 'system@system.com',
|
||||
name: 'admin',
|
||||
password: _this.config.adminPassword || _this._randomString(20),
|
||||
type: 'admin'
|
||||
}
|
||||
const result = await _this.axios.request(options)
|
||||
// console.log('admin.data: ', result.data)
|
||||
// Check if the user already exists
|
||||
let adminUser = await _this.User.findOne({ email: context.email })
|
||||
|
||||
context.email = result.data.user.email
|
||||
context.id = result.data.user._id
|
||||
context.token = result.data.token
|
||||
if (adminUser) {
|
||||
// Update the password
|
||||
adminUser.password = context.password
|
||||
} else {
|
||||
// Create a new admin user
|
||||
adminUser = new _this.User(context)
|
||||
}
|
||||
// Update context with the new user id and token
|
||||
context.id = adminUser._id
|
||||
context.token = await adminUser.generateToken()
|
||||
|
||||
// Get the mongoDB entry
|
||||
const user = await _this.User.findById(context.id)
|
||||
// Save the user
|
||||
await adminUser.save()
|
||||
|
||||
// Change the user type to admin
|
||||
user.type = 'admin'
|
||||
// console.log(`user created: ${JSON.stringify(user, null, 2)}`)
|
||||
|
||||
// Save the user model.
|
||||
await user.save()
|
||||
|
||||
// console.log(`admin user created: ${JSON.stringify(result.body, null, 2)}`)
|
||||
// console.log(`with password: ${context.password}`)
|
||||
|
||||
// Write out the system user information to a JSON file that external
|
||||
// applications like the Task Manager and the test scripts can access.
|
||||
|
||||
await jsonFiles.writeJSON(context, JSON_PATH)
|
||||
// console.log('context: ', context)
|
||||
// console.log('JSON_PATH: ', JSON_PATH)
|
||||
// Write the user data to the JSON file
|
||||
await _this.jsonFiles.writeJSON(context, JSON_PATH)
|
||||
|
||||
return context
|
||||
} catch (err) {
|
||||
// Handle existing system user.
|
||||
if (err.response.status === 422) {
|
||||
try {
|
||||
// Delete the existing user
|
||||
await _this.deleteExistingSystemUser()
|
||||
|
||||
// Call this function again.
|
||||
return _this.createSystemUser()
|
||||
} catch (err2) {
|
||||
console.error(
|
||||
'Error in admin.js/createSystemUser() while trying generate new system user.'
|
||||
)
|
||||
// process.end(1)
|
||||
throw err2
|
||||
}
|
||||
} else {
|
||||
console.log('Error in admin.js/createSystemUser: ')
|
||||
// process.end(1)
|
||||
throw err
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error in admin.js/createSystemUser()')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +96,7 @@ class Admin {
|
||||
})
|
||||
|
||||
await _this.User.deleteOne({ email: 'system@system.com' })
|
||||
return true
|
||||
} catch (err) {
|
||||
console.log('Error in admin.js/deleteExistingSystemUser()')
|
||||
throw err
|
||||
@@ -152,7 +120,7 @@ class Admin {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
data: {
|
||||
email: 'system@system.com',
|
||||
email: existingUser.email,
|
||||
password: existingUser.password
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,9 @@ class BchAdapter {
|
||||
// Choose the BCH wallet service to use.
|
||||
async selectProvider (providerId) {
|
||||
try {
|
||||
if (!providerId || typeof providerId !== 'string') {
|
||||
throw new Error('providerId must be a string!')
|
||||
}
|
||||
this.ipfs.ipfsCoordAdapter.config.preferredProvider = providerId
|
||||
|
||||
return true
|
||||
@@ -405,7 +408,7 @@ class BchAdapter {
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
|
||||
@@ -41,7 +41,7 @@ class Adapters {
|
||||
this.passport = new Passport()
|
||||
this.nodemailer = new Nodemailer()
|
||||
this.jsonFiles = new JSONFiles()
|
||||
this.bchjs = new BCHJS()
|
||||
this.bchjs = new BCHJS({ restURL: config.apiServer })
|
||||
this.config = config
|
||||
this.bch = new BCH(localConfig)
|
||||
this.p2wdb = new P2WDB(localConfig)
|
||||
|
||||
@@ -45,6 +45,10 @@ class IpfsFilesAdapter {
|
||||
this.rpcHandler = this.rpcHandler.bind(this)
|
||||
this.getStatus = this.getStatus.bind(this)
|
||||
this.selectProvider = this.selectProvider.bind(this)
|
||||
this.getFileMetadata = this.getFileMetadata.bind(this)
|
||||
this.getPins = this.getPins.bind(this)
|
||||
this.waitForRPCResponse = this.waitForRPCResponse.bind(this)
|
||||
this.pinClaim = this.pinClaim.bind(this)
|
||||
}
|
||||
|
||||
// This handler is triggered when RPC data comes in over IPFS.
|
||||
@@ -60,7 +64,7 @@ class IpfsFilesAdapter {
|
||||
this.rpcDataQueue.push(data)
|
||||
} catch (err) {
|
||||
console.error('Error in files/rpcHandler(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +81,7 @@ class IpfsFilesAdapter {
|
||||
|
||||
// Add names to the IPFS IDs for each provider.
|
||||
const initialServiceProviders =
|
||||
this.ipfs.ipfsCoordAdapter.state.ipfsFileProviders
|
||||
this.ipfs.ipfsCoordAdapter.state.ipfsFileProviders
|
||||
const serviceProviders = []
|
||||
for (let i = 0; i < initialServiceProviders.length; i++) {
|
||||
const thisProvider = initialServiceProviders[i]
|
||||
@@ -115,12 +119,16 @@ class IpfsFilesAdapter {
|
||||
// Choose the ipfs-file-pin-service to use.
|
||||
async selectProvider (providerId) {
|
||||
try {
|
||||
if (!providerId || typeof providerId !== 'string') {
|
||||
throw new Error('providerId must be a string!')
|
||||
}
|
||||
|
||||
this.ipfs.ipfsCoordAdapter.config.selectedIpfsFileProvider = providerId
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/files/getStatus()')
|
||||
wlogger.error('Error in adapters/files/selectProvider()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -139,7 +147,7 @@ class IpfsFilesAdapter {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No IPFS File Pin Service provider available yet.')
|
||||
}
|
||||
@@ -182,13 +190,16 @@ class IpfsFilesAdapter {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No IPFS File Pin Service provider available yet.')
|
||||
}
|
||||
|
||||
const { page } = inObj
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'getPins'
|
||||
endpoint: 'getPins',
|
||||
page
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
@@ -221,6 +232,10 @@ class IpfsFilesAdapter {
|
||||
// Returns a promise that resolves to data when the RPC response is recieved.
|
||||
async waitForRPCResponse (rpcId) {
|
||||
try {
|
||||
if (!rpcId) {
|
||||
throw new Error('rpcId can not be false or undefined')
|
||||
}
|
||||
|
||||
// Initialize variables for tracking the return data.
|
||||
let dataFound = false
|
||||
let cnt = 0
|
||||
@@ -258,7 +273,7 @@ class IpfsFilesAdapter {
|
||||
|
||||
cnt++
|
||||
|
||||
// Exit if data was returned, or the window for a response expires.
|
||||
// Exit if data was returned, or the window for a response expires.
|
||||
} while (!dataFound && cnt < 10)
|
||||
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
|
||||
|
||||
@@ -268,6 +283,53 @@ class IpfsFilesAdapter {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async pinClaim (inObj = {}) {
|
||||
try {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// ipfs service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No IPFS File Provider Service is available yet. Try again in a few seconds.')
|
||||
}
|
||||
|
||||
const { proofOfBurnTxid, cid, claimTxid, filename, address } = inObj
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'pinClaim',
|
||||
proofOfBurnTxid,
|
||||
cid,
|
||||
claimTxid,
|
||||
filename,
|
||||
address
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'file-pin', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error('Error in pinClaim()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = P2wdbAdapter
|
||||
|
||||
@@ -219,9 +219,10 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Error in pollForP2wdbServices()', err)
|
||||
// catch and handle known failure mode.
|
||||
if (
|
||||
err.message.includes("Cannot read property 'peerList' of undefined")
|
||||
err.message.includes('Cannot read')
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -276,6 +277,7 @@ class IpfsCoordAdapter {
|
||||
// Persist the config setting, so it can be used by other commands.
|
||||
// _this.conf.set('selectedService', thisPeer)
|
||||
console.log(`---->BCH wallet service selected: ${thisPeer}`)
|
||||
console.log('preferredProvider: ', _this.config.preferredProvider)
|
||||
}
|
||||
|
||||
// console.log('preferredProvider: ', _this.config.preferredProvider)
|
||||
@@ -286,8 +288,12 @@ class IpfsCoordAdapter {
|
||||
_this.config.preferredProvider &&
|
||||
thisPeer === _this.config.preferredProvider
|
||||
) {
|
||||
// console.log('preferredProvider: ', _this.config.preferredProvider)
|
||||
// console.log('thisPeer: ', thisPeer)
|
||||
// console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider)
|
||||
if (_this.state.selectedServiceProvider !== thisPeer) {
|
||||
console.log(`---->BCH wallet service switched to preferred peer: ${thisPeer}`)
|
||||
console.log('preferredProvider: ', _this.config.preferredProvider)
|
||||
}
|
||||
|
||||
_this.state.selectedServiceProvider = thisPeer
|
||||
@@ -310,7 +316,7 @@ class IpfsCoordAdapter {
|
||||
} catch (err) {
|
||||
// catch and handle known failure mode.
|
||||
if (
|
||||
err.message.includes("Cannot read property 'peerList' of undefined")
|
||||
err.message.includes('Cannot read')
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -366,6 +372,7 @@ class IpfsCoordAdapter {
|
||||
// Persist the config setting, so it can be used by other commands.
|
||||
// _this.conf.set('selectedService', thisPeer)
|
||||
console.log(`---->IPFS File service selected: ${thisPeer}`)
|
||||
console.log('preferredIpfsFileProvider: ', _this.config.preferredIpfsFileProvider)
|
||||
}
|
||||
|
||||
// console.log('preferredProvider: ', _this.config.preferredProvider)
|
||||
@@ -400,7 +407,7 @@ class IpfsCoordAdapter {
|
||||
} catch (err) {
|
||||
// catch and handle known failure mode.
|
||||
if (
|
||||
err.message.includes("Cannot read property 'peerList' of undefined")
|
||||
err.message.includes('Cannot read')
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
+17
-197
@@ -3,33 +3,12 @@
|
||||
This library deals with IPFS so that the apps business logic doesn't need
|
||||
to have any specific knowledge of the js-ipfs library.
|
||||
|
||||
TODO: Add the external IP address to the list of multiaddrs advertised by
|
||||
this node. See this GitHub Issue for details:
|
||||
https://github.com/Permissionless-Software-Foundation/ipfs-service-provider/issues/38
|
||||
Node creation is delegated to helia-coord's CreateHeliaNode factory,
|
||||
which encapsulates all helia/libp2p dependencies and configuration.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import { createHelia } from 'helia'
|
||||
import fs from 'fs'
|
||||
import { FsBlockstore } from 'blockstore-fs'
|
||||
import { FsDatastore } from 'datastore-fs'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { tcp } from '@libp2p/tcp'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
// import { bootstrap } from '@libp2p/bootstrap'
|
||||
// import { identifyService } from 'libp2p/identify'
|
||||
import { identify } from '@libp2p/identify'
|
||||
// import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
||||
import { circuitRelayServer } from '@libp2p/circuit-relay-v2'
|
||||
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import { publicIpv4 } from 'public-ip'
|
||||
import { multiaddr } from '@multiformats/multiaddr'
|
||||
// import { webRTC } from '@libp2p/webrtc'
|
||||
import { keychain } from '@libp2p/keychain'
|
||||
import { defaultLogger } from '@libp2p/logger'
|
||||
import { unixfs } from '@helia/unixfs'
|
||||
import CreateHeliaNode from 'helia-coord/create-helia-node'
|
||||
|
||||
// Local libraries
|
||||
import config from '../../../config/index.js'
|
||||
@@ -39,59 +18,44 @@ import JsonFiles from '../json-files.js'
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
// console.log('__dirname: ', __dirname)
|
||||
|
||||
const ROOT_DIR = `${__dirname}../../../`
|
||||
const IPFS_DIR = `${__dirname}../../../.ipfsdata/ipfs`
|
||||
|
||||
class IpfsAdapter {
|
||||
constructor (localConfig) {
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
this.fs = fs
|
||||
this.createLibp2p = createLibp2p
|
||||
this.publicIp = publicIpv4
|
||||
this.multiaddr = multiaddr
|
||||
this.jsonFiles = new JsonFiles()
|
||||
this.keychain = keychain
|
||||
this.createHelia = createHelia
|
||||
|
||||
// Properties of this class instance.
|
||||
this.isReady = false
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.start = this.start.bind(this)
|
||||
this.createNode = this.createNode.bind(this)
|
||||
this.stop = this.stop.bind(this)
|
||||
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
|
||||
this.getSeed = this.getSeed.bind(this)
|
||||
this.getKeychain = this.getKeychain.bind(this)
|
||||
}
|
||||
|
||||
// Start an IPFS node.
|
||||
async start () {
|
||||
try {
|
||||
// Ensure the directory structure exists that is needed by the IPFS node to store data.
|
||||
this.ensureBlocksDir()
|
||||
// Create the helia-coord node factory with production configuration.
|
||||
this.heliaNode = new CreateHeliaNode({
|
||||
ipfsDir: IPFS_DIR,
|
||||
tcpPort: this.config.ipfsTcpPort,
|
||||
wsPort: this.config.ipfsWsPort,
|
||||
isCircuitRelay: this.config.isCircuitRelay,
|
||||
getSeed: this.getSeed
|
||||
})
|
||||
|
||||
// Create an IPFS node
|
||||
const ipfs = await this.createNode()
|
||||
// console.log('ipfs: ', ipfs)
|
||||
// Start the IPFS node (creates dirs, libp2p, helia, detects public IP)
|
||||
const ipfs = await this.heliaNode.start()
|
||||
|
||||
this.id = this.heliaNode.id
|
||||
this.multiaddrs = this.heliaNode.multiaddrs
|
||||
|
||||
this.id = ipfs.libp2p.peerId.toString()
|
||||
console.log('IPFS ID: ', this.id)
|
||||
|
||||
// Attempt to guess our ip4 IP address.
|
||||
const ip4 = await this.publicIp()
|
||||
let detectedMultiaddr = `/ip4/${ip4}/tcp/${this.config.ipfsTcpPort}/p2p/${this.id}`
|
||||
detectedMultiaddr = this.multiaddr(detectedMultiaddr)
|
||||
|
||||
// Get the multiaddrs for the node.
|
||||
const multiaddrs = ipfs.libp2p.getMultiaddrs()
|
||||
multiaddrs.push(detectedMultiaddr)
|
||||
console.log('Multiaddrs: ', multiaddrs)
|
||||
|
||||
this.multiaddrs = multiaddrs
|
||||
console.log('Multiaddrs: ', this.multiaddrs)
|
||||
|
||||
// Signal that this adapter is ready.
|
||||
this.isReady = true
|
||||
@@ -101,127 +65,6 @@ class IpfsAdapter {
|
||||
return this.ipfs
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs.js/start()')
|
||||
|
||||
// If IPFS crashes because the /blocks directory is full, wipe the directory.
|
||||
// if (err.message.includes('No space left on device')) {
|
||||
// this.rmBlocksDir()
|
||||
// }
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
// It returns the node as an object.
|
||||
async createNode () {
|
||||
try {
|
||||
// Create block and data stores.
|
||||
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
|
||||
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
|
||||
|
||||
// const keychainInit = {
|
||||
// pass: await this.getSeed()
|
||||
// }
|
||||
|
||||
// Create an identity
|
||||
let peerId
|
||||
// console.log('this.keychain: ', this.keychain)
|
||||
// const chain = this.keychain(keychainInit)({
|
||||
// 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')
|
||||
}
|
||||
|
||||
// Configure services
|
||||
const services = {
|
||||
identify: identify(),
|
||||
pubsub: gossipsub({ allowPublishToZeroTopicPeers: true })
|
||||
}
|
||||
if (this.config.isCircuitRelay) {
|
||||
console.log('Helia (IPFS) node IS configured as Circuit Relay')
|
||||
services.relay = circuitRelayServer({ // makes the node function as a relay server
|
||||
hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit
|
||||
advertise: true,
|
||||
reservations: {
|
||||
maxReservations: 15, // how many peers are allowed to reserve relay slots on this server
|
||||
reservationClearInterval: 300 * 1000, // how often to reclaim stale reservations
|
||||
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
|
||||
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
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
|
||||
}
|
||||
|
||||
const transports = [
|
||||
tcp(),
|
||||
webSockets()
|
||||
// circuitRelayTransport({
|
||||
// discoverRelays: 3,
|
||||
// reservationConcurrency: 3
|
||||
// }),
|
||||
// webRTC()
|
||||
]
|
||||
|
||||
// libp2p is the networking layer that underpins Helia
|
||||
const libp2p = await this.createLibp2p({
|
||||
peerId,
|
||||
datastore,
|
||||
addresses: {
|
||||
listen: [
|
||||
'/ip4/127.0.0.1/tcp/0',
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`
|
||||
// '/webrtc'
|
||||
]
|
||||
},
|
||||
transports,
|
||||
connectionEncryption: [
|
||||
noise()
|
||||
],
|
||||
streamMuxers: [
|
||||
yamux()
|
||||
],
|
||||
services
|
||||
})
|
||||
|
||||
// create a Helia node
|
||||
const helia = await this.createHelia({
|
||||
blockstore,
|
||||
datastore,
|
||||
libp2p
|
||||
})
|
||||
|
||||
// Attach IPFS file system.
|
||||
const fs = unixfs(helia)
|
||||
helia.fs = fs
|
||||
|
||||
return helia
|
||||
} catch (err) {
|
||||
console.error('Error creating Helia node: ', err)
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -232,27 +75,6 @@ class IpfsAdapter {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ensure that the directories exist to store blocks from the IPFS network.
|
||||
// This function is called at startup, before the IPFS node is started.
|
||||
ensureBlocksDir () {
|
||||
try {
|
||||
!this.fs.existsSync(`${ROOT_DIR}.ipfsdata`) && this.fs.mkdirSync(`${ROOT_DIR}.ipfsdata`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}`) && this.fs.mkdirSync(`${IPFS_DIR}`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}/blockstore`) && this.fs.mkdirSync(`${IPFS_DIR}/blockstore`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
|
||||
|
||||
// !fs.existsSync(`${IPFS_DIR}/datastore/peers`) && fs.mkdirSync(`${IPFS_DIR}/datastore/peers`)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs.js/ensureBlocksDir(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function opens the seed used to generate the key for this IPFS peer.
|
||||
// The seed is stored in a JSON file. If it doesn't exist, a new one is created.
|
||||
async getSeed () {
|
||||
@@ -272,8 +94,6 @@ class IpfsAdapter {
|
||||
await this.jsonFiles.writeJSON(seed, filename)
|
||||
}
|
||||
|
||||
// console.log('getSeed() seed: ', seed)
|
||||
|
||||
return seed
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs/ipfs.js/getSeed()')
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
// Load Mongoose models.
|
||||
import Users from './models/users.js'
|
||||
import Usage from './models/usage.js'
|
||||
|
||||
class LocalDB {
|
||||
constructor () {
|
||||
// Encapsulate dependencies
|
||||
this.Users = Users
|
||||
this.Usage = Usage
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -109,6 +109,9 @@ class P2wdbAdapter {
|
||||
// Choose the BCH wallet service to use.
|
||||
async selectProvider (providerId) {
|
||||
try {
|
||||
if (!providerId || typeof providerId !== 'string') {
|
||||
throw new Error('providerId must be a string!')
|
||||
}
|
||||
this.ipfs.ipfsCoordAdapter.config.preferredP2wdbProvider = providerId
|
||||
|
||||
return true
|
||||
@@ -215,6 +218,10 @@ class P2wdbAdapter {
|
||||
// Returns a promise that resolves to data when the RPC response is recieved.
|
||||
async waitForRPCResponse (rpcId) {
|
||||
try {
|
||||
if (!rpcId) {
|
||||
throw new Error('rpcId can not be false or undefined')
|
||||
}
|
||||
|
||||
// Initialize variables for tracking the return data.
|
||||
let dataFound = false
|
||||
let cnt = 0
|
||||
|
||||
@@ -20,6 +20,13 @@ class Wallet {
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
|
||||
this._instanceWallet = this._instanceWallet.bind(this)
|
||||
this.openWallet = this.openWallet.bind(this)
|
||||
this.instanceWallet = this.instanceWallet.bind(this)
|
||||
this.incrementNextAddress = this.incrementNextAddress.bind(this)
|
||||
this.getKeyPair = this.getKeyPair.bind(this)
|
||||
this.optimize = this.optimize.bind(this)
|
||||
this.getBalance = this.getBalance.bind(this)
|
||||
}
|
||||
|
||||
// This is used for initializing the wallet, without waiting to update the wallet
|
||||
|
||||
@@ -107,7 +107,7 @@ class JSONRPC {
|
||||
|
||||
// Default return string
|
||||
let retObj = _this.defaultResponse()
|
||||
|
||||
console.log('parsedData ', parsedData)
|
||||
// Forward data on to bch adapter if this is the response of a BCH query.
|
||||
try {
|
||||
if (parsedData.payload.result.method === 'bch') {
|
||||
|
||||
@@ -39,6 +39,9 @@ class IpfsRESTControllerLib {
|
||||
this.getService = this.getService.bind(this)
|
||||
this.getFileInfo = this.getFileInfo.bind(this)
|
||||
this.getPins = this.getPins.bind(this)
|
||||
this.cid2json = this.cid2json.bind(this)
|
||||
this.downloadFile = this.downloadFile.bind(this)
|
||||
this.pinClaim = this.pinClaim.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +66,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
|
||||
@@ -78,6 +100,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 {
|
||||
@@ -91,6 +131,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
|
||||
@@ -164,12 +222,34 @@ class IpfsRESTControllerLib {
|
||||
)
|
||||
ctx.body = readStream
|
||||
} catch (err) {
|
||||
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
|
||||
console.log('Error in ipfs/controller.js/viewFile(): ', err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /ipfs/download/:cid Download a file via its IPFS CID
|
||||
* @apiPermission public
|
||||
* @apiName GetCidDownload
|
||||
* @apiGroup REST IPFS
|
||||
* @apiDescription Download a file via its IPFS CID. Returns a readable stream.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/download/bafkreieaqtdhfywyddomswogynzymukosqqgqo7lkt5lch2zwfnc55m6om
|
||||
*/
|
||||
async downloadFile (ctx) {
|
||||
try {
|
||||
const { cid } = ctx.params
|
||||
|
||||
const { readStream } = await this.useCases.ipfs.downloadCid({ cid })
|
||||
|
||||
ctx.body = readStream
|
||||
} catch (err) {
|
||||
console.log('Error in ipfs/controller.js/downloadFile(): ', err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /ipfs/service Get the IPFS ID for the File Pin service
|
||||
* @apiPermission public
|
||||
@@ -191,7 +271,7 @@ class IpfsRESTControllerLib {
|
||||
selectedIpfsFileProvider
|
||||
}
|
||||
} catch (err) {
|
||||
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
|
||||
// wlogger.error('Error in ipfs/controller.js/getService(): ', err)
|
||||
console.log('Error in ipfs/controller.js/getService(): ', err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
@@ -219,8 +299,7 @@ class IpfsRESTControllerLib {
|
||||
|
||||
ctx.body = metadata
|
||||
} catch (err) {
|
||||
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
|
||||
console.log('Error in ipfs/controller.js/getService(): ', err)
|
||||
console.log('Error in ipfs/controller.js/getFileInfo(): ', err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
@@ -238,23 +317,73 @@ class IpfsRESTControllerLib {
|
||||
*/
|
||||
async getPins (ctx) {
|
||||
try {
|
||||
// const { cid } = ctx.params
|
||||
const { page } = ctx.params
|
||||
|
||||
const ipfsFiles = this.adapters.ipfsFiles
|
||||
|
||||
const pinData = await ipfsFiles.getPins()
|
||||
const pinData = await ipfsFiles.getPins({ page })
|
||||
console.log('getPins() pinData: ', pinData)
|
||||
|
||||
ctx.body = pinData
|
||||
} catch (err) {
|
||||
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
|
||||
console.log('Error in ipfs/controller.js/getPins(): ', err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /ipfs/cid2json/:cid Given a CID, retrieve a JSON object
|
||||
* @apiPermission public
|
||||
* @apiName GetCid2Json
|
||||
* @apiGroup REST IPFS
|
||||
* @apiDescription Given a CID, retrieve a JSON object.
|
||||
* If the CID does not resolves to a JSON file, then an error is thrown.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/cid2json/bafkreigbgrvpagnmrqz2vhofifrqobigsxkdvnvikf5iqrkrbwrzirazhm
|
||||
*
|
||||
*/
|
||||
async cid2json (ctx) {
|
||||
try {
|
||||
const { cid } = ctx.params
|
||||
|
||||
const json = await this.useCases.ipfs.cid2json({ cid })
|
||||
|
||||
ctx.body = json
|
||||
} catch (err) {
|
||||
console.log('Error in ipfs/controller.js/cid2json(): ', err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /ipfs/pin-claim Process pin claim.
|
||||
* @apiPermission public
|
||||
* @apiName PinClaim
|
||||
* @apiGroup REST IPFS
|
||||
* @apiDescription Process pin claim.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "proofOfBurnTxid": "be4b63156c93f58ed311d403d9f756deda9abbc81d0fef8fbe5d769538b4261c", "cid": "bafybeied3zdwdiro7fqytyha2yfband4lwcrtozmf6shynylt3kexh26dq", "claimTxid": "c71e2f2cdf8658d90c61ac6183b8ffeeb359779807b317386044705d8352f0f2", "filename": "mutable-67ccefcca67097473e78ca10.json", "address": "bitcoincash:qqs2wrahl6azn9qdyrmp9ygeejqvzr8ruv7e9m30fr" }' http://localhost:5001/ipfs/pin-claim *
|
||||
*/
|
||||
async pinClaim (ctx) {
|
||||
try {
|
||||
const body = ctx.request.body
|
||||
|
||||
const result = await this.useCases.ipfs.pinClaim(body)
|
||||
|
||||
ctx.body = result
|
||||
} catch (err) {
|
||||
console.log('Error in ipfs/controller.js/cid2json(): ', err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// console.log('handleError() err.status: ', err.status)
|
||||
// console.log('handleError() err.message: ', err.message)
|
||||
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
@@ -263,6 +392,7 @@ class IpfsRESTControllerLib {
|
||||
ctx.throw(err.status)
|
||||
}
|
||||
} else {
|
||||
// console.log(`handleError() err.message: ${err.message}`)
|
||||
// By default use a 422 error if the HTTP status is not specified.
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
|
||||
@@ -57,10 +57,12 @@ class IpfsRouter {
|
||||
this.router.post('/connect', this.ipfsRESTController.connect)
|
||||
this.router.get('/node', this.ipfsRESTController.getThisNode)
|
||||
this.router.get('/view/:cid', this.ipfsRESTController.viewFile)
|
||||
this.router.get('/download/:cid', this.ipfsRESTController.downloadFile)
|
||||
this.router.get('/service', this.ipfsRESTController.getService)
|
||||
this.router.get('/file-info/:cid', this.ipfsRESTController.getFileInfo)
|
||||
this.router.get('/pins', this.ipfsRESTController.getPins)
|
||||
|
||||
this.router.get('/pins/:page', this.ipfsRESTController.getPins)
|
||||
this.router.get('/cid2json/:cid', this.ipfsRESTController.cid2json)
|
||||
this.router.post('/pin-claim', this.ipfsRESTController.pinClaim)
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
REST API Controller library for the /ipfs route
|
||||
REST API Controller library for the /usage route
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
@@ -13,19 +13,17 @@ class UsageRESTControllerLib {
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /ipfs REST Controller.'
|
||||
'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 /ipfs REST Controller.'
|
||||
'Instance of Use Cases library required when instantiating /usage REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.getStatus = this.getStatus.bind(this)
|
||||
|
||||
@@ -10,6 +10,8 @@ import UserRESTControllerLib from './controller.js'
|
||||
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
let _this
|
||||
|
||||
class UserRouter {
|
||||
@@ -34,6 +36,7 @@ class UserRouter {
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.config = config
|
||||
this.userRESTController = new UserRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
@@ -52,7 +55,7 @@ class UserRouter {
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', this.userRESTController.createUser)
|
||||
this.router.post('/', this.createUser)
|
||||
this.router.get('/', this.getAll)
|
||||
this.router.get('/:id', this.getById)
|
||||
this.router.put('/:id', this.updateUser)
|
||||
@@ -63,6 +66,14 @@ class UserRouter {
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
|
||||
async createUser (ctx, next) {
|
||||
if (process.env.DISABLE_NEW_ACCOUNTS) {
|
||||
await _this.validators.ensureAdmin(ctx, next)
|
||||
}
|
||||
await _this.userRESTController.createUser(ctx, next)
|
||||
return true
|
||||
}
|
||||
|
||||
async getAll (ctx, next) {
|
||||
await _this.validators.ensureUser(ctx, next)
|
||||
await _this.userRESTController.getUsers(ctx, next)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
This Controller library is concerned with timer-based functions that are
|
||||
kicked off periodicially.
|
||||
kicked off periodically.
|
||||
*/
|
||||
|
||||
import config from '../../config/index.js'
|
||||
@@ -23,58 +23,97 @@ class TimerControllers {
|
||||
|
||||
this.debugLevel = localConfig.debugLevel
|
||||
|
||||
// Constants
|
||||
this.cleanUsageInterval = 60000 * 60 // 1 hour
|
||||
this.backupUsageInterval = 60000 * 10 // 10 minutes
|
||||
this.rebootInterval = 60000 * 60 * 12 // 12 hours
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
|
||||
// Bind 'this' object to all subfunctions.
|
||||
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
|
||||
this.cleanUsage = this.cleanUsage.bind(this)
|
||||
|
||||
// this.startTimers()
|
||||
this.backupUsage = this.backupUsage.bind(this)
|
||||
this.autoReboot = this.autoReboot.bind(this)
|
||||
}
|
||||
|
||||
// Start all the time-based controllers.
|
||||
startTimers () {
|
||||
// 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)
|
||||
|
||||
// Periodically reboot this app. Helia has a slight memory leak, and occasionally
|
||||
// runs into unrecoverable network issues. Rebooting is one way to overcome these
|
||||
// black-box issues.
|
||||
this.rebootHandle = setInterval(this.autoReboot, this.rebootInterval)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
stopTimers () {
|
||||
clearInterval(this.optimizeWalletHandle)
|
||||
clearInterval(this.cleanusageHandle)
|
||||
}
|
||||
|
||||
// Replace this example function with your own timer handler.
|
||||
exampleTimerFunc (negativeTest) {
|
||||
try {
|
||||
// console.log('Example timer controller executed.')
|
||||
|
||||
if (negativeTest) throw new Error('test error')
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in exampleTimerFunc(): ', err)
|
||||
|
||||
// Note: Do not throw an error. This is a top-level function.
|
||||
return false
|
||||
}
|
||||
clearInterval(this.cleanUsageHandle)
|
||||
clearInterval(this.backupUsageHandle)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Note: Do not throw an error. This is a top-level function.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Backup the usage stats to the database
|
||||
async backupUsage () {
|
||||
try {
|
||||
clearInterval(this.backupUsageHandle)
|
||||
|
||||
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/backupUsage(): ', err)
|
||||
|
||||
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
|
||||
|
||||
// Note: Do not throw an error. This is a top-level function.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// This timer is used to auto-reboot the processes. If this is being run in Docker container
|
||||
// (the target for production), the Docker container should automatically restart the timer.
|
||||
// This is a temporary fix to the IPFS node stalling occasionally until a better fix can be
|
||||
// found.
|
||||
autoReboot () {
|
||||
console.log('Rebooting service.')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
export default TimerControllers
|
||||
|
||||
@@ -32,6 +32,9 @@ class UseCases {
|
||||
|
||||
// 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
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// import { exporter } from 'ipfs-unixfs-exporter'
|
||||
import { Duplex } from 'stream'
|
||||
import PSFFPP from 'psffpp'
|
||||
import { CID } from 'multiformats'
|
||||
|
||||
// Local libraries
|
||||
// import PinEntity from '../entities/pin.js'
|
||||
@@ -28,11 +29,15 @@ class IpfsUseCases {
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.psffpp = null // placeholder
|
||||
this.CID = CID
|
||||
|
||||
// Bind 'this' object to all class subfunctions.
|
||||
this.downloadCid = this.downloadCid.bind(this)
|
||||
// this.downloadCid2 = this.downloadCid2.bind(this)
|
||||
this.getWritePrice = this.getWritePrice.bind(this)
|
||||
this.getCidMetadata = this.getCidMetadata.bind(this)
|
||||
this.cid2json = this.cid2json.bind(this)
|
||||
this.pinClaim = this.pinClaim.bind(this)
|
||||
|
||||
// State
|
||||
this.lastWritePriceUpdate = null // Used to periodically update write price.
|
||||
@@ -42,7 +47,7 @@ class IpfsUseCases {
|
||||
// Returns a readable stream.
|
||||
async downloadCid (inObj = {}) {
|
||||
try {
|
||||
const { cid } = inObj
|
||||
let { cid } = inObj
|
||||
|
||||
if (!cid) throw new Error('CID is undefined')
|
||||
|
||||
@@ -63,6 +68,47 @@ class IpfsUseCases {
|
||||
|
||||
const helia = this.adapters.ipfs.ipfs
|
||||
|
||||
const cidClass = this.CID.parse(cid)
|
||||
|
||||
// Download the file from other nodes.
|
||||
await helia.blockstore.get(cidClass)
|
||||
|
||||
// list cid content, to dermine if this is a file-based or directory-based CID.
|
||||
const contentArray = []
|
||||
for await (const file of helia.fs.ls(cid)) {
|
||||
contentArray.push(file)
|
||||
}
|
||||
|
||||
// Handle directory-based CIDs. This is used by Token Tiger for mutable and immutable token data.
|
||||
/**
|
||||
* If the cid is a directory
|
||||
* The next block of code sends a html page with a list of links with the file names into the directory.
|
||||
* Skipping this code delivers directly the first file detected in the directory.
|
||||
*/
|
||||
const isDir = contentArray[0].path.match('/') // TODO : looking for a better way to detect if is a directory
|
||||
// 'listDir' is a flag to ignore this code on /download endpoint.
|
||||
if (isDir) {
|
||||
console.log('This CID is a directory. contentArray[0]: ', contentArray[0])
|
||||
cid = `${cid}/${contentArray[0].name}`
|
||||
|
||||
// throw new Error('CID is a directory. Not supported yet.')
|
||||
// const stream = new Stream.Readable({ read () { } })
|
||||
// for (let i = 0; i < contentArray.length; i++) {
|
||||
// const cont = contentArray[i]
|
||||
// // List all paths excluding root path.
|
||||
// if (cont.path !== cid) {
|
||||
// // Add links to the gateway with the format cid/:filename
|
||||
// stream.push(`<a href='${this.config.domainName}/ipfs/view/${cid}/${cont.name}' >/${cont.name} ( CID: ${cont.cid} )</a><hr />`)
|
||||
// }
|
||||
// }
|
||||
|
||||
// stream.push(null)
|
||||
// // return fileName as html because the controller the library <mime.lookup> sends it as html
|
||||
// return { readStream: stream }
|
||||
} else {
|
||||
console.log('This CID is not a directory.')
|
||||
}
|
||||
|
||||
// Convert the file to a Buffer.
|
||||
const fileChunks = []
|
||||
for await (const chunk of helia.fs.cat(cid)) {
|
||||
@@ -109,7 +155,8 @@ class IpfsUseCases {
|
||||
// Periodically re-validate the write-price (by re-instantiating the
|
||||
// PSFFPP library) in case it's changed.
|
||||
const now = new Date()
|
||||
const sixHours = now.getTime() * 1000 * 60 * 60 * 6
|
||||
const sixHours = 1000 * 60 * 60 * 6
|
||||
|
||||
if (this.lastWritePriceUpdate + sixHours < now.getTime()) {
|
||||
this.psffpp = new PSFFPP({ wallet })
|
||||
|
||||
@@ -151,6 +198,107 @@ class IpfsUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
// Given a CID, this function will retrieve a JSON object from the
|
||||
// ipfs-file-pin-service, using the IPFS JSON-RPC.
|
||||
async cid2json (inObj = {}) {
|
||||
try {
|
||||
const { cid } = inObj
|
||||
console.log('cid2json() cid: ', cid)
|
||||
|
||||
// Throw an error if ipfs-bch-wallet-consumer has not yet connected to an instance of ipfs-file-pin-service.
|
||||
const ipfsFileProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
|
||||
if (!ipfsFileProvider) {
|
||||
throw new Error('No IPFS File Provider Service is available yet. Try again in a few seconds.')
|
||||
}
|
||||
|
||||
// Throw an error if ipfs-bch-wallet-consumer can not communicate with the ipfs-file-pin-service.
|
||||
const ipfsFiles = this.adapters.ipfsFiles
|
||||
const metadata = await ipfsFiles.getFileMetadata({ cid })
|
||||
if (metadata.success === false) {
|
||||
throw new Error(`Could not communicate with instance of ipfs-file-pin-service ${ipfsFileProvider}. Try again in a few seconds.`)
|
||||
}
|
||||
|
||||
// Throw an error if the file is not pinned.
|
||||
const fileMetadata = metadata.fileMetadata
|
||||
if (!fileMetadata.dataPinned) {
|
||||
throw new Error(`CID ${cid} has not been pinned by ipfs-file-pin-service instance ${ipfsFileProvider}`)
|
||||
}
|
||||
// console.log('fileMetadata: ', fileMetadata)
|
||||
|
||||
// Throw an error if this is not a JSON file
|
||||
const filename = fileMetadata.filename
|
||||
|
||||
if (!filename.endsWith('.json')) {
|
||||
throw new Error(`CID ${cid} does not resolve to a JSON file.`)
|
||||
}
|
||||
|
||||
// Retrieve the CID content and store it in a Buffer.
|
||||
const helia = this.adapters.ipfs.ipfs
|
||||
const fileChunks = []
|
||||
|
||||
// list cid content. This is used to determine if the CID is a file or a directory.
|
||||
const contentArray = []
|
||||
for await (const file of helia.fs.ls(cid)) {
|
||||
contentArray.push(file)
|
||||
}
|
||||
// console.log('contentArray: ', contentArray)
|
||||
|
||||
try {
|
||||
// Handle CIDs that are files.
|
||||
for await (const chunk of helia.fs.cat(cid)) {
|
||||
fileChunks.push(chunk)
|
||||
}
|
||||
} catch (err) {
|
||||
// Extra logic here to look at the filename of the first file and make sure
|
||||
// it ends in .json.
|
||||
const name = contentArray[0].name
|
||||
if (!name.endsWith('.json')) {
|
||||
throw new Error(`CID ${cid} is a directory and its first file does not resolve to a valid JSON file.`)
|
||||
}
|
||||
|
||||
// Handle CIDs that are directorys containing a JSON file. (TokenTiger.com tokens)
|
||||
for await (const chunk of helia.fs.cat(`${cid}/${name}`)) {
|
||||
fileChunks.push(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
const fileBuf = Buffer.concat(fileChunks)
|
||||
|
||||
// Convert the Buffer into a string.
|
||||
const jsonStr = fileBuf.toString()
|
||||
|
||||
// Parse the string into a JSON object.
|
||||
let json = null
|
||||
try {
|
||||
json = JSON.parse(jsonStr)
|
||||
} catch (err) {
|
||||
throw new Error(`CID ${cid} does not resolve to a valid JSON object.`)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
json
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs-use-cases.js/cid2json(): ', err.message)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async pinClaim (inObj = {}) {
|
||||
try {
|
||||
// Throw an error if ipfs-bch-wallet-consumer can not communicate with the ipfs-file-pin-service.
|
||||
const metadata = await this.adapters.ipfsFiles.pinClaim(inObj)
|
||||
|
||||
return {
|
||||
success: metadata.success,
|
||||
message: metadata.message
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs-use-cases.js/pinClaim(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// async downloadCid2 (inObj = {}) {
|
||||
// try {
|
||||
// const { cid } = inObj
|
||||
|
||||
@@ -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,10 +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
|
||||
}
|
||||
@@ -35,7 +46,14 @@ class UsageUseCases {
|
||||
const now = new Date()
|
||||
const twentyFourHoursAgo = now.getTime() - (60000 * 60 * 24)
|
||||
|
||||
console.log('cleanUsage() now: ', now)
|
||||
console.log('cleanUsage() restCalls.length before filtering: ', restCalls.length)
|
||||
restCalls = restCalls.filter(x => x.timestamp > twentyFourHoursAgo)
|
||||
console.log('cleanUsage() restCalls.length after filtering: ', restCalls.length)
|
||||
|
||||
if (!restCalls) restCalls = []
|
||||
|
||||
return restCalls
|
||||
} catch (err) {
|
||||
console.error('Error in usage-use-cases.js/cleanUsage()')
|
||||
throw err
|
||||
@@ -58,7 +76,6 @@ class UsageUseCases {
|
||||
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 => {
|
||||
@@ -103,6 +120,62 @@ 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]
|
||||
|
||||
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)
|
||||
|
||||
restCalls = usage
|
||||
if (!restCalls) restCalls = []
|
||||
|
||||
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
|
||||
@@ -112,7 +185,7 @@ function usageMiddleware () {
|
||||
try {
|
||||
await next()
|
||||
|
||||
console.log('ctx.request: ', ctx.request)
|
||||
// console.log('ctx.request: ', ctx.request)
|
||||
const now = new Date()
|
||||
|
||||
const reqObj = {
|
||||
@@ -121,7 +194,7 @@ function usageMiddleware () {
|
||||
method: ctx.request.method,
|
||||
timestamp: now.getTime()
|
||||
}
|
||||
console.log('reqObj: ', reqObj)
|
||||
// console.log('reqObj: ', reqObj)
|
||||
|
||||
restCalls.push(reqObj)
|
||||
} catch (err) {
|
||||
@@ -132,4 +205,4 @@ function usageMiddleware () {
|
||||
}
|
||||
};
|
||||
|
||||
export { UsageUseCases, usageMiddleware }
|
||||
export { UsageUseCases, usageMiddleware, restCalls }
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -5,22 +5,11 @@ import axios from 'axios'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
import UserController from '../../../src/controllers/rest-api/users/controller.js'
|
||||
import Adapters from '../../../src/adapters/index.js'
|
||||
import UseCases from '../../../src/use-cases/index.js'
|
||||
|
||||
// import UserController from '../../../src/controllers/rest-api/users/controller.js'
|
||||
// import Adapters from '../../../src/adapters/index.js'
|
||||
import EventEmitter from 'events'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const context = {}
|
||||
const adapters = new Adapters({ eventEmitter: new EventEmitter() })
|
||||
// import UseCases from '../../../src/use-cases/index.js'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
// const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
@@ -56,9 +45,6 @@ if (!config.noMongo) {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCases({ adapters })
|
||||
uut = new UserController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
@@ -274,33 +260,6 @@ if (!config.noMongo) {
|
||||
assert.hasAnyKeys(users[0], ['type', '_id', 'email'])
|
||||
assert.isNumber(users.length)
|
||||
})
|
||||
|
||||
it('should return a 422 http status if biz-logic throws an error', async () => {
|
||||
try {
|
||||
const { token } = context
|
||||
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.useCases.user, 'getAllUsers')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url: `${LOCALHOST}/users`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
await axios(options)
|
||||
|
||||
assert.fail('Unexpected code path!')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.response.status, 422)
|
||||
assert.equal(err.response.data, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users/:id', () => {
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -14,13 +14,14 @@ describe('#adapters', () => {
|
||||
let uut, sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new Adapters()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
uut = new Adapters()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
if (sandbox) {
|
||||
sandbox.restore()
|
||||
}
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
@@ -37,6 +38,21 @@ 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 {
|
||||
|
||||
+50
-47
@@ -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')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -62,6 +49,7 @@ describe('Admin', () => {
|
||||
describe('createSystemUser()', () => {
|
||||
it('should create admin', async () => {
|
||||
try {
|
||||
await uut.deleteExistingSystemUser()
|
||||
const result = await uut.createSystemUser()
|
||||
|
||||
assert.property(result, 'email')
|
||||
@@ -72,44 +60,59 @@ describe('Admin', () => {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
it('should update admin password', async () => {
|
||||
try {
|
||||
const error1 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 422
|
||||
}
|
||||
const error2 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 500
|
||||
}
|
||||
// The loginAdmin() function in some use cases is recursive
|
||||
// after handling the 422 error, it gets called again
|
||||
sandbox
|
||||
.stub(uut.axios, 'request')
|
||||
.onFirstCall()
|
||||
.throws(error1)
|
||||
.onSecondCall()
|
||||
.throws(error2)
|
||||
uut.config.adminPassword = 'newpassword'
|
||||
|
||||
await uut.createSystemUser()
|
||||
const fakeUser = {
|
||||
password: 'oldpassword',
|
||||
save: () => { return 'token' },
|
||||
generateToken: () => { return 'token' }
|
||||
}
|
||||
|
||||
sandbox.stub(uut.User, 'findOne').resolves(fakeUser)
|
||||
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves(true)
|
||||
const result = await uut.createSystemUser()
|
||||
|
||||
assert.property(result, 'email')
|
||||
assert.property(result, 'password')
|
||||
assert.property(result, 'id')
|
||||
assert.property(result, 'token')
|
||||
|
||||
assert.equal(fakeUser.password, 'newpassword', 'password should be updated')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.User, 'findOne').throws(new Error('test error'))
|
||||
await uut.createSystemUser()
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle errors when remove user', async () => {
|
||||
describe('deleteExistingSystemUser()', () => {
|
||||
it('should delete admin', async () => {
|
||||
try {
|
||||
const error1 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 422
|
||||
}
|
||||
sandbox.stub(uut.axios, 'request').throws(error1)
|
||||
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
|
||||
|
||||
await uut.createSystemUser()
|
||||
sandbox.stub(uut.User, 'deleteOne').resolves(true)
|
||||
const result = await uut.deleteExistingSystemUser()
|
||||
assert.isTrue(result)
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle error when deleting admin', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
|
||||
await uut.deleteExistingSystemUser()
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import cloneDeep from 'lodash.clonedeep'
|
||||
import BchAdapter from '../../../src/adapters/bch/index.js'
|
||||
import adapters from '../mocks/adapters/index.js'
|
||||
import mockDataLib from '../mocks/use-cases/rest-api-mocks.js'
|
||||
import ipfsCoordMocks from '../mocks/adapters/ipfs-coord-mocks.js'
|
||||
|
||||
const eventEmitter = new EventEmitter()
|
||||
|
||||
@@ -91,7 +92,30 @@ describe('#bch-use-case', () => {
|
||||
assert.property(result, 'balances')
|
||||
assert.isArray(result.balances)
|
||||
})
|
||||
it('should return false if data is not found', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs.ipfsCoordAdapter.wallet = {
|
||||
bchjs: {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
// Fill the queue with false values.
|
||||
for (let i = 0; i < 10; i++) {
|
||||
uut.rpcDataQueue.push({ payload: { id: '1234567890' } })
|
||||
}
|
||||
sandbox.stub(uut, 'sleep').resolves()
|
||||
|
||||
const result = await uut.waitForRPCResponse(rpcId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'success')
|
||||
assert.isFalse(result.success)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.waitForRPCResponse()
|
||||
@@ -114,12 +138,46 @@ describe('#bch-use-case', () => {
|
||||
|
||||
uut.rpcHandler(data)
|
||||
})
|
||||
it('should skip errors', async () => {
|
||||
try {
|
||||
sandbox.stub(uut, 'rpcDataQueue').throws(new Error('test error'))
|
||||
await uut.rpcHandler()
|
||||
} catch (err) {
|
||||
assert.fail('Unexpected code path')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should get the status from ipfs-coord', async () => {
|
||||
uut.ipfs.ipfsCoordAdapter.state.serviceProviders = ipfsCoordMocks.peers
|
||||
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData = ipfsCoordMocks.peerData
|
||||
const result = await uut.getStatus()
|
||||
console.log('result: ', result)
|
||||
assert.isObject(result)
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = null
|
||||
await uut.getStatus()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#selectProvider', () => {
|
||||
it('should select provider', async () => {
|
||||
await uut.selectProvider('providerId to select')
|
||||
assert.equal(uut.ipfs.ipfsCoordAdapter.config.preferredProvider, 'providerId to select')
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
await uut.selectProvider()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'providerId must be a string!')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -154,6 +212,18 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
await uut.getBalances()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getUtxos', () => {
|
||||
@@ -184,6 +254,19 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'addr required when calling getUtxos')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
const addr = 'addr'
|
||||
|
||||
await uut.getUtxos(addr)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getUtxosBulk', () => {
|
||||
@@ -230,6 +313,19 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'addresses parameter must not exceed 20 elements')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
const addrs = ['addr']
|
||||
|
||||
await uut.getUtxosBulk(addrs)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
@@ -260,6 +356,19 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'hex required when calling broadcast')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
const hex = 'abc123'
|
||||
|
||||
await uut.broadcast(hex)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTransactions', () => {
|
||||
@@ -290,6 +399,19 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'address required when calling getTransactions()')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
const addr = 'abc123'
|
||||
|
||||
await uut.getTransactions(addr)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTransaction', () => {
|
||||
@@ -320,6 +442,19 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'txids required when calling getTransaction()')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
const txid = 'abc123'
|
||||
|
||||
await uut.getTransaction(txid)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getPubKey', () => {
|
||||
@@ -350,6 +485,19 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'address required when calling getPubKey()')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
const addr = 'abc123'
|
||||
|
||||
await uut.getPubKey(addr)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxoIsValid', () => {
|
||||
@@ -380,6 +528,18 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'utxo required when calling utxoIsValid()')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
const utxo = { a: 'b' }
|
||||
await uut.utxoIsValid(utxo)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData', () => {
|
||||
@@ -410,6 +570,19 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'tokenId required when calling getTokenData()')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
const tokenId = 'blah'
|
||||
|
||||
await uut.getTokenData(tokenId)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData2', () => {
|
||||
@@ -440,5 +613,18 @@ describe('#bch-use-case', () => {
|
||||
assert.equal(err.message, 'tokenId required when calling getTokenData2()')
|
||||
}
|
||||
})
|
||||
it('should throw an error if selected provider is not defined', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
|
||||
|
||||
const tokenId = 'blah'
|
||||
|
||||
await uut.getTokenData2(tokenId)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -151,7 +151,21 @@ describe('#IPFS', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#subscribeToChat', () => {
|
||||
it('should subscribe to the chat channel', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
adapters: {
|
||||
pubsub: {
|
||||
subscribeToPubsubChannel: async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await uut.subscribeToChat()
|
||||
})
|
||||
})
|
||||
describe('#pollForBchServices', () => {
|
||||
it('should find and select the wallet service', () => {
|
||||
uut.ipfsCoord = {
|
||||
@@ -169,46 +183,194 @@ describe('#IPFS', () => {
|
||||
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
|
||||
)
|
||||
})
|
||||
it('should use preferredProvider if set', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
// Define already selected service provider.
|
||||
uut.state.serviceProviders = [mockData.peers[2]]
|
||||
uut.state.selectedServiceProvider = mockData.peers[2]
|
||||
// Define preferred provider.
|
||||
uut.config.preferredProvider = mockData.peers[0]
|
||||
sandbox.stub(uut.semver, 'gt').returns(true)
|
||||
|
||||
it('should catch and report errors', () => {
|
||||
uut.pollForBchServices()
|
||||
await uut.pollForBchServices()
|
||||
|
||||
assert.equal(uut.state.selectedServiceProvider, uut.config.preferredProvider)
|
||||
})
|
||||
|
||||
it('should log unknown errors', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.semver, 'gt').throws(new Error('test error'))
|
||||
|
||||
await uut.pollForBchServices()
|
||||
|
||||
assert.equal(uut.state.selectedServiceProvider, '')
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
})
|
||||
it('should handle ipfsCoord errors', async () => {
|
||||
uut.ipfsCoord = {
|
||||
}
|
||||
|
||||
await uut.pollForBchServices()
|
||||
|
||||
assert.equal(uut.state.selectedServiceProvider, '')
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
})
|
||||
})
|
||||
|
||||
// describe('#peerInputHandler', () => {
|
||||
// it('should emit an event trigger', () => {
|
||||
// const data = 'some data'
|
||||
//
|
||||
// uut.peerInputHandler(data)
|
||||
//
|
||||
// assert.isOk(true, 'Not throwing an error is a success')
|
||||
// })
|
||||
//
|
||||
// it('should catch and report errors', () => {
|
||||
// // Force an error
|
||||
// sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
|
||||
//
|
||||
// uut.peerInputHandler()
|
||||
//
|
||||
// assert.isOk(true, 'Not throwing an error is a success.')
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('#subscribeToChat', () => {
|
||||
it('should subscribe to the chat channel', async () => {
|
||||
describe('#pollForP2wdbServices', () => {
|
||||
it('should poll for P2WDB services', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
adapters: {
|
||||
pubsub: {
|
||||
subscribeToPubsubChannel: async () => {
|
||||
}
|
||||
}
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.semver, 'gt').returns(true)
|
||||
|
||||
await uut.pollForP2wdbServices()
|
||||
|
||||
assert.equal(uut.state.selectedP2wdbProvider, mockData.peers[2])
|
||||
})
|
||||
it('should use preferredP2wdbProvider if set', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
|
||||
await uut.subscribeToChat()
|
||||
uut.config.preferredP2wdbProvider = mockData.peers[2]
|
||||
sandbox.stub(uut.semver, 'gt').returns(true)
|
||||
|
||||
await uut.pollForP2wdbServices()
|
||||
|
||||
assert.equal(uut.state.selectedP2wdbProvider, uut.config.preferredP2wdbProvider)
|
||||
})
|
||||
it('should handle a peer that does not match the protocol', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.semver, 'gt').returns(false)
|
||||
|
||||
await uut.pollForP2wdbServices()
|
||||
|
||||
assert.equal(uut.state.selectedP2wdbProvider, '')
|
||||
})
|
||||
it('should log unknown errors', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.semver, 'gt').throws(new Error('test error'))
|
||||
|
||||
await uut.pollForP2wdbServices()
|
||||
|
||||
assert.equal(uut.state.selectedP2wdbProvider, '')
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
})
|
||||
it('should handle ipfsCoord errors', async () => {
|
||||
uut.ipfsCoord = {
|
||||
}
|
||||
|
||||
await uut.pollForP2wdbServices()
|
||||
|
||||
assert.equal(uut.state.selectedP2wdbProvider, '')
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#pollForIpfsFileServices', () => {
|
||||
it('should poll for IPFS File services', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.semver, 'gt').returns(true)
|
||||
|
||||
await uut.pollForIpfsFileServices()
|
||||
|
||||
assert.equal(uut.state.selectedIpfsFileProvider, mockData.peers[3])
|
||||
})
|
||||
it('should use preferredIpfsFileProvider if set', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
// Define already selected service provider.
|
||||
uut.state.ipfsFileProviders = [mockData.peers[0]]
|
||||
uut.state.selectedIpfsFileProvider = mockData.peers[0]
|
||||
|
||||
uut.config.preferredIpfsFileProvider = mockData.peers[3]
|
||||
sandbox.stub(uut.semver, 'satisfies').returns(true)
|
||||
|
||||
await uut.pollForIpfsFileServices()
|
||||
|
||||
assert.equal(uut.state.selectedIpfsFileProvider, uut.config.preferredIpfsFileProvider)
|
||||
})
|
||||
it('should handle a peer that does not match the protocol', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.semver, 'satisfies').returns(false)
|
||||
|
||||
await uut.pollForIpfsFileServices()
|
||||
|
||||
assert.equal(uut.state.selectedIpfsFileProvider, '')
|
||||
})
|
||||
it('should log unknown errors', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.semver, 'satisfies').throws(new Error('test error'))
|
||||
|
||||
await uut.pollForIpfsFileServices()
|
||||
|
||||
assert.equal(uut.state.selectedIpfsFileProvider, '')
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
})
|
||||
it('should handle ipfsCoord errors', async () => {
|
||||
uut.ipfsCoord = {
|
||||
}
|
||||
|
||||
await uut.pollForIpfsFileServices()
|
||||
|
||||
assert.equal(uut.state.selectedIpfsFileProvider, '')
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
Unit tests for the IPFS Files Adapter.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import adapters from '../mocks/adapters/index.js'
|
||||
import IPFSFilesAdapter from '../../../src/adapters/ipfs-files/index.js'
|
||||
// import IPFSMock from '../mocks/ipfs-mock.js'
|
||||
|
||||
import mockDataLib from '../mocks/adapters/ipfs-coord-mocks.js'
|
||||
import mockDataRPC from '../mocks/use-cases/rest-api-mocks.js'
|
||||
|
||||
describe('#IPFS-Files', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let mockData
|
||||
|
||||
beforeEach(() => {
|
||||
const ipfs = adapters.ipfs
|
||||
uut = new IPFSFilesAdapter({ ipfs })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
mockData = cloneDeep(mockDataLib)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
|
||||
clearInterval(uut.pollBchServiceInterval)
|
||||
clearInterval(uut.pollP2wdbServiceInterval)
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if ipfs instance is not included', () => {
|
||||
try {
|
||||
uut = new IPFSFilesAdapter()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'An instance of IPFS must be passed when instantiating the IPFS Files Adapter library.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#rpcHandler', () => {
|
||||
it('should add RPC data to queue', () => {
|
||||
const data = {
|
||||
payload: {
|
||||
id: '123'
|
||||
}
|
||||
}
|
||||
|
||||
uut.rpcHandler(data)
|
||||
})
|
||||
it('should skip errors', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.rpcDataQueue, 'push').throws(new Error('test error'))
|
||||
await uut.rpcHandler()
|
||||
} catch (err) {
|
||||
assert.fail('Unexpected code path')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getStatus', () => {
|
||||
it('should get the status from ipfs-coord', async () => {
|
||||
uut.ipfs.ipfsCoordAdapter.state.ipfsFileProviders = mockData.peers
|
||||
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = { peerData: mockData.peerData }
|
||||
|
||||
await uut.getStatus()
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = null
|
||||
await uut.getStatus()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#selectProvider', () => {
|
||||
it('should select provider', async () => {
|
||||
await uut.selectProvider('providerId to select')
|
||||
assert.equal(uut.ipfs.ipfsCoordAdapter.config.selectedIpfsFileProvider, 'providerId to select')
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
await uut.selectProvider()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'providerId must be a string!')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getFileMetadata', () => {
|
||||
it('should get file metadata', async () => {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = mockData.peers[0]
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({
|
||||
success: true
|
||||
})
|
||||
const result = await uut.getFileMetadata({ cid: 'cid to get' })
|
||||
assert.equal(result.success, true)
|
||||
})
|
||||
it('should handle error if no cid is provided', async () => {
|
||||
try {
|
||||
await uut.getFileMetadata()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'getFileMetadata() cid input hash must be a string.')
|
||||
}
|
||||
})
|
||||
it('should handle error if no provider is selected', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = null
|
||||
await uut.getFileMetadata({ cid: 'cid to get' })
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'No IPFS File Pin Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getPins', () => {
|
||||
it('should get pins', async () => {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = mockData.peers[0]
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({
|
||||
success: true
|
||||
})
|
||||
const result = await uut.getPins()
|
||||
assert.equal(result.success, true)
|
||||
})
|
||||
it('should handle error if no provider is selected', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = null
|
||||
await uut.getPins()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'No IPFS File Pin Service provider available yet.')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#waitForRPCResponse', () => {
|
||||
it('should resolve when data is received', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs.ipfsCoordAdapter.wallet = {
|
||||
bchjs: {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
uut.rpcDataQueue.push(mockDataRPC.rpcData)
|
||||
|
||||
const result = await uut.waitForRPCResponse(rpcId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'success')
|
||||
assert.equal(result.success, true)
|
||||
assert.property(result, 'balances')
|
||||
assert.isArray(result.balances)
|
||||
})
|
||||
it('should return false if data is not found', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs.ipfsCoordAdapter.wallet = {
|
||||
bchjs: {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
// Fill the queue with false values.
|
||||
for (let i = 0; i < 10; i++) {
|
||||
uut.rpcDataQueue.push({ payload: { id: '1234567890' } })
|
||||
}
|
||||
sandbox.stub(uut.ipfs.ipfsCoordAdapter.bchjs.Util, 'sleep').resolves()
|
||||
|
||||
const result = await uut.waitForRPCResponse(rpcId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'success')
|
||||
assert.isFalse(result.success)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.wallet = {
|
||||
bchjs: {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await uut.waitForRPCResponse()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('error: ', err)
|
||||
assert.include(err.message, 'rpcId can not be false or undefined')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#pinClaim', () => {
|
||||
it('should process pinClaim', async () => {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = mockData.peers[0]
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({
|
||||
success: true
|
||||
})
|
||||
const result = await uut.pinClaim({
|
||||
proofOfBurnTxid: 'proofOfBurnTxid to pin',
|
||||
cid: 'cid to pin',
|
||||
claimTxid: 'claimTxid to pin',
|
||||
filename: 'filename to pin',
|
||||
address: 'address to pin'
|
||||
})
|
||||
assert.equal(result.success, true)
|
||||
})
|
||||
it('should handle error if no provider is selected', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = null
|
||||
await uut.pinClaim({
|
||||
proofOfBurnTxid: 'proofOfBurnTxid to pin',
|
||||
cid: 'cid to pin',
|
||||
claimTxid: 'claimTxid to pin',
|
||||
filename: 'filename to pin',
|
||||
address: 'address to pin'
|
||||
})
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'No IPFS File Provider Service is available yet. Try again in a few seconds.')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,26 +5,17 @@
|
||||
// Global npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import { peerIdFromString } from '@libp2p/peer-id'
|
||||
|
||||
// Local libraries
|
||||
import IPFSLib from '../../../src/adapters/ipfs/ipfs.js'
|
||||
// import create from '../mocks/ipfs-mock.js'
|
||||
import config from '../../../config/index.js'
|
||||
import createHeliaLib from '../mocks/helia-mock.js'
|
||||
|
||||
// config.isProduction = true;
|
||||
describe('#IPFS-adapter', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let ipfs
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new IPFSLib()
|
||||
|
||||
ipfs = cloneDeep(createHeliaLib)
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
@@ -39,26 +30,37 @@ describe('#IPFS-adapter', () => {
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
})
|
||||
|
||||
it('should instantiate dev IPFS Lib in production mode.', async () => {
|
||||
config.isProduction = true
|
||||
const _uut = new IPFSLib()
|
||||
assert.exists(_uut)
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
config.isProduction = false
|
||||
})
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should return a promise that resolves into an instance of IPFS.', async () => {
|
||||
// Mock dependencies.
|
||||
sandbox.stub(uut, 'createNode').resolves(ipfs)
|
||||
sandbox.stub(uut, 'publicIp').resolves('192.168.2.4')
|
||||
sandbox.stub(uut, 'multiaddr').returns('/ip4/fake-multiaddr')
|
||||
// Mock the heliaNode that gets created inside start()
|
||||
const mockIpfs = {
|
||||
libp2p: {
|
||||
getMultiaddrs: () => [],
|
||||
peerId: 'fake-id'
|
||||
}
|
||||
}
|
||||
|
||||
// Override the start method to mock the heliaNode creation
|
||||
uut.start = async function () {
|
||||
this.heliaNode = {
|
||||
start: async () => mockIpfs,
|
||||
id: 'fake-id',
|
||||
multiaddrs: ['/ip4/fake-multiaddr']
|
||||
}
|
||||
|
||||
const ipfs = await this.heliaNode.start()
|
||||
|
||||
this.id = this.heliaNode.id
|
||||
this.multiaddrs = this.heliaNode.multiaddrs
|
||||
this.isReady = true
|
||||
this.ipfs = ipfs
|
||||
|
||||
return this.ipfs
|
||||
}
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
// Assert properties of the instance are set.
|
||||
assert.equal(uut.isReady, true)
|
||||
@@ -71,8 +73,9 @@ describe('#IPFS-adapter', () => {
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut, 'createNode').rejects(new Error('test error'))
|
||||
// Force an error by making getSeed throw, which will cause
|
||||
// CreateHeliaNode to fail during start.
|
||||
sandbox.stub(uut, 'getSeed').rejects(new Error('test error'))
|
||||
|
||||
await uut.start()
|
||||
assert.fail('Unexpected code path.')
|
||||
@@ -97,119 +100,12 @@ describe('#IPFS-adapter', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#ensureBlocksDir', () => {
|
||||
it('should create directory if it does not exist', () => {
|
||||
// Force desired code path
|
||||
sandbox.stub(uut.fs, 'existsSync').returns(false)
|
||||
sandbox.stub(uut.fs, 'mkdirSync').returns(true)
|
||||
|
||||
const result = uut.ensureBlocksDir()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should report and throw errors', () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut.fs, 'existsSync').throws(new Error('test error'))
|
||||
|
||||
try {
|
||||
uut.ensureBlocksDir()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createNode', () => {
|
||||
it('should report and throw errors', async () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut, 'createLibp2p').rejects(new Error('test error'))
|
||||
|
||||
try {
|
||||
await uut.createNode()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should create an IPFS node from Helia', async () => {
|
||||
uut.config.isCircuitRelay = false
|
||||
const result = await uut.createNode()
|
||||
// console.log('result: ', result)
|
||||
|
||||
// Assert the returned IPFS node has expected properties
|
||||
assert.property(result, 'libp2p')
|
||||
assert.property(result, 'blockstore')
|
||||
|
||||
// Stop the IPFS node
|
||||
await result.stop()
|
||||
})
|
||||
|
||||
it('should create a Circuit Relay if configured', async () => {
|
||||
uut.config.isCircuitRelay = true
|
||||
const result = await uut.createNode()
|
||||
|
||||
// Assert the returned IPFS node has expected properties
|
||||
assert.property(result, 'libp2p')
|
||||
assert.property(result, 'blockstore')
|
||||
|
||||
// Stop the IPFS node
|
||||
await result.stop()
|
||||
})
|
||||
|
||||
it('should create a new private key on first run', async () => {
|
||||
uut.config.isCircuitRelay = false
|
||||
|
||||
// Mock dependencies and force desired code path.
|
||||
let beenCalled = false
|
||||
sandbox.stub(uut, 'getKeychain').resolves({
|
||||
exportPeerId: async () => {
|
||||
if (!beenCalled) {
|
||||
beenCalled = true
|
||||
throw new Error('test error')
|
||||
}
|
||||
return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP')
|
||||
},
|
||||
createKey: async () => {}
|
||||
})
|
||||
sandbox.stub(uut, 'createLibp2p').resolves()
|
||||
sandbox.stub(uut, 'createHelia').resolves({})
|
||||
|
||||
const result = await uut.createNode()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'fs')
|
||||
})
|
||||
|
||||
it('should not use circuit relay when CONNECT_PREF is set to direct', async () => {
|
||||
process.env.CONNECT_PREF = 'direct'
|
||||
|
||||
uut.config.isCircuitRelay = false
|
||||
const result = await uut.createNode()
|
||||
// console.log('result: ', result)
|
||||
|
||||
delete process.env.CONNECT_PREF
|
||||
|
||||
// Assert the returned IPFS node has expected properties
|
||||
assert.property(result, 'libp2p')
|
||||
assert.property(result, 'blockstore')
|
||||
|
||||
// Stop the IPFS node
|
||||
await result.stop()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getSeed', () => {
|
||||
it('should read the seed from the JSON file', async () => {
|
||||
// Mock dependencies and force desired code path
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').resolves('12345678')
|
||||
|
||||
const result = await uut.getSeed()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
@@ -220,7 +116,6 @@ describe('#IPFS-adapter', () => {
|
||||
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves()
|
||||
|
||||
const result = await uut.getSeed()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
Unit tests for P2WDB Adapter.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import adapters from '../mocks/adapters/index.js'
|
||||
import P2WDBAdapter from '../../../src/adapters/p2wdb/index.js'
|
||||
// import IPFSMock from '../mocks/ipfs-mock.js'
|
||||
|
||||
import mockDataLib from '../mocks/adapters/ipfs-coord-mocks.js'
|
||||
import mockDataRPC from '../mocks/use-cases/rest-api-mocks.js'
|
||||
|
||||
describe('#P2WDB Adapter', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let mockData
|
||||
|
||||
beforeEach(() => {
|
||||
const ipfs = adapters.ipfs
|
||||
uut = new P2WDBAdapter({ ipfs })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
mockData = cloneDeep(mockDataLib)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
|
||||
clearInterval(uut.pollBchServiceInterval)
|
||||
clearInterval(uut.pollP2wdbServiceInterval)
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if ipfs instance is not included', () => {
|
||||
try {
|
||||
uut = new P2WDBAdapter()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'An instance of IPFS must be passed when instantiating the P2WDB Adapter library.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#rpcHandler', () => {
|
||||
it('should add RPC data to queue', () => {
|
||||
const data = {
|
||||
payload: {
|
||||
id: '123'
|
||||
}
|
||||
}
|
||||
|
||||
uut.rpcHandler(data)
|
||||
})
|
||||
it('should skip errors', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.rpcDataQueue, 'push').throws(new Error('test error'))
|
||||
await uut.rpcHandler()
|
||||
} catch (err) {
|
||||
assert.fail('Unexpected code path')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getStatus', () => {
|
||||
it('should get the status from ipfs-coord', async () => {
|
||||
uut.ipfs.ipfsCoordAdapter.state.p2wdbProviders = mockData.peers
|
||||
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = { peerData: mockData.peerData }
|
||||
|
||||
await uut.getStatus()
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = null
|
||||
await uut.getStatus()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#selectProvider', () => {
|
||||
it('should select provider', async () => {
|
||||
await uut.selectProvider('providerId to select')
|
||||
assert.equal(uut.ipfs.ipfsCoordAdapter.config.preferredP2wdbProvider, 'providerId to select')
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
await uut.selectProvider()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'providerId must be a string!')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#waitForRPCResponse', () => {
|
||||
it('should resolve when data is received', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs.ipfsCoordAdapter.wallet = {
|
||||
bchjs: {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
uut.rpcDataQueue.push(mockDataRPC.rpcData)
|
||||
|
||||
const result = await uut.waitForRPCResponse(rpcId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'success')
|
||||
assert.equal(result.success, true)
|
||||
assert.property(result, 'balances')
|
||||
assert.isArray(result.balances)
|
||||
})
|
||||
it('should return false if data is not found', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs.ipfsCoordAdapter.wallet = {
|
||||
bchjs: {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
// Fill the queue with false values.
|
||||
for (let i = 0; i < 10; i++) {
|
||||
uut.rpcDataQueue.push({ payload: { id: '1234567890' } })
|
||||
}
|
||||
sandbox.stub(uut.ipfs.ipfsCoordAdapter.bchjs.Util, 'sleep').resolves()
|
||||
|
||||
const result = await uut.waitForRPCResponse(rpcId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'success')
|
||||
assert.isFalse(result.success)
|
||||
})
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.wallet = {
|
||||
bchjs: {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await uut.waitForRPCResponse()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('error: ', err)
|
||||
assert.include(err.message, 'rpcId can not be false or undefined')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#writeEntry', () => {
|
||||
it('should handle error if no provider is selected', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = null
|
||||
await uut.writeEntry({
|
||||
txid: 'txid to write',
|
||||
signature: 'signature to write',
|
||||
message: 'message to write',
|
||||
data: 'data to write'
|
||||
})
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'No P2WDB Service provider available yet.')
|
||||
}
|
||||
})
|
||||
it('should process writeEntry', async () => {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = mockData.peers[0]
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({
|
||||
success: true
|
||||
})
|
||||
const result = await uut.writeEntry({
|
||||
txid: 'txid to write',
|
||||
signature: 'signature to write',
|
||||
message: 'message to write',
|
||||
data: 'data to write'
|
||||
})
|
||||
assert.equal(result.success, true)
|
||||
})
|
||||
})
|
||||
describe('#getEntryByHash', () => {
|
||||
it('should handle error if hash is not provided', async () => {
|
||||
try {
|
||||
await uut.getEntryByHash()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'getEntry() input hash must be a string.')
|
||||
}
|
||||
})
|
||||
it('should handle error if no provider is selected', async () => {
|
||||
try {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = null
|
||||
await uut.getEntryByHash('hash to get')
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'No P2WDB Service provider available yet.')
|
||||
}
|
||||
})
|
||||
it('should process getEntryByHash', async () => {
|
||||
uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = mockData.peers[0]
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({
|
||||
success: true
|
||||
})
|
||||
const result = await uut.getEntryByHash('hash to get')
|
||||
assert.equal(result.success, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,6 +54,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', () => {
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -162,6 +162,40 @@ describe('#JSON RPC', () => {
|
||||
assert.equal(obj.result.method, 'auth')
|
||||
assert.equal(obj.id, id)
|
||||
})
|
||||
it('should route to bch handler on success payload', async () => {
|
||||
const id = uid()
|
||||
const json = jsonrpc.success(id, { method: 'bch' })
|
||||
const str = JSON.stringify(json)
|
||||
|
||||
// Mock the controller.
|
||||
sandbox.stub(uut.adapters.bch, 'rpcHandler').resolves('true')
|
||||
|
||||
const result = await uut.router(str, 'peerA')
|
||||
// console.log(result)
|
||||
|
||||
const obj = JSON.parse(result.retStr)
|
||||
// console.log('obj: ', obj)
|
||||
|
||||
assert.equal(obj.result.value, 'true')
|
||||
assert.equal(obj.id, id)
|
||||
})
|
||||
it('should route to bch handler on success payload', async () => {
|
||||
const id = uid()
|
||||
const json = jsonrpc.success(id, { method: 'file-pin' })
|
||||
const str = JSON.stringify(json)
|
||||
|
||||
// Mock the controller.
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'rpcHandler').resolves('true')
|
||||
|
||||
const result = await uut.router(str, 'peerA')
|
||||
// console.log(result)
|
||||
|
||||
const obj = JSON.parse(result.retStr)
|
||||
// console.log('obj: ', obj)
|
||||
|
||||
assert.equal(obj.result.value, 'true')
|
||||
assert.equal(obj.id, id)
|
||||
})
|
||||
|
||||
it('should route to about handler', async () => {
|
||||
const id = uid()
|
||||
@@ -188,6 +222,31 @@ describe('#JSON RPC', () => {
|
||||
assert.equal(obj.result.method, 'about')
|
||||
assert.equal(obj.id, id)
|
||||
})
|
||||
it('should route to bch handler', async () => {
|
||||
const id = uid()
|
||||
const userCall = jsonrpc.request(id, 'bch', { endpoint: 'getAll' })
|
||||
const jsonStr = JSON.stringify(userCall, null, 2)
|
||||
|
||||
// Mock the controller.
|
||||
sandbox.stub(uut.adapters.bch, 'rpcHandler').resolves('true')
|
||||
|
||||
// Force ipfs-coord communication.
|
||||
uut.ipfsCoord.ipfs = {
|
||||
orbitdb: {
|
||||
sendToDb: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.router(jsonStr, 'peerA')
|
||||
// console.log(result)
|
||||
|
||||
const obj = JSON.parse(result.retStr)
|
||||
// console.log('obj: ', obj)
|
||||
|
||||
assert.equal(obj.result.value, 'true')
|
||||
assert.equal(obj.result.method, 'bch')
|
||||
assert.equal(obj.id, id)
|
||||
})
|
||||
|
||||
it('should exit quietly for duplicate RPC message', async () => {
|
||||
const id = uid()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -62,6 +62,18 @@ describe('#BCH-REST-Controller', () => {
|
||||
})
|
||||
|
||||
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 = {
|
||||
@@ -467,4 +479,67 @@ describe('#BCH-REST-Controller', () => {
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getService', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter, 'state').value(null)
|
||||
ctx.request.body = {
|
||||
service: 'blah'
|
||||
}
|
||||
|
||||
await uut.getService(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter, 'state').value({ selectedServiceProvider: 'selected service' })
|
||||
ctx.request.body = {
|
||||
service: 'selected service'
|
||||
}
|
||||
|
||||
await uut.getService(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#postProvider', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
uut.config.freezeProvider = true
|
||||
ctx.request.body = {
|
||||
providerId: 'selected provider id'
|
||||
}
|
||||
|
||||
await uut.postProvider(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Consumer has preferredProvider set in environment variable. Refusing to switch providers.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
uut.config.freezeProvider = false
|
||||
ctx.request.body = {
|
||||
providerId: 'selected provider id'
|
||||
}
|
||||
|
||||
sandbox.stub(uut.adapters.bch, 'selectProvider').resolves(true)
|
||||
await uut.postProvider(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ let sandbox
|
||||
|
||||
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('#Users-REST-Router', () => {
|
||||
describe('#BCH-REST-Router', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -75,4 +75,108 @@ describe('#Users-REST-Router', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getStatus', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'getStatus').resolves(true)
|
||||
|
||||
await uut.getStatus()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#postProvider', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'postProvider').resolves(true)
|
||||
|
||||
await uut.postProvider()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#postBalance', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'balance').resolves(true)
|
||||
|
||||
await uut.postBalance()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#postUtxos', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'utxos').resolves(true)
|
||||
|
||||
await uut.postUtxos()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#postUtxosBulk', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'utxosBulk').resolves(true)
|
||||
|
||||
await uut.postUtxosBulk()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#postBroadcast', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'broadcast').resolves(true)
|
||||
|
||||
await uut.postBroadcast()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#postTxHistory', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'txHistory').resolves(true)
|
||||
|
||||
await uut.postTxHistory()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#postTxData', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'txData').resolves(true)
|
||||
|
||||
await uut.postTxData()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#postPubKey', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'pubKey').resolves(true)
|
||||
|
||||
await uut.postPubKey()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#utxoIsValid', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'utxoIsValid').resolves(true)
|
||||
|
||||
await uut.utxoIsValid()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#getTokenData', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'getTokenData').resolves(true)
|
||||
|
||||
await uut.getTokenData()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#getTokenData2', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'getTokenData2').resolves(true)
|
||||
|
||||
await uut.getTokenData2()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#getService', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.bchRESTController, 'getService').resolves(true)
|
||||
|
||||
await uut.getService()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -248,4 +248,247 @@ describe('#IPFS REST API', () => {
|
||||
assert.property(ctx.body, 'thisNode')
|
||||
})
|
||||
})
|
||||
describe('#viewFile', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
ctx.params = {
|
||||
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
|
||||
}
|
||||
sandbox.stub(uut.useCases.ipfs, 'downloadCid').throws(new Error('test error'))
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {}
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.viewFile(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
ctx.params = {
|
||||
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
|
||||
}
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.useCases.ipfs, 'downloadCid').resolves({ success: true, readStream: 'readStream' })
|
||||
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {
|
||||
ipfsCoord: {
|
||||
thisNode: {}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.viewFile(ctx)
|
||||
|
||||
assert.exists(ctx.body)
|
||||
})
|
||||
})
|
||||
describe('#downloadFile', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
ctx.params = {
|
||||
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
|
||||
}
|
||||
// Force an error
|
||||
sandbox.stub(uut.useCases.ipfs, 'downloadCid').throws(new Error('test error'))
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {}
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.downloadFile(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
ctx.params = {
|
||||
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
|
||||
}
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.useCases.ipfs, 'downloadCid').resolves({ success: true, readStream: 'readStream' })
|
||||
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {
|
||||
ipfsCoord: {
|
||||
thisNode: {}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.downloadFile(ctx)
|
||||
|
||||
assert.exists(ctx.body)
|
||||
})
|
||||
})
|
||||
describe('#getService', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {}
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.getService(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read properties of undefined')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
// Mock dependencies
|
||||
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {
|
||||
state: {
|
||||
selectedIpfsFileProvider: 'provider'
|
||||
}
|
||||
}
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.getService(ctx)
|
||||
|
||||
assert.isTrue(ctx.body.success)
|
||||
})
|
||||
})
|
||||
describe('#getFileInfo', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
ctx.params = {
|
||||
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
|
||||
}
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').throws(new Error('test error'))
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.getFileInfo(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
ctx.params = {
|
||||
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
|
||||
}
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, filename: 'filename' })
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.getFileInfo(ctx)
|
||||
|
||||
assert.isTrue(ctx.body.success)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getPins', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getPins').throws(new Error('test error'))
|
||||
|
||||
ctx.request.body = {}
|
||||
ctx.params = { page: 1 }
|
||||
|
||||
await uut.getPins(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 () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getPins').resolves({ success: true })
|
||||
|
||||
ctx.request.body = {}
|
||||
ctx.params = { page: 1 }
|
||||
|
||||
await uut.getPins(ctx)
|
||||
|
||||
assert.isTrue(ctx.body.success)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#cid2json', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
ctx.params = {
|
||||
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
|
||||
}
|
||||
sandbox.stub(uut.useCases.ipfs, 'cid2json').throws(new Error('test error'))
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.cid2json(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.params = {
|
||||
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
|
||||
}
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.useCases.ipfs, 'cid2json').resolves({ success: true })
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.cid2json(ctx)
|
||||
|
||||
assert.isTrue(ctx.body.success)
|
||||
})
|
||||
})
|
||||
describe('#pinClaim', () => {
|
||||
it('should return 422 status on biz logic error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.useCases.ipfs, 'pinClaim').throws(new Error('test error'))
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.pinClaim(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 () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.useCases.ipfs, 'pinClaim').resolves({ success: true })
|
||||
|
||||
ctx.request.body = {}
|
||||
|
||||
await uut.pinClaim(ctx)
|
||||
|
||||
assert.isTrue(ctx.body.success)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,223 @@
|
||||
/*
|
||||
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 P2wdbController from '../../../../../src/controllers/rest-api/p2wdb/controller.js'
|
||||
import { context } from '../../../mocks/ctx-mock.js'
|
||||
// import blah '../../../../unit/mocks/ctx-mock.js'
|
||||
// console.log('blah: ', blah)
|
||||
let uut
|
||||
let sandbox
|
||||
let ctx
|
||||
|
||||
describe('#P2WDB-REST-Controller', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new P2wdbController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
ctx = context()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new P2wdbController()
|
||||
|
||||
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 P2wdbController({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should return 422 status on arbitrary biz logic error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.p2wdb, 'getStatus')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await uut.getStatus(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.p2wdb, 'getStatus').resolves('p2wdb')
|
||||
|
||||
await uut.getStatus(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
|
||||
// Assert that expected properties exist in the returned data.
|
||||
assert.equal(ctx.response.body.status, 'p2wdb')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#postProvider', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.p2wdb, 'selectProvider')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
addresses: 'blah'
|
||||
}
|
||||
|
||||
await uut.postProvider(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.p2wdb, 'selectProvider').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
addresses: 'blah'
|
||||
}
|
||||
|
||||
await uut.postProvider(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
describe('#getEntryByHash', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.p2wdb, 'getEntryByHash')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
hash: 'blah'
|
||||
}
|
||||
|
||||
await uut.getEntryByHash(ctx)
|
||||
} catch (err) {
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.p2wdb, 'getEntryByHash').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
hash: 'blah'
|
||||
}
|
||||
|
||||
await uut.getEntryByHash(ctx)
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#writeEntry', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.p2wdb, 'writeEntry')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
txid: 'blah',
|
||||
signature: 'blah',
|
||||
message: 'blah',
|
||||
data: 'blah'
|
||||
}
|
||||
|
||||
await uut.writeEntry(ctx)
|
||||
} catch (err) {
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.p2wdb, 'writeEntry').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
txid: 'blah',
|
||||
signature: 'blah',
|
||||
message: 'blah',
|
||||
data: 'blah'
|
||||
}
|
||||
|
||||
await uut.writeEntry(ctx)
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /users 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 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 attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getStatus', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.p2wdbRESTController, 'getStatus').resolves(true)
|
||||
|
||||
await uut.getStatus()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#postProvider', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.p2wdbRESTController, 'postProvider').resolves(true)
|
||||
|
||||
await uut.postProvider()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#entryFromHash', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.p2wdbRESTController, 'getEntryByHash').resolves(true)
|
||||
|
||||
await uut.entryFromHash()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#write', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.p2wdbRESTController, 'writeEntry').resolves(true)
|
||||
|
||||
await uut.write()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /price 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 PriceController from '../../../../../src/controllers/rest-api/price/controller.js'
|
||||
import { context } from '../../../mocks/ctx-mock.js'
|
||||
// import blah '../../../../unit/mocks/ctx-mock.js'
|
||||
// console.log('blah: ', blah)
|
||||
let uut
|
||||
let sandbox
|
||||
let ctx
|
||||
|
||||
describe('#PRICE-REST-Controller', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new PriceController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
ctx = context()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new PriceController()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating /price REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new PriceController({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating /price REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getUSD', () => {
|
||||
it('should return 422 status on arbitrary biz logic error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.axios, 'request')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await uut.getUSD(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.axios, 'request').resolves({ data: { data: { rates: { USD: 1.00 } } } })
|
||||
|
||||
await uut.getUSD(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
|
||||
// Assert that expected properties exist in the returned data.
|
||||
assert.equal(ctx.response.body.usd, 1.00)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getXecPrice', () => {
|
||||
it('should return 422 status on arbitrary biz logic error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.axios, 'request').rejects(new Error('test error'))
|
||||
await uut.getXecPrice(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 () => {
|
||||
sandbox.stub(uut.axios, 'request').resolves({ data: { data: { ticker: { last: 1.00 } } } })
|
||||
await uut.getXecPrice(ctx)
|
||||
assert.equal(ctx.status, 200)
|
||||
assert.equal(ctx.response.body.usd, 1.00)
|
||||
})
|
||||
})
|
||||
describe('#getPsffppWritePrice', () => {
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.useCases.ipfs, 'getWritePrice').resolves(1.00)
|
||||
await uut.getPsffppWritePrice(ctx)
|
||||
assert.equal(ctx.status, 200)
|
||||
assert.equal(ctx.response.body.psfPrice, 1.00)
|
||||
})
|
||||
it('should return 422 status on arbitrary biz logic error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.useCases.ipfs, 'getWritePrice').rejects(new Error('test error'))
|
||||
await uut.getPsffppWritePrice(ctx)
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /users 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 PriceRouter from '../../../../../src/controllers/rest-api/price/index.js'
|
||||
let uut
|
||||
let sandbox
|
||||
// let ctx
|
||||
|
||||
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('#Price-REST-Router', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new PriceRouter({ 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 PriceRouter()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating Price REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new PriceRouter({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating Price 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('#getPrice', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.priceRESTController, 'getUSD').resolves(true)
|
||||
|
||||
await uut.getPrice()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getXecPrice', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.priceRESTController, 'getXecPrice').resolves(true)
|
||||
|
||||
await uut.getXecPrice()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
describe('#getPsffppWritePrice', () => {
|
||||
it('should route to controller', async () => {
|
||||
const spy = sandbox.stub(uut.priceRESTController, 'getPsffppWritePrice').resolves(true)
|
||||
await uut.getPsffppWritePrice()
|
||||
assert.isTrue(spy.calledOnce)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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,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.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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,70 @@ 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, 'ensureUser').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, 'ensureUser').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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -66,15 +66,47 @@ describe('#Timer-Controllers', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#exampleTimerFunc', () => {
|
||||
// describe('#exampleTimerFunc', () => {
|
||||
// it('should kick off the Use Case', async () => {
|
||||
// const result = await uut.exampleTimerFunc()
|
||||
|
||||
// assert.equal(result, true)
|
||||
// })
|
||||
|
||||
// it('should return false on error', async () => {
|
||||
// const result = await uut.exampleTimerFunc(true)
|
||||
|
||||
// assert.equal(result, false)
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('#cleanUsage', () => {
|
||||
it('should kick off the Use Case', async () => {
|
||||
const result = await uut.exampleTimerFunc()
|
||||
const result = await uut.cleanUsage()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return false on error', async () => {
|
||||
const result = await uut.exampleTimerFunc(true)
|
||||
sandbox.stub(uut.useCases.usage, 'cleanUsage').throws(new Error('test error'))
|
||||
const result = await uut.cleanUsage()
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
@@ -9,16 +9,25 @@ let currentEnv
|
||||
describe('#config', () => {
|
||||
before(() => {
|
||||
// Backup the current environment setting.
|
||||
currentEnv = process.env.SVC_ENV
|
||||
currentEnv = process.env.CONSUMER_ENV
|
||||
|
||||
// Clear CONSUMER_ENV for the first test to ensure default behavior
|
||||
delete process.env.CONSUMER_ENV
|
||||
})
|
||||
|
||||
after(() => {
|
||||
// Restore the environment setting before starting these tests.
|
||||
process.env.SVC_ENV = currentEnv
|
||||
if (currentEnv) {
|
||||
process.env.CONSUMER_ENV = currentEnv
|
||||
} else {
|
||||
delete process.env.CONSUMER_ENV
|
||||
}
|
||||
})
|
||||
|
||||
it('Should return development environment config by default', async () => {
|
||||
const importedConfig = await import('../../../config/index.js')
|
||||
// Ensure CONSUMER_ENV is not set for this test
|
||||
delete process.env.CONSUMER_ENV
|
||||
const importedConfig = await import('../../../config/index.js?foo=bar0')
|
||||
const config = importedConfig.default
|
||||
// console.log('config: ', config)
|
||||
|
||||
@@ -29,7 +38,7 @@ describe('#config', () => {
|
||||
// Hack to dynamically import a library multiple times:
|
||||
// https://github.com/denoland/deno/issues/6946
|
||||
|
||||
process.env.SVC_ENV = 'test'
|
||||
process.env.CONSUMER_ENV = 'test'
|
||||
|
||||
const importedConfig2 = await import('../../../config/index.js?foo=bar1')
|
||||
const config = importedConfig2.default
|
||||
@@ -39,7 +48,7 @@ describe('#config', () => {
|
||||
})
|
||||
|
||||
it('Should return prod environment config', async () => {
|
||||
process.env.SVC_ENV = 'prod'
|
||||
process.env.CONSUMER_ENV = 'prod'
|
||||
|
||||
process.env.WALLET_INTERFACE = 'web2'
|
||||
process.env.APISERVER = 'https://api.fullstack.cash/v5/'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('#server', () => {
|
||||
sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true)
|
||||
sandbox.stub(uut.controllers, 'attachControllers').resolves()
|
||||
uut.config.env = 'dev'
|
||||
|
||||
uut.config.port = 5040
|
||||
const result = await uut.startServer()
|
||||
// console.log('result: ', result)
|
||||
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
/*
|
||||
Mocks for the Adapter library.
|
||||
*/
|
||||
|
||||
class BlockstoreMock {
|
||||
constructor () {
|
||||
this.get = async () => {}
|
||||
}
|
||||
}
|
||||
class FsMock {
|
||||
constructor () {
|
||||
this.ls = async function* () {
|
||||
yield { path: 'test.txt', cid: 'QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZxY5X5x5x5x5' }
|
||||
}
|
||||
this.cat = async function* () {
|
||||
yield Buffer.from('test data')
|
||||
}
|
||||
}
|
||||
}
|
||||
class IpfsAdapter {
|
||||
constructor () {
|
||||
this.ipfs = {
|
||||
blockstore: new BlockstoreMock(),
|
||||
fs: new FsMock(),
|
||||
files: {
|
||||
stat: () => {}
|
||||
}
|
||||
@@ -14,7 +30,7 @@ class IpfsAdapter {
|
||||
|
||||
class IpfsCoordAdapter {
|
||||
constructor () {
|
||||
|
||||
this.config = {}
|
||||
this.ipfsCoord = {
|
||||
adapters: {
|
||||
ipfs: {
|
||||
@@ -34,7 +50,9 @@ class IpfsCoordAdapter {
|
||||
this.peerInputHandler = () => {}
|
||||
|
||||
this.state = {
|
||||
serviceProviders: []
|
||||
serviceProviders: [],
|
||||
serviceProvidersByType: '',
|
||||
selectedIpfsFileProvider:''
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +97,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
|
||||
}
|
||||
@@ -136,7 +187,46 @@ class BchUseCaseMock {
|
||||
async waitForRPCResponse () {
|
||||
return {}
|
||||
}
|
||||
async selectProvider (){
|
||||
return true
|
||||
}
|
||||
}
|
||||
const bch = new BchUseCaseMock()
|
||||
const wallet = {
|
||||
bchWallet: bch
|
||||
|
||||
export default { ipfs, localdb, bch }
|
||||
}
|
||||
|
||||
const ipfsFiles = {
|
||||
pinClaim: () => {
|
||||
return {
|
||||
success: true,
|
||||
message: 'Pin claimed'
|
||||
}
|
||||
},
|
||||
getFileMetadata: () => {
|
||||
return {
|
||||
success: true,
|
||||
message: 'Files metadata'
|
||||
}
|
||||
},
|
||||
getPins: () => {
|
||||
return {
|
||||
success: true,
|
||||
message: 'Pins'
|
||||
}
|
||||
},
|
||||
rpcHandler: () => {
|
||||
return {
|
||||
success: true,
|
||||
message: 'RPC handler'
|
||||
}
|
||||
}
|
||||
}
|
||||
const p2wdb = {
|
||||
getStatus: async () => {},
|
||||
selectProvider: async () => {},
|
||||
getEntryByHash: async () => {},
|
||||
writeEntry: async () => {}
|
||||
}
|
||||
export default { ipfs, localdb, bch, wallet, ipfsFiles, p2wdb }
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
const peers = [
|
||||
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN'
|
||||
'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
|
||||
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mT3',
|
||||
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mD4'
|
||||
]
|
||||
|
||||
const peerData = [
|
||||
@@ -110,7 +112,113 @@ const peerData = [
|
||||
},
|
||||
updatedAt: '2021-08-24T23:13:20.411Z'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
from: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mT3',
|
||||
channel: 'psf-ipfs-coordination-001',
|
||||
data: {
|
||||
apiName: 'ipfs-coord-announce',
|
||||
apiVersion: '1.3.2',
|
||||
apiInfo:
|
||||
'You should put an IPFS hash or web URL here to your documentation.',
|
||||
ipfsId: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
type: 'node.js',
|
||||
ipfsMultiaddrs: [
|
||||
'/ip4/127.0.0.1/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'/ip4/127.0.0.1/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'/ip4/192.168.0.2/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'/ip4/192.168.0.2/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
|
||||
],
|
||||
orbitdb:
|
||||
'/orbitdb/zdpuAntCMCcmZuFy2hq5F7kpcti3yHa34LLZHzyVqorkoSpAJ/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ221082417',
|
||||
circuitRelays: [],
|
||||
isCircuitRelay: false,
|
||||
cryptoAddresses: [
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'cashAddr',
|
||||
address: 'bitcoincash:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5gjzggc37'
|
||||
},
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'slpAddr',
|
||||
address: 'simpleledger:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5yffnac0q'
|
||||
}
|
||||
],
|
||||
encryptPubKey:
|
||||
'021db6e97650659653ba61dd493dc348a7429cdcbafd4fb73b08b1223bf5bd98df',
|
||||
jsonLd: {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'trout-bch-wallet-service-dev',
|
||||
version: '1.11.12',
|
||||
protocol: 'p2wdb',
|
||||
description:
|
||||
'IPFS service providing BCH blockchain access needed by a wallet.',
|
||||
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'Permissionless Software Foundation',
|
||||
url: 'https://PSFoundation.cash'
|
||||
},
|
||||
identifier: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
|
||||
},
|
||||
updatedAt: '2021-08-24T23:13:19.306Z'
|
||||
}
|
||||
},
|
||||
{
|
||||
from: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mD4',
|
||||
channel: 'psf-ipfs-coordination-001',
|
||||
data: {
|
||||
apiName: 'ipfs-coord-announce',
|
||||
apiVersion: '1.3.2',
|
||||
apiInfo:
|
||||
'You should put an IPFS hash or web URL here to your documentation.',
|
||||
ipfsId: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
type: 'node.js',
|
||||
ipfsMultiaddrs: [
|
||||
'/ip4/127.0.0.1/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'/ip4/127.0.0.1/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'/ip4/192.168.0.2/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'/ip4/192.168.0.2/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
|
||||
],
|
||||
orbitdb:
|
||||
'/orbitdb/zdpuAntCMCcmZuFy2hq5F7kpcti3yHa34LLZHzyVqorkoSpAJ/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ221082417',
|
||||
circuitRelays: [],
|
||||
isCircuitRelay: false,
|
||||
cryptoAddresses: [
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'cashAddr',
|
||||
address: 'bitcoincash:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5gjzggc37'
|
||||
},
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'slpAddr',
|
||||
address: 'simpleledger:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5yffnac0q'
|
||||
}
|
||||
],
|
||||
encryptPubKey:
|
||||
'021db6e97650659653ba61dd493dc348a7429cdcbafd4fb73b08b1223bf5bd98df',
|
||||
jsonLd: {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'trout-bch-wallet-service-dev',
|
||||
version: '1.11.12',
|
||||
protocol: 'ipfs-file-pin-service',
|
||||
description:
|
||||
'IPFS service providing BCH blockchain access needed by a wallet.',
|
||||
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'Permissionless Software Foundation',
|
||||
url: 'https://PSFoundation.cash'
|
||||
},
|
||||
identifier: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
|
||||
},
|
||||
updatedAt: '2021-08-24T23:13:19.306Z'
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
// module.exports = {
|
||||
|
||||
@@ -17,7 +17,7 @@ class MockBchWallet {
|
||||
this.walletInfoPromise = true;
|
||||
this.walletInfo = mockWallet;
|
||||
this.initialize = async () => {}
|
||||
this.bchjs = new BCHJS();
|
||||
this.bchjs = new BCHJS({ restURL: 'https://free-bch.fullstack.cash/' });
|
||||
this.burnTokens = async () => {
|
||||
return { success: true, txid: 'txid' };
|
||||
};
|
||||
|
||||
@@ -49,6 +49,58 @@ class BchUseCaseMock {
|
||||
}
|
||||
}
|
||||
|
||||
class UsageUseCaseMock {
|
||||
async cleanUsage() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getRestSummary() {
|
||||
return true
|
||||
}
|
||||
|
||||
async getTopIps(params) {
|
||||
return true
|
||||
}
|
||||
|
||||
async getTopEndpoints(existingUser, newData) {
|
||||
return true
|
||||
}
|
||||
|
||||
async clearUsage() {
|
||||
return true
|
||||
}
|
||||
|
||||
async saveUsage() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class IpfsUseCaseMock {
|
||||
async downloadCid() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async cid2json() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getCidMetadata() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getService() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async pinClaim() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getWritePrice() {
|
||||
return 1.00
|
||||
}
|
||||
}
|
||||
|
||||
class UseCasesMock {
|
||||
constuctor(localConfig = {}) {
|
||||
// this.user = new UserUseCaseMock(localConfig)
|
||||
@@ -58,6 +110,8 @@ class UseCasesMock {
|
||||
|
||||
user = new UserUseCaseMock()
|
||||
bch = new BchUseCaseMock()
|
||||
usage = new UsageUseCaseMock()
|
||||
ipfs = new IpfsUseCaseMock()
|
||||
}
|
||||
|
||||
export default UseCasesMock;
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
Unit tests for the ipfs-use-cases.js file.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local support libraries
|
||||
// const testUtils = require('../../utils/test-utils')
|
||||
|
||||
// Unit under test (uut)
|
||||
import UseCases from '../../../src/use-cases/ipfs-use-cases.js'
|
||||
|
||||
import adapters from '../mocks/adapters/index.js'
|
||||
describe('#ipfs-use-cases', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
before(async () => {
|
||||
// Delete all previous users in the database.
|
||||
// await testUtils.deleteAllUsers()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
uut = new UseCases({ adapters })
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {
|
||||
state: {
|
||||
selectedIpfsFileProvider: 'provider'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new UseCases()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
|
||||
// This is here to prevent the linter from complaining.
|
||||
assert.isOk(uut)
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of adapters must be passed in when instantiating IPFS Use Cases library.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#downloadCid', () => {
|
||||
it('should download a file from IPFS', async () => {
|
||||
sandbox.stub(uut, 'getCidMetadata').resolves('file.txt')
|
||||
|
||||
const result = await uut.downloadCid({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
|
||||
assert.property(result, 'filename')
|
||||
assert.property(result, 'readStream')
|
||||
})
|
||||
it('should handle directory-based CIDs', async () => {
|
||||
sandbox.stub(uut, 'getCidMetadata').resolves('file.txt')
|
||||
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(async function * () {
|
||||
yield { path: 'dir1/test.txt', cid: 'QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZxY5X5x5x5x5' }
|
||||
})
|
||||
const result = await uut.downloadCid({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
|
||||
assert.property(result, 'filename')
|
||||
assert.property(result, 'readStream')
|
||||
})
|
||||
it('should throw an error if cid is not provided ', async () => {
|
||||
try {
|
||||
await uut.downloadCid({})
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'CID is undefined')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getWritePrice', () => {
|
||||
it('should get price', async () => {
|
||||
const result = await uut.getWritePrice()
|
||||
assert.isNumber(result)
|
||||
})
|
||||
it('should handle undefined psffpp instance', async () => {
|
||||
uut.psffpp = null
|
||||
const result = await uut.getWritePrice()
|
||||
assert.isNumber(result)
|
||||
})
|
||||
it('should re-instantiate psffpp instance', async () => {
|
||||
const psffppMock = {}
|
||||
uut.psffpp = psffppMock
|
||||
const lastWritePriceUpdate = new Date()
|
||||
lastWritePriceUpdate.setHours(lastWritePriceUpdate.getHours() - 10)
|
||||
uut.lastWritePriceUpdate = lastWritePriceUpdate.getTime()
|
||||
const result = await uut.getWritePrice()
|
||||
assert.isNumber(result)
|
||||
assert.notEqual(uut.lastWritePriceUpdate, lastWritePriceUpdate.getTime())
|
||||
assert.notEqual(uut.psffpp, psffppMock)
|
||||
})
|
||||
|
||||
it('should handle error ', async () => {
|
||||
try {
|
||||
uut.lastWritePriceUpdate = new Date().getTime()
|
||||
uut.psffpp = {
|
||||
getMcWritePrice: () => {
|
||||
throw new Error('test error')
|
||||
}
|
||||
}
|
||||
await uut.getWritePrice()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#getCidMetadata', () => {
|
||||
it('should get cid metadata', async () => {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: true, filename: 'test.json' } })
|
||||
const result = await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
assert.isString(result)
|
||||
assert.equal(result, 'test.json')
|
||||
})
|
||||
|
||||
it('should handle error ', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').throws(new Error('test error'))
|
||||
await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle non-success response from getFileMetadata', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: false, message: 'test error' })
|
||||
await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#cid2json', () => {
|
||||
it('should get cid2json', async () => {
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {
|
||||
state: {
|
||||
selectedIpfsFileProvider: 'provider'
|
||||
}
|
||||
}
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
|
||||
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
|
||||
yield { name: 'test.json' }
|
||||
})
|
||||
sandbox.stub(JSON, 'parse').returns(true)
|
||||
const result = await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
assert.isObject(result)
|
||||
assert.property(result, 'success')
|
||||
assert.property(result, 'json')
|
||||
})
|
||||
|
||||
it('should throw error if selectedIpfsFileProvider is not defined', async () => {
|
||||
try {
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {
|
||||
state: {
|
||||
selectedIpfsFileProvider: null
|
||||
}
|
||||
}
|
||||
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'No IPFS File Provider Service is available yet. Try again in a few seconds')
|
||||
}
|
||||
})
|
||||
it('should throw error if file metadata cant be fetched', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: false })
|
||||
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'Could not communicate with instance of ipfs-file-pin-service')
|
||||
}
|
||||
})
|
||||
it('should throw error if cid is not pinned', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: false } })
|
||||
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'has not been pinned by ipfs-file-pin-service instance')
|
||||
}
|
||||
})
|
||||
it('should throw error if filename has no .json extension', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: true, filename: 'test' } })
|
||||
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'does not resolve to a JSON file')
|
||||
}
|
||||
})
|
||||
it('should handle error if file cant be retrieved', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
|
||||
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat').throws(new Error('test error'))
|
||||
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
|
||||
yield { name: 'test.json' }
|
||||
})
|
||||
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle error if cid is a directory', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
|
||||
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat')
|
||||
.onCall(0).throws(new Error('test error'))
|
||||
.onCall(1).callsFake(function * () {
|
||||
yield { name: 'test.json' }
|
||||
})
|
||||
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
|
||||
yield { name: 'test/directory/' }
|
||||
})
|
||||
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'file does not resolve to a valid JSON file')
|
||||
}
|
||||
})
|
||||
it('should get content from cid/name.json format', async () => {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
|
||||
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat')
|
||||
.onCall(0).throws(new Error('test error'))
|
||||
.onCall(1).callsFake(function * () {
|
||||
yield Buffer.from('{"test": "test"}')
|
||||
})
|
||||
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
|
||||
yield { name: 'test/directory/test.json' }
|
||||
})
|
||||
|
||||
const result = await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
assert.isObject(result)
|
||||
assert.property(result, 'success')
|
||||
assert.property(result, 'json')
|
||||
})
|
||||
it('should handle if content cant be parsed', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
|
||||
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
|
||||
yield { name: 'test.json' }
|
||||
})
|
||||
sandbox.stub(JSON, 'parse').throws(new Error())
|
||||
|
||||
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'not resolve to a valid JSON object')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#pinClaim', () => {
|
||||
it('should process pinClaim', async () => {
|
||||
const result = await uut.pinClaim()
|
||||
assert.isObject(result)
|
||||
assert.property(result, 'success')
|
||||
assert.property(result, 'message')
|
||||
})
|
||||
|
||||
it('should handle error ', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.adapters.ipfsFiles, 'pinClaim').throws(new Error('test error'))
|
||||
await uut.pinClaim()
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
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 })
|
||||
|
||||
console.log('restCalls: ', restCalls)
|
||||
// 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 * 48), // 48 hours ago
|
||||
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)
|
||||
}
|
||||
})
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import mongoose from 'mongoose'
|
||||
import config from '../../config/index.js'
|
||||
import User from '../../src/adapters/localdb/models/users.js'
|
||||
|
||||
const EMAIL = 'test@test.com'
|
||||
const PASSWORD = 'pass'
|
||||
const EMAIL = process.env.EMAIL || 'test@test3.com'
|
||||
const PASSWORD = process.env.PASSWORD || 'pass'
|
||||
|
||||
async function addUser () {
|
||||
// Connect to the Mongo Database.
|
||||
@@ -13,8 +14,6 @@ async function addUser () {
|
||||
{ useNewUrlParser: true, useUnifiedTopology: true }
|
||||
)
|
||||
|
||||
const User = require('../../src/models/users')
|
||||
|
||||
const userData = {
|
||||
email: EMAIL,
|
||||
password: PASSWORD
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import mongoose from 'mongoose'
|
||||
import config from '../../config/index.js'
|
||||
import User from '../../src/models/users.js'
|
||||
import User from '../../src/adapters/localdb/models/users.js'
|
||||
|
||||
async function getUsers () {
|
||||
// Connect to the Mongo Database.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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')
|
||||
|
||||
// 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