mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer.git
synced 2026-09-22 09:12:03 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8faadfc5c1 | ||
|
|
fe0ad56395 | ||
|
|
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 | ||
|
|
173f706a61 | ||
|
|
39fc919d35 | ||
|
|
6f0726aeb8 | ||
|
|
2568818ea1 | ||
|
|
c721da1562 | ||
|
|
3606a2d31b | ||
|
|
764905b041 | ||
|
|
556c6ec522 | ||
|
|
04395b70a6 | ||
|
|
4564090830 | ||
|
|
81521151f4 | ||
|
|
5a819c1d9c | ||
|
|
f2f3208fda | ||
|
|
7fe888cdda | ||
|
|
db3f1bf2cf | ||
|
|
512a49200d | ||
|
|
c6019df364 | ||
|
|
b7988253df | ||
|
|
5f64257dca | ||
|
|
2fbdccb883 | ||
|
|
ecc10afac3 | ||
|
|
b55afd5f93 | ||
|
|
12037a5e57 | ||
|
|
4fc54aa9c5 | ||
|
|
2bd7f0c45c | ||
|
|
900423339e | ||
|
|
d907afa6bb | ||
|
|
9b116d82b7 | ||
|
|
1d1bf50ee3 | ||
|
|
5002004697 | ||
|
|
5b0fac4301 | ||
|
|
9584810b16 | ||
|
|
f311a3b9c6 | ||
|
|
83f16709d8 | ||
|
|
fe91f4b875 | ||
|
|
2d9a2b811c | ||
|
|
9360cf6f77 | ||
|
|
514b65ed24 | ||
|
|
0c7f3088a8 | ||
|
|
aef88eb369 | ||
|
|
2f6cc72f41 | ||
|
|
12cab02300 | ||
|
|
04a908261a | ||
|
|
f16eacc3f2 | ||
|
|
83898742f1 | ||
|
|
1795918719 | ||
|
|
6894217078 | ||
|
|
3ffbed27b5 | ||
|
|
c30b697bc6 | ||
|
|
df2f69050b | ||
|
|
b49979971c | ||
|
|
b372e3d260 | ||
|
|
707b385b8a | ||
|
|
6cb140a491 | ||
|
|
c43ee8c25a | ||
|
|
0a7ec11510 | ||
|
|
064ac36dc7 | ||
|
|
5e2cf86f4b | ||
|
|
425d2ada65 |
+2
-1
@@ -62,7 +62,7 @@ docs
|
||||
.nyc_output
|
||||
coverage
|
||||
database/
|
||||
system-user-*.json
|
||||
#system-user-*.json
|
||||
orbitdb
|
||||
ipfsdata
|
||||
.ipfsdata
|
||||
@@ -73,3 +73,4 @@ run-dev.sh
|
||||
!README.md
|
||||
|
||||
wallet.json
|
||||
system-user-dev.json
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# Pedigree
|
||||
|
||||
This repository was forked from [koa-api-boilerplate](https://github.com/christroutner/koa-api-boilerplate). Code changes from that upstream repository is frequently pulled in and merged to this repository.
|
||||
This repository was forked from [ipfs-service-provider](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider). Code changes from that upstream repository is frequently pulled in and merged to this repository.
|
||||
|
||||
@@ -12,9 +12,9 @@ More details are available at [CashStack.info](https://CashStack.info).
|
||||
|
||||
## Requirements
|
||||
|
||||
- node **^16.20.2**
|
||||
- npm **^8.19.4**
|
||||
- Docker **^20.10.8**
|
||||
- node **^20.16.0**
|
||||
- npm **^10.8.1**
|
||||
- Docker **^24.0.7**
|
||||
- Docker Compose **^1.27.4**
|
||||
|
||||
## Installation
|
||||
|
||||
+26
-15
@@ -25,6 +25,7 @@ import config from '../config/index.js' // this first.
|
||||
|
||||
import AdminLib from '../src/adapters/admin.js'
|
||||
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
|
||||
import { usageMiddleware } from '../src/use-cases/usage-use-cases.js'
|
||||
import wlogger from '../src/adapters/wlogger.js'
|
||||
import Controllers from '../src/controllers/index.js'
|
||||
import { applyPassportMods } from '../config/passport.js'
|
||||
@@ -45,16 +46,18 @@ class Server {
|
||||
const app = new Koa()
|
||||
app.keys = [this.config.session]
|
||||
|
||||
// Connect to the Mongo Database.
|
||||
this.mongoose.Promise = global.Promise
|
||||
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
console.log(
|
||||
`Connecting to MongoDB with this connection string: ${this.config.database}`
|
||||
)
|
||||
await this.mongoose.connect(this.config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
if (!this.config.noMongo) {
|
||||
// Connect to the Mongo Database.
|
||||
this.mongoose.Promise = global.Promise
|
||||
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
console.log(
|
||||
`Connecting to MongoDB with this connection string: ${this.config.database}`
|
||||
)
|
||||
await this.mongoose.connect(this.config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
}
|
||||
|
||||
console.log(`Starting environment: ${this.config.env}`)
|
||||
console.log(`Debug level: ${this.config.debugLevel}`)
|
||||
@@ -65,6 +68,7 @@ class Server {
|
||||
app.use(bodyParser())
|
||||
app.use(session())
|
||||
app.use(errorMiddleware())
|
||||
app.use(usageMiddleware())
|
||||
|
||||
// Used to generate the docs.
|
||||
app.use(mount('/', serve(`${process.cwd()}/docs`)))
|
||||
@@ -81,7 +85,10 @@ class Server {
|
||||
// Enable CORS for testing
|
||||
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
|
||||
// Dev Note: This line must come BEFORE controllers.attachRESTControllers()
|
||||
app.use(cors({ origin: '*' }))
|
||||
app.use(cors({
|
||||
origin: '*',
|
||||
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS'
|
||||
}))
|
||||
|
||||
// Wait for any adapters to initialize.
|
||||
await this.controllers.initAdapters()
|
||||
@@ -102,9 +109,11 @@ class Server {
|
||||
this.server = await app.listen(this.config.port)
|
||||
console.log(`Server started on ${this.config.port}`)
|
||||
|
||||
// Create the system admin user.
|
||||
const success = await this.adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
if (!this.config.noMongo) {
|
||||
// Create the system admin user.
|
||||
const success = await this.adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
}
|
||||
|
||||
// Attach the other IPFS controllers.
|
||||
// Skip if this is a test environment.
|
||||
@@ -116,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
+11
-1
@@ -43,6 +43,9 @@ export default {
|
||||
? process.env.EMAILPASS
|
||||
: 'emailpassword',
|
||||
|
||||
// Enable or Disable the usage of Mongo DB.
|
||||
noMongo: process.env.NO_MONGO ? true : false,
|
||||
|
||||
// BEGIN WALLET CONFIGURATION
|
||||
|
||||
// BCH Mnemonic for generating encryption keys and payment address
|
||||
@@ -128,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,
|
||||
@@ -167,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
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"email": "system@system.com",
|
||||
"name": "admin",
|
||||
"password": "c9JjJxZmTs5bWmWC6adQ",
|
||||
"type": "admin",
|
||||
"id": "68d47521e6060ba7332301b1",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4ZDQ3NTIxZTYwNjBiYTczMzIzMDFiMSIsImlhdCI6MTc2MDY0MzI5NH0.SgoWnrP0YZZtPZ2V_C7HsfKoRHphfoIODN3-pltsGbo"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"password": "02PjvhoMGoM7pJylp80Z",
|
||||
"email": "system@system.com",
|
||||
"id": "645e49a45a9f5a8e0a54a083",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0NWU0OWE0NWE5ZjVhOGUwYTU0YTA4MyIsImlhdCI6MTY4MzkwMDgzNn0.sXeT-G9kS5Nu1dnZUGsNm6FhxrcNaCHhnQ-tLqsJc9E"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"email": "system@system.com",
|
||||
"name": "admin",
|
||||
"password": "UdbCjMgm7T8efeyUfcdN",
|
||||
"type": "admin",
|
||||
"id": "68daea62e9b47af49f86dae9",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4ZGFlYTYyZTliNDdhZjQ5Zjg2ZGFlOSIsImlhdCI6MTc1OTE3NzMxNH0.-mFCzyDri0OLMz-oM03qyYPj7srEH_6HFLGzgCf_JLI"
|
||||
}
|
||||
@@ -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
+7615
-26479
File diff suppressed because it is too large
Load Diff
+26
-20
@@ -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,24 +25,28 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
|
||||
"dependencies": {
|
||||
"@chainsafe/libp2p-gossipsub": "11.0.1",
|
||||
"@chainsafe/libp2p-noise": "14.1.0",
|
||||
"@chainsafe/libp2p-yamux": "6.0.1",
|
||||
"@helia/unixfs": "3.0.1",
|
||||
"@libp2p/bootstrap": "10.0.7",
|
||||
"@libp2p/circuit-relay-v2": "1.0.13",
|
||||
"@libp2p/identify": "1.0.6",
|
||||
"@libp2p/keychain": "4.0.7",
|
||||
"@libp2p/logger": "4.0.5",
|
||||
"@libp2p/tcp": "9.0.7",
|
||||
"@libp2p/webrtc": "4.0.17",
|
||||
"@chainsafe/libp2p-gossipsub": "14.1.0",
|
||||
"@chainsafe/libp2p-noise": "16.0.1",
|
||||
"@chainsafe/libp2p-yamux": "7.0.1",
|
||||
"@helia/unixfs": "4.0.2",
|
||||
"@libp2p/bootstrap": "11.0.26",
|
||||
"@libp2p/circuit-relay-v2": "3.2.2",
|
||||
"@libp2p/config": "1.1.0",
|
||||
"@libp2p/identify": "3.0.22",
|
||||
"@libp2p/keychain": "5.0.14",
|
||||
"@libp2p/logger": "5.1.8",
|
||||
"@libp2p/ping": "2.0.22",
|
||||
"@libp2p/tcp": "10.1.2",
|
||||
"@libp2p/webrtc": "5.2.2",
|
||||
"@libp2p/websockets": "9.2.2",
|
||||
"@multiformats/multiaddr": "12.3.5",
|
||||
"axios": "0.27.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blockstore-fs": "1.1.8",
|
||||
"datastore-fs": "9.1.6",
|
||||
"blockstore-fs": "2.0.2",
|
||||
"datastore-fs": "10.0.2",
|
||||
"glob": "7.1.6",
|
||||
"helia": "2.1.0",
|
||||
"helia-coord": "1.5.8",
|
||||
"helia": "5.2.1",
|
||||
"helia-coord": "1.7.2",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-bch-lib": "1.3.0",
|
||||
@@ -56,14 +61,15 @@
|
||||
"koa-router": "10.0.0",
|
||||
"koa-static": "5.0.0",
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "1.2.1",
|
||||
"libp2p": "2.7.2",
|
||||
"line-reader": "0.4.0",
|
||||
"mime-types": "2.1.35",
|
||||
"minimal-slp-wallet": "5.11.2",
|
||||
"minimal-slp-wallet": "5.13.2",
|
||||
"mongoose": "5.13.14",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.7.5",
|
||||
"passport-local": "1.0.0",
|
||||
"psffpp": "1.2.0",
|
||||
"public-ip": "6.0.1",
|
||||
"semver": "7.3.5",
|
||||
"winston": "3.3.3",
|
||||
|
||||
@@ -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_16.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_20.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"]
|
||||
|
||||
@@ -22,13 +22,16 @@ services:
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 4000mb
|
||||
mem_limit: 1000mb
|
||||
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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+84
-61
@@ -19,17 +19,20 @@ 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'
|
||||
import { identify, identifyPush } 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 { webRTC, webRTCDirect } from '@libp2p/webrtc'
|
||||
import { keychain } from '@libp2p/keychain'
|
||||
import { defaultLogger } from '@libp2p/logger'
|
||||
import { unixfs } from '@helia/unixfs'
|
||||
// import { generateKeyPairFromSeed } from '@libp2p/crypto/keys'
|
||||
// import crypto from 'crypto'
|
||||
import { ping } from '@libp2p/ping'
|
||||
import { loadOrCreateSelfKey } from '@libp2p/config'
|
||||
|
||||
// Local libraries
|
||||
import config from '../../../config/index.js'
|
||||
@@ -64,8 +67,8 @@ class IpfsAdapter {
|
||||
this.createNode = this.createNode.bind(this)
|
||||
this.stop = this.stop.bind(this)
|
||||
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
|
||||
// this.getPrivateKey = this.getPrivateKey.bind(this)
|
||||
this.getSeed = this.getSeed.bind(this)
|
||||
this.getKeychain = this.getKeychain.bind(this)
|
||||
}
|
||||
|
||||
// Start an IPFS node.
|
||||
@@ -111,19 +114,6 @@ class IpfsAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async getKeychain (datastore) {
|
||||
const keychainInit = {
|
||||
pass: await this.getSeed()
|
||||
}
|
||||
|
||||
const chain = this.keychain(keychainInit)({
|
||||
datastore,
|
||||
logger: defaultLogger()
|
||||
})
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// This function creates an IPFS node using Helia.
|
||||
// It returns the node as an object.
|
||||
async createNode () {
|
||||
@@ -132,83 +122,70 @@ class IpfsAdapter {
|
||||
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
|
||||
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
|
||||
|
||||
// const keychainInit = {
|
||||
// pass: await this.getSeed()
|
||||
// }
|
||||
// Get the private key.
|
||||
// const privateKey = await this.getPrivateKey()
|
||||
// console.log('privateKey: ', privateKey)
|
||||
|
||||
// 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')
|
||||
const keychainInit = {
|
||||
selfKey: 'myKey',
|
||||
pass: await this.getSeed()
|
||||
}
|
||||
const privateKey = await loadOrCreateSelfKey(datastore, keychainInit)
|
||||
|
||||
// Configure services
|
||||
const services = {
|
||||
identify: identify(),
|
||||
pubsub: gossipsub({ allowPublishToZeroPeers: true })
|
||||
identifyPush: identifyPush(),
|
||||
pubsub: gossipsub({ allowPublishToZeroTopicPeers: true }),
|
||||
ping: ping(),
|
||||
keychain: keychain(keychainInit)
|
||||
}
|
||||
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
|
||||
}
|
||||
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')
|
||||
}
|
||||
|
||||
// Configure transports
|
||||
let transports
|
||||
if (process.env.CONNECT_PREF === 'direct') {
|
||||
transports = [
|
||||
tcp(),
|
||||
webSockets(),
|
||||
webRTC()
|
||||
]
|
||||
} else {
|
||||
transports = [
|
||||
tcp(),
|
||||
webSockets(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 3,
|
||||
reservationConcurrency: 3
|
||||
}),
|
||||
webRTC()
|
||||
]
|
||||
}
|
||||
const transports = [
|
||||
tcp(),
|
||||
webSockets(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 3,
|
||||
reservationConcurrency: 3
|
||||
}),
|
||||
webRTC(),
|
||||
webRTCDirect()
|
||||
]
|
||||
|
||||
// libp2p is the networking layer that underpins Helia
|
||||
const libp2p = await this.createLibp2p({
|
||||
peerId,
|
||||
// peerId,
|
||||
privateKey,
|
||||
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'
|
||||
'/webrtc',
|
||||
'/p2p-circuit'
|
||||
]
|
||||
},
|
||||
transports,
|
||||
connectionEncryption: [
|
||||
connectionEncrypters: [
|
||||
noise()
|
||||
],
|
||||
streamMuxers: [
|
||||
@@ -254,6 +231,10 @@ class IpfsAdapter {
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}/datastore/pkcs8`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore/pkcs8`)
|
||||
|
||||
// !fs.existsSync(`${IPFS_DIR}/datastore/peers`) && fs.mkdirSync(`${IPFS_DIR}/datastore/peers`)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs.js/ensureBlocksDir(): ', err)
|
||||
@@ -288,6 +269,48 @@ class IpfsAdapter {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the private key from disk, or generate a new one and save it,
|
||||
// if it doesn't exist.
|
||||
// async getPrivateKey () {
|
||||
// try {
|
||||
// const filename = `${IPFS_DIR}/privkey.json`
|
||||
|
||||
// let privKeyHex
|
||||
// try {
|
||||
// // Try to read the JSON file containing the seed.
|
||||
// privKeyHex = await this.jsonFiles.readJSON(filename)
|
||||
// // console.log('saved privKeyHex: ', privKeyHex)
|
||||
// } catch (err) {
|
||||
// // Generate a new private key and save it to disk.
|
||||
|
||||
// // Generate a new private key and save it to disk.
|
||||
// const randomBuffer = crypto.randomBytes(32)
|
||||
// // console.log('randomBuffer: ', randomBuffer)
|
||||
|
||||
// privKeyHex = randomBuffer.toString('hex')
|
||||
// // console.log('new privKeyHex: ', privKeyHex)
|
||||
|
||||
// // Save the newly generated seed
|
||||
// await this.jsonFiles.writeJSON(privKeyHex, filename)
|
||||
// }
|
||||
|
||||
// // Convert the saved hex string to a buffer.
|
||||
// const privKeyBuf = Buffer.from(privKeyHex, 'hex')
|
||||
|
||||
// // Convert the buffer to a Uint8Array 'seed'
|
||||
// const seed = new Uint8Array(privKeyBuf.buffer, privKeyBuf.byteOffset, privKeyBuf.byteLength)
|
||||
// // console.log('seed: ', seed)
|
||||
|
||||
// // Generate a ED25519 key pair.
|
||||
// const privKey = await generateKeyPairFromSeed('Ed25519', seed)
|
||||
|
||||
// return privKey
|
||||
// } catch (err) {
|
||||
// console.error('Error in adapters/ipfs/ipfs.js/getPrivateKey(): ', err)
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
export default IpfsAdapter
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,6 +23,13 @@ class Controllers {
|
||||
this.useCases = new UseCases(localConfig)
|
||||
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
||||
this.config = config
|
||||
|
||||
// Bind 'this' object to all subfunction
|
||||
this.initAdapters = this.initAdapters.bind(this)
|
||||
this.initUseCases = this.initUseCases.bind(this)
|
||||
this.attachRESTControllers = this.attachRESTControllers.bind(this)
|
||||
this.attachControllers = this.attachControllers.bind(this)
|
||||
this.attachRPCControllers = this.attachRPCControllers.bind(this)
|
||||
}
|
||||
|
||||
// Spin up any adapter libraries that have async startup needs.
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -15,6 +15,8 @@ import BchRESTController from './bch/index.js'
|
||||
import IpfsRESTController from './ipfs/index.js'
|
||||
import P2wdbRESTController from './p2wdb/index.js'
|
||||
import PriceRESTController from './price/index.js'
|
||||
import config from '../../../config/index.js'
|
||||
import UsageRESTController from './usage/index.js'
|
||||
|
||||
class RESTControllers {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -32,7 +34,11 @@ class RESTControllers {
|
||||
)
|
||||
}
|
||||
|
||||
// console.log('Controllers localConfig: ', localConfig)
|
||||
// Bind 'this' object to all subfunctions.
|
||||
this.attachRESTControllers = this.attachRESTControllers.bind(this)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
}
|
||||
|
||||
attachRESTControllers (app) {
|
||||
@@ -41,13 +47,15 @@ class RESTControllers {
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Attach the REST API Controllers associated with the /auth route
|
||||
const authRESTController = new AuthRESTController(dependencies)
|
||||
authRESTController.attach(app)
|
||||
if (!this.config.noMongo) {
|
||||
// Attach the REST API Controllers associated with the /auth route
|
||||
const authRESTController = new AuthRESTController(dependencies)
|
||||
authRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /user route
|
||||
const userRouter = new UserRouter(dependencies)
|
||||
userRouter.attach(app)
|
||||
// Attach the REST API Controllers associated with the /user route
|
||||
const userRouter = new UserRouter(dependencies)
|
||||
userRouter.attach(app)
|
||||
}
|
||||
|
||||
// Attach the REST API Controllers associated with the /contact route
|
||||
const contactRESTController = new ContactRESTController(dependencies)
|
||||
@@ -72,6 +80,10 @@ class RESTControllers {
|
||||
// Attach the REST API Controllers associated with the /price route
|
||||
const priceRESTController = new PriceRESTController(dependencies)
|
||||
priceRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /usage route
|
||||
const usageRESTController = new UsageRESTController(dependencies)
|
||||
usageRESTController.attach(app)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,12 +167,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 +216,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 +244,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 +262,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 +337,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())
|
||||
|
||||
@@ -28,7 +28,10 @@ class PriceRESTControllerLib {
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// _this = this
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.getUSD = this.getUSD.bind(this)
|
||||
this.getXecPrice = this.getXecPrice.bind(this)
|
||||
this.getPsffppWritePrice = this.getPsffppWritePrice.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +92,22 @@ class PriceRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the write price in PSF tokens to pin 1MB to the PSFFPP.
|
||||
// TODO: Add API docs.
|
||||
// curl -X GET http://localhost:5015/price/psffpp
|
||||
async getPsffppWritePrice (ctx) {
|
||||
try {
|
||||
const psfPrice = await this.useCases.ipfs.getWritePrice()
|
||||
|
||||
ctx.body = { psfPrice }
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in GET /price/getPsffppWritePrice', err)
|
||||
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
|
||||
@@ -53,6 +53,7 @@ class PriceRouter {
|
||||
// Define the routes and attach the controller.
|
||||
this.router.get('/usd', this.getPrice)
|
||||
this.router.get('/xecusd', this.getXecPrice)
|
||||
this.router.get('/psffpp', this.getPsffppWritePrice)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
@@ -66,6 +67,10 @@ class PriceRouter {
|
||||
async getXecPrice (ctx, next) {
|
||||
await _this.priceRESTController.getXecPrice(ctx, next)
|
||||
}
|
||||
|
||||
async getPsffppWritePrice (ctx, next) {
|
||||
await _this.priceRESTController.getPsffppWritePrice(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = PriceRouter
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
REST API Controller library for the /usage route
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
|
||||
// Local libraries
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
class UsageRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /usage REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /usage REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.getStatus = this.getStatus.bind(this)
|
||||
this.getTopIps = this.getTopIps.bind(this)
|
||||
this.getTopEndpoints = this.getTopEndpoints.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /usage Get status on IPFS infrastructure
|
||||
* @apiPermission public
|
||||
* @apiName GetUsageStatus
|
||||
* @apiGroup REST Usage
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage
|
||||
*
|
||||
*/
|
||||
getStatus (ctx) {
|
||||
try {
|
||||
// const status = await this.adapters.ipfs.getStatus()
|
||||
const status = this.useCases.usage.getRestSummary()
|
||||
|
||||
ctx.body = { status }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in usage/controller.js/getStatus(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /usage/ips Get top IP addresses consuming the REST API
|
||||
* @apiPermission public
|
||||
* @apiName GetUsageIPs
|
||||
* @apiGroup REST Usage
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/ips
|
||||
*
|
||||
*/
|
||||
getTopIps (ctx) {
|
||||
try {
|
||||
const ips = this.useCases.usage.getTopIps()
|
||||
|
||||
ctx.body = { ips }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in usage/controller.js/getTopIps(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /usage/endpoints Get top endpoints consumed from the REST API
|
||||
* @apiPermission public
|
||||
* @apiName GetUsageEndpoints
|
||||
* @apiGroup REST Usage
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/endpoints
|
||||
*
|
||||
*/
|
||||
getTopEndpoints (ctx) {
|
||||
try {
|
||||
const endpoints = this.useCases.usage.getTopEndpoints()
|
||||
|
||||
ctx.body = { endpoints }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in usage/controller.js/getTopEndpoints(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
ctx.throw(err.status, err.message)
|
||||
} else {
|
||||
ctx.throw(err.status)
|
||||
}
|
||||
} else {
|
||||
// By default use a 422 error if the HTTP status is not specified.
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = IpfsRESTControllerLib
|
||||
export default UsageRESTControllerLib
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
REST API library for the /usage route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
import UsageRESTControllerLib from './controller.js'
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
// let _this
|
||||
|
||||
class UsageRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating IPFS REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.usageRESTController = new UsageRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/usage'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
// _this = this
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error(
|
||||
'Must pass app object when attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.get('/', this.usageRESTController.getStatus)
|
||||
this.router.get('/ips', this.usageRESTController.getTopIps)
|
||||
this.router.get('/endpoints', this.usageRESTController.getTopEndpoints)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = BchRouter
|
||||
export default UsageRouter
|
||||
@@ -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,43 +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.startTimers()
|
||||
this.cleanUsage = this.cleanUsage.bind(this)
|
||||
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 * 10)
|
||||
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)
|
||||
clearInterval(this.backupUsageHandle)
|
||||
}
|
||||
|
||||
// Replace this example function with your own timer handler.
|
||||
exampleTimerFunc (negativeTest) {
|
||||
// Clean the usage state so that stats reflect the last 24 hours.
|
||||
cleanUsage () {
|
||||
try {
|
||||
// console.log('Example timer controller executed.')
|
||||
clearInterval(this.cleanUsageHandle)
|
||||
|
||||
if (negativeTest) throw new Error('test error')
|
||||
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 exampleTimerFunc(): ', 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
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// Local libraries
|
||||
import UserUseCases from './user.js'
|
||||
import IpfsUseCases from './ipfs-use-cases.js'
|
||||
import { UsageUseCases } from './usage-use-cases.js'
|
||||
|
||||
class UseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -26,10 +27,14 @@ class UseCases {
|
||||
// console.log('use-cases/index.js localConfig: ', localConfig)
|
||||
this.user = new UserUseCases(localConfig)
|
||||
this.ipfs = new IpfsUseCases(localConfig)
|
||||
this.usage = new UsageUseCases(localConfig)
|
||||
}
|
||||
|
||||
// Run any startup Use Cases at the start of the app.
|
||||
async start () {
|
||||
// Load the usage stats from the database
|
||||
await this.usage.loadUsage()
|
||||
|
||||
console.log('Async Use Cases have been started.')
|
||||
|
||||
return true
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// import RetryQueue from '@chris.troutner/retry-queue'
|
||||
// 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'
|
||||
@@ -26,18 +28,26 @@ class IpfsUseCases {
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.exporter = exporter
|
||||
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.
|
||||
}
|
||||
|
||||
// Download a pinned file, given its CID.
|
||||
// Returns a readable stream.
|
||||
async downloadCid (inObj = {}) {
|
||||
try {
|
||||
const { cid } = inObj
|
||||
let { cid } = inObj
|
||||
|
||||
if (!cid) throw new Error('CID is undefined')
|
||||
|
||||
@@ -58,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)) {
|
||||
@@ -83,6 +134,44 @@ class IpfsUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the price (in PSF tokens) to pin 1MB to the PSFFPP network. This price
|
||||
// is set on-chain by the PSF Minting Council. PSFoundation.cash
|
||||
async getWritePrice (inObj = {}) {
|
||||
try {
|
||||
const wallet = this.adapters.wallet.bchWallet
|
||||
// console.log('getWritePrice() wallet: ', wallet)
|
||||
|
||||
// Instantiate the psffpp library if it hasn't already been.
|
||||
// Dev Note: Important to only instantiate once, since the write price
|
||||
// is cached by the psffpp library. Instantiating only once improves
|
||||
// performance of price lookup.
|
||||
if (!this.psffpp) {
|
||||
this.psffpp = new PSFFPP({ wallet })
|
||||
|
||||
const now = new Date()
|
||||
this.lastWritePriceUpdate = now.getTime()
|
||||
}
|
||||
|
||||
// Periodically re-validate the write-price (by re-instantiating the
|
||||
// PSFFPP library) in case it's changed.
|
||||
const now = new Date()
|
||||
const sixHours = 1000 * 60 * 60 * 6
|
||||
|
||||
if (this.lastWritePriceUpdate + sixHours < now.getTime()) {
|
||||
this.psffpp = new PSFFPP({ wallet })
|
||||
|
||||
this.lastWritePriceUpdate = now.getTime()
|
||||
}
|
||||
|
||||
const writePrice = await this.psffpp.getMcWritePrice()
|
||||
|
||||
return writePrice
|
||||
} catch (err) {
|
||||
console.error('Error in use-cases/ipfs-use-cases.js/getWritePrice()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Given a CID, this function will retrieve file metadata from the
|
||||
// ipfs-file-pin-service, using the IPFS JSON-RPC. This metadata includes
|
||||
// the filename, which can be used to infer mime-type, so that it can be
|
||||
@@ -109,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
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
Use Case library for tracking usage. This library contains business logic
|
||||
for tracking the usage of REST API and JSON RPC calls. This library is used
|
||||
by admins to keep an eye on how many API calls were made in a 24-hour and
|
||||
1-hour time period.
|
||||
|
||||
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
|
||||
// the Usage Use Case class instance.
|
||||
let restCalls = []
|
||||
|
||||
class UsageUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('User localConfig: ', localConfig)
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of adapters must be passed in when instantiating Usage Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Clean up the state by removing entries that are older than 24 hours. This
|
||||
// ensures stats reflect only the last 24 hours.
|
||||
// This function is called by a Timer Controller.
|
||||
cleanUsage () {
|
||||
try {
|
||||
const now = new Date()
|
||||
const twentyFourHoursAgo = now.getTime() - (60000 * 60 * 24)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Track the calls to a REST API
|
||||
getRestSummary (inObj = {}) {
|
||||
try {
|
||||
console.log(`getRestSummary(): There have been ${restCalls.length} REST calls`)
|
||||
|
||||
return restCalls.length
|
||||
} catch (err) {
|
||||
console.error('Error in usage-use-cases.js/getRestSummary()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the top 20 IP addresses from the stats.
|
||||
getTopIps () {
|
||||
try {
|
||||
const ips = restCalls.map(x => x.ip)
|
||||
// Create a Map to count occurrences of each IP address string
|
||||
const countMap = new Map()
|
||||
ips.forEach(ip => {
|
||||
countMap.set(ip, (countMap.get(ip) || 0) + 1)
|
||||
})
|
||||
|
||||
// Convert the Map into an array of objects with `str` and `cnt` properties
|
||||
const result = Array.from(countMap, ([ip, cnt]) => ({ ip, cnt }))
|
||||
|
||||
// Sort the results by the `cnt` property in descending order
|
||||
result.sort((a, b) => b.cnt - a.cnt)
|
||||
|
||||
// Ensure the result has at most 20 elements
|
||||
return result.slice(0, 20)
|
||||
} catch (err) {
|
||||
console.error('Error in usage-use-cases.js/getTopIps()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the top 20 most consumed endpoints.
|
||||
getTopEndpoints () {
|
||||
try {
|
||||
const endpoints = restCalls.map(x => `${x.method} ${x.url}`)
|
||||
|
||||
// Create a Map to count occurrences of each IP address string
|
||||
const countMap = new Map()
|
||||
endpoints.forEach(endpoint => {
|
||||
countMap.set(endpoint, (countMap.get(endpoint) || 0) + 1)
|
||||
})
|
||||
|
||||
// Convert the Map into an array of objects with `str` and `cnt` properties
|
||||
const result = Array.from(countMap, ([endpoint, cnt]) => ({ endpoint, cnt }))
|
||||
|
||||
// Sort the results by the `cnt` property in descending order
|
||||
result.sort((a, b) => b.cnt - a.cnt)
|
||||
|
||||
// Ensure the result has at most 20 elements
|
||||
return result.slice(0, 20)
|
||||
} catch (err) {
|
||||
console.error('Error in usage-use-cases.js/getTopEndpoints()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// details from the request object.
|
||||
function usageMiddleware () {
|
||||
return async (ctx, next) => {
|
||||
try {
|
||||
await next()
|
||||
|
||||
// console.log('ctx.request: ', ctx.request)
|
||||
const now = new Date()
|
||||
|
||||
const reqObj = {
|
||||
ip: ctx.request.ip,
|
||||
url: ctx.request.url,
|
||||
method: ctx.request.method,
|
||||
timestamp: now.getTime()
|
||||
}
|
||||
// console.log('reqObj: ', reqObj)
|
||||
|
||||
restCalls.push(reqObj)
|
||||
} catch (err) {
|
||||
ctx.status = err.status || 500
|
||||
ctx.body = err.message
|
||||
ctx.app.emit('error', err, ctx)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { UsageUseCases, usageMiddleware, restCalls }
|
||||
@@ -0,0 +1,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,116 +13,114 @@ 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 = {}
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
const app = new Server()
|
||||
if (!config.noMongo) {
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
// const app = new Server()
|
||||
|
||||
// This should be the first instruction. It starts the REST API server.
|
||||
await app.startServer()
|
||||
// This should be the first instruction. It starts the REST API server.
|
||||
// await app.startServer()
|
||||
|
||||
// Stop the IPFS node for the rest of the e2e tests.
|
||||
// await app.controllers.adapters.ipfs.stop()
|
||||
// TODO:
|
||||
|
||||
// Delete all previous users in the database.
|
||||
await testUtils.deleteAllUsers()
|
||||
// Stop the IPFS node for the rest of the e2e tests.
|
||||
// await app.controllers.adapters.ipfs.stop()
|
||||
|
||||
// Create a new admin user.
|
||||
await adminLib.createSystemUser()
|
||||
// Delete all previous users in the database.
|
||||
await testUtils.deleteAllUsers()
|
||||
|
||||
const userObj = {
|
||||
email: 'test@test.com',
|
||||
password: 'pass',
|
||||
name: 'test'
|
||||
}
|
||||
const testUser = await testUtils.createUser(userObj)
|
||||
// console.log('TestUser: ', testUser)
|
||||
|
||||
context.user = testUser.user
|
||||
context.token = testUser.token
|
||||
})
|
||||
|
||||
describe('POST /auth', () => {
|
||||
it('should throw 401 if credentials are incorrect', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
|
||||
const result = await axios(options)
|
||||
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
console.log(
|
||||
`result stringified: ${JSON.stringify(result.data, null, 2)}`
|
||||
)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
const userObj = {
|
||||
email: 'test@test.com',
|
||||
password: 'pass',
|
||||
name: 'test'
|
||||
}
|
||||
const testUser = await testUtils.createUser(userObj)
|
||||
// console.log('TestUser: ', testUser)
|
||||
|
||||
context.user = testUser.user
|
||||
context.token = testUser.token
|
||||
})
|
||||
|
||||
it('should throw 401 if email is wrong format', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'wrongEmail',
|
||||
password: 'wrongpassword'
|
||||
describe('POST /auth', () => {
|
||||
it('should throw 401 if credentials are incorrect', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
|
||||
const result = await axios(options)
|
||||
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
console.log(
|
||||
`result stringified: ${JSON.stringify(result.data, null, 2)}`
|
||||
)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
await axios(options)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should auth user', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'pass'
|
||||
it('should throw 401 if email is wrong format', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'wrongEmail',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert(result.status === 200, 'Status Code 200 expected.')
|
||||
assert(
|
||||
result.data.user.email === 'test@test.com',
|
||||
'Email of test expected'
|
||||
)
|
||||
assert(
|
||||
result.data.user.password === undefined,
|
||||
'Password expected to be omited'
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(
|
||||
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
|
||||
)
|
||||
throw err
|
||||
}
|
||||
await axios(options)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should auth user', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'pass'
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert(result.status === 200, 'Status Code 200 expected.')
|
||||
assert(
|
||||
result.data.user.email === 'test@test.com',
|
||||
'Email of test expected'
|
||||
)
|
||||
assert(
|
||||
result.data.user.password === undefined,
|
||||
'Password expected to be omited'
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(
|
||||
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
|
||||
)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,115 +0,0 @@
|
||||
import { assert } from 'chai'
|
||||
import Admin from '../../../src/adapters/admin.js'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
describe('Admin', () => {
|
||||
beforeEach(() => {
|
||||
uut = new Admin()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('loginAdmin()', () => {
|
||||
it('should logind admin', async () => {
|
||||
try {
|
||||
const error = new Error('test error')
|
||||
error.response = {
|
||||
status: 422
|
||||
}
|
||||
// sandbox.stub(uut.axios, 'request').onFirstCall().throws(error)
|
||||
|
||||
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')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
try {
|
||||
// Returns an erroneous password to force
|
||||
// an auth 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')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('createSystemUser()', () => {
|
||||
it('should create admin', async () => {
|
||||
try {
|
||||
const result = await uut.createSystemUser()
|
||||
|
||||
assert.property(result, 'email')
|
||||
assert.property(result, 'password')
|
||||
assert.property(result, 'id')
|
||||
assert.property(result, 'token')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', 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)
|
||||
|
||||
await uut.createSystemUser()
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle errors when remove user', 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()
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -37,6 +37,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 {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import { assert } from 'chai'
|
||||
import Admin from '../../../src/adapters/admin.js'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
describe('Admin', () => {
|
||||
beforeEach(() => {
|
||||
uut = new Admin()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
if (!config.noMongo) {
|
||||
describe('loginAdmin()', () => {
|
||||
it('should login admin', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'pass' })
|
||||
sandbox.stub(uut.axios, 'request').resolves(true)
|
||||
|
||||
const result = await uut.loginAdmin()
|
||||
assert.isTrue(result)
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
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.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('createSystemUser()', () => {
|
||||
it('should create admin', async () => {
|
||||
try {
|
||||
await uut.deleteExistingSystemUser()
|
||||
const result = await uut.createSystemUser()
|
||||
|
||||
assert.property(result, 'email')
|
||||
assert.property(result, 'password')
|
||||
assert.property(result, 'id')
|
||||
assert.property(result, 'token')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
it('should update admin password', async () => {
|
||||
try {
|
||||
uut.config.adminPassword = 'newpassword'
|
||||
|
||||
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')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteExistingSystemUser()', () => {
|
||||
it('should delete admin', async () => {
|
||||
try {
|
||||
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.')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -6,7 +6,7 @@
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import { peerIdFromString } from '@libp2p/peer-id'
|
||||
// import { peerIdFromString } from '@libp2p/peer-id'
|
||||
|
||||
// Local libraries
|
||||
import IPFSLib from '../../../src/adapters/ipfs/ipfs.js'
|
||||
@@ -165,17 +165,17 @@ describe('#IPFS-adapter', () => {
|
||||
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 () => {}
|
||||
})
|
||||
// const 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({})
|
||||
|
||||
|
||||
@@ -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,12 +9,12 @@ let currentEnv
|
||||
describe('#config', () => {
|
||||
before(() => {
|
||||
// Backup the current environment setting.
|
||||
currentEnv = process.env.SVC_ENV
|
||||
currentEnv = process.env.CONSUMER_ENV
|
||||
})
|
||||
|
||||
after(() => {
|
||||
// Restore the environment setting before starting these tests.
|
||||
process.env.SVC_ENV = currentEnv
|
||||
process.env.CONSUMER_ENV = currentEnv
|
||||
})
|
||||
|
||||
it('Should return development environment config by default', async () => {
|
||||
@@ -29,7 +29,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 +39,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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user