Compare commits

...
39 Commits
Author SHA1 Message Date
Chris Troutner 63b08d1acb Merge pull request #11 from Permissionless-Software-Foundation/ct-unstable
feat(test): Added e2e test simulating a consumer of JSON RPC
2021-07-18 15:51:22 -07:00
Chris Troutner fa05fcf3d4 feat(test): Added e2e test simulating a consumer of JSON RPC 2021-07-18 15:46:32 -07:00
Chris Troutner f9846e95fd Created client scheme for JSON RPC 2021-07-18 08:09:27 -07:00
Chris Troutner def4f9003c Got simulated client to send a JSON RPC command 2021-07-17 19:51:56 -07:00
Chris Troutner 06cdc03287 Got client to connect to service 2021-07-17 19:14:11 -07:00
Chris Troutner c3bfbbb6e1 Merge pull request #10 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Setting name with an env var
2021-07-17 16:22:51 -07:00
Chris Troutner c7ed9eb3fd fix(ipfs-coord): Setting name with an env var 2021-07-17 16:19:03 -07:00
Chris Troutner a023202703 Adding debug log for mongodb connection string 2021-07-17 11:33:17 -07:00
Chris Troutner e31dceb07f Merge pull request #9 from Permissionless-Software-Foundation/ct-unstable
fix(production): Adjusting production settings
2021-07-17 11:25:38 -07:00
Chris Troutner af0e727ba8 fix(production): Fixed merge conflicts 2021-07-17 11:24:00 -07:00
Chris Troutner 6f64fc4652 fix(production): Adjusting production settings 2 2021-07-17 11:16:57 -07:00
Chris Troutner e91dd7f834 feat(production): Adjusting production settings 2021-07-17 20:16:19 +02:00
Chris Troutner e717124fc7 Merge pull request #8 from Permissionless-Software-Foundation/ct-unstable
fix(production): Updated Docker container
2021-07-17 10:55:48 -07:00
Chris Troutner 3c8d2cfdd3 fix(production): Updated Docker container 2021-07-17 10:54:00 -07:00
Chris Troutner 71fbebe672 Merge pull request #7 from Permissionless-Software-Foundation/ct-unstable
feat(JWT): Added automatic JWT handling for FullStack.cash
2021-07-17 10:08:00 -07:00
Chris Troutner cab819081a Got everything passing and working 2021-07-17 10:06:29 -07:00
Chris Troutner 4f0adbb773 Updated to ipfs-coord v4.0.1 2021-07-17 09:12:37 -07:00
Chris Troutner e04cb1f05a Got all tests passing again 2021-07-17 08:15:02 -07:00
Chris Troutner 6f619db666 Updated unit tests 2021-07-16 18:51:38 -07:00
Chris Troutner 126bee86bd feat(JWT): Added automatic JWT handling for FullStack.cash 2021-07-16 17:26:51 -07:00
Chris Troutner 3761d139f1 Merge pull request #6 from Permissionless-Software-Foundation/ct-unstable
fix(endpoints): Tested endpoints, fixed some bugs and docs
2021-07-16 15:27:55 -07:00
Chris Troutner 006727cc9d fix(bch-js): Updating bch-js to v4.20.2 2021-07-16 15:26:21 -07:00
Chris Troutner bcd2a54aa7 fix(endpoints): Tested endpoints, fixed some bugs and docs 2021-07-16 15:19:03 -07:00
Chris Troutner 70155c7b90 Merge pull request #5 from Permissionless-Software-Foundation/ct-unstable
Added broadcast and transaction endpoints
2021-07-16 10:53:10 -07:00
Chris Troutner 7035e1777b feat(transaction): Added transaction endpoint to REST and JSON 2021-07-16 10:51:47 -07:00
Chris Troutner ed82c1102e feat(broadcast): Added endpoint for REST and JSON APIs 2021-07-16 08:00:40 -07:00
Chris Troutner baad128ba2 Merge pull request #4 from Permissionless-Software-Foundation/ct-unstable
feat(utxos): Added utxos endpoing to REST and JSON RPC
2021-07-15 19:07:11 -07:00
Chris Troutner 57ce5c12f5 feat(utxos): Added utxos endpoing to REST and JSON RPC 2021-07-15 19:04:57 -07:00
Chris Troutner 6aa2e1e44a Converted fulcrum endpoints to bch endpoints 2021-07-15 13:31:24 -07:00
Chris Troutner 762e07e662 Merge pull request #3 from Permissionless-Software-Foundation/ct-unstable
feat(balance): Added Fulcrum balance call to REST and JSON
2021-07-15 09:33:46 -07:00
Chris Troutner 720508e7c4 Merge branch 'master' into ct-unstable 2021-07-15 09:31:58 -07:00
Chris Troutner 8a558f212f feat(balance): Added Fulcrum balance call to REST and JSON 2021-07-15 09:30:31 -07:00
Chris Troutner 4223262323 Merge pull request #2 from Permissionless-Software-Foundation/ct-unstable
feat(Fulcrum): Added transactions endpoint for REST and JSON RPC
2021-07-14 15:26:47 -07:00
Chris Troutner e68200ed9c Added 100% unit tests for Fulcrum endpoints 2021-07-14 15:23:26 -07:00
Chris Troutner 322a10cd74 Adding unit tests for Fulcrum REST transaction endpoint 2021-07-14 14:55:40 -07:00
Chris Troutner 32af7a7233 feat(Fulcrum): Added transactions endpoint for REST and JSON RPC 2021-07-13 18:35:08 -07:00
Chris Troutner f68e63f7d6 Merge pull request #1 from Permissionless-Software-Foundation/ct-unstable
Testing CI/CD
2021-07-13 16:58:39 -07:00
Chris Troutner 568b2df7d0 testing webhook 2021-07-13 16:55:26 -07:00
Chris Troutner 1472a4868a fix(CI): Testing CI 2021-07-13 16:53:30 -07:00
43 changed files with 2685 additions and 582 deletions
+9
View File
@@ -10,6 +10,15 @@ This repository is forked from the [ipfs-service-provider](https://github.com/Pe
As part of the end-to-end testing, a virtual client can be started that will interrogate every endpoint provided by this API server. This client can be forked by developers to write their own apps that use this IPFS-based service.
## Roadmap
This repository is one step in a longer path. Here are the major milestones of this path:
- Add wallet-based calls to ipfs-bch-wallet-service (this repository).
- Create a miniature version of bch-js that makes calls over JSON RPC (bch-js-ipfs).
- Given a variable, minimal-slp-wallet could call bch-js or bch-js-ipfs for network calls.
- Fork slp-cli-wallet and add `daemon` feature to spin up IPFS node and work with bch-js-ipfs.
## Requirements
- node **^14.17.0**
+12 -3
View File
@@ -29,6 +29,9 @@ async function startServer () {
// Connect to the Mongo Database.
mongoose.Promise = global.Promise
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
console.log(
`Connecting to MongoDB with this connection string: ${config.database}`
)
await mongoose.connect(config.database, {
useUnifiedTopology: true,
useNewUrlParser: true
@@ -54,7 +57,7 @@ async function startServer () {
// Attach REST API and JSON RPC controllers to the app.
const controllers = require('../src/controllers')
controllers.attachControllers(app)
await controllers.attachControllers(app)
// Enable CORS for testing
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
@@ -69,8 +72,14 @@ async function startServer () {
console.log(`Server started on ${config.port}`)
// Create the system admin user.
const success = await adminLib.createSystemUser()
if (success) console.log('System admin user created.')
try {
const success = await adminLib.createSystemUser()
if (success) console.log('System admin user created.')
} catch (err) {
console.warn(
'Error trying to create system admin. Perhaps one already exists?'
)
}
return app
}
+37 -8
View File
@@ -5,6 +5,10 @@
/* eslint no-unneeded-ternary:0 */
const ipfsCoordName = process.env.COORD_NAME
? process.env.COORD_NAME
: 'ipfs-bch-wallet-service'
module.exports = {
// Configure TCP port.
port: process.env.PORT || 5001,
@@ -13,27 +17,52 @@ module.exports = {
logPass: 'test',
// Email server settings if nodemailer email notifications are used.
emailServer: process.env.EMAILSERVER ? process.env.EMAILSERVER : 'mail.someserver.com',
emailUser: process.env.EMAILUSER ? process.env.EMAILUSER : 'noreply@someserver.com',
emailPassword: process.env.EMAILPASS ? process.env.EMAILPASS : 'emailpassword',
emailServer: process.env.EMAILSERVER
? process.env.EMAILSERVER
: 'mail.someserver.com',
emailUser: process.env.EMAILUSER
? process.env.EMAILUSER
: 'noreply@someserver.com',
emailPassword: process.env.EMAILPASS
? process.env.EMAILPASS
: 'emailpassword',
// FullStack.cash account information, used for automatic JWT handling.
AUTHSERVER: process.env.AUTHSERVER
? process.env.AUTHSERVER
: 'https://auth.fullstack.cash',
APISERVER: process.env.APISERVER
? process.env.APISERVER
: 'https://api.fullstack.cash/v5/',
FULLSTACKLOGIN: process.env.FULLSTACKLOGIN
? process.env.FULLSTACKLOGIN
: 'demo@demo.com',
FULLSTACKPASS: process.env.FULLSTACKPASS ? process.env.FULLSTACKPASS : 'demo',
// IPFS settings.
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
// Information passed to other IPFS peers about this node.
apiInfo: 'https://ipfs-service-provider.fullstack.cash/',
apiInfo: 'https://ipfs-bch-wallet-service.fullstack.cash/',
ipfsCoordName: ipfsCoordName,
// JSON-LD and Schema.org schema with info about this app.
announceJsonLd: {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'ipfs-service-provider',
description: 'This is a generic IPFS Serivice Provider that uses JSON RPC over IPFS to communicate with it. This instance has not been customized. Source code: https://github.com/Permissionless-Software-Foundation/ipfs-service-provider',
documentation: 'https://ipfs-service-provider.fullstack.cash/',
name: ipfsCoordName,
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'
}
}
},
// IPFS Ports
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 5668,
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 5669
}
+1 -1
View File
@@ -7,6 +7,6 @@
module.exports = {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/ipfs-service-dev',
database: 'mongodb://localhost:27017/bch-service-dev',
env: 'dev'
}
+3 -1
View File
@@ -10,6 +10,8 @@
module.exports = {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
database: process.env.DBURL
? process.env.DBURL
: 'mongodb://172.17.0.1:5555/bch-service-prod',
env: 'prod'
}
+1 -1
View File
@@ -7,6 +7,6 @@
module.exports = {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/ipfs-service-test',
database: 'mongodb://localhost:27017/bch-service-test',
env: 'test'
}
-25
View File
@@ -1,25 +0,0 @@
# Start the testnet server with the command 'docker-compose up -d'
koa-mongodb:
image: mongo
container_name: mongo-koa
ports:
- "5555:27017" # <host port>:<container port>
volumes:
- ./database:/data/db
command: mongod --smallfiles --logpath=/dev/null # -- quiet
restart: always
koa:
build: ./production/
dockerfile: Dockerfile
container_name: koa
links:
- koa-mongodb
ports:
- "5001:5001" # <host port>:<container port>
volumes:
# - ./logs:/home/coinjoin/consolidating-coinjoin/logs
- ./keys:/home/safeuser/keys
restart: always
+489 -488
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -8,7 +8,7 @@
"test": "npm run test:all",
"test:all": "export SVC_ENV=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
"test:unit": "export SVC_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
"test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
"test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 30000 test/e2e/automated/",
"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",
@@ -23,14 +23,15 @@
},
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-service",
"dependencies": {
"@psf/bch-js": "^4.20.1",
"@psf/bch-js": "^4.20.2",
"axios": "^0.21.1",
"bcryptjs": "^2.4.3",
"glob": "^7.1.6",
"ipfs": "^0.54.4",
"ipfs-coord": "^3.2.0",
"ipfs-coord": "^4.0.1",
"jsonrpc-lite": "^2.2.0",
"jsonwebtoken": "^8.5.1",
"jwt-bch-lib": "^1.3.0",
"kcors": "^2.2.2",
"koa": "^2.13.1",
"koa-bodyparser": "^4.3.0",
+11 -10
View File
@@ -29,13 +29,13 @@ RUN echo 'abcd8765' | sudo -S pwd
# Clone the rest.bitcoin.com repository
WORKDIR /home/safeuser
RUN git clone https://github.com/christroutner/koa-api-boilerplate
RUN mv koa-api-boilerplate koa
RUN mkdir keys
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-service
# RUN mv koa-api-boilerplate koa
# RUN mkdir keys
# Switch to the desired branch. `master` is usually stable,
# and `stage` has the most up-to-date changes.
WORKDIR /home/safeuser/koa
WORKDIR /home/safeuser/ipfs-bch-wallet-service
# For development: switch to unstable branch
# RUN git checkout unstable
@@ -46,13 +46,14 @@ RUN npm install
# Generate the API docs
RUN npm run docs
VOLUME /home/safeuser/keys
# Expose the port the API will be served on.
EXPOSE 5001
# Calling out the directorys that should be mounted as external volumes.
RUN mkdir /home/safeuser/ipfsdata
RUN mkdir /home/safeuser/orbitdb
VOLUME /home/safeuser/ipfsdata
VOLUME /home/safeuser/orbitdb
# Start the application.
COPY start-production start-production
CMD ["./start-production"]
COPY start-production.sh start-production.sh
CMD ["./start-production.sh"]
#CMD ["npm", "start"]
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# Remove all untagged docker images.
docker rmi $(docker images | grep "^<none>" | awk '{print $3}')
+28
View File
@@ -0,0 +1,28 @@
# Start the testnet server with the command 'docker-compose up -d'
bch-wallet-mongodb:
image: mongo
container_name: mongo-bch-service
ports:
- '5555:27017' # <host port>:<container port>
volumes:
- ./database:/data/db
command: mongod --logpath=/dev/null # -- quiet
restart: always
bch-wallet-service:
build: ./
dockerfile: Dockerfile
container_name: bch-wallet-service
links:
- bch-wallet-mongodb
ports:
# <host port>:<container port>
- '5001:5001' # REST API
- '5668:5668' # IPFS TCP Port
- '5669:5669' # IPFS WS Port
volumes:
- ../ipfsdata:/home/safeuser/ipfsdata
- ../orbitdb:/home/safeuser/orbitdb
restart: always
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
export KOA_ENV=production
npm start
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# Customize these environment variables for your own installation.
export AUTHSERVER=https://auth.fullstack.cash
export APISERVER=https://api.fullstack.cash/v5/
export FULLSTACKLOGIN=demo@demo.com
export FULLSTACKPASS=demo
# Ports
export PORT=5001 # REST API
export IPFS_TCP_PORT=5668 # IPSF TCP Port
export IPFS_WS_PORT=5669 # IPFS WS Port
# Production database connection string.
export DBURL=mongodb://172.17.0.1:5555/ipfs-service-prod
#export ENABLE_CIRCUIT_RELAY=1
export SVC_ENV=production
npm start
+107
View File
@@ -0,0 +1,107 @@
/*
A library of utility functions for working with FullStack.cash JWT tokens.
Feel free to copy this library into your own app, as well as the unit tests
for this file.
*/
const JwtLib = require('jwt-bch-lib')
const BCHJS = require('@psf/bch-js')
class FullStackJWT {
constructor (localConfig = {}) {
// Input Validation
this.authServer = localConfig.authServer
if (!this.authServer || typeof this.authServer !== 'string') {
throw new Error(
'Must pass a url for the AUTH server when instantiating FullStackJWT class.'
)
}
this.apiServer = localConfig.apiServer
if (!this.apiServer || typeof this.apiServer !== 'string') {
throw new Error(
'Must pass a url for the API server when instantiating FullStackJWT class.'
)
}
this.login = localConfig.login
if (!this.login || typeof this.login !== 'string') {
throw new Error(
'Must pass a FullStack.cash login (email) instantiating FullStackJWT class.'
)
}
this.password = localConfig.password
if (!this.password || typeof this.password !== 'string') {
throw new Error(
'Must pass a FullStack.cash account password when instantiating FullStackJWT class.'
)
}
// Encapsulate dependencies
this.jwtLib = new JwtLib({
// Overwrite default values with the values in the config file.
server: this.server,
login: this.login,
password: this.password
})
// State
this.apiToken = '' // Default value.
this.bchjs = {}
}
// Get's a JWT token from FullStack.cash.
async getJWT () {
try {
// Skip connecting FullStack.cash auth server to the network if this is an E2E test.
if (process.env.E2ETEST) {
this.apiToken = 'faketoken'
return this.apiToken
}
// Log into the auth server.
await this.jwtLib.register()
this.apiToken = this.jwtLib.userData.apiToken
if (!this.apiToken) {
throw new Error('This account does not have a JWT')
}
console.log(`Retrieved JWT token: ${this.apiToken}\n`)
// Ensure the JWT token is valid to use.
const isValid = await this.jwtLib.validateApiToken()
// Get a new token with the same API level, if the existing token is not
// valid (probably expired).
if (!isValid.isValid) {
this.apiToken = await this.jwtLib.getApiToken(
this.jwtLib.userData.apiLevel
)
console.log(
`The JWT token was not valid. Retrieved new JWT token: ${this.apiToken}\n`
)
} else {
console.log('JWT token is valid.\n')
}
return this.apiToken
} catch (err) {
console.error(
`Error trying to log into ${this.server} and retrieve JWT token.`
)
throw err
}
}
// Create an instance of bchjs with the validated JWT token. Returns this
// instance of bch-js.
instanceBchjs () {
this.bchjs = new BCHJS({
restURL: this.apiServer,
apiToken: this.apiToken
})
return this.bchjs
}
}
module.exports = FullStackJWT
+16 -1
View File
@@ -4,6 +4,9 @@
https://troutsblog.com/blog/clean-architecture
*/
// Load the config file
const config = require('../../config')
// Load individual adapter libraries.
const IPFSAdapter = require('./ipfs')
const LocalDB = require('./localdb')
@@ -12,6 +15,7 @@ const Passport = require('./passport')
const Nodemailer = require('./nodemailer')
const { wlogger } = require('./wlogger')
const JSONFiles = require('./json-files')
const FullStackJWT = require('./fullstack-jwt')
// Instantiate adapter libraries.
const ipfs = new IPFSAdapter()
@@ -21,6 +25,15 @@ const passport = new Passport()
const nodemailer = new Nodemailer()
const jsonFiles = new JSONFiles()
// Get a valid JWT API key and instance bch-js.
const fullStackJwt = new FullStackJWT({
authServer: config.AUTHSERVER,
apiServer: config.APISERVER,
login: config.FULLSTACKLOGIN,
password: config.FULLSTACKPASS
})
const bchjs = {} // Placeholder.
module.exports = {
ipfs,
localdb,
@@ -28,5 +41,7 @@ module.exports = {
passport,
nodemailer,
wlogger,
jsonFiles
jsonFiles,
fullStackJwt,
bchjs
}
+10 -2
View File
@@ -19,8 +19,15 @@ class IPFS {
// Provides a global start() function that triggers the start() function in
// the underlying libraries.
async start () {
async start (localConfig = {}) {
try {
const bchjs = localConfig.bchjs
if (!bchjs) {
throw new Error(
'Instance of bch-js must be passed when instantiating IPFS adapter.'
)
}
// Start IPFS
await this.ipfsAdapter.start()
console.log('IPFS is ready.')
@@ -30,7 +37,8 @@ class IPFS {
// Start ipfs-coord
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
ipfs: this.ipfs
ipfs: this.ipfs,
bchjs
})
await this.ipfsCoordAdapter.start()
console.log('ipfs-coord is ready.')
+14 -5
View File
@@ -6,7 +6,7 @@
// Global npm libraries
const IpfsCoord = require('ipfs-coord')
const BCHJS = require('@psf/bch-js')
// const BCHJS = require('@psf/bch-js')
// Local libraries
const config = require('../../../config')
@@ -23,11 +23,16 @@ class IpfsCoordAdapter {
'Instance of IPFS must be passed when instantiating ipfs-coord.'
)
}
this.bchjs = localConfig.bchjs
if (!this.bchjs) {
throw new Error(
'Instance of bch-js must be passed when instantiating ipfs-coord.'
)
}
// Encapsulate dependencies
this.IpfsCoord = IpfsCoord
this.ipfsCoord = {}
this.bchjs = new BCHJS()
// this.rpc = new JSONRPC()
this.config = config
@@ -37,7 +42,7 @@ class IpfsCoordAdapter {
_this = this
}
async start () {
async start (localConfig = {}) {
this.ipfsCoord = new this.IpfsCoord({
ipfs: this.ipfs,
type: 'node.js',
@@ -49,8 +54,12 @@ class IpfsCoordAdapter {
announceJsonLd: this.config.announceJsonLd
})
// Wait for the ipfs-coord library to signal that it is ready.
await this.ipfsCoord.isReady()
// Skip connecting ipfs-coord to the network if this is an E2E test.
if (!process.env.E2ETEST) {
// Wait for the ipfs-coord library to signal that it is ready.
await this.ipfsCoord.ipfs.start()
await this.ipfsCoord.isReady()
}
// Signal that this adapter is ready.
this.isReady = true
+1
View File
@@ -63,6 +63,7 @@ class IpfsAdapter {
// Stop the IPFS node if we're running tests.
if (this.config.env === 'test') {
console.log('Stopping IPFS for tests.')
await this.ipfs.stop()
}
+16 -5
View File
@@ -22,13 +22,24 @@ const RESTControllers = require('./rest-api')
// Top-level function for this library.
// Start the various Controllers and attach them to the app.
async function attachControllers (app) {
// Attach the REST controllers to the Koa app.
attachRESTControllers(app)
try {
// Get a JWT token and instantiate bch-js with it. Then pass that instance
// to all the rest of the apps controllers and adapters.
await adapters.fullStackJwt.getJWT()
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
adapters.bchjs = await adapters.fullStackJwt.instanceBchjs()
// Start IPFS.
await adapters.ipfs.start()
// Attach the REST controllers to the Koa app.
attachRESTControllers(app)
attachRPCControllers()
// Start IPFS.
await adapters.ipfs.start({ bchjs: adapters.bchjs })
attachRPCControllers()
} catch (err) {
console.error('Error in attachControllers()')
throw err
}
}
function attachRESTControllers (app) {
+279
View File
@@ -0,0 +1,279 @@
/*
This is the JSON RPC router for the Fulcrum API
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
// const BCHJS = require('@psf/bch-js')
// Local libraries
// const UserLib = require('../../../use-cases/user')
const Validators = require('../validators')
const RateLimit = require('../rate-limit')
class BCHRPC {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating BCH JSON RPC Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating BCH JSON RPC Controller.'
)
}
// Encapsulate dependencies
this.userLib = this.useCases.user
this.jsonrpc = jsonrpc
this.validators = new Validators(localConfig)
this.rateLimit = new RateLimit()
// this.bchjs = new BCHJS()
this.bchjs = this.adapters.bchjs
}
// Top-level router for this library. All other methods in this class are for
// a specific endpoint. This method routes incoming calls to one of those
// methods.
async bchRouter (rpcData) {
let endpoint = 'unknown'
try {
// console.log('fulcrumRouter rpcData: ', rpcData)
endpoint = rpcData.payload.params.endpoint
// let user
// Route the call based on the value of the method property.
switch (endpoint) {
case 'transactions':
await this.rateLimit.limiter(rpcData.from)
return await this.transactions(rpcData)
case 'balance':
await this.rateLimit.limiter(rpcData.from)
return await this.balance(rpcData)
case 'utxos':
await this.rateLimit.limiter(rpcData.from)
return await this.utxos(rpcData)
case 'broadcast':
await this.rateLimit.limiter(rpcData.from)
return await this.broadcast(rpcData)
case 'transaction':
await this.rateLimit.limiter(rpcData.from)
return await this.transaction(rpcData)
}
} catch (err) {
console.error('Error in BCHRPC/rpcRouter()')
// throw err
return {
success: false,
status: err.status || 500,
message: err.message,
endpoint
}
}
}
/**
* @api {JSON} /bch Transactions
* @apiPermission public
* @apiName Transactions
* @apiGroup JSON BCH
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "transactions", "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"]}}
*
*/
async transactions (rpcData) {
try {
// console.log('createUser rpcData: ', rpcData)
const addrs = rpcData.payload.params.addresses
const data = await this.bchjs.Electrumx.transactions(addrs)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
const retObj = data
retObj.status = 200
return retObj
} catch (err) {
// console.error('Error in createUser()')
// throw err
// Return an error response
return {
success: false,
status: 422,
message: err.message,
endpoint: 'transactions'
}
}
}
/**
* @api {JSON} /bch Balance
* @apiPermission public
* @apiName Balance
* @apiGroup JSON BCH
* @apiDescription This endpoint wraps the bchjs.Electrumx.balance([]) function.
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "balance", "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"]}}
*
*/
async balance (rpcData) {
try {
// console.log('createUser rpcData: ', rpcData)
const addrs = rpcData.payload.params.addresses
const data = await this.bchjs.Electrumx.balance(addrs)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
const retObj = data
retObj.status = 200
return retObj
} catch (err) {
// console.error('Error in createUser()')
// throw err
// Return an error response
return {
success: false,
status: 422,
message: err.message,
endpoint: 'balance'
}
}
}
/**
* @api {JSON} /bch UTXOs
* @apiPermission public
* @apiName UTXOs
* @apiGroup JSON BCH
* @apiDescription This endpoint wraps the bchjs.Utxos.get() function. This
* endpoint returns UTXOs held at an address, hydrated
* with token information.
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxos", "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"}}
*
*/
async utxos (rpcData) {
try {
// console.log('createUser rpcData: ', rpcData)
const addr = rpcData.payload.params.address
const data = await this.bchjs.Utxo.get(addr)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
const retObj = data
retObj.status = 200
return retObj
} catch (err) {
// console.error('Error in createUser()')
// throw err
// Return an error response
return {
success: false,
status: 422,
message: err.message,
endpoint: 'utxos'
}
}
}
/**
* @api {JSON} /bch Broadcast
* @apiPermission public
* @apiName Broadcast
* @apiGroup JSON BCH
* @apiDescription Broadcast a transaction to the BCH network.
* The transaction should be encoded as a hexidecimal string.
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "broadcast", "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"}}
*
*/
async broadcast (rpcData) {
try {
// console.log('createUser rpcData: ', rpcData)
const hex = rpcData.payload.params.hex
const data = await this.bchjs.RawTransactions.sendRawTransaction(hex)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
const retObj = data
retObj.status = 200
return retObj
} catch (err) {
// console.error('Error in createUser()')
// throw err
// Return an error response
return {
success: false,
status: 422,
message: err.message,
endpoint: 'broadcast'
}
}
}
/**
* @api {JSON} /bch Transaction
* @apiPermission public
* @apiName Transaction
* @apiGroup JSON BCH
* @apiDescription Get data about a specific transaction.
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "transaction", "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b"}}
*
*/
async transaction (rpcData) {
try {
// console.log('transaction rpcData: ', rpcData)
const txid = rpcData.payload.params.txid
const data = await this.bchjs.Transaction.get(txid.toString())
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
const retObj = data
retObj.status = 200
return retObj
} catch (err) {
// console.error('Error in createUser()')
// throw err
// Return an error response
return {
success: false,
status: 422,
message: err.message,
endpoint: 'transaction'
}
}
}
}
module.exports = BCHRPC
+7 -2
View File
@@ -10,6 +10,7 @@ const { wlogger } = require('../../adapters/wlogger')
const UserController = require('./users')
const AuthController = require('./auth')
const AboutController = require('./about')
const BCHController = require('./bch')
let _this
@@ -35,6 +36,7 @@ class JSONRPC {
this.userController = new UserController(localConfig)
this.authController = new AuthController(localConfig)
this.aboutController = new AboutController()
this.bchController = new BCHController(localConfig)
_this = this
}
@@ -43,8 +45,8 @@ class JSONRPC {
// which controller to route the instruction to.
async router (str, from) {
try {
// console.log('router str: ', str)
// console.log('router from: ', from)
console.log('router str: ', str)
console.log('router from: ', from)
// Exit quietly if 'from' is not specified.
if (!from || typeof from !== 'string') {
@@ -80,6 +82,9 @@ class JSONRPC {
break
case 'about':
retObj = await _this.aboutController.aboutRouter(parsedData)
break
case 'bch':
retObj = await _this.bchController.bchRouter(parsedData)
}
// console.log('retObj: ', retObj)
+2 -2
View File
@@ -14,7 +14,7 @@ class RateLimit {
// Set default rate limit options.
this.defaultOptions = {
interval: { min: 1 },
max: 60,
max: 600, // 600 RPM while prototyping.
onLimitReached: this.onLimitReached
}
@@ -48,7 +48,7 @@ class RateLimit {
onLimitReached () {
try {
const error = new Error() // Establish provided options as the default options.
error.message = 'Too many requests, please try again later.'
error.message = 'Too many requests, please slow down your requests.'
error.status = 429
throw error
} catch (error) {
+239
View File
@@ -0,0 +1,239 @@
/*
Controller for the /fulcrum REST API endpoints.
*/
// const BCHJS = require('@psf/bch-js')
let _this
class BCHRESTController {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating BCH REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating BCH REST Controller.'
)
}
// this.bchjs = new BCHJS()
this.bchjs = this.adapters.bchjs
_this = this
}
/**
* @api {post} /bch/transactions Transactions
* @apiName Transactions
* @apiGroup REST BCH
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"] }' localhost:5001/bch/transactions
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
*
* success:true,
* data: <data>
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async transactions (ctx) {
try {
const addrs = ctx.request.body.addresses
const data = await _this.bchjs.Electrumx.transactions(addrs)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
_this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/balance Balance
* @apiName Balance
* @apiGroup REST BCH
* @apiDescription This endpoint returns the balance in BCH for an address.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"] }' localhost:5001/bch/balance
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* success:true,
* data: <data>
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async balance (ctx) {
try {
const addrs = ctx.request.body.addresses
const data = await _this.bchjs.Electrumx.balance(addrs)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
_this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/utxos UTXOs
* @apiName UTXOs
* @apiGroup REST BCH
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
* with token information.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/utxos
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* success:true,
* data: <data>
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async utxos (ctx) {
try {
const address = ctx.request.body.address
const utxos = await _this.bchjs.Utxo.get(address)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
ctx.body = utxos
} catch (err) {
_this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/broadcast Broadcast
* @apiName Broadcast
* @apiGroup REST BCH
* @apiDescription Broadcast a transaction to the BCH network.
* The transaction should be encoded as a hexidecimal string.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000" }' localhost:5001/bch/broadcast
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* success:true,
* data: <data>
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async broadcast (ctx) {
try {
const hex = ctx.request.body.hex
const txid = await _this.bchjs.RawTransactions.sendRawTransaction(hex)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
ctx.body = txid
} catch (err) {
_this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/transaction Transaction
* @apiName Transaction
* @apiGroup REST BCH
* @apiDescription Get data about a specific transaction.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/transaction
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* success:true,
* data: <data>
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async transaction (ctx) {
try {
const txid = ctx.request.body.txid
const data = await _this.bchjs.Transaction.get(txid)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
ctx.body = data
} catch (err) {
_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 = BCHRESTController
+60
View File
@@ -0,0 +1,60 @@
/*
REST API library for /fulcrum route.
*/
// Public npm libraries.
const Router = require('koa-router')
// Local libraries.
const BCHRESTController = require('./controller')
class BCHRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating BCH REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating BCH REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.bchRESTController = new BCHRESTController(dependencies)
// Instantiate the router and set the base route.
const baseUrl = '/bch'
this.router = new Router({ prefix: baseUrl })
}
attach (app) {
if (!app) {
throw new Error(
'Must pass app object when attaching REST API controllers.'
)
}
// Define the routes and attach the controller.
this.router.post('/transactions', this.bchRESTController.transactions)
this.router.post('/balance', this.bchRESTController.balance)
this.router.post('/utxos', this.bchRESTController.utxos)
this.router.post('/broadcast', this.bchRESTController.broadcast)
this.router.post('/transaction', this.bchRESTController.transaction)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
}
module.exports = BCHRouter
+5
View File
@@ -11,6 +11,7 @@ const AuthRESTController = require('./auth')
const UserRouter = require('./users')
const ContactRESTController = require('./contact')
const LogsRESTController = require('./logs')
const BCHRESTController = require('./bch')
class RESTControllers {
constructor (localConfig = {}) {
@@ -52,6 +53,10 @@ class RESTControllers {
// Attach the REST API Controllers associated with the /logs route
const logsRESTController = new LogsRESTController(dependencies)
logsRESTController.attach(app)
// Attach the REST API Controllers associated with the /fulcrum route
const bchRESTController = new BCHRESTController(dependencies)
bchRESTController.attach(app)
}
}
+21 -15
View File
@@ -22,25 +22,31 @@ const LOCALHOST = `http://localhost:${config.port}`
describe('Auth', () => {
before(async () => {
// This should be the first instruction. It starts the REST API server.
await app.startServer()
try {
process.env.E2ETEST = 'true'
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
// This should be the first instruction. It starts the REST API server.
await app.startServer()
// 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'
// Create a new admin user.
await adminLib.createSystemUser()
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
} catch (err) {
console.log('err in auth beforeAll: ', err)
}
const testUser = await testUtils.createUser(userObj)
// console.log('TestUser: ', testUser)
context.user = testUser.user
context.token = testUser.token
})
describe('POST /auth', () => {
+11
View File
@@ -0,0 +1,11 @@
# Consumer Simulation E2E Test
The files in this directory simulates a consumer of web services, using JSON RPC over IPFS. It starts a new IPFS node that connects to the ipfs-bch-wallet-service and exercises each JSON RPC endpoint.
## Instructions
Execute these steps in order to run the test:
1. Run your local copy of ipfs-bch-wallet-service service provider.
2. Update the [test-data.json](./test-data.json) file with the IPFS ID of the ipfs-bch-wallet-service.
3. In another terminal, run the client simulation with `node client.e2e.js`
+29
View File
@@ -0,0 +1,29 @@
/*
An end-to-end (e2e) test simulating a client connecting over IPFS and
that exercises each JSON RPC endpoint.
*/
const TestUtils = require('./lib/test-utils')
const testUtils = new TestUtils()
const testData = require('./test-data.json')
async function startTest () {
try {
console.log('E2E TEST: Starting consumer E2E tests...')
await testUtils.startIpfs()
await testUtils.connectToUut(testData.uutAddr, testData.uutId)
await testUtils.testBalance(testData.uutId)
await testUtils.testTransaction(testData.uutId)
console.log('E2E TEST: Tests completed successfully!')
process.exit()
} catch (err) {
console.error('Error in startTest(): ', err)
}
}
startTest()
+217
View File
@@ -0,0 +1,217 @@
/*
A class library of test utility functions.
*/
// Public npm libraries
const BCHJS = require('@psf/bch-js')
const IpfsCoord = require('ipfs-coord')
const IPFS = require('ipfs')
const EventEmitter = require('events')
const { v4: uid } = require('uuid')
const jsonrpc = require('jsonrpc-lite')
let _this
class TestUtils {
constructor (localConfig = {}) {
// Encapsulate dependencies.
this.bchjs = new BCHJS()
this.eventEmitter = new EventEmitter()
this.uid = uid
this.jsonrpc = jsonrpc
_this = this
}
// This handler function recieves data from other ipfs-coord peers, like
// the ipfs-bch-wallet-service that we're testing.
// It emits the data as an 'rpcData' event. The tests will listen for this
// event.
rpcHandler (inData) {
try {
// console.log('Data recieved by rpcHandler: ', inData)
const jsonData = JSON.parse(inData)
_this.eventEmitter.emit('rpcData', jsonData)
} catch (err) {
console.error('Error in rpcHandler: ', err)
// Do not throw an error. This is a top-level function.
}
}
// Send the RPC command to the service, wait a period of time for a response.
// Timeout if a response is not recieved.
async sendRPC (ipfsId, cmdStr) {
try {
// Send the RPC command to the server/service.
await this.ipfsCoord.ipfs.orbitdb.sendToDb(ipfsId, cmdStr)
let retData
// This event is triggered when the response comes back.
this.eventEmitter.on('rpcData', inData => {
retData = inData
})
// Used for calculating the timeout.
const start = new Date()
let now = start
let timeDiff = 0
// Wait for the response from the server. Exit once the response is
// recieved, or a timeout occurs.
do {
await this.bchjs.Util.sleep(1000)
now = new Date()
timeDiff = now.getTime() - start.getTime()
// console.log('timeDiff: ', timeDiff)
} while (
// Exit once the RPC data comes back, or if a period of time passes.
!retData || // eslint-disable-line no-unmodified-loop-condition
timeDiff > 10000
)
return retData
} catch (err) {
console.error('Error in sendRPC')
throw err
}
}
async startIpfs () {
try {
// Start the IPFS node.
this.ipfs = await IPFS.create()
await this.ipfs.config.profiles.apply('server')
// Start ipfs-coord.
this.ipfsCoord = new IpfsCoord({
ipfs: this.ipfs,
type: 'node.js',
// type: 'browser',
bchjs: this.bchjs,
privateLog: this.rpcHandler, // Default to console.log
isCircuitRelay: false,
apiInfo: 'none',
announceJsonLd: announceJsonLd
})
await this.ipfsCoord.ipfs.start()
await this.ipfsCoord.isReady()
// Wait to let ipfs-coord connect to subnet peers.
await this.bchjs.Util.sleep(30000)
} catch (err) {
console.error('Error in startIpfs().')
throw err
}
}
// Connect to the unit under test (uut)
async connectToUut (addr, ipfsId) {
try {
try {
await this.ipfs.swarm.connect(addr)
console.log(`E2E TEST: Connected to IPFS node ${addr}`)
} catch (err) {
throw new Error('Could not connect to UUT IPFS node.')
}
// console.log(
// 'ipfs-coord peer info: ',
// this.ipfsCoord.ipfs.peers.state.peers[ipfsId]
// )
if (!this.ipfsCoord.ipfs.peers.state.peers[ipfsId]) {
throw new Error('Could not find UUT in ipfs-coord list of peers.')
}
} catch (err) {
console.error('Error in connectToUut()')
console.log(
'Is the service running? Did you update the test-data.json file?'
)
process.exit()
}
}
// Get the balance for a BCH address over JSON RPC.
async testBalance (ipfsId) {
try {
// Generate the JSON RPC command
const id = uid()
const cmd = jsonrpc.request(id, 'bch', {
endpoint: 'balance',
addresses: ['bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj']
})
const cmdStr = JSON.stringify(cmd)
// console.log(`Publishing message to ${ipfsId}`)
console.log('E2E TEST: Sending Balance command...')
const result = await this.sendRPC(ipfsId, cmdStr)
// console.log('result: ', result)
if (result.result.value.success && result.result.value.balances) {
console.log('E2E TEST: balance test passed.')
return true
} else {
console.log('E2E TEST: balance test failed.')
this.failTest()
}
} catch (err) {
console.error('Error in testBalance()')
throw err
}
}
// Get the info a on transaction over JSON RPC.
async testTransaction (ipfsId) {
try {
// Generate the JSON RPC command
const id = uid()
const cmd = jsonrpc.request(id, 'bch', {
endpoint: 'transaction',
txid: '01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
})
const cmdStr = JSON.stringify(cmd)
// console.log(`Publishing message to ${ipfsId}`)
console.log('E2E TEST: Sending Transaction command...')
const result = await this.sendRPC(ipfsId, cmdStr)
// console.log('result: ', result)
if (result.result.value.status && result.result.value.confirmations) {
console.log('E2E TEST: transaction test passed.')
return true
} else {
console.log('E2E TEST: transaction test failed.')
this.failTest()
}
} catch (err) {
console.error('Error in testTransaction()')
throw err
}
}
failTest () {
console.log('Exiting test program.')
process.exit()
}
}
const announceJsonLd = {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'e2e-test-client',
description: 'A test client runing e2e tests on ipfs-bch-wallet-service.',
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
}
}
module.exports = TestUtils
+4
View File
@@ -0,0 +1,4 @@
{
"uutAddr": "/ip4/127.0.0.1/tcp/5668/p2p/QmZSyLnRQWMBknVNjUroLQnrcmDnUVAUU4pMQ2LGhh6b9v",
"uutId": "QmZSyLnRQWMBknVNjUroLQnrcmDnUVAUU4pMQ2LGhh6b9v"
}
@@ -0,0 +1,162 @@
/*
Unit tests for the jwt-bch-lib and fullstack-jwt.js adapter library.
*/
const assert = require('chai').assert
const sinon = require('sinon')
const FullStackJWT = require('../../../src/adapters/fullstack-jwt')
describe('#FullStackJWT', () => {
let sandbox
let uut
beforeEach(() => {
sandbox = sinon.createSandbox()
const localConfig = {
authServer: 'someserver',
apiServer: 'someserver',
login: 'somelogin',
password: 'somepassword'
}
uut = new FullStackJWT(localConfig)
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if auth server is not specified', () => {
try {
uut = new FullStackJWT()
assert.fail('Unexpected code path')
console.log(uut) // For linting.
} catch (err) {
assert.include(
err.message,
'Must pass a url for the AUTH server when instantiating FullStackJWT class.'
)
}
})
it('should throw an error if api server is not specified', () => {
try {
const localConfig = {
authServer: 'someserver'
}
uut = new FullStackJWT(localConfig)
assert.fail('Unexpected code path')
console.log(uut) // For linting.
} catch (err) {
assert.include(
err.message,
'Must pass a url for the API server when instantiating FullStackJWT class.'
)
}
})
it('should throw an error if login is not specified', () => {
try {
const localConfig = {
authServer: 'someserver',
apiServer: 'someserver'
}
uut = new FullStackJWT(localConfig)
assert.fail('Unexpected code path')
console.log(uut) // For linting.
} catch (err) {
assert.include(
err.message,
'Must pass a FullStack.cash login (email) instantiating FullStackJWT class.'
)
}
})
it('should throw an error if login is not specified', () => {
try {
const localConfig = {
authServer: 'someserver',
apiServer: 'someserver',
login: 'somelogin'
}
uut = new FullStackJWT(localConfig)
assert.fail('Unexpected code path')
console.log(uut) // For linting.
} catch (err) {
assert.include(
err.message,
'Must pass a FullStack.cash account password when instantiating FullStackJWT class.'
)
}
})
})
describe('#getJWT', () => {
it('should return the JWT token', async () => {
// Mock dependencies to force a code path.
sandbox.stub(uut.jwtLib, 'register').resolves({})
uut.jwtLib.userData.apiToken = 'abc123'
sandbox.stub(uut.jwtLib, 'validateApiToken').resolves({ isValid: true })
const result = await uut.getJWT()
// console.log('result: ', result)
assert.equal(result, 'abc123')
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut.jwtLib, 'register').rejects(new Error('test error'))
await uut.getJWT()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should throw an error if user does not have a JWT', async () => {
try {
// Mock dependencies to force a code path.
sandbox.stub(uut.jwtLib, 'register').resolves({})
await uut.getJWT()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err.message: ', err.message)
assert.include(err.message, 'This account does not have a JWT')
}
})
it('should retrieve a new JWT token if the old one invalid', async () => {
// Mock dependencies to force a code path.
sandbox.stub(uut.jwtLib, 'register').resolves({})
uut.jwtLib.userData.apiToken = 'abc123'
uut.jwtLib.userData.apiLevel = 30
sandbox.stub(uut.jwtLib, 'validateApiToken').resolves({ isValid: false })
sandbox.stub(uut.jwtLib, 'getApiToken').resolves('xyz789')
const result = await uut.getJWT()
// console.log('result: ', result)
assert.equal(result, 'xyz789')
})
})
describe('#instanceBchjs', () => {
it('should return an instance of bch-js', () => {
const result = uut.instanceBchjs()
// console.log('result: ', result)
assert.property(result, 'restURL')
})
})
})
+16 -1
View File
@@ -15,7 +15,8 @@ describe('#IPFS', () => {
beforeEach(() => {
const ipfs = IPFSMock.create()
uut = new IPFSCoordAdapter({ ipfs })
const bchjs = {}
uut = new IPFSCoordAdapter({ ipfs, bchjs })
sandbox = sinon.createSandbox()
})
@@ -35,6 +36,20 @@ describe('#IPFS', () => {
)
}
})
it('should throw an error if bchjs instance is not included', () => {
try {
const ipfs = IPFSMock.create()
uut = new IPFSCoordAdapter({ ipfs })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of bch-js must be passed when instantiating ipfs-coord.'
)
}
})
})
describe('#start', () => {
+16 -2
View File
@@ -22,12 +22,26 @@ describe('#IPFS-adapter-index', () => {
afterEach(() => sandbox.restore())
describe('#start', () => {
it('should throw error if bch-js is not passed', async () => {
try {
await uut.start()
assert.fail('Unexpected code path.')
} catch (err) {
// console.log(err)
assert.include(
err.message,
'Instance of bch-js must be passed when instantiating IPFS adapter.'
)
}
})
it('should return a promise that resolves into an instance of IPFS.', async () => {
// Mock dependencies.
uut.ipfsAdapter = new IPFSMock()
uut.IpfsCoordAdapter = IPFSCoordMock
const result = await uut.start()
const result = await uut.start({ bchjs: {} })
assert.equal(result, true)
})
@@ -37,7 +51,7 @@ describe('#IPFS-adapter-index', () => {
// Force an error
sandbox.stub(uut.ipfsAdapter, 'start').rejects(new Error('test error'))
await uut.start()
await uut.start({ bchjs: {} })
assert.fail('Unexpected code path.')
} catch (err) {
+17 -1
View File
@@ -3,7 +3,7 @@
*/
// Public npm libraries
// const assert = require('chai').assert
const assert = require('chai').assert
const sinon = require('sinon')
const adapters = require('../../../src/adapters')
@@ -23,6 +23,8 @@ describe('#Controllers', () => {
it('should attach the controllers', async () => {
// mock IPFS
sandbox.stub(adapters.ipfs, 'start').resolves({})
sandbox.stub(adapters.fullStackJwt, 'getJWT').resolves({})
sandbox.stub(adapters.fullStackJwt, 'instanceBchjs').resolves({})
adapters.ipfs.ipfsCoordAdapter = {
attachRPCRouter: () => {}
}
@@ -33,5 +35,19 @@ describe('#Controllers', () => {
await attachControllers(app)
})
it('should catch and throw errors', async () => {
try {
// Force an error
sandbox
.stub(adapters.fullStackJwt, 'getJWT')
.rejects(new Error('test error'))
await attachControllers()
} catch (err) {
// console.log('err.message: ', err.message)
assert.include(err.message, 'test error')
}
})
})
})
@@ -69,7 +69,7 @@ describe('#rate-limit', () => {
assert.equal(error.status, 429)
assert.include(
error.message,
'Too many requests, please try again later.'
'Too many requests, please slow down your requests.'
)
}
})
@@ -101,7 +101,7 @@ describe('#rate-limit', () => {
} catch (error) {
assert.include(
error.message,
'Too many requests, please try again later.'
'Too many requests, please slow down your requests.'
)
}
})
@@ -0,0 +1,423 @@
/*
Unit tests for the json-rpc/auth/index.js file.
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
const sinon = require('sinon')
const assert = require('chai').assert
const { v4: uid } = require('uuid')
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries
const BCHRPC = require('../../../../src/controllers/json-rpc/bch')
const RateLimit = require('../../../../src/controllers/json-rpc/rate-limit')
const adapters = require('../../mocks/adapters')
const UseCasesMock = require('../../mocks/use-cases')
describe('#BCHRPC', () => {
let uut
let sandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
const useCases = new UseCasesMock()
uut = new BCHRPC({ adapters, useCases })
uut.rateLimit = new RateLimit({ max: 100 })
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new BCHRPC()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating BCH JSON RPC Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new BCHRPC({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating BCH JSON RPC Controller.'
)
}
})
})
describe('#bchRouter', () => {
it('should route to the transactions method', async () => {
// Mock dependencies
sandbox.stub(uut, 'transactions').resolves(true)
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const txCall = jsonrpc.request(id, 'bch', {
endpoint: 'transactions'
})
const jsonStr = JSON.stringify(txCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result, true)
})
it('should route to the balance method', async () => {
// Mock dependencies
sandbox.stub(uut, 'balance').resolves(true)
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const txCall = jsonrpc.request(id, 'bch', {
endpoint: 'balance'
})
const jsonStr = JSON.stringify(txCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result, true)
})
it('should route to the utxos method', async () => {
// Mock dependencies
sandbox.stub(uut, 'utxos').resolves(true)
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const txCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxos'
})
const jsonStr = JSON.stringify(txCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result, true)
})
it('should route to the broadcast method', async () => {
// Mock dependencies
sandbox.stub(uut, 'broadcast').resolves(true)
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'broadcast'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result, true)
})
it('should route to the transaction method', async () => {
// Mock dependencies
sandbox.stub(uut, 'transaction').resolves(true)
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'transaction'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result, true)
})
it('should return 500 status on routing issue', async () => {
// Mock dependencies
sandbox.stub(uut, 'transactions').rejects(new Error('test error'))
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const txCall = jsonrpc.request(id, 'bch', {
endpoint: 'transactions'
})
const jsonStr = JSON.stringify(txCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result.success, false)
assert.equal(result.status, 500)
assert.equal(result.message, 'test error')
assert.equal(result.endpoint, 'transactions')
})
})
describe('#transactions', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox
.stub(uut.bchjs.Electrumx, 'transactions')
.resolves({ success: true })
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const txCall = jsonrpc.request(id, 'bch', {
endpoint: 'transactions',
addresses: 'testAddr'
})
const jsonStr = JSON.stringify(txCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.transactions(rpcData)
// console.log('response: ', response)
assert.equal(response.success, true)
assert.equal(response.status, 200)
})
it('should return an error for invalid address', async () => {
// Force an error
sandbox
.stub(uut.bchjs.Electrumx, 'transactions')
.rejects(new Error('Invalid address'))
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const txCall = jsonrpc.request(id, 'bch', {
endpoint: 'transactions',
addresses: 'testAddr'
})
const jsonStr = JSON.stringify(txCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.transactions(rpcData)
// console.log('response: ', response)
assert.equal(response.success, false)
assert.equal(response.status, 422)
assert.equal(response.message, 'Invalid address')
assert.equal(response.endpoint, 'transactions')
})
})
describe('#balance', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox.stub(uut.bchjs.Electrumx, 'balance').resolves({ success: true })
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'balance',
addresses: 'testAddr'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.balance(rpcData)
// console.log('response: ', response)
assert.equal(response.success, true)
assert.equal(response.status, 200)
})
it('should return an error for invalid address', async () => {
// Force an error
sandbox
.stub(uut.bchjs.Electrumx, 'balance')
.rejects(new Error('Invalid address'))
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'balance',
addresses: 'testAddr'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.balance(rpcData)
// console.log('response: ', response)
assert.equal(response.success, false)
assert.equal(response.status, 422)
assert.equal(response.message, 'Invalid address')
assert.equal(response.endpoint, 'balance')
})
})
describe('#utxos', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox.stub(uut.bchjs.Utxo, 'get').resolves({ success: true })
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxos',
addresses: 'testAddr'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.utxos(rpcData)
// console.log('response: ', response)
assert.equal(response.success, true)
assert.equal(response.status, 200)
})
it('should return an error for invalid address', async () => {
// Force an error
sandbox.stub(uut.bchjs.Utxo, 'get').rejects(new Error('Invalid address'))
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxos',
addresses: 'testAddr'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.utxos(rpcData)
// console.log('response: ', response)
assert.equal(response.success, false)
assert.equal(response.status, 422)
assert.equal(response.message, 'Invalid address')
assert.equal(response.endpoint, 'utxos')
})
})
describe('#broadcast', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox
.stub(uut.bchjs.RawTransactions, 'sendRawTransaction')
.resolves({ success: true })
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'broadcast',
hex: 'testData'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.broadcast(rpcData)
// console.log('response: ', response)
assert.equal(response.success, true)
assert.equal(response.status, 200)
})
it('should return an error for invalid input', async () => {
// Force an error
sandbox
.stub(uut.bchjs.RawTransactions, 'sendRawTransaction')
.rejects(new Error('Invalid data'))
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'broadcast',
hex: 'testHex'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.broadcast(rpcData)
// console.log('response: ', response)
assert.equal(response.success, false)
assert.equal(response.status, 422)
assert.equal(response.message, 'Invalid data')
assert.equal(response.endpoint, 'broadcast')
})
})
describe('#transaction', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox.stub(uut.bchjs.Transaction, 'get').resolves({ success: true })
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'transaction',
txid: 'testData'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.transaction(rpcData)
// console.log('response: ', response)
assert.equal(response.success, true)
assert.equal(response.status, 200)
})
it('should return an error for invalid input', async () => {
// Force an error
sandbox
.stub(uut.bchjs.Transaction, 'get')
.rejects(new Error('Invalid data'))
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'transaction',
txid: 'testTxid'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.transaction(rpcData)
// console.log('response: ', response)
assert.equal(response.success, false)
assert.equal(response.status, 422)
assert.equal(response.message, 'Invalid data')
assert.equal(response.endpoint, 'transaction')
})
})
})
@@ -179,5 +179,33 @@ 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: 'transactions'
})
const jsonStr = JSON.stringify(userCall, null, 2)
// Mock the controller.
sandbox.stub(uut.bchController, 'bchRouter').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)
})
})
})
@@ -0,0 +1,262 @@
/*
Unit tests for the REST API handler for the /users endpoints.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
// Local support libraries
const adapters = require('../../../mocks/adapters')
const UseCasesMock = require('../../../mocks/use-cases')
// const app = require('../../../mocks/app-mock')
const BCHRESTController = require('../../../../../src/controllers/rest-api/bch/controller')
let uut
let sandbox
let ctx
const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#BCH-REST-Router', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new BCHRESTController({ 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 BCHRESTController()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating BCH REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new BCHRESTController({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating BCH REST Controller.'
)
}
})
})
describe('#transactions', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox
.stub(uut.bchjs.Electrumx, 'transactions')
.resolves({ success: true })
ctx.request.body = {
addresses: 'testAddr'
}
await uut.transactions(ctx)
// console.log('ctx.body: ', ctx.body)
assert.equal(ctx.body.success, true)
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox
.stub(uut.bchjs.Electrumx, 'transactions')
.rejects(new Error('test error'))
ctx.request.body = {
addresses: 'testAddr'
}
await uut.transactions(ctx)
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
})
describe('#balance', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox.stub(uut.bchjs.Electrumx, 'balance').resolves({ success: true })
ctx.request.body = {
addresses: 'testAddr'
}
await uut.balance(ctx)
// console.log('ctx.body: ', ctx.body)
assert.equal(ctx.body.success, true)
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox
.stub(uut.bchjs.Electrumx, 'balance')
.rejects(new Error('test error'))
ctx.request.body = {
addresses: 'testAddr'
}
await uut.balance(ctx)
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
})
describe('#utxos', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox.stub(uut.bchjs.Utxo, 'get').resolves({ success: true })
ctx.request.body = {
addresses: 'testAddr'
}
await uut.utxos(ctx)
// console.log('ctx.body: ', ctx.body)
assert.equal(ctx.body.success, true)
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut.bchjs.Utxo, 'get').rejects(new Error('test error'))
ctx.request.body = {
addresses: 'testAddr'
}
await uut.utxos(ctx)
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
})
describe('#broadcast', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox
.stub(uut.bchjs.RawTransactions, 'sendRawTransaction')
.resolves({ success: true })
ctx.request.body = {
hex: 'testData'
}
await uut.broadcast(ctx)
// console.log('ctx.body: ', ctx.body)
assert.equal(ctx.body.success, true)
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox
.stub(uut.bchjs.RawTransactions, 'sendRawTransaction')
.rejects(new Error('test error'))
ctx.request.body = {
hex: 'testData'
}
await uut.broadcast(ctx)
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
})
describe('#transaction', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
sandbox.stub(uut.bchjs.Transaction, 'get').resolves({ success: true })
ctx.request.body = {
txid: 'testData'
}
await uut.transaction(ctx)
// console.log('ctx.body: ', ctx.body)
assert.equal(ctx.body.success, true)
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox
.stub(uut.bchjs.Transaction, 'get')
.rejects(new Error('test error'))
ctx.request.body = {
txid: 'testData'
}
await uut.transaction(ctx)
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
})
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,78 @@
/*
Unit tests for the REST API handler for the /bch endpoints.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
// Local support libraries
const adapters = require('../../../mocks/adapters')
const UseCasesMock = require('../../../mocks/use-cases')
// const app = require('../../../mocks/app-mock')
const BCHRouter = require('../../../../../src/controllers/rest-api/bch')
let uut
let sandbox
// let ctx
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#BCH-REST-Router', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new BCHRouter({ 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 BCHRouter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating BCH REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new BCHRouter({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating BCH 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.'
)
}
})
})
})
+17 -1
View File
@@ -47,4 +47,20 @@ const localdb = {
}
}
module.exports = { ipfs, localdb }
const bchjs = {
Electrumx: {
transactions: () => {},
balance: () => {}
},
Utxo: {
get: () => {}
},
Transaction: {
get: () => {}
},
RawTransactions: {
sendRawTransaction: () => {}
}
}
module.exports = { ipfs, localdb, bchjs }
+6
View File
@@ -3,6 +3,12 @@
*/
class IPFSCoord {
constructor () {
this.ipfs = {
async start () {}
}
}
async isReady () {
return true
}