diff --git a/README.md b/README.md index f1b352f..9713611 100644 --- a/README.md +++ b/README.md @@ -2,48 +2,50 @@ [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) -This application crawls the Bitcoin Cash (BCH) blockchain and indexes all the SLP token transactions. This code base is intended to be a replacement for [SLPDB](https://github.com/Permissionless-Software-Foundation/docker-slpdb). The work is based on [this report](https://gist.github.com/christroutner/77c46f1fa9adaf593074d41a508a6401) and the work was funded by [this Flipstarter](https://flipstarter.fullstack.cash/). +This application crawls the Bitcoin Cash (BCH) blockchain and indexes SLP token transactions. This code base is intended to be a replacement for [SLPDB](https://github.com/Permissionless-Software-Foundation/docker-slpdb). The work is based on [this report](https://gist.github.com/christroutner/77c46f1fa9adaf593074d41a508a6401) and the work was funded by [this Flipstarter](https://flipstarter.fullstack.cash/). -This indexer is one part of a collection of blockchain infrastructure. To understand how all the pieces fit together, read the [Cash Stack Documentation](https://permissionless-software-foundation.github.io/cashstack.info/). +This indexer is one part of a collection of blockchain infrastructure. To understand how all the pieces fit together, read the [Cash Stack Documentation](https://cashstack.info). -## Development Status +If you have question or need help, ask in the [community support Telegram channel](https://t.me/psf_slp). -Current status: **Beta** +## Videos -This project is using conventional development milestones: -- Alpha = Under active development. Bugs are expected, things are expected to break. -- Beta = Some bugs still exist, but code is mature enough for careful roll-out into production. -- Production = Code has been heavily tested and code commits have slowed in frequency. App is ready for normal operators. +- [Installing the psf-slp-indexer](https://youtu.be/5gF4ON9lRHI) +- [Additional Infrastructure Videos](https://psfoundation.cash/video/) in the 'Dev Ops & Infrastructure' section. -Regular status updates will be reported at the [bi-weekly PSF Technical Steering Committee meetings](https://github.com/Permissionless-Software-Foundation/TSC/issues). Updates will also be reported in [this Telegram channel](https://t.me/psf_slp). +## Installation and Usage -**See the [developer documentation](./dev-docs) for more information.** +This software is intended to be run inside a Docker container, controlled with Docker Compose, on a Ubuntu 20 OS. -### Videos - -- [Installing the psf-slp-indexer](https://youtu.be/5gF4ON9lRHI) -- [Additional Infrastructure Videos](https://psfoundation.cash/video/) in the 'Dev Ops & Infrastructure' section. +- Enter the `production/docker` directory. +- Build the image with `docker-compose build --no-cache` +- Ensure you have a BCHN full node running and fully synced. [docker-bchn](https://github.com/Permissionless-Software-Foundation/docker-bchn) is recommended for running a full node. +- Start the indexer with `docker-compose up -d` ## Features -- Written in [standard JavaScript](https://www.npmjs.com/package/standard), using the [Clean Architecture](https://troutsblog.com/blog/clean-architecture) design pattern. +- Written in [standard JavaScript](https://www.npmjs.com/package/standard), using the [Clean Architecture](https://christroutner.github.io/trouts-blog/blog/clean-architecture) design pattern. - 100% unit test coverage. This allows for operational reliability and easy code collaboration. -- [GPLv2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) Licensed to encourage wide adoption and free use throughout the BCH ecosystem. +- [GPLv2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) Licensed to encourage wide adoption and free use throughout the crypto ecosystem. - [LevelDB](https://github.com/google/leveldb) used for fast, efficient indexing and querying. - Drastically reduced memory usage, compared to SLPDB. - Fast indexing using transaction maps. - Docker container for easy deployment and horizontal scaling. -## Requirements +## Development Environment + +**See the [developer documentation](./dev-docs) for more information.** + +### Requirements - Ubuntu Linux OS v20.4+ -- node **^14.17.0** -- npm **^7.13.0** +- node **^16.17.0** +- npm **^8.15.0** -## Installation +### Dev Environment Installation -Customize the [slp-indexer.sh](./slp-indexer.sh) bash shell script to point to the a BCH full node with the standard JSON RPC. [docker-bchn](https://github.com/Permissionless-Software-Foundation/docker-bchn) is recommended. +Customize the [slp-indexer.sh](./slp-indexer.sh) bash shell script to point to the a BCH full node with the standard JSON RPC. [docker-bchn](https://github.com/Permissionless-Software-Foundation/docker-bchn) is recommended for running a full node. ``` git clone https://github.com/Permissionless-Software-Foundation/psf-slp-indexer diff --git a/bin/server.js b/bin/server.js index e6c4ea9..11140ea 100644 --- a/bin/server.js +++ b/bin/server.js @@ -13,28 +13,20 @@ import Koa from 'koa' import bodyParser from 'koa-bodyparser' import convert from 'koa-convert' import logger from 'koa-logger' -import mongoose from 'mongoose' import session from 'koa-generic-session' -import passport from 'koa-passport' import mount from 'koa-mount' import serve from 'koa-static' import cors from 'kcors' // Local libraries 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 wlogger from '../src/adapters/wlogger.js' import Controllers from '../src/controllers/index.js' -import { applyPassportMods } from '../config/passport.js' class Server { constructor () { // Encapsulate dependencies - this.adminLib = new AdminLib() this.controllers = new Controllers() - this.mongoose = mongoose this.config = config this.process = process } @@ -45,17 +37,6 @@ 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 - }) - console.log(`Starting environment: ${this.config.env}`) console.log(`Debug level: ${this.config.debugLevel}`) @@ -64,7 +45,6 @@ class Server { app.use(convert(logger())) app.use(bodyParser()) app.use(session()) - app.use(errorMiddleware()) // Used to generate the docs. app.use(mount('/', serve(`${process.cwd()}/docs`))) @@ -72,12 +52,6 @@ class Server { // Mount the page for displaying logs. app.use(mount('/logs', serve(`${process.cwd()}/config/logs`))) - // User Authentication - // require('../config/passport') - applyPassportMods(passport) - app.use(passport.initialize()) - app.use(passport.session()) - // Enable CORS for testing // THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION // Dev Note: This line must come BEFORE controllers.attachRESTControllers() @@ -102,10 +76,6 @@ 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.') - // Attach the other IPFS controllers. // Skip if this is a test environment. if (this.config.env !== 'test') { diff --git a/config/passport.js b/config/passport.js deleted file mode 100644 index f28c540..0000000 --- a/config/passport.js +++ /dev/null @@ -1,59 +0,0 @@ -// import passport from 'koa-passport'; -import User from '../src/adapters/localdb/models/users.js' -import Strategy from 'passport-local' - -async function passportCallback (email, password, done) { - try { - const user = await User.findOne({ email }) - if (!user) { - return done(null, false) - } - - try { - const isMatch = await user.validatePassword(password) - - if (!isMatch) { - return done(null, false) - } - - done(null, user) - } catch (err) { - done(err) - } - } catch (err) { - return done(err) - } -} - -function applyPassportMods (passport) { - passport.serializeUser((user, done) => { - done(null, user.id) - }) - - passport.deserializeUser(async (id, done) => { - try { - const user = await User.findById(id, '-password') - done(null, user) - } catch (err) { - done(err) - } - }) - - passport.use( - 'local', - new Strategy( - { - usernameField: 'email', - passwordField: 'password' - }, - passportCallback - ) - ) - - return true -} - -// For testing -// export default { passport, passportCallback }; -// export default applyPassportMods -export { applyPassportMods, passportCallback } diff --git a/package.json b/package.json index 3e445fe..f9c4bb3 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,14 @@ { "name": "psf-slp-indexer", - "version": "1.0.0", + "version": "3.0.0", "description": "Indexer for validating SLP transactions. Uses LevelDB.", "main": "index.js", "type": "module", "scripts": { "start": "node --max_old_space_size=16000 index.js", "test": "npm run test:all", - "test:all": "export SVC_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/", + "test:all": "export SVC_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit/", "test:unit": "export SVC_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit/", - "test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 15000 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", diff --git a/production/docker/Dockerfile b/production/docker/Dockerfile index 6a427cb..d15233c 100644 --- a/production/docker/Dockerfile +++ b/production/docker/Dockerfile @@ -77,6 +77,7 @@ EXPOSE 5010 # Start the application. #COPY start-production.sh start-production.sh +VOLUME start-production.sh CMD ["./start-production.sh"] #CMD ["npm", "start"] diff --git a/production/docker/docker-compose.yml b/production/docker/docker-compose.yml index 290534b..a59f049 100644 --- a/production/docker/docker-compose.yml +++ b/production/docker/docker-compose.yml @@ -3,16 +3,6 @@ version: '3.9' services: - mongo-slp-indexer: - image: mongo:4.2.0 - container_name: mongo-slp-indexer - ports: - - '5555:27017' # : - volumes: - - ../data/database:/data/db - command: mongod --logpath=/dev/null # -- quiet - restart: always - slp-indexer: build: . container_name: slp-indexer @@ -22,11 +12,12 @@ services: max-size: '10m' max-file: '10' #mem_limit: 500mb - links: - - mongo-slp-indexer + #links: + # - mongo-slp-indexer ports: - '5010:5010' # : volumes: - ../data/ipfsdata:/home/safeuser/psf-slp-indexer/.ipfsdata - ../data/leveldb:/home/safeuser/psf-slp-indexer/leveldb + - ./start-production.sh:/home/safeuser/psf-slp-indexer/start-production.sh restart: always diff --git a/src/adapters/admin.js b/src/adapters/admin.js deleted file mode 100644 index 8974b82..0000000 --- a/src/adapters/admin.js +++ /dev/null @@ -1,182 +0,0 @@ -/* - A library for working with the system admin user. This is an auto-generated - account with 'admin' privledges, for interacting with private APIs. - - The admin account is regenerated every time the server is started. This improves - security by not having stale passwords for the account. The login information - and JWT token for the admin account is written to a JSON file, for easy - retrieval by other apps running on the server that may need admin privledges - to access private APIs. - - This library is really more of an Adapter to the internal systems default - admin user. It's not really a central Entity, which is why this library lives - in the Adapter directory. -*/ - -// Global npm libraries -import axios from 'axios' -import mongoose from 'mongoose' - -// Local libraries -import User from '../adapters/localdb/models/users.js' -import config from '../../config/index.js' -import JsonFiles from '../adapters/json-files.js' - -// Hack to get __dirname back. -// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/ -import * as url from 'url' -const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) - -const jsonFiles = new JsonFiles() - -const JSON_FILE = `system-user-${config.env}.json` -const JSON_PATH = `${__dirname.toString()}../../config/${JSON_FILE}` - -const LOCALHOST = `http://localhost:${config.port}` -const context = {} - -let _this -class Admin { - constructor () { - this.axios = axios - this.User = User - this.config = config - this.jsonFiles = jsonFiles - this.context = context - - _this = this - } - - // Create the first user in the system. A 'admin' level system user that is - // 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 result = await _this.axios.request(options) - // console.log('admin.data: ', result.data) - - context.email = result.data.user.email - context.id = result.data.user._id - context.token = result.data.token - - // Get the mongoDB entry - const user = await _this.User.findById(context.id) - - // 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) - - 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 - } - } - } - - async deleteExistingSystemUser () { - try { - mongoose.Promise = global.Promise - mongoose.set('useCreateIndex', true) // Stop deprecation warning. - - await mongoose.connect(config.database, { - useNewUrlParser: true, - useUnifiedTopology: true - }) - - await _this.User.deleteOne({ email: 'system@system.com' }) - } catch (err) { - console.log('Error in admin.js/deleteExistingSystemUser()') - throw err - } - } - - async loginAdmin () { - // console.log(`loginAdmin() running.`) - let existingUser - - try { - // Read the exising file - existingUser = await _this.jsonFiles.readJSON(JSON_PATH) - // console.log(`existingUser: ${JSON.stringify(existingUser, null, 2)}`) - - // Log in as the user. - const options = { - method: 'POST', - url: `${LOCALHOST}/auth`, - headers: { - Accept: 'application/json' - }, - data: { - email: 'system@system.com', - password: existingUser.password - } - } - const result = await _this.axios.request(options) - // console.log(`result1: ${JSON.stringify(result, null, 2)}`) - return result - } catch (err) { - console.error('Error in admin.js/loginAdmin().') - - // console.error(`existingUser: ${JSON.stringify(existingUser, null, 2)}`) - - throw err - } - } - - _randomString (length) { - let text = '' - const possible = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' - for (let i = 0; i < length; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)) - } - return text - } -} - -export default Admin diff --git a/src/adapters/index.js b/src/adapters/index.js index b9c9239..3ae59fb 100644 --- a/src/adapters/index.js +++ b/src/adapters/index.js @@ -10,9 +10,8 @@ import BCHJS from '@psf/bch-js' // Load individual adapter libraries. import IPFSAdapter from './ipfs/index.js' -import LocalDB from './localdb/index.js' +// import LocalDB from './localdb/index.js' import LogsAPI from './logapi.js' -import Passport from './passport.js' import Nodemailer from './nodemailer.js' // const { wlogger } = require('./wlogger') @@ -26,9 +25,7 @@ class Adapters { constructor (localConfig = {}) { // Encapsulate dependencies this.ipfs = new IPFSAdapter() - this.localdb = new LocalDB() this.logapi = new LogsAPI() - this.passport = new Passport() this.nodemailer = new Nodemailer() this.jsonFiles = new JSONFiles() this.bchjs = new BCHJS() diff --git a/src/adapters/localdb/index.js b/src/adapters/localdb/index.js deleted file mode 100644 index be6b2cf..0000000 --- a/src/adapters/localdb/index.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - This library encapsulates code concerned with MongoDB and Mongoose models. -*/ - -// Load Mongoose models. -import Users from './models/users.js' - -class LocalDB { - constructor () { - // Encapsulate dependencies - this.Users = Users - } -} - -export default LocalDB diff --git a/src/adapters/localdb/models/users.js b/src/adapters/localdb/models/users.js deleted file mode 100644 index 8ed2e13..0000000 --- a/src/adapters/localdb/models/users.js +++ /dev/null @@ -1,57 +0,0 @@ -// Global npm libraries -import mongoose from 'mongoose' -import bcrypt from 'bcryptjs' -import jwt from 'jsonwebtoken' - -// Local libraries -import config from '../../../../config/index.js' - -const User = new mongoose.Schema({ - type: { type: String, default: 'user' }, - name: { type: String }, - username: { type: String }, - password: { type: String, required: true }, - email: { - type: String, - required: true, - unique: true - } -}) - -// Before saving, convert the password to a hash. -User.pre('save', async function preSave (next) { - const user = this - - if (!user.isModified('password')) { - return next() - } - - const salt = await bcrypt.genSalt(10) - const hash = await bcrypt.hash(user.password, salt) - - user.password = hash - - next(null) -}) - -// Validate the password by comparing to the saved hash. -User.methods.validatePassword = async function validatePassword (password) { - const user = this - - const isMatch = await bcrypt.compare(password, user.password) - - return isMatch -} - -// Generate a JWT token. -User.methods.generateToken = function generateToken () { - const user = this - - const token = jwt.sign({ id: user.id }, config.token) - // console.log(`config.token: ${config.token}`) - // console.log(`generated token: ${token}`) - return token -} - -// export default mongoose.model('user', User) -export default mongoose.model('user', User) diff --git a/src/adapters/passport.js b/src/adapters/passport.js deleted file mode 100644 index 903816a..0000000 --- a/src/adapters/passport.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - koa-passport is an authorization library used for different authentication schemes. -*/ - -import passport from 'koa-passport' - -let _this -class Passport { - constructor () { - _this = this - this.passport = passport - } - - async authUser (ctx) { - return new Promise((resolve, reject) => { - try { - if (!ctx) throw new Error('ctx is required') - - _this.passport.authenticate('local', (err, user) => { - try { - if (err) throw err - - resolve(user) - } catch (err) { - return reject(err) - } - })(ctx, null) - } catch (err) { - return reject(err) - } - }) - } -} - -export default Passport diff --git a/src/controllers/json-rpc/auth/index.js b/src/controllers/json-rpc/auth/index.js deleted file mode 100644 index 1e40cdf..0000000 --- a/src/controllers/json-rpc/auth/index.js +++ /dev/null @@ -1,177 +0,0 @@ -/* - This is the JSON RPC router for the users API -*/ - -// Public npm libraries -import jsonrpc from 'jsonrpc-lite' - -// Local libraries -// const AuthLib = require('../../lib/auth') -// const UserLib = require('../../../use-cases/user') -import wlogger from '../../../adapters/wlogger.js' - -import RateLimit from '../rate-limit.js' - -class AuthRPC { - constructor (localConfig = {}) { - // Dependency Injection. - this.adapters = localConfig.adapters - if (!this.adapters) { - throw new Error( - 'Instance of Adapters library required when instantiating Auth JSON RPC Controller.' - ) - } - this.useCases = localConfig.useCases - if (!this.useCases) { - throw new Error( - 'Instance of Use Cases library required when instantiating Auth JSON RPC Controller.' - ) - } - - // Encapsulate dependencies - // this.authLib = new AuthLib() - this.jsonrpc = jsonrpc - this.userLib = this.useCases.user - this.rateLimit = new RateLimit() - } - - // 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 authRouter (rpcData) { - let endpoint = 'unknown' - - try { - // console.log('authRouter rpcData: ', rpcData) - - endpoint = rpcData.payload.params.endpoint - - // Route the call based on the requested endpoint. - switch (endpoint) { - case 'authUser': - await this.rateLimit.limiter(rpcData.from) - return await this.authUser(rpcData) - } - } catch (err) { - console.error('Error in AuthRPC/authRouter()') - // throw err - - return { - success: false, - status: 500, - message: err.message, - endpoint - } - } - } - - /** - * @api {JSON} /auth Get JWT Token - * @apiPermission public - * @apiName AuthUser - * @apiGroup JSON Auth - * - * @apiExample Example usage: - * {"jsonrpc":"2.0","id":"556","method":"auth","params":{ "endpoint": "authUser", "login": "test555@test.com", "password": "password"}} - * - * @apiParam {String} login Email(required). - * @apiParam {String} password Password(required). - * @apiParam {string} endpoint (required) - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} user.type User type (admin or user) - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.email User email - * @apiSuccess {String} token JWT. - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "jsonrpc": "2.0", - * "id": "556", - * "result": { - * "method": "auth", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "endpoint": "authUser", - * "userId": "607de52d426f3d3148b3a467", - * "userType": "user", - * "userName": "testy tester", - * "userEmail": "test555@test.com", - * "apiToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwN2RlNTJkNDI2ZjNkMzE0OGIzYTQ2NyIsImlhdCI6MTYxODg2NTcwM30.acGe5ZiBAAcbOcPQDIhvc3z0KjnuYZd1Y5pJJJC9mJQ", - * "status": 200, - * "success": true, - * "message": "" - * } - * } - *} - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "jsonrpc": "2.0", - * "id": "556", - * "result": { - * "method": "auth", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "success": false, - * "status": 422, - * "message": "User not found", - * "endpoint": "authUser" - * } - * } - * } - */ - async authUser (rpcData) { - try { - // console.log('authUser rpcData: ', rpcData) - - if (!rpcData.payload.params.login) { - throw new Error('login must be specified') - } - if (!rpcData.payload.params.password) { - throw new Error('password must be specified') - } - - const login = rpcData.payload.params.login - const password = rpcData.payload.params.password - - const user = await this.userLib.authUser(login, password) - // console.log('user: ', user) - - const token = user.generateToken() - - const response = { - endpoint: 'authUser', - userId: user._id, - userType: user.type, - userName: user.name, - userEmail: user.email, - apiToken: token, - status: 200, - success: true, - message: '' - } - - return response - } catch (err) { - // console.error('Error in authUser()') - wlogger.error('Error in authUser(): ', err) - // throw err - - // Return an error response - return { - success: false, - status: 422, - message: err.message, - endpoint: 'authUser' - } - } - } -} - -export default AuthRPC diff --git a/src/controllers/json-rpc/index.js b/src/controllers/json-rpc/index.js index cd997f7..2ad5315 100644 --- a/src/controllers/json-rpc/index.js +++ b/src/controllers/json-rpc/index.js @@ -8,8 +8,6 @@ import jsonrpc from 'jsonrpc-lite' // Local support libraries import wlogger from '../../adapters/wlogger.js' -import UserController from './users/index.js' -import AuthController from './auth/index.js' import AboutController from './about/index.js' let _this @@ -33,8 +31,6 @@ class JSONRPC { // Encapsulate dependencies this.ipfsCoord = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord this.jsonrpc = jsonrpc - this.userController = new UserController(localConfig) - this.authController = new AuthController(localConfig) this.aboutController = new AboutController() // Cache to store IDs of processed JSON RPC commands. Used to prevent @@ -102,12 +98,12 @@ class JSONRPC { // Route the command to the appropriate route handler. switch (parsedData.payload.method) { - case 'users': - retObj = await _this.userController.userRouter(parsedData) - break - case 'auth': - retObj = await _this.authController.authRouter(parsedData) - break + // case 'users': + // retObj = await _this.userController.userRouter(parsedData) + // break + // case 'auth': + // retObj = await _this.authController.authRouter(parsedData) + // break case 'about': retObj = await _this.aboutController.aboutRouter(parsedData) } diff --git a/src/controllers/json-rpc/users/index.js b/src/controllers/json-rpc/users/index.js deleted file mode 100644 index 55f6247..0000000 --- a/src/controllers/json-rpc/users/index.js +++ /dev/null @@ -1,537 +0,0 @@ -/* - This is the JSON RPC router for the users API -*/ - -// Public npm libraries -import jsonrpc from 'jsonrpc-lite' - -// Local libraries -// const UserLib = require('../../../use-cases/user') -import Validators from '../validators.js' - -import RateLimit from '../rate-limit.js' - -class UserRPC { - constructor (localConfig = {}) { - // Dependency Injection. - this.adapters = localConfig.adapters - if (!this.adapters) { - throw new Error( - 'Instance of Adapters library required when instantiating User JSON RPC Controller.' - ) - } - this.useCases = localConfig.useCases - if (!this.useCases) { - throw new Error( - 'Instance of Use Cases library required when instantiating User JSON RPC Controller.' - ) - } - - // Encapsulate dependencies - this.userLib = this.useCases.user - this.jsonrpc = jsonrpc - this.validators = new Validators(localConfig) - this.rateLimit = new RateLimit() - } - - // 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 userRouter (rpcData) { - let endpoint = 'unknown' - try { - // console.log('userRouter rpcData: ', rpcData) - - endpoint = rpcData.payload.params.endpoint - let user - - // Route the call based on the value of the method property. - switch (endpoint) { - case 'createUser': - await this.rateLimit.limiter(rpcData.from) - return await this.createUser(rpcData) - - case 'getAllUsers': - await this.validators.ensureUser(rpcData) - await this.rateLimit.limiter(rpcData.from) - return await this.getAll(rpcData) - - case 'getUser': - user = await this.validators.ensureUser(rpcData) - await this.rateLimit.limiter(rpcData.from) - return await this.getUser(rpcData, user) - - case 'updateUser': - user = await this.validators.ensureTargetUserOrAdmin(rpcData) - await this.rateLimit.limiter(rpcData.from) - return await this.updateUser(rpcData, user) - - case 'deleteUser': - user = await this.validators.ensureTargetUserOrAdmin(rpcData) - await this.rateLimit.limiter(rpcData.from) - return await this.deleteUser(rpcData, user) - } - } catch (err) { - console.error('Error in UsersRPC/rpcRouter()') - // throw err - - return { - success: false, - status: err.status || 500, - message: err.message, - endpoint - } - } - } - - /** - * @api {JSON} /users Create a new user - * @apiPermission public - * @apiName CreateUser - * @apiGroup JSON Users - * - * @apiExample Example usage: - * {"jsonrpc":"2.0","id":"555","method":"users","params":{ "endpoint": "createUser", "email": "test555@test.com", "name": "testy tester", "password": "password"}} - * - * @apiParam {String} email Email(required). - * @apiParam {String} password Password(required). - * @apiParam {String} name name or handle(optional). - * @apiParam {string} endpoint (required) - - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} user.type User type (admin or user) - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * @apiSuccess {String} users.email User email - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "jsonrpc": "2.0", - * "id": "555", - * "result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "userData": { - * "type": "user", - * "_id": "607dd3e6426f3d3148b3a466", - * "email": "test555@test.com", - * "name": "testy tester", - * "__v": 0 - * }, - * "token": "eyJhbGciOiJIUzI1NiIs1nR5cCI6IkpXVTJ9.eyJpZCI6IjYwN2RkM2U2NDI2ZjNkMzE0OGIzYTQ2NiIsImlhdCI6MTYxODg1ODk4Mn0.in4vzxDqqyCd7LpuhG3xlXeBqrJ5bp9GJPwhaoVzldI", - * "endpoint": "createUser", - * "success": true, - * "status": 200, - * "message": "" - * } - * } - *} - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "jsonrpc": "2.0", - * "id": "123", - * "result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "success": false, - * "status": 422, - * "message": "Unprocessable Entity", - * "endpoint": "getUser" - * } - * } - *} - */ - async createUser (rpcData) { - try { - // console.log('createUser rpcData: ', rpcData) - - const retObj = await this.userLib.createUser(rpcData.payload.params) - - // Add generic JSON RPC properties that every entry gets. - retObj.endpoint = 'createUser' - retObj.success = true - retObj.status = 200 - retObj.message = '' - - return retObj - } catch (err) { - // console.error('Error in createUser()') - // throw err - - // Return an error response - return { - success: false, - status: 422, - message: err.message, - endpoint: 'createUser' - } - } - } - - /** - * @api {JSON} /users Get all users - * @apiPermission public - * @apiName GetAllUsers - * @apiGroup JSON Users - * - * @apiExample Example usage: - * {"jsonrpc":"2.0","id":"555","method":"users","params":{ "endpoint": "getAllUsers", "apiToken": ""}} - * - * @apiParam {String} apiToken (required) - * @apiParam {string} endpoint (required) - * - * @apiSuccess {Object[]} users Array of user objects - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} user.type User type (admin or user) - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * @apiSuccess {String} users.email User email - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "users": [ - * { - * "type": "user", - * "_id": "6070bc6da931e73d4d9e108d", - * "email": "test678@test.com", - * "name": "testy tester", - * "__v": 0 - * } - * ], - * "endpoint": "getAllUsers", - * "success": true, - * "status": 200, - * "message": "" - * } - * } - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "jsonrpc": "2.0", - * "id": "123", - * "result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "success": false, - * "status": 422, - * "message": "", - * "endpoint": "getAllUsers" - * } - * } - */ - // Get all Users. - async getAll () { - try { - const users = await this.userLib.getAllUsers() - - return { - users, - endpoint: 'getAllUsers', - success: true, - status: 200, - message: '' - } - } catch (err) { - // console.error('Error in getAll()') - // throw err - - // Return an error response - return { - success: false, - status: 422, - message: err.message, - endpoint: 'getAllUsers' - } - } - } - - /** - * @api {JSON} /users Get a user - * @apiPermission public - * @apiName GetAUser - * @apiGroup JSON Users - * - * @apiExample Example usage: - * {"jsonrpc":"2.0","id":"123","method":"users","params":{ "endpoint": "getUser", "apiToken": "", "userId": "<_id>"}} - * - * @apiParam {String} apiToken (required) - * @apiParam {String} userId (required) - * @apiParam {string} endpoint (required) - * - * @apiSuccess {Object[]} users Array of user objects - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} user.type User type (admin or user) - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * @apiSuccess {String} users.email User email - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * result": { - * "jsonrpc": "2.0", - * "id": "123", - * "result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "user": { - * "type": "user", - * "_id": "607dd3e6426f3d3148b3a466", - * "email": "test555@test.com", - * "name": "testy tester", - * "__v": 0 - * }, - * "endpoint": "getUser", - * "success": true, - * "status": 200, - * "message": "" - * } - *} - * - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "jsonrpc": "2.0", - * "id": "123", - * "result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "success": false, - * "status": 422, - * "message": "Unprocessable Entity", - * "endpoint": "getUser" - * } - * } - * - */ - // Get a specific user. - async getUser (rpcData, userModel) { - try { - // console.log('getUser rpcData: ', rpcData) - - // Throw error if rpcData does not include 'userId' property for target user. - const userId = rpcData.payload.params.userId - - const user = await this.userLib.getUser({ id: userId }) - - return { - user, - endpoint: 'getUser', - success: true, - status: 200, - message: '' - } - } catch (err) { - // console.error('Error in getUser()') - // throw err - - // Return an error response - return { - success: false, - status: 422, - message: err.message, - endpoint: 'getUser' - } - } - } - - /** - * @api {JSON} /users Update a user - * @apiPermission public - * @apiName UpdateAUser - * @apiGroup JSON Users - * - * @apiExample Example usage: - * {"jsonrpc":"2.0","id":"123","method":"users","params":{ "endpoint": "updateUser", "apiToken": "", "userId": "<_id>", "name": "test999"}} - * - * @apiParam {String} apiToken (required) - * @apiParam {String} userId (required) - * @apiParam {string} endpoint (required) - * @apiParam {String} email Email(Optional). - * @apiParam {String} password Password(Optional). - * @apiParam {String} name name or handle(Optional). - * - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} user.type User type (admin or user) - * @apiSuccess {String} users.name Updated name - * @apiSuccess {String} users.username Updated username - * @apiSuccess {String} users.email Updated email - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * result": { - * "jsonrpc": "2.0", - * "id": "123", - * "result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "user": { - * "type": "user", - * "_id": "607dd3e6426f3d3148b3a466", - * "email": "test555@test.com", - * "name": "test001", - * "__v": 0 - * }, - * "endpoint": "updateUser", - * "success": true, - * "status": 200, - * "message": "" - * } - * } - *} - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "jsonrpc": "2.0", - * "id": "123", - * "result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "success": false, - * "status": 422, - * "message": "Unprocessable Entity.", - * "endpoint": "updateUser" - * } - * } - * } - * - */ - async updateUser (rpcData, userModel) { - try { - // console.log('updateUser rpcData: ', rpcData) - - const newData = rpcData.payload.params - - const user = await this.userLib.updateUser(userModel, newData) - - return { - user, - endpoint: 'updateUser', - success: true, - status: 200, - message: '' - } - } catch (err) { - // console.log('updateUser err: ', err) - - // Return an error response - return { - success: false, - status: 422, - message: err.message, - endpoint: 'updateUser' - } - } - } - - /** - * @api {JSON} /users Delete a user - * @apiPermission public - * @apiName DeleteAUser - * @apiGroup JSON Users - * - * - * @apiExample Example usage: - * {"jsonrpc":"2.0","id":"123","method":"users","params":{ "endpoint": "deleteUser", "userId": "<_id>", "apiToken": ""}} - * - * @apiParam {String} apiToken (required) - * @apiParam {String} userId (required) - * @apiParam {string} endpoint (required) - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * result": { - * "jsonrpc": "2.0", - * "id": "123", - * "result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "endpoint": "deleteUser", - * "success": true, - * "status": 200, - * "message": "" - * } - * } - *} - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "jsonrpc": "2.0", - * "id": "123", - * "result": { - * "method": "users", - * "reciever": "Qmc2uJhg7yrqaNaoTJRDkzrAyVe82e9JMFQcxrBUjbdXyC", - * "value": { - * "success": false, - * "status": 422, - * "message": "Unprocessable Entity", - * "endpoint": "deleteUser" - * } - * } - *} - * - * - */ - async deleteUser (rpcData, userModel) { - try { - // console.log('deleteUser rpcData: ', rpcData) - - await this.userLib.deleteUser(userModel) - - const retObj = { - endpoint: 'deleteUser', - success: true, - status: 200, - message: '' - } - - return retObj - } catch (err) { - // console.error('Error in deleteUser()') - // throw err - - // Return an error response - return { - success: false, - status: 422, - message: err.message, - endpoint: 'deleteUser' - } - } - } - - // TODO create deleteUser() -} - -export default UserRPC diff --git a/src/controllers/json-rpc/validators.js b/src/controllers/json-rpc/validators.js deleted file mode 100644 index 32e662c..0000000 --- a/src/controllers/json-rpc/validators.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - Validators for the JSON RPC -*/ -/* eslint no-useless-catch: 0 */ - -// Public npm libraries -import jwt from 'jsonwebtoken' - -// Local libraries -import config from '../../../config/index.js' - -// const UserModel = require('../../adapters/localdb/models/users') - -class Validators { - constructor (localConfig = {}) { - // Dependency Injection. - this.adapters = localConfig.adapters - if (!this.adapters) { - throw new Error( - 'Instance of Adapters library required when instantiating JSON RPC Validators library.' - ) - } - - // Encapsulate dependencies - this.config = config - this.jwt = jwt - this.UserModel = this.adapters.localdb.Users - } - - // Returns if user passes a valid JWT token that resolves to a valid user. - // Otherwise it throws an error. - async ensureUser (rpcData) { - try { - // console.log('rpcData: ', rpcData) - - const apiToken = rpcData.payload.params.apiToken - if (!apiToken) throw new Error('apiToken JWT required as a parameter') - - const decoded = this.jwt.verify(apiToken, this.config.token) - - const user = await this.UserModel.findById(decoded.id, '-password') - if (!user) throw new Error('User not found!') - - return user - } catch (err) { - // console.error('Error in ensureUser()') - throw err - } - } - - // This middleware ensures that the :id used in the API endpoint matches the - // the ID used in the JWT, or failing that, the ID used in the JWT matches - // an Admin user. This prevents situations like users updating other users - // profiles or non-admins deleting users. - async ensureTargetUserOrAdmin (rpcData) { - try { - // console.log('rpcData: ', rpcData) - - // Ensure the JWT is passed in. - const apiToken = rpcData.payload.params.apiToken - if (!apiToken) throw new Error('apiToken JWT required as a parameter') - - // Ensure a target user ID is provided. - const targetUserId = rpcData.payload.params.userId - if (!targetUserId) throw new Error('userId must be specified') - - // Decode the JWT token. - const decoded = this.jwt.verify(apiToken, this.config.token) - - // Get the user described by the JWT token. - const user = await this.UserModel.findById(decoded.id, '-password') - if (!user) throw new Error('User not found!') - - // If this current user is an admin, then quietly exit. - if (user.type === 'admin') return true - - // Throw an error if the JWT token does not match the targeted user. - if (user._id.toString() !== targetUserId) { - throw new Error('User is neither admin nor target user.') - } - - // Get the user model for the targeted User - const targetedUser = await this.UserModel.findById( - targetUserId, - '-password' - ) - - // Return the user model. - return targetedUser - } catch (error) { - // console.error('Error in ensureUser()') - throw error - } - } -} - -export default Validators diff --git a/src/controllers/rest-api/auth/controller.js b/src/controllers/rest-api/auth/controller.js deleted file mode 100644 index c468417..0000000 --- a/src/controllers/rest-api/auth/controller.js +++ /dev/null @@ -1,99 +0,0 @@ -import Passport from '../../../adapters/passport.js' -const passport = new Passport() - -let _this - -class AuthRESTController { - constructor (localConfig = {}) { - // Dependency Injection. - this.adapters = localConfig.adapters - if (!this.adapters) { - throw new Error( - 'Instance of Adapters library required when instantiating Auth REST Controller.' - ) - } - this.useCases = localConfig.useCases - if (!this.useCases) { - throw new Error( - 'Instance of Use Cases library required when instantiating Auth REST Controller.' - ) - } - - _this = this - this.passport = passport - } - - /** - * @apiDefine TokenError - * @apiError Unauthorized Invalid JWT token - * - * @apiErrorExample {json} Unauthorized-Error: - * HTTP/1.1 401 Unauthorized - * { - * "status": 401, - * "error": "Unauthorized" - * } - */ - - /** - * @api {post} /auth Authenticate user - * @apiName AuthUser - * @apiGroup Auth - * - * @apiParam {String} username User username. - * @apiParam {String} password User password. - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X POST -d '{ "username": "johndoe@gmail.com", "password": "foo" }' localhost:5000/auth - * - * @apiSuccess {Object} user User object - * @apiSuccess {ObjectId} user._id User id - * @apiSuccess {String} user.name User name - * @apiSuccess {String} user.username User username - * @apiSuccess {String} token Encoded JWT - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "username": "johndoe" - * }, - * "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" - * } - * - * @apiError Unauthorized Incorrect credentials - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 401 Unauthorized - * { - * "status": 401, - * "error": "Unauthorized" - * } - */ - async authUser (ctx, next) { - try { - // Retrieve the user from the database after they've proven the correct - // password. - const user = await _this.passport.authUser(ctx, next) - if (!user) { - ctx.throw(401) - } - - const token = user.generateToken() - - const response = user.toJSON() - - delete response.password - - ctx.body = { - token, - user: response - } - } catch (err) { - ctx.throw(401) - } - } -} - -export default AuthRESTController diff --git a/src/controllers/rest-api/auth/index.js b/src/controllers/rest-api/auth/index.js deleted file mode 100644 index 9fbe35a..0000000 --- a/src/controllers/rest-api/auth/index.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - REST API library for auth route. -*/ - -// Public npm libraries. -import Router from 'koa-router' - -// Local libraries. -import AuthRESTController from './controller.js' - -class AuthRouter { - constructor (localConfig = {}) { - // Dependency Injection. - this.adapters = localConfig.adapters - if (!this.adapters) { - throw new Error( - 'Instance of Adapters library required when instantiating PostEntry REST Controller.' - ) - } - this.useCases = localConfig.useCases - if (!this.useCases) { - throw new Error( - 'Instance of Use Cases library required when instantiating PostEntry REST Controller.' - ) - } - - // Encapsulate dependencies. - this.authRESTController = new AuthRESTController(localConfig) - - // Instantiate the router and set the base route. - const baseUrl = '/auth' - this.router = new Router({ prefix: baseUrl }) - } - - attach (app) { - if (!app) { - throw new Error( - 'Must pass app object when attached REST API controllers.' - ) - } - - // Define the routes and attach the controller. - this.router.post('/', this.authRESTController.authUser) - - // Attach the Controller routes to the Koa app. - app.use(this.router.routes()) - app.use(this.router.allowedMethods()) - } -} - -export default AuthRouter diff --git a/src/controllers/rest-api/index.js b/src/controllers/rest-api/index.js index 22b8e95..27b642e 100644 --- a/src/controllers/rest-api/index.js +++ b/src/controllers/rest-api/index.js @@ -7,8 +7,6 @@ // Public npm libraries. // Local libraries -import AuthRESTController from './auth/index.js' -import UserRouter from './users/index.js' import ContactRESTController from './contact/index.js' import LogsRESTController from './logs/index.js' import SlpRESTController from './slp/index.js' @@ -38,14 +36,6 @@ class RESTControllers { useCases: this.useCases } - // 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 /contact route const contactRESTController = new ContactRESTController(dependencies) contactRESTController.attach(app) diff --git a/src/controllers/rest-api/middleware/error.js b/src/controllers/rest-api/middleware/error.js deleted file mode 100644 index d1a44b2..0000000 --- a/src/controllers/rest-api/middleware/error.js +++ /dev/null @@ -1,11 +0,0 @@ -export default function errorMiddleware () { - return async (ctx, next) => { - try { - await next() - } catch (err) { - ctx.status = err.status || 500 - ctx.body = err.message - ctx.app.emit('error', err, ctx) - } - } -}; diff --git a/src/controllers/rest-api/middleware/validators.js b/src/controllers/rest-api/middleware/validators.js deleted file mode 100644 index 8c3a88c..0000000 --- a/src/controllers/rest-api/middleware/validators.js +++ /dev/null @@ -1,194 +0,0 @@ -/* - REST API validator middleware. - - These are a series of functions that ensure the user making a REST API - matches a user in the database (or not). In can do fine-grain user control - such as telling the difference between an admin, a normal user, and an - anonymous user. - - This middleware is used to gatekeep access to different REST API resources. - - CT 9/17/22: - This library was lightly refactored to make it work with the new unit tests. - This not and the commented code below can be deleted once it is verified that - this refactor did not result in any breaking changes. -*/ - -import User from '../../../adapters/localdb/models/users.js' - -import config from '../../../../config/index.js' -import jwt from 'jsonwebtoken' -import wlogger from '../../../adapters/wlogger.js' - -let _this - -class Validators { - constructor () { - this.User = User - this.jwt = jwt - this.config = config - - _this = this - } - - async ensureUser (ctx, next) { - try { - const token = _this.getToken(ctx) - - if (!token) { - throw new Error('Token could not be retrieved from header') - } - - let decoded = null - try { - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - // console.log(`config: ${JSON.stringify(config, null, 2)}`) - decoded = _this.jwt.verify(token, config.token) - } catch (err) { - throw new Error('Could not verify JWT') - } - - ctx.state.user = await _this.User.findById(decoded.id, '-password') - - if (!ctx.state.user) { - // console.log('Err: Could not find user.') - throw new Error('Could not find user') - } - - // return next() - return true - } catch (error) { - // console.log('Ensure user error: ', error) - // console.log('ctx: ', ctx) - ctx.status = 401 - ctx.throw(401, error.message) - } - } - - // This funciton is almost identical to ensureUser, except at the end, it verifies - // that the 'type' associated with the user equals 'admin'. - async ensureAdmin (ctx, next) { - try { - // console.log(`getToken: ${typeof (getToken)}`) - const token = _this.getToken(ctx) - - if (!token) { - // console.log(`Err: Token not provided.`) - // ctx.throw(401) - throw new Error('Token could not be retrieved from header') - } - - let decoded = null - try { - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - // console.log(`config: ${JSON.stringify(config, null, 2)}`) - decoded = _this.jwt.verify(token, config.token) - } catch (err) { - // console.log(`Err: Token could not be decoded: ${err}`) - // ctx.throw(401) - throw new Error('Could not verify JWT') - } - - ctx.state.user = await _this.User.findById(decoded.id, '-password') - if (!ctx.state.user) { - // console.log(`Err: Could not find user.`) - // ctx.throw(401) - throw new Error('Could not find user') - } - - if (ctx.state.user.type !== 'admin') { - // ctx.throw(401, 'not admin') - throw new Error('User is not an admin') - } - - // return next() - return true - } catch (error) { - ctx.status = 401 - ctx.throw(401, error.message) - } - } - - // This middleware ensures that the :id used in the API endpoint matches the - // the ID used in the JWT, or failing that, the ID used in the JWT matches - // an Admin user. This prevents situations like users updating other users - // profiles or non-admins deleting users. - async ensureTargetUserOrAdmin (ctx, next) { - try { - // console.log(`getToken: ${typeof (getToken)}`) - const token = _this.getToken(ctx) - - if (!token) { - // console.log(`Err: Token not provided.`) - // ctx.throw(401) - throw new Error('Token could not be retrieved from header') - } - - // The user ID targeted in this API call. - const targetId = ctx.params.id - // console.log(`targetId: ${JSON.stringify(targetId, null, 2)}`) - - let decoded = null - try { - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - // console.log(`config: ${JSON.stringify(config, null, 2)}`) - decoded = _this.jwt.verify(token, config.token) - } catch (err) { - console.log(`Err: Token could not be decoded: ${err}`) - // ctx.throw(401) - throw new Error('Could not verify JWT') - } - - ctx.state.user = await _this.User.findById(decoded.id, '-password') - if (!ctx.state.user) { - // console.log(`Err: Could not find user.`) - // ctx.throw(401) - throw new Error('Could not find user') - } - // console.log('ctx.state.user: ', ctx.state.user) - - // console.log(`ctx.state.user: ${JSON.stringify(ctx.state.user, null, 2)}`) - // Ensure the calling user and the target user are the same. - - if (ctx.state.user._id.toString() !== targetId.toString()) { - wlogger.verbose( - `Calling user and target user do not match! Calling user: ${ctx.state.user._id}, Target user: ${targetId}` - ) - - // If they don't match, then the calling user better be an admin. - if (ctx.state.user.type !== 'admin') { - // ctx.throw(401, 'not admin') - throw new Error('User is not an admin') - } else { - wlogger.verbose("It's ok. The user is an admin.") - } - } - - // return next() - return true - } catch (error) { - // console.log('Error in ensureTargetUserOrAdmin(): ', error) - ctx.status = 401 - ctx.throw(401, error.message) - } - } - - getToken (ctx) { - const header = ctx.request.header.authorization - if (!header) { - return null - } - const parts = header.split(' ') - if (parts.length !== 2) { - return null - } - const scheme = parts[0] - const token = parts[1] - if (/^Bearer$/i.test(scheme)) { - return token - } - return null - } -} - -export default Validators diff --git a/src/controllers/rest-api/slp/index.js b/src/controllers/rest-api/slp/index.js index 1a676e1..d0b72c0 100644 --- a/src/controllers/rest-api/slp/index.js +++ b/src/controllers/rest-api/slp/index.js @@ -3,14 +3,10 @@ */ // Public npm libraries. -// const Router = require('koa-router') import Router from 'koa-router' // Local libraries. -// const SlpRESTControllerLib = require('./controller') -// const Validators = require('../middleware/validators') import SlpRESTControllerLib from './controller.js' -import Validators from '../middleware/validators.js' // let _this @@ -37,7 +33,7 @@ class SlpRouter { // Encapsulate dependencies. this.slpRESTController = new SlpRESTControllerLib(dependencies) - this.validators = new Validators() + // this.validators = new Validators() // Instantiate the router and set the base route. const baseUrl = '/slp' diff --git a/src/controllers/rest-api/users/controller.js b/src/controllers/rest-api/users/controller.js deleted file mode 100644 index b11e3b2..0000000 --- a/src/controllers/rest-api/users/controller.js +++ /dev/null @@ -1,284 +0,0 @@ -/* - REST API Controller library for the /user route -*/ - -import wlogger from '../../../adapters/wlogger.js' - -let _this - -class UserRESTControllerLib { - constructor (localConfig = {}) { - // Dependency Injection. - this.adapters = localConfig.adapters - if (!this.adapters) { - throw new Error( - 'Instance of Adapters library required when instantiating /users REST Controller.' - ) - } - this.useCases = localConfig.useCases - if (!this.useCases) { - throw new Error( - 'Instance of Use Cases library required when instantiating /users REST Controller.' - ) - } - - // Encapsulate dependencies - this.UserModel = this.adapters.localdb.Users - // this.userUseCases = this.useCases.user - - _this = this - } - - /** - * @api {post} /users Create a new user - * @apiPermission user - * @apiName CreateUser - * @apiGroup REST Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5010/users - * - * @apiParam {Object} user User object (required) - * @apiParam {String} user.email Email - * @apiParam {String} user.password Password - * @apiParam {String} user.name name or handle - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} user.type User type (admin or user) - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * @apiSuccess {String} users.email User email - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "email": "email@format.com", - * "password": "somestrongpassword" - * } - * } - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "status": 422, - * "error": "Unprocessable Entity" - * } - */ - async createUser (ctx) { - try { - const userObj = ctx.request.body.user - - const { userData, token } = await _this.useCases.user.createUser(userObj) - // console.log('userData: ', userData) - // console.log('token: ', token) - - ctx.body = { - user: userData, - token - } - } catch (err) { - // console.log(`err.message: ${err.message}`) - // console.log('err: ', err) - // ctx.throw(422, err.message) - _this.handleError(ctx, err) - } - } - - /** - * @api {get} /users Get all users - * @apiPermission user - * @apiName GetUsers - * @apiGroup REST Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X GET localhost:5000/users - * - * @apiSuccess {Object[]} users Array of user objects - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} user.type User type (admin or user) - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * @apiSuccess {String} users.email User email - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "users": [{ - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "email": "email@format.com" - * }] - * } - * - * @apiUse TokenError - */ - async getUsers (ctx) { - try { - const users = await _this.useCases.user.getAllUsers() - - ctx.body = { users } - } catch (err) { - wlogger.error('Error in users/controller.js/getUsers(): ', err) - ctx.throw(422, err.message) - } - } - - /** - * @api {get} /users/:id Get user by id - * @apiPermission user - * @apiName GetUser - * @apiGroup REST Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X GET localhost:5010/users/56bd1da600a526986cf65c80 - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} user.type User type (admin or user) - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * @apiSuccess {String} users.email User email - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "email": "email@format.com" - * } - * } - * - * @apiUse TokenError - */ - async getUser (ctx, next) { - try { - const user = await _this.useCases.user.getUser(ctx.params) - - ctx.body = { - user - } - } catch (err) { - _this.handleError(ctx, err) - } - - if (next) { - return next() - } - } - - /** - * @api {put} /users/:id Update a user - * @apiPermission user - * @apiName UpdateUser - * @apiGroup REST Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X PUT -d '{ "user": { "name": "Cool new Name" } }' localhost:5000/users/56bd1da600a526986cf65c80 - * - * @apiParam {Object} user User object (required) - * @apiParam {String} user.name Name. - * @apiParam {String} user.email Email. - * @apiParam {String} user.password Password. (optional) - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} user.type User type (admin or user) - * @apiSuccess {String} users.name Updated name - * @apiSuccess {String} users.username Updated username - * @apiSuccess {String} users.email Updated email - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "name": "Cool new name" - * "email": "email@format.com" - * } - * } - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "status": 422, - * "error": "Unprocessable Entity" - * } - * - * @apiUse TokenError - */ - async updateUser (ctx) { - try { - const existingUser = ctx.body.user - const newData = ctx.request.body.user - - const user = await _this.useCases.user.updateUser(existingUser, newData) - - ctx.body = { - user - } - } catch (err) { - ctx.throw(422, err.message) - } - } - - /** - * @api {delete} /users/:id Delete a user - * @apiPermission user - * @apiName DeleteUser - * @apiGroup REST Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X DELETE localhost:5000/users/56bd1da600a526986cf65c80 - * - * @apiSuccess {StatusCode} 200 - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "success": true - * } - * - * @apiUse TokenError - */ - async deleteUser (ctx) { - try { - const user = ctx.body.user - - // await user.remove() - await _this.useCases.user.deleteUser(user) - - ctx.status = 200 - ctx.body = { - success: true - } - } catch (err) { - ctx.throw(422, err.message) - } - } - - // DRY error handler - handleError (ctx, err) { - // If an HTTP status is specified by the buisiness logic, use that. - if (err.status) { - if (err.message) { - ctx.throw(err.status, err.message) - } else { - ctx.throw(err.status) - } - } else { - // By default use a 422 error if the HTTP status is not specified. - ctx.throw(422, err.message) - } - } -} - -export default UserRESTControllerLib diff --git a/src/controllers/rest-api/users/index.js b/src/controllers/rest-api/users/index.js deleted file mode 100644 index 0001861..0000000 --- a/src/controllers/rest-api/users/index.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - REST API library for /user route. -*/ - -// Public npm libraries. -import Router from 'koa-router' - -// Local libraries. -import UserRESTControllerLib from './controller.js' - -import Validators from '../middleware/validators.js' - -let _this - -class UserRouter { - constructor (localConfig = {}) { - // Dependency Injection. - this.adapters = localConfig.adapters - if (!this.adapters) { - throw new Error( - 'Instance of Adapters library required when instantiating PostEntry REST Controller.' - ) - } - this.useCases = localConfig.useCases - if (!this.useCases) { - throw new Error( - 'Instance of Use Cases library required when instantiating PostEntry REST Controller.' - ) - } - - const dependencies = { - adapters: this.adapters, - useCases: this.useCases - } - - // Encapsulate dependencies. - this.userRESTController = new UserRESTControllerLib(dependencies) - this.validators = new Validators() - - // Instantiate the router and set the base route. - const baseUrl = '/users' - 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.post('/', this.userRESTController.createUser) - this.router.get('/', this.getAll) - this.router.get('/:id', this.getById) - this.router.put('/:id', this.updateUser) - this.router.delete('/:id', this.deleteUser) - - // Attach the Controller routes to the Koa app. - app.use(this.router.routes()) - app.use(this.router.allowedMethods()) - } - - async getAll (ctx, next) { - await _this.validators.ensureUser(ctx, next) - await _this.userRESTController.getUsers(ctx, next) - } - - async getById (ctx, next) { - await _this.validators.ensureUser(ctx, next) - await _this.userRESTController.getUser(ctx, next) - } - - async updateUser (ctx, next) { - await _this.validators.ensureTargetUserOrAdmin(ctx, next) - await _this.userRESTController.getUser(ctx, next) - await _this.userRESTController.updateUser(ctx, next) - } - - async deleteUser (ctx, next) { - await _this.validators.ensureTargetUserOrAdmin(ctx, next) - await _this.userRESTController.getUser(ctx, next) - await _this.userRESTController.deleteUser(ctx, next) - } -} - -export default UserRouter diff --git a/src/entities/user.js b/src/entities/user.js deleted file mode 100644 index 02f2899..0000000 --- a/src/entities/user.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - User Entity -*/ - -class User { - validate ({ name, email, password } = {}) { - // Input Validation - if (!email || typeof email !== 'string') { - throw new Error("Property 'email' must be a string!") - } - if (!password || typeof password !== 'string') { - throw new Error("Property 'password' must be a string!") - } - if (!name || typeof name !== 'string') { - throw new Error("Property 'name' must be a string!") - } - - const userData = { name, email, password } - - return userData - } -} - -export default User diff --git a/src/use-cases/index.js b/src/use-cases/index.js index 9dee416..0d94654 100644 --- a/src/use-cases/index.js +++ b/src/use-cases/index.js @@ -4,8 +4,6 @@ https://troutsblog.com/blog/clean-architecture */ -import UserUseCases from './user.js' - class UseCases { constructor (localConfig = {}) { this.adapters = localConfig.adapters @@ -14,9 +12,6 @@ class UseCases { 'Instance of adapters must be passed in when instantiating Use Cases library.' ) } - - // console.log('use-cases/index.js localConfig: ', localConfig) - this.user = new UserUseCases(localConfig) } // Run any startup Use Cases at the start of the app. @@ -25,11 +20,6 @@ class UseCases { console.log('Async Use Cases have been started.') return true - // } catch (err) { - // console.error('Error in use-cases/index.js/start()') - // // console.log(err) - // throw err - // } } } diff --git a/src/use-cases/user.js b/src/use-cases/user.js deleted file mode 100644 index 75baadd..0000000 --- a/src/use-cases/user.js +++ /dev/null @@ -1,185 +0,0 @@ -/* - This library contains business-logic for dealing with users. Most of these - functions are called by the /user REST API endpoints. -*/ - -import UserEntity from '../entities/user.js' - -import wlogger from '../adapters/wlogger.js' - -class UserLib { - 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 User Use Cases library.' - ) - } - - // Encapsulate dependencies - this.UserEntity = new UserEntity() - this.UserModel = this.adapters.localdb.Users - } - - // Create a new user model and add it to the Mongo database. - async createUser (userObj) { - try { - // Input Validation - - const userEntity = this.UserEntity.validate(userObj) - const user = new this.UserModel(userEntity) - - // Enforce default value of 'user' - user.type = 'user' - // console.log('user: ', user) - - // Save the new user model to the database. - await user.save() - - // Generate a JWT token for the user. - const token = user.generateToken() - - // Convert the database model to a JSON object. - const userData = user.toJSON() - // console.log('userData: ', userData) - - // Delete the password property. - delete userData.password - - return { userData, token } - } catch (err) { - // console.log('createUser() error: ', err) - wlogger.error('Error in lib/users.js/createUser()') - throw err - } - } - - // Returns an array of all user models in the Mongo database. - async getAllUsers () { - try { - // Get all user models. Delete the password property from each model. - const users = await this.UserModel.find({}, '-password') - - return users - } catch (err) { - wlogger.error('Error in lib/users.js/getAllUsers()') - throw err - } - } - - // Get the model for a specific user. - async getUser (params) { - try { - const { id } = params - - const user = await this.UserModel.findById(id, '-password') - - // Throw a 404 error if the user isn't found. - if (!user) { - const err = new Error('User not found') - err.status = 404 - throw err - } - - return user - } catch (err) { - // console.log('Error in getUser: ', err) - - if (err.status === 404) throw err - - // Return 422 for any other error - err.status = 422 - err.message = 'Unprocessable Entity' - throw err - } - } - - async updateUser (existingUser, newData) { - try { - // console.log('existingUser: ', existingUser) - // console.log('newData: ', newData) - - // Input Validation - // Optional inputs, but they must be strings if included. - if (newData.email && typeof newData.email !== 'string') { - throw new Error("Property 'email' must be a string!") - } - if (newData.name && typeof newData.name !== 'string') { - throw new Error("Property 'name' must be a string!") - } - if (newData.password && typeof newData.password !== 'string') { - throw new Error("Property 'password' must be a string!") - } - - // Save a copy of the original user type. - const userType = existingUser.type - // console.log('userType: ', userType) - - // If user 'type' property is sent by the client - if (newData.type) { - if (typeof newData.type !== 'string') { - throw new Error("Property 'type' must be a string!") - } - - // Unless the calling user is an admin, they can not change the user type. - if (userType !== 'admin') { - throw new Error("Property 'type' can only be changed by Admin user") - } - } - - // Overwrite any existing data with the new data. - Object.assign(existingUser, newData) - - // Save the changes to the database. - await existingUser.save() - - // Delete the password property. - delete existingUser.password - - return existingUser - } catch (err) { - wlogger.error('Error in lib/users.js/updateUser()') - throw err - } - } - - async deleteUser (user) { - try { - await user.remove() - } catch (err) { - wlogger.error('Error in lib/users.js/deleteUser()') - throw err - } - } - - // Used to authenticate a user. If the login and password salt match a user in - // the database, then it returns the user model. The Koa REST API uses the - // Passport library for this functionality. This function is used to - // authenticate users who login via the JSON RPC. - async authUser (login, passwd) { - try { - // console.log('login: ', login) - // console.log('passwd: ', passwd) - - const user = await this.UserModel.findOne({ email: login }) - if (!user) { - throw new Error('User not found') - } - - const isMatch = await user.validatePassword(passwd) - - if (!isMatch) { - throw new Error('Login credential do not match') - } - - return user - } catch (err) { - // console.error('Error in users.js/authUser()') - console.log('') - throw err - } - } -} - -export default UserLib diff --git a/test/e2e/automated/README.md b/test/e2e/automated/README.md deleted file mode 100644 index 283c815..0000000 --- a/test/e2e/automated/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Automated End-to-end Tests - -This contains the original boilerplate tests, which are end-to-end tests. These tests are fully automated and test the system directly by making REST API calls with axios. - -These tests function exactly the same as a normal user would, by making real REST API calls to the software. As a result, they are fine for testing internal system components like authorization and user handling. However, they are inappropriate for testing sophisticated endpoints that involve complex operations. For example, interacting with a blockchain, pinging other network systems, or writing data to a secondary database. - -There is some redundancy between these tests and the unit tests. The focus is on *how* the tests are executed. The unit tests call the libraries directly (internally). These e2e tests use the REST API (externally). diff --git a/test/e2e/automated/a01-auth.rest-e2e.js b/test/e2e/automated/a01-auth.rest-e2e.js deleted file mode 100644 index f78f51a..0000000 --- a/test/e2e/automated/a01-auth.rest-e2e.js +++ /dev/null @@ -1,137 +0,0 @@ -/* - End-to-end tests for /auth endpoints. - - This test sets up the environment for other e2e tests. -*/ - -// Public npm libraries -import { assert } from 'chai' -import axios from 'axios' -import sinon from 'sinon' - -// Local support libraries -import config from '../../../config/index.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', () => { - let sandbox - - before(async () => { - try { - sandbox = sinon.createSandbox() - - const app = new Server() - - // This should be the first instruction. It starts the REST API server. - await app.startServer() - console.log('App has started.') - - // Stop the IPFS node for the rest of the e2e tests. - // await app.controllers.adapters.ipfs.stop() - - // Delete all previous users in the database. - await testUtils.deleteAllUsers() - - // Create a new admin user. - await adminLib.createSystemUser() - - const userObj = { - email: 'test@test.com', - password: 'pass', - name: 'test' - } - const testUser = await testUtils.createUser(userObj) - // console.log('TestUser: ', testUser) - - context.user = testUser.user - context.token = testUser.token - } catch (err) { - console.log('Error starting app: ', err) - } - }) - - afterEach(() => sandbox.restore()) - - 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.') - } - }) - - it('should throw 401 if email is wrong format', async () => { - try { - const options = { - method: 'post', - url: `${LOCALHOST}/auth`, - data: { - email: 'wrongEmail', - password: 'wrongpassword' - } - } - - 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 - } - }) - }) -}) diff --git a/test/e2e/automated/a02-users.rest-e2e.js b/test/e2e/automated/a02-users.rest-e2e.js deleted file mode 100644 index 4e6f8d0..0000000 --- a/test/e2e/automated/a02-users.rest-e2e.js +++ /dev/null @@ -1,787 +0,0 @@ -import testUtils from '../../utils/test-utils.js' -import { assert } from 'chai' -import config from '../../../config/index.js' -import axios from 'axios' -import sinon from 'sinon' -import util from 'util' - -import UserController from '../../../src/controllers/rest-api/users/controller.js' -import Adapters from '../../../src/adapters/index.js' -import UseCases from '../../../src/use-cases/index.js' -util.inspect.defaultOptions = { depth: 1 } - -const LOCALHOST = `http://localhost:${config.port}` - -const context = {} -const adapters = new Adapters() -let uut -let sandbox - -// const mockContext = require('../../unit/mocks/ctx-mock').context - -describe('Users', () => { - before(async () => { - // console.log(`config: ${JSON.stringify(config, null, 2)}`) - - // Create a second test user. - const userObj = { - email: 'test2@test.com', - password: 'pass2', - name: 'test2' - } - const testUser = await testUtils.createUser(userObj) - // console.log(`testUser2: ${JSON.stringify(testUser, null, 2)}`) - - context.user2 = testUser.user - context.token2 = testUser.token - context.id2 = testUser.user._id - - // Get the JWT used to log in as the admin 'system' user. - const adminJWT = await testUtils.getAdminJWT() - console.log(`adminJWT: ${adminJWT}`) - context.adminJWT = adminJWT - - // const admin = await testUtils.loginAdminUser() - // context.adminJWT = admin.token - - // const admin = await adminLib.loginAdmin() - // console.log(`admin: ${JSON.stringify(admin, null, 2)}`) - }) - - beforeEach(() => { - const useCases = new UseCases({ adapters }) - uut = new UserController({ adapters, useCases }) - - sandbox = sinon.createSandbox() - }) - - afterEach(() => sandbox.restore()) - - describe('POST /users - Create User', () => { - it('should reject signup when data is incomplete', async () => { - try { - const options = { - method: 'POST', - url: `${LOCALHOST}/users`, - data: { - email: 'test2@test.com' - } - } - - await axios(options) - - assert(false, 'Unexpected result') - } catch (err) { - assert(err.response.status === 422, 'Error code 422 expected.') - } - }) - - it('should reject signup if no email property is provided', async () => { - try { - const options = { - method: 'POST', - url: `${LOCALHOST}/users`, - data: { - user: { - password: 'pass2' - } - } - } - await axios(options) - - assert(false, 'Unexpected result') - } catch (err) { - // console.log('err', err) - assert.equal(err.response.status, 422) - assert.include(err.response.data, "Property 'email' must be a string") - } - }) - - it('should reject signup if no password property is provided', async () => { - try { - const options = { - method: 'POST', - url: `${LOCALHOST}/users`, - data: { - user: { - email: 'test2@test.com' - } - } - } - await axios(options) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.response.status, 422) - assert.include( - err.response.data, - "Property 'password' must be a string" - ) - } - }) - - it('should reject if name property property is not string', async () => { - try { - const options = { - method: 'POST', - url: `${LOCALHOST}/users`, - data: { - user: { - email: 'test322@test.com', - password: 'supersecretpassword', - name: 1234 - } - } - } - await axios(options) - - assert.fail('Unexpected result') - } catch (err) { - // console.log(err) - assert.equal(err.response.status, 422) - assert.include(err.response.data, "Property 'name' must be a string") - } - }) - - it("should signup of type 'user' by default", async () => { - const options = { - method: 'post', - url: `${LOCALHOST}/users`, - data: { - user: { - email: 'test3@test.com', - password: 'supersecretpassword', - name: 'test3' - } - } - } - const result = await axios(options) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - context.user = result.data.user - context.token = result.data.token - - assert(result.status === 200, 'Status Code 200 expected.') - assert( - result.data.user.email === 'test3@test.com', - 'Email of test expected' - ) - assert( - result.data.user.password === undefined, - 'Password expected to be omited' - ) - assert.property(result.data, 'token', 'Token property exists.') - assert.equal(result.data.user.type, 'user') - }) - }) - - describe('GET /users', () => { - it('should not fetch users if the authorization header is missing', async () => { - try { - const options = { - method: 'GET', - url: `${LOCALHOST}/users`, - headers: { - Accept: 'application/json' - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should not fetch users if the authorization header is missing the scheme', async () => { - try { - const options = { - method: 'GET', - url: `${LOCALHOST}/users`, - headers: { - Accept: 'application/json', - Authorization: '1' - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should not fetch users if the authorization header has invalid scheme', async () => { - const { token } = context - try { - const options = { - method: 'GET', - url: `${LOCALHOST}/users`, - headers: { - Accept: 'application/json', - Authorization: `Unknown ${token}` - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should not fetch users if token is invalid', async () => { - try { - const options = { - method: 'GET', - url: `${LOCALHOST}/users`, - headers: { - Accept: 'application/json', - Authorization: 'Bearer 1' - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should fetch all users', async () => { - const { token } = context - - const options = { - method: 'GET', - url: `${LOCALHOST}/users`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - } - } - const result = await axios(options) - - const users = result.data.users - // console.log(`users: ${util.inspect(users)}`) - - assert.hasAnyKeys(users[0], ['type', '_id', 'email']) - assert.isNumber(users.length) - }) - - it('should return a 422 http status if biz-logic throws an error', async () => { - try { - const { token } = context - - // Force an error - sandbox - .stub(uut.useCases.user, 'getAllUsers') - .rejects(new Error('test error')) - - const options = { - method: 'GET', - url: `${LOCALHOST}/users`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - } - } - await axios(options) - - assert.fail('Unexpected code path!') - } catch (err) { - // console.log(err) - assert.equal(err.response.status, 422) - assert.equal(err.response.data, 'test error') - } - }) - }) - - describe('GET /users/:id', () => { - it('should not fetch user if token is invalid', async () => { - try { - const options = { - method: 'GET', - url: `${LOCALHOST}/users/1`, - headers: { - Accept: 'application/json', - Authorization: 'Bearer 1' - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it("should throw 404 if user doesn't exist", async () => { - const { token } = context - - try { - const options = { - method: 'GET', - url: `${LOCALHOST}/users/5fa4bd7ee1828f5f4d8ed004`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 404) - } - }) - - it('should throw 422 for invalid input', async () => { - const { token } = context - - try { - const options = { - method: 'GET', - url: `${LOCALHOST}/users/1`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 422) - } - }) - - it('should fetch own user', async () => { - const _id = context.user._id - const token = context.token - - const options = { - method: 'GET', - url: `${LOCALHOST}/users/${_id}`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - } - } - const result = await axios(options) - - const user = result.data.user - // console.log(`user: ${util.inspect(user)}`) - - assert.property(user, 'type') - assert.property(user, 'email') - - assert.property(user, '_id') - assert.equal(user._id, _id) - - assert.notProperty( - user, - 'password', - 'Password property should not be returned' - ) - }) - }) - - describe('PUT /users/:id', () => { - it('should not update user if token is invalid', async () => { - try { - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/1`, - headers: { - Accept: 'application/json', - Authorization: 'Bearer 1' - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should throw 401 if non-admin updating other user', async () => { - const { token } = context - - try { - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/1`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should not be able to update user type', async () => { - try { - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/${context.user._id.toString()}`, - headers: { - Authorization: `Bearer ${context.token}` - }, - data: { - user: { - email: 'test@test.com', - password: 'password', - name: 'new name', - type: 'test' - } - } - } - await axios(options) - - // console.log(`Users: ${JSON.stringify(result.data, null, 2)}`) - - // assert(result.status === 200, 'Status Code 200 expected.') - // assert(result.data.user.type === 'user', 'Type should be unchanged.') - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 422) - assert.include( - err.response.data, - "Property 'type' can only be changed by Admin user" - ) - } - }) - - it('should not be able to update other user when not admin', async () => { - try { - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/${context.user2._id.toString()}`, - headers: { - Authorization: `Bearer ${context.token}` - }, - data: { - user: { - name: 'This should not work' - } - } - } - await axios(options) - - // console.log(`result: ${JSON.stringify(result.data, null, 2)}`) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should not be able to update if name property is wrong', async () => { - try { - const _id = context.user._id - const token = context.token - - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/${_id}`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - }, - data: { - user: { - email: 'testToUpdate@test.com', - name: {}, - password: 'password' - } - } - } - await axios(options) - } catch (error) { - assert.equal(error.response.status, 422) - assert.include(error.response.data, "Property 'name' must be a string!") - } - }) - - it('should not be able to update if password property is not string', async () => { - const { token } = context - const _id = context.user._id - try { - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/${_id}`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - }, - data: { - user: { - password: 1234, - email: 'test@test.com', - name: 'test' - } - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 422) - assert.include( - err.response.data, - "Property 'password' must be a string!" - ) - } - }) - - it('should not be able to update if email is not string', async () => { - const { token } = context - const _id = context.user._id - try { - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/${_id}`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - }, - data: { - user: { - email: 1234 - } - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 422) - assert.include(err.response.data, "Property 'email' must be a string!") - } - }) - - it('should not be able to update type property if is not string', async () => { - try { - const _id = context.user._id - const token = context.token - - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/${_id}`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - }, - data: { - user: { - type: 1, - email: 'test@test.com', - name: 'test', - password: 'password' - } - } - } - await axios(options) - } catch (err) { - assert.equal(err.response.status, 422) - assert.include(err.response.data, "Property 'type' must be a string!") - } - }) - - it('should be able to update other user when admin', async () => { - const adminJWT = context.adminJWT - - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/${context.user2._id.toString()}`, - headers: { - Authorization: `Bearer ${adminJWT}` - }, - data: { - user: { - name: 'This should work', - email: 'test4@test.com', - password: 'password' - } - } - } - - const result = await axios(options) - // console.log(`result stringified: ${JSON.stringify(result, null, 2)}`) - - const userName = result.data.user.name - assert.equal(userName, 'This should work') - }) - - it('should update user with minimum inputs', async () => { - const _id = context.user._id - const token = context.token - - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/${_id}`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - }, - data: { - user: { email: 'testToUpdate@test.com' } - } - } - - const result = await axios(options) - const user = result.data.user - // console.log(`user: ${util.inspect(user)}`) - - assert.property(user, 'type') - assert.property(user, 'email') - - assert.property(user, '_id') - assert.equal(user._id, _id) - - assert.notProperty( - user, - 'password', - 'Password property should not be returned' - ) - assert.equal(user.email, 'testToUpdate@test.com') - }) - - it('should update user with all inputs', async () => { - const _id = context.user._id - const token = context.token - - const options = { - method: 'PUT', - url: `${LOCALHOST}/users/${_id}`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - }, - data: { - user: { - email: 'testToUpdate@test.com', - name: 'my name', - username: 'myUsername' - } - } - } - const result = await axios(options) - - const user = result.data.user - // console.log(`user: ${util.inspect(user)}`) - - assert.property(user, 'type') - assert.property(user, 'email') - assert.property(user, 'name') - - assert.property(user, '_id') - assert.equal(user._id, _id) - assert.notProperty( - user, - 'password', - 'Password property should not be returned' - ) - assert.equal(user.name, 'my name') - assert.equal(user.email, 'testToUpdate@test.com') - assert.equal(user.username, 'myUsername') - }) - }) - - describe('DELETE /users/:id', () => { - it('should not delete user if token is invalid', async () => { - try { - const options = { - method: 'DELETE', - url: `${LOCALHOST}/users/1`, - headers: { - Accept: 'application/json', - Authorization: 'Bearer 1' - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should throw 401 if deleting invalid user', async () => { - const { token } = context - - try { - const options = { - method: 'DELETE', - url: `${LOCALHOST}/users/1`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - } - } - await axios(options) - - assert.equal(true, false, 'Unexpected behavior') - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should not be able to delete other users unless admin', async () => { - try { - const options = { - method: 'DELETE', - url: `${LOCALHOST}/users/${context.user2._id.toString()}`, - headers: { - Authorization: `Bearer ${context.token}` - } - } - await axios(options) - } catch (err) { - assert.equal(err.response.status, 401) - } - }) - - it('should delete own user', async () => { - const _id = context.user._id - const token = context.token - - const options = { - method: 'DELETE', - url: `${LOCALHOST}/users/${_id}`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${token}` - } - } - const result = await axios(options) - // console.log(`result: ${util.inspect(result.data.success)}`) - - assert.equal(result.data.success, true) - }) - - it('should be able to delete other users when admin', async () => { - const id = context.id2 - const adminJWT = context.adminJWT - - const options = { - method: 'DELETE', - url: `${LOCALHOST}/users/${id}`, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${adminJWT}` - } - } - const result = await axios(options) - // console.log(`result: ${util.inspect(result.data)}`) - - assert.equal(result.data.success, true) - }) - }) -}) diff --git a/test/e2e/automated/a04-contact.rest-e2e.js b/test/e2e/automated/a04-contact.rest-e2e.js deleted file mode 100644 index 374d9bb..0000000 --- a/test/e2e/automated/a04-contact.rest-e2e.js +++ /dev/null @@ -1,178 +0,0 @@ -import config from '../../../config/index.js' -import axios from 'axios' -import { assert } from 'chai' -import sinon from 'sinon' - -import { context as mockContext } from '../../unit/mocks/ctx-mock.js' -import ContactController from '../../../src/controllers/rest-api/contact/controller.js' - -// Mock data -// const mockData = require('./mocks/contact-mocks') - -const LOCALHOST = `http://localhost:${config.port}` -let uut -let sandbox - -describe('Contact', () => { - beforeEach(() => { - uut = new ContactController() - - sandbox = sinon.createSandbox() - }) - - afterEach(() => sandbox.restore()) - - describe('POST /contact/email', () => { - it('should throw error if email property is not provided', async () => { - try { - const options = { - method: 'POST', - url: `${LOCALHOST}/contact/email`, - data: { - obj: { - formMessage: 'message' - } - } - } - - await axios(options) - - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // console.log(`result stringified: ${JSON.stringify(result, null, 2)}`) - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.response.status, 422) - assert.include(err.response.data, "Property 'email' must be a string!") - } - }) - - it('should throw error if formMessage property is not provided', async () => { - try { - const options = { - method: 'POST', - url: `${LOCALHOST}/contact/email`, - data: { - obj: { - email: 'email@email.com' - } - } - } - - await axios(options) - - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // console.log(`result stringified: ${JSON.stringify(result, null, 2)}`) - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.response.status, 422) - assert.include( - err.response.data, - "Property 'formMessage' must be a string!" - ) - } - }) - - it('should throw error if email list provided is not a array', async () => { - try { - const options = { - method: 'POST', - url: `${LOCALHOST}/contact/email`, - data: { - obj: { - email: 'email@email.com', - formMessage: 'test message', - emailList: 1 - } - } - } - - await axios(options) - - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // console.log(`result stringified: ${JSON.stringify(result, null, 2)}`) - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.response.status, 422) - assert.include( - err.response.data, - "Property 'emailList' must be a array of emails!" - ) - } - }) - - it('should throw error if email list provided is a empty array', async () => { - try { - const options = { - method: 'POST', - url: `${LOCALHOST}/contact/email`, - data: { - obj: { - email: 'email@email.com', - formMessage: 'test message', - emailList: [] - } - } - } - - await axios(options) - - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // console.log(`result stringified: ${JSON.stringify(result, null, 2)}`) - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.response.status, 422) - assert.include( - err.response.data, - "Property 'emailList' must be a array of emails!" - ) - } - }) - - it('should send email with minimun input', async () => { - try { - // Mock live network calls. - sandbox.stub(uut.contactLib, 'sendEmail').resolves(true) - - // Mock the context object. - const ctx = mockContext() - ctx.request = { - body: { - obj: { - email: 'email@email.com', - formMessage: 'test message' - } - } - } - await uut.email(ctx) - } catch (err) { - assert(false, 'Unexpected result') - } - }) - - it('should send email with all inputs', async () => { - try { - // Mock live network calls. - sandbox.stub(uut.contactLib, 'sendEmail').resolves(true) - - // Mock the context object. - const ctx = mockContext() - ctx.request = { - body: { - obj: { - email: 'email@email.com', - formMessage: 'test message', - emailList: ['email@email.com'] - } - } - } - await uut.email(ctx) - } catch (err) { - assert(false, 'Unexpected result') - } - }) - }) -}) diff --git a/test/e2e/automated/a06-logapi.rest-e2e.js b/test/e2e/automated/a06-logapi.rest-e2e.js deleted file mode 100644 index 63c3b9a..0000000 --- a/test/e2e/automated/a06-logapi.rest-e2e.js +++ /dev/null @@ -1,130 +0,0 @@ -import config from '../../../config/index.js' -import { assert } from 'chai' -import axios from 'axios' -import sinon from 'sinon' -import util from 'util' - -import LogsController from '../../../src/controllers/rest-api/logs/controller.js' -import { context as mockContext } from '../../unit/mocks/ctx-mock.js' -util.inspect.defaultOptions = { depth: 1 } - -const LOCALHOST = `http://localhost:${config.port}` - -let sandbox -let uut -describe('LogsApi', () => { - beforeEach(() => { - uut = new LogsController() - - sandbox = sinon.createSandbox() - }) - - afterEach(() => sandbox.restore()) - - describe('POST /logs', () => { - it('should return false if password is not provided', async () => { - try { - const options = { - method: 'post', - url: `${LOCALHOST}/logs`, - data: {} - } - - const result = await axios(options) - assert.isFalse(result.data.success) - } catch (err) { - assert(false, 'Unexpected result') - } - }) - - it('should return log', async () => { - try { - const options = { - method: 'post', - url: `${LOCALHOST}/logs`, - data: { - password: 'test' - } - } - - const result = await axios(options) - - assert.isTrue(result.data.success) - assert.isArray(result.data.data) - assert.property(result.data.data[0], 'message') - assert.property(result.data.data[0], 'level') - assert.property(result.data.data[0], 'timestamp') - } catch (err) { - assert(false, 'Unexpected result') - } - }) - - it('should return false if files are not found!', async () => { - try { - sandbox.stub(uut.logsApiLib, 'getLogs').resolves({ - success: false, - data: 'file does not exist' - }) - - const ctx = mockContext() - ctx.request = { - body: { - password: 'test' - } - } - await uut.getLogs(ctx) - - assert.isFalse(ctx.body.success) - assert.include(ctx.body.data, 'file does not exist') - } catch (err) { - assert.fail('Unexpected result') - } - }) - - it('should catch and handle errors', async () => { - try { - // Force an error - sandbox - .stub(uut.logsApiLib.fs, 'existsSync') - .throws(new Error('test error')) - - // Mock the context object. - const ctx = mockContext() - - ctx.request = { - body: { - password: 'test' - } - } - - await uut.getLogs(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.include(err.message, 'test error') - } - }) - - it('should throw unhandled error', async () => { - try { - // Force an error - sandbox.stub(uut.logsApiLib.fs, 'existsSync').throws(new Error()) - - // Mock the context object. - const ctx = mockContext() - - ctx.request = { - body: { - password: 'test' - } - } - - await uut.getLogs(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.include(err.message, 'Unhandled error') - } - }) - }) -}) diff --git a/test/e2e/automated/a08-validators.rest-e2e.js b/test/e2e/automated/a08-validators.rest-e2e.js deleted file mode 100644 index 042345e..0000000 --- a/test/e2e/automated/a08-validators.rest-e2e.js +++ /dev/null @@ -1,304 +0,0 @@ -/* -import { assert } from 'chai'; -import testUtils from '../../utils/test-utils.js'; -import Validators from '../../../src/controllers/rest-api/middleware/validators.js'; -import sinon from 'sinon'; -import { context as mockContext } from '../../unit/mocks/ctx-mock.js'; -import util from 'util'; -util.inspect.defaultOptions = { depth: 1 } - -const context = {} - -let sandbox -let uut -describe('Validators', () => { - before(async () => { - // console.log(`config: ${JSON.stringify(config, null, 2)}`) - - // Create a second test user. - const userObj = { - email: 'testvalidator@test.com', - password: 'pass2', - name: 'testvalidator' - } - const testUser = await testUtils.createUser(userObj) - // console.log(`testUser2: ${JSON.stringify(testUser, null, 2)}`) - - context.user = testUser.user - context.token = testUser.token - context.id = testUser.user._id - - // Get the JWT used to log in as the admin 'system' user. - const adminJWT = await testUtils.getAdminJWT() - // console.log(`adminJWT: ${adminJWT}`) - context.adminJWT = adminJWT - - // const admin = await testUtils.loginAdminUser() - // context.adminJWT = admin.token - - // const admin = await adminLib.loginAdmin() - // console.log(`admin: ${JSON.stringify(admin, null, 2)}`) - }) - beforeEach(() => { - uut = new Validators() - - sandbox = sinon.createSandbox() - }) - - afterEach(() => sandbox.restore()) - - describe('ensureUser()', () => { - it('should throw 401 if user cant be found', async () => { - try { - // Force an error - sandbox.stub(uut.User, 'findById').resolves(false) - - // Mock the context object. - const ctx = mockContext() - ctx.request = { - header: { - authorization: `Bearer ${context.token}` - } - } - - await uut.ensureUser(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'Unauthorized') - } - }) - - it('should throw 401 if token not found', async () => { - try { - // Mock the context object. - const ctx = mockContext() - - await uut.ensureUser(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'Unauthorized') - } - }) - - it('should throw 401 if token is invalid', async () => { - try { - // Mock the context object. - const ctx = mockContext() - ctx.request = { - header: { - authorization: 'Bearer 1' - } - } - await uut.ensureUser(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'Unauthorized') - } - }) - - it('should return true if user is admin', async () => { - // Mock the context object. - const ctx = mockContext() - ctx.params = { id: context.id } - - ctx.request = { - header: { - authorization: `Bearer ${context.adminJWT}` - } - } - - const result = await uut.ensureUser(ctx) - - assert.equal(result, true) - }) - }) - - describe('ensureAdmin()', () => { - it('should throw 401 if token not found', async () => { - try { - // Mock the context object. - const ctx = mockContext() - - await uut.ensureAdmin(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'Unauthorized') - } - }) - - it('should throw 401 if token is invalid', async () => { - try { - // Mock the context object. - const ctx = mockContext() - ctx.request = { - header: { - authorization: 'Bearer 1' - } - } - await uut.ensureAdmin(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'Unauthorized') - } - }) - - it('should throw 401 if user cant be found', async () => { - try { - // Force an error - sandbox.stub(uut.User, 'findById').resolves(false) - - // Mock the context object. - const ctx = mockContext() - ctx.request = { - header: { - authorization: `Bearer ${context.token}` - } - } - await uut.ensureAdmin(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'Unauthorized') - } - }) - - it('should throw 401 if user is not admin type', async () => { - try { - // Mock the context object. - const ctx = mockContext() - ctx.request = { - header: { - authorization: `Bearer ${context.token}` - } - } - await uut.ensureAdmin(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'not admin') - } - }) - - it('should return true if user is admin', async () => { - // Mock the context object. - const ctx = mockContext() - ctx.request = { - header: { - authorization: `Bearer ${context.adminJWT}` - } - } - - const result = await uut.ensureAdmin(ctx) - - assert.equal(result, true) - }) - }) - - describe('ensureTargetUserOrAdmin()', () => { - it('should throw 401 if token not found', async () => { - try { - // Mock the context object. - const ctx = mockContext() - ctx.params = { id: context.id } - await uut.ensureTargetUserOrAdmin(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'Unauthorized') - } - }) - - it('should throw 401 if token is invalid', async () => { - try { - // Mock the context object. - const ctx = mockContext() - ctx.params = { id: context.id } - - ctx.request = { - header: { - authorization: 'Bearer 1' - } - } - await uut.ensureTargetUserOrAdmin(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'Unauthorized') - } - }) - - it('should throw 401 if user cant be found', async () => { - try { - // Force an error - sandbox.stub(uut.User, 'findById').resolves(false) - - // Mock the context object. - const ctx = mockContext() - ctx.params = { id: context.id } - - ctx.request = { - header: { - authorization: `Bearer ${context.token}` - } - } - await uut.ensureTargetUserOrAdmin(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'Unauthorized') - } - }) - - it('should throw 401 if user is not admin type', async () => { - try { - // Mock the context object. - const ctx = mockContext() - ctx.params = { id: 'Target Id' } - - ctx.request = { - header: { - authorization: `Bearer ${context.token}` - } - } - await uut.ensureTargetUserOrAdmin(ctx) - - assert(false, 'Unexpected result') - } catch (err) { - assert.equal(err.status, 401) - assert.include(err.message, 'not admin') - } - }) - - it('should return true if user is admin', async () => { - // Mock the context object. - const ctx = mockContext() - ctx.params = { id: context.id } - - ctx.request = { - header: { - authorization: `Bearer ${context.adminJWT}` - } - } - - const result = await uut.ensureTargetUserOrAdmin(ctx) - - assert.equal(result, true) - }) - }) -}) -*/ diff --git a/test/e2e/automated/a09-admin.rest-e2e.js b/test/e2e/automated/a09-admin.rest-e2e.js deleted file mode 100644 index 833423f..0000000 --- a/test/e2e/automated/a09-admin.rest-e2e.js +++ /dev/null @@ -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') - } - }) - }) -}) diff --git a/test/e2e/manual/address-data.js b/test/e2e/manual/address-data.js deleted file mode 100644 index 898fe9b..0000000 --- a/test/e2e/manual/address-data.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - Retrieve token stats for a token ID. -*/ - -// Public npm libraries -const axios = require('axios') - -const SERVER = 'https://psf-slp-indexer.fullstack.cash' -const address = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' - -async function start () { - try { - const result = await axios.post(`${SERVER}/slp/address`, { - address - }) - // console.log('result.data: ', result.data) - - // const tokenData = result.data.tokenData - // tokenData.totalTxs = tokenData.txs.length - // tokenData.txs = [] - - console.log(`address data: ${JSON.stringify(result.data, null, 2)}`) - // console.log('token data: ', tokenData) - } catch (err) { - console.error(err) - } -} -start() diff --git a/test/e2e/manual/dag-large.js b/test/e2e/manual/dag-large.js deleted file mode 100644 index 2f2d3cf..0000000 --- a/test/e2e/manual/dag-large.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - This e2e tests requires a live database synced to 738800 or higher. - - This test validates changes made 5/5/22 that assumes a DAG to be valid if - it grows beyond a certain size. -*/ - -// Global npm libraries -const BCHJS = require('@psf/bch-js') -const assert = require('chai').assert - -// Local libraries -const DAG = require('../../../src/adapters/slp-indexer/lib/dag') -const Cache = require('../../../src/adapters/slp-indexer/lib/cache') -const LevelDb = require('../../../src/adapters/slp-indexer/lib/level-db') - -describe('#dag', () => { - let uut - - beforeEach(() => { - const levelDb = new LevelDb() - const { txDb } = levelDb.openDbs() - const bchjs = new BCHJS() - - const cache = new Cache({ bchjs, txDb }) - uut = new DAG({ cache, txDb }) - }) - - describe('#large-dags', () => { - it('should assume large dag is valid at cut-off', async () => { - const txid = '0cb4824d3e41790f4af5fe1c402c26d2c75767c250a14eb9b03982a802569c62' - const tokenId = 'd6876f0fce603be43f15d34348bb1de1a8d688e1152596543da033a060cff798' - - const result = await uut.crawlDag(txid, tokenId) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.isValid, true) - }) - }) -}) diff --git a/test/e2e/manual/token-data.js b/test/e2e/manual/token-data.js deleted file mode 100644 index 796cdd3..0000000 --- a/test/e2e/manual/token-data.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - Retrieve token stats for a token ID. -*/ - -// Public npm libraries -const axios = require('axios') - -const SERVER = 'https://psf-slp-indexer.fullstack.cash' -const TOKENID = - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' - -async function start () { - try { - const result = await axios.post(`${SERVER}/slp/token`, { - tokenId: TOKENID - }) - - const tokenData = result.data.tokenData - tokenData.totalTxs = tokenData.txs.length - // tokenData.txs = [] - - console.log(`token data: ${JSON.stringify(tokenData, null, 2)}`) - // console.log('token data: ', tokenData) - } catch (err) { - console.error(err) - } -} -start() diff --git a/test/unit/adapters/passport.adapter.unit.js b/test/unit/adapters/passport.adapter.unit.js deleted file mode 100644 index 536fe54..0000000 --- a/test/unit/adapters/passport.adapter.unit.js +++ /dev/null @@ -1,45 +0,0 @@ -import { assert } from 'chai' -import PassportLib from '../../../src/adapters/passport.js' -import sinon from 'sinon' - -let uut -let sandbox - -describe('#passport.js', () => { - beforeEach(() => { - uut = new PassportLib() - - sandbox = sinon.createSandbox() - }) - - afterEach(() => sandbox.restore()) - - describe('authUser()', () => { - it('should throw error if ctx is not provided', async () => { - try { - await uut.authUser() - assert(false, 'Unexpected result') - } catch (err) { - assert.include(err.message, 'ctx is required') - } - }) - - it('Should throw error if the passport library fails', async () => { - try { - const error = new Error('cant auth user') - const user = null - - // Mock calls - // https://sinonjs.org/releases/latest/stubs/ - // About yields - sandbox.stub(uut.passport, 'authenticate').yields(error, user) - - const ctx = {} - await uut.authUser(ctx) - assert(false, 'Unexpected result') - } catch (err) { - assert.include(err.message, 'cant auth user') - } - }) - }) -}) diff --git a/test/unit/adapters/users.adapter.unit.js b/test/unit/adapters/users.adapter.unit.js deleted file mode 100644 index 286d1eb..0000000 --- a/test/unit/adapters/users.adapter.unit.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - Unit tests for the users Mongoose model. -*/ - -import { assert } from 'chai' - -import sinon from 'sinon' -import mongoose from 'mongoose' - -import User from '../../../src/adapters/localdb/models/users.js' -import config from '../../../config/index.js' - -// Set the environment variable to signal this is a test. -process.env.SVC_ENV = 'test' - -describe('#User-Adapter', () => { - // let uut - let sandbox - let testuser - - before(async () => { - // Connect to the Mongo Database. - console.log(`Connecting to database: ${config.database}`) - mongoose.Promise = global.Promise - mongoose.set('useCreateIndex', true) // Stop deprecation warning. - await mongoose.connect(config.database, { - useUnifiedTopology: true, - useNewUrlParser: true - }) - - testuser = new User({ - email: 'test983@test.com', - name: 'test983', - password: 'password' - }) - }) - - beforeEach(async () => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => sandbox.restore()) - - after(async () => { - await testuser.remove() - - mongoose.connection.close() - }) - - describe('#save', () => { - it('should replace the password with a salt', async () => { - await testuser.save() - // console.log('testuser: ', testuser) - - assert.notEqual(testuser.password, 'password') - }) - }) - - describe('#validatePassword', () => { - it('should return true when password matches', async () => { - const result = await testuser.validatePassword('password') - // console.log('result: ', result) - - assert.equal(result, true) - }) - - it('should return false when password does not match', async () => { - const result = await testuser.validatePassword('wrongpassword') - // console.log('result: ', result) - - assert.equal(result, false) - }) - }) - - describe('#generateToken', () => { - it('should generate a JWT token', () => { - const token = testuser.generateToken() - // console.log('token: ', token) - - assert.include(token, 'eyJ') - }) - }) -}) diff --git a/test/unit/controllers/json-rpc/a10-rpc.unit.js b/test/unit/controllers/json-rpc/a10-rpc.unit.js index cbf583b..2f77c65 100644 --- a/test/unit/controllers/json-rpc/a10-rpc.unit.js +++ b/test/unit/controllers/json-rpc/a10-rpc.unit.js @@ -118,44 +118,6 @@ describe('#JSON RPC', () => { assert.isOk('Not throwing an error is a pass.') }) - it('should route to users handler', async () => { - const id = uid() - const userCall = jsonrpc.request(id, 'users', { endpoint: 'getAll' }) - const jsonStr = JSON.stringify(userCall, null, 2) - - // Mock the users controller. - sandbox.stub(uut.userController, 'userRouter').resolves('true') - - 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, 'users') - assert.equal(obj.id, id) - }) - - it('should route to auth handler', async () => { - const id = uid() - const userCall = jsonrpc.request(id, 'auth', { endpoint: 'getAll' }) - const jsonStr = JSON.stringify(userCall, null, 2) - - // Mock the controller. - sandbox.stub(uut.authController, 'authRouter').resolves('true') - - 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, 'auth') - assert.equal(obj.id, id) - }) - it('should route to about handler', async () => { const id = uid() const userCall = jsonrpc.request(id, 'about', { endpoint: 'getAll' }) diff --git a/test/unit/controllers/json-rpc/a12-validators.unit.js b/test/unit/controllers/json-rpc/a12-validators.unit.js deleted file mode 100644 index 3648080..0000000 --- a/test/unit/controllers/json-rpc/a12-validators.unit.js +++ /dev/null @@ -1,309 +0,0 @@ -/* - Unit tests for the JSON RPC validator middleware. - - TODO: ensureTargetUserOrAdmin: it should exit quietly if user is an admin. -*/ - -// Public npm libraries -import jsonrpc from 'jsonrpc-lite' - -import sinon from 'sinon' -import { assert } from 'chai' -import { v4 as uid } from 'uuid' - -// Local libraries -import Validators from '../../../../src/controllers/json-rpc/validators.js' - -import adapters from '../../mocks/adapters/index.js' - -// Set the environment variable to signal this is a test. -process.env.SVC_ENV = 'test' - -describe('#validators', () => { - let uut - let sandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - - uut = new Validators({ adapters }) - }) - - afterEach(() => sandbox.restore()) - - describe('#constructor', () => { - it('should throw an error if adapters is not passed in.', () => { - try { - uut = new Validators() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Adapters library required when instantiating JSON RPC Validators library.' - ) - } - }) - }) - - describe('#ensureUser', () => { - it('should return user model for valid JWT token', async () => { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'getAll', - apiToken: 'fakeJWTToken' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - // Mock external dependencies. - sandbox.stub(uut.jwt, 'verify').returns(true) - sandbox.stub(uut.UserModel, 'findById').resolves(true) - - const user = await uut.ensureUser(rpcData) - // console.log('user: ', user) - - // For this test, we return a value of 'true' instead of actual user data. - assert.equal(user, true) - }) - - it('should throw an error if JWT token is not included', async () => { - try { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'getAll' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - await uut.ensureUser(rpcData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, 'apiToken JWT required as a parameter') - } - }) - - it('should throw an error if JWT token can not be decoded', async () => { - try { - const token = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwNmQxYTlkNTgxNTVjMjIzNWFmMTNhMSIsImlhdCI6MTYxNzc2Mjk3M30.6JkM1v0n71Mzsd3qzClzlMKtq6HlD0umoauG23N9FFF' - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'getAll', - apiToken: token - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - await uut.ensureUser(rpcData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, 'invalid signature') - } - }) - - it('should throw an error if the user can not be found', async () => { - try { - // Force 'error not found' error - sandbox.stub(uut.UserModel, 'findById').resolves(null) - sandbox.stub(uut.jwt, 'verify').returns(true) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'getAll', - apiToken: 'fakeJWTToken' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - await uut.ensureUser(rpcData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, 'User not found!') - } - }) - }) - - describe('#ensureTargetUserOrAdmin', () => { - it('should return user model for valid JWT token', async () => { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'deleteUser', - apiToken: 'fakeJWTToken', - userId: 'abc123' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - // Mock external dependencies. - sandbox.stub(uut.jwt, 'verify').returns(true) - sandbox.stub(uut.UserModel, 'findById').resolves({ _id: 'abc123' }) - - const user = await uut.ensureTargetUserOrAdmin(rpcData) - // console.log('user: ', user) - - // Assert that the mocked data expected is returned. - assert.equal(user._id, 'abc123') - }) - - it('should throw error if JWT token is not provided', async () => { - try { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'deleteUser' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - await uut.ensureTargetUserOrAdmin(rpcData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, 'apiToken JWT required as a parameter') - } - }) - - it('should throw error if user ID is not specified', async () => { - try { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'deleteUser', - apiToken: 'fakeJWTToken' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - await uut.ensureTargetUserOrAdmin(rpcData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, 'userId must be specified') - } - }) - - it('should throw error if JWT token can not be decoded', async () => { - try { - const token = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwNmU0YzkxYzdlYWNjN2Q4NWJjOGI0NCIsImlhdCI6MTYxNzg0MTI5N30.n1sas7YlqtmhBlNDBY_IXxQCrIQTiE8UITqy0PJAFFF' - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'deleteUser', - apiToken: token, - userId: 'abc123' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - await uut.ensureTargetUserOrAdmin(rpcData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, 'invalid signature') - } - }) - - it('should throw an error if user can not be found', async () => { - try { - // Mock external dependencies. - sandbox.stub(uut.jwt, 'verify').returns(true) - sandbox.stub(uut.UserModel, 'findById').resolves(null) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'deleteUser', - apiToken: 'fakeJWTToken', - userId: 'abc123' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - await uut.ensureTargetUserOrAdmin(rpcData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, 'User not found!') - } - }) - - it('should throw an error if JWT is from a different user', async () => { - try { - // Mock external dependencies. - sandbox.stub(uut.jwt, 'verify').returns(true) - sandbox.stub(uut.UserModel, 'findById').resolves({ _id: 'badId' }) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'deleteUser', - apiToken: 'fakeJWTToken', - userId: 'abc123' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - await uut.ensureTargetUserOrAdmin(rpcData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, 'User is neither admin nor target user.') - } - }) - - it('should return true if user is an admin', async () => { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'deleteUser', - apiToken: 'fakeJWTToken', - userId: 'abc123' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - // Mock external dependencies. - sandbox.stub(uut.jwt, 'verify').returns(true) - sandbox - .stub(uut.UserModel, 'findById') - .resolves({ _id: 'abc123', type: 'admin' }) - - const user = await uut.ensureTargetUserOrAdmin(rpcData) - // console.log('user: ', user) - - // Assert that the mocked data expected is returned. - assert.equal(user, true) - }) - }) -}) diff --git a/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js b/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js deleted file mode 100644 index 8972bd3..0000000 --- a/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js +++ /dev/null @@ -1,196 +0,0 @@ -/* - Unit tests for the json-rpc/auth/index.js file. -*/ - -// Public npm libraries -import jsonrpc from 'jsonrpc-lite' - -import sinon from 'sinon' -import { assert } from 'chai' -import { v4 as uid } from 'uuid' - -// Local libraries -import AuthRPC from '../../../../src/controllers/json-rpc/auth/index.js' - -import RateLimit from '../../../../src/controllers/json-rpc/rate-limit.js' -import adapters from '../../mocks/adapters/index.js' -import UseCasesMock from '../../mocks/use-cases/index.js' - -// Set the environment variable to signal this is a test. -process.env.SVC_ENV = 'test' - -describe('#AuthRPC', () => { - let uut - let sandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - - const useCases = new UseCasesMock() - - uut = new AuthRPC({ 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 AuthRPC() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Adapters library required when instantiating Auth JSON RPC Controller.' - ) - } - }) - - it('should throw an error if useCases are not passed in', () => { - try { - uut = new AuthRPC({ adapters }) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Use Cases library required when instantiating Auth JSON RPC Controller.' - ) - } - }) - }) - - describe('#authRouter', () => { - it('should route to the authUser method', async () => { - // Mock dependencies - sandbox.stub(uut, 'authUser').resolves(true) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const authCall = jsonrpc.request(id, 'auth', { endpoint: 'authUser' }) - const jsonStr = JSON.stringify(authCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - rpcData.from = 'Origin request' - - const result = await uut.authRouter(rpcData) - - assert.equal(result, true) - }) - - it('should return 500 status on routing issue', async () => { - // Mock dependencies - sandbox.stub(uut, 'authUser').rejects(new Error('test error')) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const authCall = jsonrpc.request(id, 'auth', { endpoint: 'authUser' }) - const jsonStr = JSON.stringify(authCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - rpcData.from = 'Origin request' - - const result = await uut.authRouter(rpcData) - - assert.equal(result.success, false) - assert.equal(result.status, 500) - assert.equal(result.message, 'test error') - assert.equal(result.endpoint, 'authUser') - }) - }) - - describe('#authUser', () => { - it('should return a JWT token if user successfully authenticates', async () => { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const authCall = jsonrpc.request(id, 'auth', { - endpoint: 'authUser', - login: 'test543@test.com', - password: 'password' - }) - const jsonStr = JSON.stringify(authCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - const response = await uut.authUser(rpcData) - // console.log('response: ', response) - - assert.equal(response.endpoint, 'authUser') - assert.property(response, 'userId') - // assert.equal(response.userType, 'user') - assert.property(response, 'userName') - assert.property(response, 'userEmail') - assert.property(response, 'apiToken') - assert.equal(response.status, 200) - assert.equal(response.success, true) - assert.property(response, 'message') - }) - - it('should return an error for invalid credentials', async () => { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const authCall = jsonrpc.request(id, 'auth', { - endpoint: 'authUser', - login: 'test543@test.com', - password: 'badpassword' - }) - const jsonStr = JSON.stringify(authCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - // Force an error. - sandbox - .stub(uut.userLib, 'authUser') - .rejects(new Error('Login credential do not match')) - - const response = await uut.authUser(rpcData) - // console.log('response: ', response) - - assert.equal(response.success, false) - assert.equal(response.status, 422) - assert.equal(response.message, 'Login credential do not match') - assert.equal(response.endpoint, 'authUser') - }) - - it('should throw an error if login is not provided', async () => { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const authCall = jsonrpc.request(id, 'auth', { - endpoint: 'authUser' - }) - const jsonStr = JSON.stringify(authCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - const response = await uut.authUser(rpcData) - // console.log('response: ', response) - - assert.equal(response.success, false) - assert.equal(response.status, 422) - assert.equal(response.message, 'login must be specified') - assert.equal(response.endpoint, 'authUser') - }) - - it('should throw an error if password is not provided', async () => { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const authCall = jsonrpc.request(id, 'auth', { - endpoint: 'authUser', - login: 'test543@test.com' - }) - const jsonStr = JSON.stringify(authCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - const response = await uut.authUser(rpcData) - // console.log('response: ', response) - - assert.equal(response.success, false) - assert.equal(response.status, 422) - assert.equal(response.message, 'password must be specified') - assert.equal(response.endpoint, 'authUser') - }) - }) -}) diff --git a/test/unit/controllers/json-rpc/users.json-rpc-controller.unit.js b/test/unit/controllers/json-rpc/users.json-rpc-controller.unit.js deleted file mode 100644 index d749cae..0000000 --- a/test/unit/controllers/json-rpc/users.json-rpc-controller.unit.js +++ /dev/null @@ -1,414 +0,0 @@ -/* - Unit tests for the rpc/users/index.js file. -*/ - -// Public npm libraries -import jsonrpc from 'jsonrpc-lite' - -import sinon from 'sinon' -import { assert } from 'chai' -import { v4 as uid } from 'uuid' - -// Local libraries -import UserRPC from '../../../../src/controllers/json-rpc/users/index.js' - -import adapters from '../../mocks/adapters/index.js' -import UseCasesMock from '../../mocks/use-cases/index.js' - -// Set the environment variable to signal this is a test. -process.env.SVC_ENV = 'test' - -describe('#UserRPC', () => { - let uut - let sandbox - let testUser - - beforeEach(() => { - sandbox = sinon.createSandbox() - - const useCases = new UseCasesMock() - - uut = new UserRPC({ adapters, useCases }) - }) - - afterEach(() => sandbox.restore()) - - describe('#constructor', () => { - it('should throw an error if adapters are not passed in', () => { - try { - uut = new UserRPC() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Adapters library required when instantiating User JSON RPC Controller.' - ) - } - }) - - it('should throw an error if useCases are not passed in', () => { - try { - uut = new UserRPC({ adapters }) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Use Cases library required when instantiating User JSON RPC Controller.' - ) - } - }) - }) - - describe('#createUser', () => { - it('should create a new user', async () => { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'createUser', - email: 'test973@test.com', - name: 'test973', - password: 'password' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - const result = await uut.createUser(rpcData) - // console.log('result: ', result) - - // CreateUser() specific return values. - // assert.equal(result.userData.type, 'user') - // assert.equal(result.userData.email, 'test973@test.com') - // assert.equal(result.userData.name, 'test973') - // assert.property(result.userData, '_id') - // assert.property(result, 'token') - - // Generic JSON RPC return values - assert.equal(result.endpoint, 'createUser') - assert.equal(result.success, true) - assert.equal(result.status, 200) - assert.equal(result.message, '') - - // Save the user ID for future tests. - testUser = result - }) - - it('should return error data if biz logic throws an error', async () => { - // Force an error - sandbox.stub(uut.userLib, 'createUser').rejects(new Error('test error')) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'createUser', - email: 'test973@test.com', - name: 'test973', - password: 'password' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - const result = await uut.createUser(rpcData) - // console.log('result: ', result) - - // Generic JSON RPC return values - assert.equal(result.endpoint, 'createUser') - assert.equal(result.success, false) - assert.equal(result.status, 422) - assert.equal(result.message, 'test error') - }) - }) - - describe('#userRouter', () => { - it('should route to the createUser method', async () => { - // Mock dependencies - sandbox.stub(uut, 'createUser').resolves(true) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { endpoint: 'createUser' }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - rpcData.from = 'Origin request' - - const result = await uut.userRouter(rpcData) - - assert.equal(result, true) - }) - - it('should route to the getAllUsers method', async () => { - // Mock dependencies - sandbox.stub(uut, 'getAll').resolves(true) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'getAllUsers', - apiToken: testUser.token - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - rpcData.from = 'Origin request' - - // Force middleware to pass. - sandbox.stub(uut.validators, 'ensureUser').resolves(true) - - const result = await uut.userRouter(rpcData) - // console.log('result', result) - assert.equal(result, true) - }) - - it('should route to the updateUser method', async () => { - // Mock dependencies - sandbox.stub(uut, 'updateUser').resolves(true) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'updateUser', - apiToken: 'fakeJWTToken', - userId: 'abc123' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - rpcData.from = 'Origin request' - - // Force middleware to pass. - sandbox.stub(uut.validators, 'ensureTargetUserOrAdmin').resolves(true) - - const result = await uut.userRouter(rpcData) - // console.log('result: ', result) - - assert.equal(result, true) - }) - - it('should route to the getUser method', async () => { - // Mock dependencies - sandbox.stub(uut, 'getUser').resolves(true) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'getUser', - apiToken: testUser.token - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - rpcData.from = 'Origin request' - - // Force middleware to pass. - sandbox.stub(uut.validators, 'ensureUser').resolves(true) - - const result = await uut.userRouter(rpcData) - // console.log('result: ', result) - - assert.equal(result, true) - }) - - it('should route to the deleteUsers method', async () => { - // Mock dependencies - sandbox.stub(uut, 'deleteUser').resolves(true) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'deleteUser', - apiToken: 'fakeJWTToken', - userId: 'abc123' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - rpcData.from = 'Origin request' - - // Force middleware to pass. - sandbox.stub(uut.validators, 'ensureTargetUserOrAdmin').resolves(true) - - const result = await uut.userRouter(rpcData) - // console.log('result: ', result) - - assert.equal(result, true) - }) - - it('should return 500 status on routing issue', async () => { - // Force an error - sandbox.stub(uut, 'createUser').rejects(new Error('test error')) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { endpoint: 'createUser' }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - rpcData.from = 'Origin request' - - const result = await uut.userRouter(rpcData) - // console.log('result: ', result) - - assert.equal(result.success, false) - assert.equal(result.status, 500) - assert.equal(result.message, 'test error') - assert.equal(result.endpoint, 'createUser') - }) - }) - - describe('#getAllUsers', () => { - it('should return all users', async () => { - const result = await uut.getAll() - // console.log('getAll result: ', result) - - // Endpoint specific properties - assert.property(result, 'users') - - // Generic JSON RPC return values - assert.equal(result.endpoint, 'getAllUsers') - assert.equal(result.success, true) - assert.equal(result.status, 200) - assert.equal(result.message, '') - }) - - it('should return error data if biz logic throws an error', async () => { - // Force an error - sandbox.stub(uut.userLib, 'getAllUsers').rejects(new Error('test error')) - - const result = await uut.getAll() - // console.log('result: ', result) - - // Generic JSON RPC return values - assert.equal(result.endpoint, 'getAllUsers') - assert.equal(result.success, false) - assert.equal(result.status, 422) - assert.equal(result.message, 'test error') - }) - }) - - describe('#updateUser', () => { - it('should update a user', async () => { - // Get the user model for the test user. - // const testUserModel = await UserModel.findById( - // testUser.userData._id, - // '-password' - // ) - - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'updateUser', - // userId: testUser.userData._id.toString(), - userId: 'abc123', - name: 'test777' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - const result = await uut.updateUser(rpcData, {}) - // console.log('updateUser result: ', result) - - // Endpoint specific properties - assert.property(result, 'user') - // assert.property(result.user, 'type') - // assert.property(result.user, '_id') - // assert.property(result.user, 'email') - // assert.property(result.user, 'name') - - // Generic JSON RPC return values - assert.equal(result.endpoint, 'updateUser') - assert.equal(result.success, true) - assert.equal(result.status, 200) - assert.equal(result.message, '') - }) - - it('should return error data if biz logic throws an error', async () => { - // Force an error by not specifying an user ID. - const result = await uut.updateUser() - // console.log('result: ', result) - - // Generic JSON RPC return values - assert.equal(result.endpoint, 'updateUser') - assert.equal(result.success, false) - assert.equal(result.status, 422) - assert.include(result.message, 'Cannot read') - }) - }) - - describe('#getUser', () => { - it('should return a specific user', async () => { - // Generate the parsed data that the main router would pass to this - // endpoint. - const id = uid() - const userCall = jsonrpc.request(id, 'users', { - endpoint: 'getUser', - userId: 'abc123' - }) - const jsonStr = JSON.stringify(userCall, null, 2) - const rpcData = jsonrpc.parse(jsonStr) - - const result = await uut.getUser(rpcData) - // console.log('getUser result: ', result) - - // Endpoint specific properties - assert.property(result, 'user') - // assert.property(result.user, 'type') - // assert.property(result.user, '_id') - // assert.property(result.user, 'email') - // assert.property(result.user, 'name') - - // Generic JSON RPC return values - assert.equal(result.endpoint, 'getUser') - assert.equal(result.success, true) - assert.equal(result.status, 200) - assert.equal(result.message, '') - }) - - it('should return error data if biz logic throws an error', async () => { - // Force an error by not specifying an user ID. - const result = await uut.getUser() - // console.log('result: ', result) - - // Generic JSON RPC return values - assert.equal(result.endpoint, 'getUser') - assert.equal(result.success, false) - assert.equal(result.status, 422) - assert.include(result.message, 'Cannot read') - }) - }) - - describe('#deleteUser', () => { - it('should delete a user', async () => { - // Get the user model for the test user. - // const testUserModel = await UserModel.findById( - // testUser.userData._id, - // '-password' - // ) - - await uut.deleteUser({}, {}) - // console.log(result) - - assert.isOk('Not throwing an error is a success') - }) - - it('should return error data if biz logic throws an error', async () => { - // Force an error: - sandbox - .stub(uut.userLib, 'deleteUser') - .rejects(new Error('Cannot read property')) - - const result = await uut.deleteUser() - // console.log('result: ', result) - - // Generic JSON RPC return values - assert.equal(result.endpoint, 'deleteUser') - assert.equal(result.success, false) - assert.equal(result.status, 422) - assert.include(result.message, 'Cannot read property') - }) - }) -}) diff --git a/test/unit/controllers/rest-api/auth/auth.rest.controller.unit.js b/test/unit/controllers/rest-api/auth/auth.rest.controller.unit.js deleted file mode 100644 index 9c53d05..0000000 --- a/test/unit/controllers/rest-api/auth/auth.rest.controller.unit.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - 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 AuthRESTController from '../../../../../src/controllers/rest-api/auth/controller.js' - -import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js' - -let uut -let sandbox -let ctx - -describe('#Auth-REST-Router', () => { - // const testUser = {} - - beforeEach(() => { - const useCases = new UseCasesMock() - uut = new AuthRESTController({ 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 AuthRESTController() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Adapters library required when instantiating Auth REST Controller.' - ) - } - }) - - it('should throw an error if useCases are not passed in', () => { - try { - uut = new AuthRESTController({ adapters }) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Use Cases library required when instantiating Auth REST Controller.' - ) - } - }) - }) - - describe('#authUser', () => { - it('should authorize a user', async () => { - // Mock dependencies - const user = { - toJSON: () => { - return { password: 'password' } - }, - generateToken: () => {} - } - sandbox.stub(uut.passport, 'authUser').resolves(user) - - await uut.authUser(ctx) - }) - - it('should catch and throw an 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') - } - }) - }) -}) diff --git a/test/unit/controllers/rest-api/auth/auth.rest.router.unit.js b/test/unit/controllers/rest-api/auth/auth.rest.router.unit.js deleted file mode 100644 index 3a559f9..0000000 --- a/test/unit/controllers/rest-api/auth/auth.rest.router.unit.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - 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 AuthRouter from '../../../../../src/controllers/rest-api/auth/index.js' - -let uut -let sandbox -// let ctx - -// const mockContext = require('../../../../unit/mocks/ctx-mock').context - -describe('#Auth-REST-Router', () => { - // const testUser = {} - - beforeEach(() => { - const useCases = new UseCasesMock() - uut = new AuthRouter({ 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 AuthRouter() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Adapters library required when instantiating PostEntry REST Controller.' - ) - } - }) - - it('should throw an error if useCases are not passed in', () => { - try { - uut = new AuthRouter({ adapters }) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Use Cases library required when instantiating PostEntry REST Controller.' - ) - } - }) - }) - - describe('#attach', () => { - it('should throw an error if app is not passed in.', () => { - try { - uut.attach() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Must pass app object when attached REST API controllers.' - ) - } - }) - }) -}) diff --git a/test/unit/controllers/rest-api/middleware/validators-unit.js b/test/unit/controllers/rest-api/middleware/validators-unit.js deleted file mode 100644 index 6a29c00..0000000 --- a/test/unit/controllers/rest-api/middleware/validators-unit.js +++ /dev/null @@ -1,306 +0,0 @@ -/* - Unit tests for the REST API middleware that validates users. -*/ - -// Public npm libraries -import { assert } from 'chai' -import sinon from 'sinon' - -// Local libraries -import Validators from '../../../../../src/controllers/rest-api/middleware/validators.js' -import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js' - -describe('#Validators', () => { - let uut - let ctx - let sandbox - - beforeEach(() => { - uut = new Validators() - - // Mock the context object. - ctx = mockContext() - - sandbox = sinon.createSandbox() - }) - - afterEach(() => sandbox.restore()) - - describe('#getToken', () => { - it('should return null if no header is provided', () => { - const result = uut.getToken(ctx) - - assert.equal(result, null) - }) - - it('should return null if header is not in two parts', () => { - ctx.request.header.authorization = 'Bearer' - - const result = uut.getToken(ctx) - - assert.equal(result, null) - }) - - it('should return null if first part of header does not container the word bearer', () => { - ctx.request.header.authorization = 'some thing' - - const result = uut.getToken(ctx) - - assert.equal(result, null) - }) - - it('should return the JWT token from the header', () => { - const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzMjNiNTUwNzgxYWYzNTc4YzI0ZmU5YiIsImlhdCI6MTY2MzQ0NDczNCwiZXhwIjoxNjYzNTMxMTM0fQ.BY5sOfXc4z5axS98CdTfyqnO9y2wijOlwnv52rcvxHA' - ctx.request.header.authorization = `Bearer ${jwt}` - - const result = uut.getToken(ctx) - - assert.equal(result, jwt) - }) - }) - - describe('#ensureUser', () => { - it('should throw error if token is not provided', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns() - - await uut.ensureUser(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should throw error if token can not be verified', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').throws(new Error('test error')) - - await uut.ensureUser(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should throw error if user can not be found in database', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves(false) - - await uut.ensureUser(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should return true if the user is verified', async () => { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves({ user: 'alice' }) - - const result = await uut.ensureUser(ctx) - - assert.equal(result, true) - }) - }) - - describe('#ensureUser', () => { - it('should throw error if token is not provided', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns() - - await uut.ensureAdmin(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should throw error if token can not be verified', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').throws(new Error('test error')) - - await uut.ensureAdmin(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should throw error if user can not be found in database', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves(false) - - await uut.ensureAdmin(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should throw error if user is not an admin', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves({ type: 'user' }) - - await uut.ensureAdmin(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should return true if the user is an admin', async () => { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves({ type: 'admin' }) - - const result = await uut.ensureAdmin(ctx) - - assert.equal(result, true) - }) - }) - - describe('#ensureTargetUserOrAdmin', () => { - it('should throw error if token is not provided', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns() - - await uut.ensureTargetUserOrAdmin(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should throw error if token can not be verified', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').throws(new Error('test error')) - - ctx.params = { - id: '456' - } - - await uut.ensureTargetUserOrAdmin(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should throw error if user can not be found in database', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves(false) - - ctx.params = { - id: '456' - } - - await uut.ensureTargetUserOrAdmin(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should throw error if user is not an admin', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves({ type: 'user' }) - - ctx.params = { - id: '456' - } - - await uut.ensureTargetUserOrAdmin(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should throw error is user is not admin or target user', async () => { - try { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves({ type: 'user', _id: '123' }) - - ctx.params = { - id: '456' - } - - await uut.ensureTargetUserOrAdmin(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(ctx.status, 401) - } - }) - - it('should return true if the user is an admin', async () => { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves({ type: 'admin', _id: '123' }) - - ctx.params = { - id: '456' - } - - const result = await uut.ensureTargetUserOrAdmin(ctx) - - assert.equal(result, true) - }) - - it('should return true if the user is the target user', async () => { - // Mock dependencies and force desired code path - sandbox.stub(uut, 'getToken').returns('fake-jwt') - sandbox.stub(uut.jwt, 'verify').returns({}) - sandbox.stub(uut.User, 'findById').resolves({ type: 'user', _id: '123' }) - - ctx.params = { - id: '123' - } - - const result = await uut.ensureTargetUserOrAdmin(ctx) - - assert.equal(result, true) - }) - }) -}) diff --git a/test/unit/controllers/rest-api/users/users.rest.controller.unit.js b/test/unit/controllers/rest-api/users/users.rest.controller.unit.js deleted file mode 100644 index 45b1b81..0000000 --- a/test/unit/controllers/rest-api/users/users.rest.controller.unit.js +++ /dev/null @@ -1,257 +0,0 @@ -/* - 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' -import UserController from '../../../../../src/controllers/rest-api/users/controller.js' - -import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js' -let uut -let sandbox -let ctx - -describe('#Users-REST-Controller', () => { - // const testUser = {} - - beforeEach(() => { - const useCases = new UseCasesMock() - uut = new UserController({ 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 UserController() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Adapters library required when instantiating /users REST Controller.' - ) - } - }) - - it('should throw an error if useCases are not passed in', () => { - try { - uut = new UserController({ adapters }) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Use Cases library required when instantiating /users REST Controller.' - ) - } - }) - }) - - describe('#POST /users', () => { - it('should return 422 status on biz logic error', async () => { - try { - await uut.createUser(ctx) - - assert.fail('Unexpected result') - } catch (err) { - // console.log(err) - assert.equal(err.status, 422) - assert.include(err.message, 'Cannot read') - } - }) - - it('should return 200 status on success', async () => { - ctx.request.body = { - user: { - email: 'test02@test.com', - password: 'test', - name: 'test02' - } - } - - await uut.createUser(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, 'user') - assert.property(ctx.response.body, 'token') - - // Used by downstream tests. - // testUser = ctx.response.body.user - // console.log('testUser: ', testUser) - }) - }) - - describe('GET /users', () => { - it('should return 422 status on arbitrary biz logic error', async () => { - try { - // Force an error - sandbox - .stub(uut.useCases.user, 'getAllUsers') - .rejects(new Error('test error')) - - await uut.getUsers(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(err.status, 422) - assert.include(err.message, 'test error') - } - }) - - it('should return 200 status on success', async () => { - await uut.getUsers(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, 'users') - }) - }) - - describe('GET /users/:id', () => { - it('should return 422 status on arbitrary biz logic error', async () => { - try { - // Force an error - sandbox - .stub(uut.useCases.user, 'getUser') - .rejects(new Error('test error')) - - await uut.getUser(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.user, 'getUser').resolves({ _id: '123' }) - - await uut.getUser(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, 'user') - }) - - it('should return other error status passed by biz logic', async () => { - try { - // Mock dependencies - const testErr = new Error('test error') - testErr.status = 404 - sandbox.stub(uut.useCases.user, 'getUser').rejects(testErr) - - await uut.getUser(ctx) - - assert.fail('Unexpected result') - } catch (err) { - assert.equal(err.status, 404) - assert.include(err.message, 'test error') - } - }) - }) - - describe('PUT /users/:id', () => { - it('should return 422 if no input data given', async () => { - try { - await uut.updateUser(ctx) - - assert.fail('Unexpected result') - } catch (err) { - // console.log(err) - assert.equal(err.status, 422) - assert.include(err.message, 'Cannot read') - } - }) - - it('should return 200 on success', async () => { - // Prep the testUser data. - // console.log('testUser: ', testUser) - // testUser.password = 'password' - // delete testUser.type - - // Replace the testUser variable with an actual model from the DB. - // const existingUser = await User.findById(testUser._id) - - ctx.body = { - user: {} - } - ctx.request.body = { - user: {} - } - - // Mock dependencies - sandbox.stub(uut.useCases.user, 'updateUser').resolves({}) - - await uut.updateUser(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, 'user') - }) - }) - - describe('DELETE /users/:id', () => { - it('should return 422 if no input data given', async () => { - try { - await uut.deleteUser(ctx) - - assert.fail('Unexpected result') - } catch (err) { - // console.log(err) - assert.equal(err.status, 422) - assert.include(err.message, 'Cannot read') - } - }) - - it('should return 200 status on success', async () => { - // Replace the testUser variable with an actual model from the DB. - const existingUser = {} - - ctx.body = { - user: existingUser - } - - await uut.deleteUser(ctx) - - // Assert the expected HTTP response - assert.equal(ctx.status, 200) - }) - }) - - describe('#handleError', () => { - it('should still throw error if there is no message', () => { - try { - const err = { - status: 404 - } - - uut.handleError(ctx, err) - } catch (err) { - assert.include(err.message, 'Not Found') - } - }) - }) -}) diff --git a/test/unit/controllers/rest-api/users/users.rest.router.unit.js b/test/unit/controllers/rest-api/users/users.rest.router.unit.js deleted file mode 100644 index 798264f..0000000 --- a/test/unit/controllers/rest-api/users/users.rest.router.unit.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - 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 UserRouter from '../../../../../src/controllers/rest-api/users/index.js' - -let uut -let sandbox -// let ctx - -// const mockContext = require('../../../../unit/mocks/ctx-mock').context - -describe('#Users-REST-Router', () => { - // const testUser = {} - - beforeEach(() => { - const useCases = new UseCasesMock() - uut = new UserRouter({ 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 UserRouter() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Adapters library required when instantiating PostEntry REST Controller.' - ) - } - }) - - it('should throw an error if useCases are not passed in', () => { - try { - uut = new UserRouter({ adapters }) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of Use Cases library required when instantiating PostEntry 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.' - ) - } - }) - }) -}) diff --git a/test/unit/entities/user.entity.unit.js b/test/unit/entities/user.entity.unit.js deleted file mode 100644 index 4ef6de9..0000000 --- a/test/unit/entities/user.entity.unit.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - Unit tests for the User entity library. -*/ - -import { assert } from 'chai' - -import sinon from 'sinon' -import User from '../../../src/entities/user.js' - -let sandbox -let uut - -describe('#User-Entity', () => { - before(async () => {}) - - beforeEach(() => { - uut = new User() - - sandbox = sinon.createSandbox() - }) - - afterEach(() => sandbox.restore()) - - describe('#validate', () => { - it('should throw an error if email is not provided', () => { - try { - uut.validate() - } catch (err) { - assert.include(err.message, "Property 'email' must be a string!") - } - }) - - it('should throw an error if password is not provided', () => { - try { - uut.validate({ email: 'test@test.com' }) - } catch (err) { - assert.include(err.message, "Property 'password' must be a string!") - } - }) - - it('should throw an error if name is not provided', () => { - try { - uut.validate({ email: 'test@test.com', password: 'test' }) - } catch (err) { - assert.include(err.message, "Property 'name' must be a string!") - } - }) - - it('should return a User object', () => { - const inputData = { - email: 'test@test.com', - password: 'test', - name: 'test' - } - - const entry = uut.validate(inputData) - // console.log('entry: ', entry) - - assert.property(entry, 'email') - assert.equal(entry.email, inputData.email) - - assert.property(entry, 'password') - assert.equal(entry.password, inputData.password) - - assert.property(entry, 'name') - assert.equal(entry.name, inputData.name) - }) - }) -}) diff --git a/test/unit/misc/passport.unit.js b/test/unit/misc/passport.unit.js deleted file mode 100644 index 6cd1705..0000000 --- a/test/unit/misc/passport.unit.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - Unit tests for the passport library. -*/ - -// Public npm libraries -import { assert } from 'chai' -import sinon from 'sinon' -import passport from 'koa-passport' - -// Local libraries -import User from '../../../src/adapters/localdb/models/users.js' - -import { applyPassportMods, passportCallback } from '../../../config/passport.js' -import adaptersMock from '../mocks/adapters/index.js' - -describe('#passport', () => { - let sandbox - let id - let done - - beforeEach(() => { - sandbox = sinon.createSandbox() - - id = 'abc123' - done = () => {} - }) - - afterEach(() => sandbox.restore()) - - describe('#passportCallback', () => { - it('should return if user is found', () => { - // Mock Users model. - sandbox.stub(User, 'findOne').resolves({ id }) - - passportCallback(id, 'password', done) - }) - - it('should return if password is validated', () => { - // Mock Users model. - sandbox.stub(User, 'findOne').resolves(new adaptersMock.localdb.Users()) - - passportCallback(id, 'password', done) - }) - - it('should catch a high-level error', () => { - // Force an error - sandbox.stub(User, 'findOne').rejects(new Error('test error')) - - passportCallback(id, 'password', done) - }) - }) - - describe('#applyPassportMods', () => { - it('should apply modifications to default passport behavior', () => { - const result = applyPassportMods(passport) - - assert.equal(result, true) - }) - }) - - describe('#serializeUser', () => { - it('should serialize a user', () => { - const user = { - id: 'abc123' - } - const done = () => {} - - applyPassportMods(passport) - - passport.serializeUser(user, done) - }) - }) - - describe('#deserializeUser', () => { - it('should deserialize a user', () => { - // Mock Users model. - sandbox.stub(User, 'findById').resolves({ id }) - - applyPassportMods(passport) - - passport.deserializeUser(id, done) - }) - - it('should catch and handle errors', () => { - // Force an error - sandbox.stub(User, 'findById').rejects(new Error('test error')) - - applyPassportMods(passport) - - passport.deserializeUser(id, done) - }) - }) -}) diff --git a/test/unit/misc/server-unit.js b/test/unit/misc/server-unit.js index 9a49030..41e01fe 100644 --- a/test/unit/misc/server-unit.js +++ b/test/unit/misc/server-unit.js @@ -23,11 +23,9 @@ describe('#server', () => { describe('#startServer', () => { it('should start the server', async () => { // Mock dependencies - sandbox.stub(uut.mongoose, 'connect').resolves() sandbox.stub(uut.controllers, 'initAdapters').resolves() sandbox.stub(uut.controllers, 'initUseCases').resolves() sandbox.stub(uut.controllers, 'attachRESTControllers').resolves() - sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true) sandbox.stub(uut.controllers, 'attachControllers').resolves() sandbox.stub(uut.controllers.adapters.slpIndexer, 'start').resolves() uut.config.env = 'dev' @@ -46,7 +44,7 @@ describe('#server', () => { it('should exit on failure', async () => { // Force an error - sandbox.stub(uut.mongoose, 'connect').rejects(new Error('test error')) + sandbox.stub(uut.controllers, 'initAdapters').rejects(new Error('test error')) // Prevent default behavior of exiting the program. sandbox.stub(uut, 'sleep').resolves() diff --git a/test/unit/use-cases/users.use-case.unit.js b/test/unit/use-cases/users.use-case.unit.js deleted file mode 100644 index a73ed24..0000000 --- a/test/unit/use-cases/users.use-case.unit.js +++ /dev/null @@ -1,416 +0,0 @@ -/* - Unit tests for the src/lib/users.js business logic library. - - TODO: verify that an admin can change the type of a user -*/ - -// 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 UserLib from '../../../src/use-cases/user.js' - -import adapters from '../mocks/adapters/index.js' - -describe('#users-use-case', () => { - let uut - let sandbox - let testUser = {} - - before(async () => { - // Delete all previous users in the database. - // await testUtils.deleteAllUsers() - }) - - beforeEach(() => { - sandbox = sinon.createSandbox() - - uut = new UserLib({ adapters }) - }) - - afterEach(() => sandbox.restore()) - - describe('#constructor', () => { - it('should throw an error if adapters are not passed in', () => { - try { - uut = new UserLib() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include( - err.message, - 'Instance of adapters must be passed in when instantiating User Use Cases library.' - ) - } - }) - }) - - describe('#createUser', () => { - it('should throw an error if no input is given', async () => { - try { - await uut.createUser() - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - // assert.equal(err.status, 422) - assert.include(err.message, "Property 'email' must be a string!") - } - }) - - it('should throw an error if email is not provided', async () => { - try { - await uut.createUser({}) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, "Property 'email' must be a string!") - } - }) - - it('should throw an error if password is not provided', async () => { - try { - const usrObj = { - email: 'test@test.com' - } - - await uut.createUser(usrObj) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, "Property 'password' must be a string!") - } - }) - - it('should throw an error if name is not provided', async () => { - try { - const usrObj = { - email: 'test@test.com', - password: 'password' - } - - await uut.createUser(usrObj) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, "Property 'name' must be a string!") - } - }) - - it('should catch and throw DB errors', async () => { - try { - // Force an error with the database. - sandbox.stub(uut, 'UserModel').throws(new Error('test error')) - - const usrObj = { - email: 'test@test.com', - password: 'password', - name: 'test' - } - - await uut.createUser(usrObj) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'test error') - } - }) - - it('should create a new user in the DB', async () => { - // Note: The user created in this test is used by the getUser, update, - // and delete tests. - - const usrObj = { - email: 'test01@test.com', - password: 'test', - name: 'test01' - } - - const { userData, token } = await uut.createUser(usrObj) - - testUser = userData - - // Commented out because there is some sophisticated mocking required that - // I didn't have time to figure out. -CT 6/11/21 - // Assert that the user model has the expected properties with expected values. - // assert.property(userData, 'type') - // assert.equal(userData.type, 'user') - // assert.property(userData, '_id') - // assert.property(userData, 'email') - // assert.property(userData, 'name') - - // Assert that the JWT token was generated for this user. - assert.isString(token) - assert.include(token, '123') - }) - }) - - describe('#getAllUsers', () => { - it('should return all users from the database', async () => { - await uut.getAllUsers() - // console.log(`users: ${JSON.stringify(users, null, 2)}`) - - // assert.isArray(users) - }) - - it('should catch and throw an error', async () => { - try { - // Force an error. - sandbox.stub(uut.UserModel, 'find').rejects(new Error('test error')) - - await uut.getAllUsers() - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'test error') - } - }) - }) - - describe('#getUser', () => { - it('should throw 422 if no id given.', async () => { - try { - await uut.getUser() - - assert.fail('Unexpected code path.') - } catch (err) { - // console.log(err) - assert.equal(err.status, 422) - assert.include(err.message, 'Unprocessable Entity') - } - }) - - it('should throw 422 for malformed id', async () => { - try { - // Force an error. - sandbox - .stub(uut.UserModel, 'findById') - .rejects(new Error('Unprocessable Entity')) - - const params = { id: 1 } - await uut.getUser(params) - - assert.fail('Unexpected code path.') - } catch (err) { - // console.log(err) - assert.equal(err.status, 422) - assert.include(err.message, 'Unprocessable Entity') - } - }) - - it('should throw 404 if user is not found', async () => { - try { - const params = { id: '5fa4bd7ee1828f5f4d3ed004' } - await uut.getUser(params) - - assert.fail('Unexpected code path.') - } catch (err) { - // console.log(err) - assert.equal(err.status, 404) - assert.include(err.message, 'User not found') - } - }) - - it('should return the user model', async () => { - sandbox.stub(uut.UserModel, 'findById').resolves({ _id: 'abc123' }) - - const params = { id: testUser._id } - const result = await uut.getUser(params) - // console.log('result: ', result) - - // Replace the JSON model with an actual Mongoos model. Used by later - // test cases. - testUser = result - - // Assert that the expected properties for the user model exist. - // assert.property(result, 'type') - assert.property(result, '_id') - // assert.property(result, 'email') - // assert.property(result, 'name') - }) - }) - - describe('#updateUser', () => { - it('should throw an error if no input is given', async () => { - try { - await uut.updateUser() - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, 'Cannot read') - } - }) - - it('should throw an error if email is not a string', async () => { - try { - await uut.updateUser(testUser, { - email: 1234 - }) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, "Property 'email' must be a string!") - } - }) - - it('should throw an error if name is not a string', async () => { - try { - const newData = { - name: 1234 - } - - await uut.updateUser(testUser, newData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, "Property 'name' must be a string!") - } - }) - - it('should throw an error if non-string password given', async () => { - try { - const newData = { - email: 'test@test.com', - name: 'test', - password: 1234 - } - - await uut.updateUser(testUser, newData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, "Property 'password' must be a string!") - } - }) - - it('should throw an error for malformed type given', async () => { - try { - const newData = { - email: 'test@test.com', - password: 'password', - name: 'test', - type: 1234 - } - - await uut.updateUser(testUser, newData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include(err.message, "Property 'type' must be a string!") - } - }) - - it('should throw an error if normal user tries to change themselves into an admin', async () => { - try { - const newData = { - email: 'test@test.com', - password: 'password', - name: 'test', - type: 'admin' - } - - await uut.updateUser(testUser, newData) - - assert.fail('Unexpected code path') - } catch (err) { - // console.log(err) - assert.include( - err.message, - "Property 'type' can only be changed by Admin user" - ) - } - }) - - it('should update the user model', async () => { - const newData = { - email: 'test@test.com', - password: 'password', - name: 'testy tester' - } - testUser.save = async () => {} - - const result = await uut.updateUser(testUser, newData) - - // Assert that expected properties and values exist. - assert.property(result, '_id') - assert.property(result, 'email') - assert.equal(result.email, 'test@test.com') - assert.property(result, 'name') - assert.equal(result.name, 'testy tester') - }) - - // TODO: verify that an admin can change the type of a user - }) - - describe('#authUser', () => { - it('should return a user db model after successful authentication', async () => { - // sandbox.stub(uut.UserModel, 'findOne').resolves(true) - - await uut.authUser('test@test.com', 'password') - // console.log('user: ', user) - - // assert.property(user, '_id') - // assert.property(user, 'email') - // assert.property(user, 'name') - }) - - it('should throw an error if no user matches the login', async () => { - try { - sandbox.stub(uut.UserModel, 'findOne').resolves(false) - - await uut.authUser('noone@nowhere.com', 'password') - // console.log('user: ', user) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'User not found') - } - }) - - it('should throw an error if password does not match', async () => { - try { - // Force authentication to fial. - adapters.localdb.validatePassword = () => { - return false - } - - await uut.authUser('test@test.com', 'badpassword') - // console.log('user: ', user) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'Login credential do not match') - } - }) - }) - - describe('#deleteUser', () => { - it('should throw error if no user provided', async () => { - try { - await uut.deleteUser() - - assert.fail('Unexpected code path.') - } catch (err) { - // console.log(err) - assert.include(err.message, 'Cannot read') - } - }) - - it('should delete the user from the database', async () => { - testUser = new adapters.localdb.Users() - - await uut.deleteUser(testUser) - - assert.isOk('Not throwing an error is a pass!') - }) - }) -}) diff --git a/util/users/createUsers.js b/util/users/createUsers.js deleted file mode 100644 index 3fc75e4..0000000 --- a/util/users/createUsers.js +++ /dev/null @@ -1,38 +0,0 @@ -import mongoose from 'mongoose' -import config from '../../config/index.js' - -const EMAIL = 'test@test.com' -const PASSWORD = 'pass' - -async function addUser () { - // Connect to the Mongo Database. - mongoose.Promise = global.Promise - mongoose.set('useCreateIndex', true) // Stop deprecation warning. - await mongoose.connect( - config.database, - { useNewUrlParser: true, useUnifiedTopology: true } - ) - - const User = require('../../src/models/users') - - const userData = { - email: EMAIL, - password: PASSWORD - } - - const user = new User(userData) - - // Enforce default value of 'user' - user.type = 'user' - - await user.save() - - await mongoose.connection.close() - - console.log(`User ${EMAIL} created.`) -} -addUser() - -export default { - addUser -} diff --git a/util/users/delete-all-test-users.js b/util/users/delete-all-test-users.js deleted file mode 100644 index 53c7d7f..0000000 --- a/util/users/delete-all-test-users.js +++ /dev/null @@ -1,33 +0,0 @@ -import mongoose from 'mongoose' - -// Force test environment -// make sure environment variable is set before this file gets called. -// see test script in package.json. -// process.env.KOA_ENV = 'test' -import config from '../../config/index.js' - -import User from '../../src/models/users/js' - -async function deleteUsers () { - // Connect to the Mongo Database. - mongoose.Promise = global.Promise - mongoose.set('useCreateIndex', true) // Stop deprecation warning. - await mongoose.connect(config.database, { - useUnifiedTopology: true, - useNewUrlParser: true - }) - - // 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() - } - - mongoose.connection.close() -} - -deleteUsers() diff --git a/util/users/getUsers.js b/util/users/getUsers.js deleted file mode 100644 index 56e1109..0000000 --- a/util/users/getUsers.js +++ /dev/null @@ -1,19 +0,0 @@ -import mongoose from 'mongoose' -import config from '../../config/index.js' -import User from '../../src/models/users.js' - -async function getUsers () { - // Connect to the Mongo Database. - mongoose.Promise = global.Promise - mongoose.set('useCreateIndex', true) // Stop deprecation warning. - await mongoose.connect( - config.database, - { useNewUrlParser: true, useUnifiedTopology: true } - ) - - const users = await User.find({}, '-password') - console.log(`users: ${JSON.stringify(users, null, 2)}`) - - mongoose.connection.close() -} -getUsers()