Fixing package.json

This commit is contained in:
Chris Troutner
2020-11-23 08:15:39 -08:00
21 changed files with 2695 additions and 2401 deletions
+4 -1
View File
@@ -12,7 +12,10 @@ const cors = require('kcors')
// Local libraries
const config = require('../config') // this first.
const adminLib = require('../src/lib/admin')
const AdminLib = require('../src/lib/admin')
const adminLib = new AdminLib()
const errorMiddleware = require('../src/middleware')
const wlogger = require('../src/lib/wlogger')
+852 -1896
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -8,7 +8,7 @@
"test": "export KOA_ENV=test && npm run prep-test && nyc --reporter=text --timeout 15000 ./node_modules/.bin/mocha --exit",
"lint": "standard --env mocha --fix",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage": "export KOA_ENV=test && npm run prep-test && nyc report --reporter=text-lcov | coveralls",
"coverage:report": "export KOA_ENV=test && npm run prep-test && nyc --reporter=html mocha --exit",
"prep-test": "node util/users/delete-all-test-users.js"
},
@@ -47,7 +47,7 @@
"koa-router": "^9.0.1",
"koa-static": "^5.0.0",
"line-reader": "^0.4.0",
"mongoose": "^5.7.5",
"mongoose": "^5.10.15",
"nodemailer": "^6.4.10",
"passport-local": "^1.0.0",
"winston": "^3.2.1",
@@ -65,7 +65,8 @@
"husky": "^4.2.5",
"mocha": "^7.0.1",
"nyc": "^15.0.0",
"semantic-release": "^17.0.0",
"semantic-release": "^17.2.3",
"sinon": "^9.2.1",
"standard": "^14.3.1"
},
"release": {
+34 -21
View File
@@ -13,8 +13,9 @@
const axios = require('axios').default
const mongoose = require('mongoose')
const User = require('../models/users')
const jsonFiles = require('./utils/json-files')
const config = require('../../config')
const JsonFiles = require('./utils/json-files')
const jsonFiles = new JsonFiles()
const JSON_FILE = `system-user-${config.env}.json`
const JSON_PATH = `${__dirname}/../../config/${JSON_FILE}`
@@ -22,13 +23,25 @@ const JSON_PATH = `${__dirname}/../../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 function createSystemUser () {
async createSystemUser () {
// Create the system user.
try {
context.password = _randomString(20)
context.password = _this._randomString(20)
const options = {
method: 'POST',
@@ -40,18 +53,17 @@ async function createSystemUser () {
}
}
}
const result = await axios(options)
const result = await _this.axios.request(options)
context.email = result.data.user.email
context.id = result.data.user._id
context.token = result.data.token
// Get the mongoDB entry
const user = await User.findById(context.id)
const user = await _this.User.findById(context.id)
// Change the user type to admin
user.type = 'admin'
// console.log(`user: ${JSON.stringify(user, null, 2)}`)
// console.log(`user created: ${JSON.stringify(user, null, 2)}`)
// Save the user model.
await user.save()
@@ -61,6 +73,7 @@ async function createSystemUser () {
// 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)
return context
@@ -69,58 +82,60 @@ async function createSystemUser () {
if (err.response.status === 422) {
try {
// Delete the existing user
await deleteExistingSystemUser()
await _this.deleteExistingSystemUser()
// Call this function again.
return createSystemUser()
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: ' + JSON.stringify(err, null, 2))
console.log('Error in admin.js/createSystemUser: ')
// process.end(1)
throw err
}
}
}
async function deleteExistingSystemUser () {
async deleteExistingSystemUser () {
try {
mongoose.Promise = global.Promise
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
await mongoose.connect(config.database, { useNewUrlParser: true, useUnifiedTopology: true })
await User.deleteOne({ email: 'system@system.com' })
await _this.User.deleteOne({ email: 'system@system.com' })
} catch (err) {
console.log('Error in admin.js/deleteExistingSystemUser()')
throw err
}
}
async function loginAdmin () {
async loginAdmin () {
// console.log(`loginAdmin() running.`)
let existingUser
try {
// Read the exising file
existingUser = await jsonFiles.readJSON(JSON_PATH)
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 axios(options)
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().')
@@ -131,7 +146,7 @@ async function loginAdmin () {
}
}
function _randomString (length) {
_randomString (length) {
var text = ''
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
for (var i = 0; i < length; i++) {
@@ -139,8 +154,6 @@ function _randomString (length) {
}
return text
}
module.exports = {
createSystemUser,
loginAdmin
}
module.exports = Admin
+18 -18
View File
@@ -17,6 +17,20 @@ class NodeMailer {
this.config = config
_this = this
_this.transporter = _this.createTransporter()
}
createTransporter () {
const transporter = _this.nodemailer.createTransport({
host: _this.config.emailServer,
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: _this.config.emailUser, // generated ethereal user
pass: _this.config.emailPassword // generated ethereal password
}
})
return transporter
}
// Validate email
@@ -44,10 +58,7 @@ class NodeMailer {
throw new Error("Property 'to' must be a array!")
}
const isEmailList = await _this.validateEmailArray(data.to)
if (!isEmailList) {
throw new Error("Property 'to' must be array of email format!")
}
await _this.validateEmailArray(data.to)
if (!data.formMessage || typeof data.formMessage !== 'string') {
throw new Error("Property 'message' must be a string!")
@@ -61,18 +72,6 @@ class NodeMailer {
throw new Error("Property 'payloadTitle' must be a string!")
}
// create reusable transporter object using the default SMTP transport
const transporter = await _this.nodemailer.createTransport({
host: _this.config.emailServer,
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: _this.config.emailUser, // generated ethereal user
pass: _this.config.emailPassword // generated ethereal password
}
})
// console.log(`transporter: ${JSON.stringify(transporter)}`)
const msg = data.formMessage.replace(/(\r\n|\n|\r)/g, '<br />')
const now = new Date()
@@ -115,7 +114,7 @@ class NodeMailer {
${htmlData}
</p>`
// send mail with defined transport object
const info = await transporter.sendMail({
const info = await _this.transporter.sendMail({
// from: `${data.email}`, // sender address
from: 'noreply@launchpadip.net',
to: `${to}`, // list of receivers
@@ -125,8 +124,9 @@ class NodeMailer {
html: htmlMsg
})
console.log('Message sent: %s', info.messageId)
return info
} catch (err) {
console.log('Error in sendEmail()')
wlogger.error('Error in lib/nodemailer.js/sendEmail()')
throw err
}
}
+30 -16
View File
@@ -1,39 +1,55 @@
/*
A utility file for reading and writing JSON files.
*/
'use strict'
const fs = require('fs')
let _this
class JsonFiles {
constructor () {
this.fs = fs
_this = this
}
// Writes out a JSON file of any object passed to the function.
// This is used for testing.
function writeJSON (obj, fileName) {
writeJSON (obj, fileName) {
return new Promise(function (resolve, reject) {
try {
if (!obj) {
throw new Error('obj property is required')
}
if (!fileName || typeof fileName !== 'string') {
throw new Error('fileName property must be a string')
}
const fileStr = JSON.stringify(obj, null, 2)
fs.writeFile(fileName, fileStr, function (err) {
_this.fs.writeFile(fileName, fileStr, function (err) {
if (err) {
console.error('Error while trying to write file: ', err)
return reject(err)
console.error('Error while trying to write file: ')
throw err
} else {
// console.log(`${fileName} written successfully!`)
return resolve()
}
})
} catch (err) {
console.error('Error trying to write out object in util.js/_writeJSON().', err)
console.error('Error trying to write out object in util.js/_writeJSON().')
return reject(err)
}
})
}
// Read and parse a JSON file.
function readJSON (fileName) {
readJSON (fileName) {
return new Promise(function (resolve, reject) {
try {
fs.readFile(fileName, (err, data) => {
if (!fileName || typeof fileName !== 'string') {
throw new Error('fileName property must be a string')
}
_this.fs.readFile(fileName, (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
console.log('Admin .json file not found!')
@@ -41,7 +57,7 @@ function readJSON (fileName) {
console.log(`err: ${JSON.stringify(err, null, 2)}`)
}
return reject(err)
throw err
}
const obj = JSON.parse(data)
@@ -49,13 +65,11 @@ function readJSON (fileName) {
return resolve(obj)
})
} catch (err) {
console.error('Error trying to read JSON file in util.js/_readJSON().', err)
console.error('Error trying to read JSON file in util.js/_readJSON().')
return reject(err)
}
})
}
module.exports = {
writeJSON,
readJSON
}
module.exports = JsonFiles
+39 -17
View File
@@ -4,9 +4,22 @@ const getToken = require('../lib/auth')
const jwt = require('jsonwebtoken')
const wlogger = require('../lib/wlogger')
async function ensureUser (ctx, next) {
let _this
class Validators {
constructor () {
this.User = User
this.getToken = getToken
this.jwt = jwt
this.config = config
_this = this
}
async ensureUser (ctx, next) {
try {
// console.log(`getToken: ${typeof (getToken)}`)
const token = getToken(ctx)
const token = _this.getToken(ctx)
if (!token) {
// console.log(`Err: Token not provided.`)
@@ -17,26 +30,30 @@ async function ensureUser (ctx, next) {
try {
// console.log(`token: ${JSON.stringify(token, null, 2)}`)
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = jwt.verify(token, config.token)
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
}
ctx.state.user = await User.findById(decoded.id, '-password')
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
}
return next()
} catch (error) {
ctx.throw(401)
}
}
// This funciton is almost identical to ensureUser, except at the end, it verifies
// that the 'type' associated with the user equals 'admin'.
async function ensureAdmin (ctx, next) {
async ensureAdmin (ctx, next) {
try {
// console.log(`getToken: ${typeof (getToken)}`)
const token = getToken(ctx)
const token = _this.getToken(ctx)
if (!token) {
// console.log(`Err: Token not provided.`)
@@ -47,13 +64,13 @@ async function ensureAdmin (ctx, next) {
try {
// console.log(`token: ${JSON.stringify(token, null, 2)}`)
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = jwt.verify(token, config.token)
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
}
ctx.state.user = await User.findById(decoded.id, '-password')
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
@@ -64,6 +81,9 @@ async function ensureAdmin (ctx, next) {
}
return next()
} catch (error) {
ctx.throw(401, error.message)
}
}
// This middleware ensures that the :id used in the API endpoint matches the
@@ -71,9 +91,10 @@ async function ensureAdmin (ctx, next) {
// an Admin user. This prevents situations like users updating other users
// profiles or non-admins deleting users.
// TODO Tests must be developed before developing this function.
async function ensureTargetUserOrAdmin (ctx, next) {
async ensureTargetUserOrAdmin (ctx, next) {
try {
// console.log(`getToken: ${typeof (getToken)}`)
const token = getToken(ctx)
const token = _this.getToken(ctx)
if (!token) {
// console.log(`Err: Token not provided.`)
@@ -88,13 +109,13 @@ async function ensureTargetUserOrAdmin (ctx, next) {
try {
// console.log(`token: ${JSON.stringify(token, null, 2)}`)
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = jwt.verify(token, config.token)
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
}
ctx.state.user = await User.findById(decoded.id, '-password')
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
@@ -102,6 +123,7 @@ async function ensureTargetUserOrAdmin (ctx, next) {
// 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: ${
@@ -118,10 +140,10 @@ async function ensureTargetUserOrAdmin (ctx, next) {
}
return next()
} catch (error) {
ctx.throw(401, error.message)
}
}
}
module.exports = {
ensureUser,
ensureAdmin,
ensureTargetUserOrAdmin
}
module.exports = Validators
+6 -4
View File
@@ -65,11 +65,13 @@ class Contact {
success: true
}
} catch (err) {
ctx.body = {
success: false
}
// ctx.body = {
// success: false
// }
// console.error(`Error: `, err)
throw err
// throw err
ctx.throw(422, err.message)
}
}
}
+31 -16
View File
@@ -8,6 +8,9 @@ let _this
class LogsApi {
constructor () {
_this = this
_this.fs = fs
_this.lineReader = lineReader
_this.config = config
}
/**
@@ -56,20 +59,18 @@ class LogsApi {
// console.log(`password: ${password}`)
// Password matches the password set in the config file.
if (password === config.logPass) {
if (password === _this.config.logPass) {
// Generate the full path and file name for the current log file.
const fullPath = _this.generateFileName()
// console.log(`fullPath: ${JSON.stringify(fullPath, null, 2)}`)
// Throw an error if the file does not exist.
fs.stat(fullPath, (err, stat) => {
if (err) {
if (!_this.fs.existsSync(fullPath)) {
ctx.body = {
success: false,
data: 'file does not exist'
}
}
})
} else {
// Read in the data from the log file.
const data = await _this.readLines(fullPath)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
@@ -81,6 +82,7 @@ class LogsApi {
success: true,
data: filteredData
}
}
// Password does not match password in config file.
} else {
@@ -89,21 +91,23 @@ class LogsApi {
}
}
} catch (err) {
if (err) {
if (err && err.message) {
ctx.throw(500, err.message)
} else {
ctx.throw(500, 'Unhandled error')
console.log('unhandled error: ', err)
}
}
}
// Sorts the log data by their timestamp. Returns the LIMIT or less elements.
filterLogs (data) {
filterLogs (data, LIMIT = 100) {
try {
if (!Array.isArray(data)) {
throw new Error('Data must be array')
}
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
const LIMIT = 100 // Max number of entries to return.
// const LIMIT = 100 // Max number of entries to return.
// Sort the elements by date.
data.sort(function (a, b) {
@@ -142,7 +146,7 @@ class LogsApi {
const thisYear = now.getFullYear()
const filename = `koa-${
config.env
_this.config.env
}-${thisYear}-${thisMonth}-${thisDate}.log`
// console.log(`filename: ${filename}`)
const logDir = `${__dirname}/../../../logs/`
@@ -157,23 +161,33 @@ class LogsApi {
}
// Promise based read-file
readFile (path, opts = 'utf8') {
/* readFile (path, opts = 'utf8') {
return new Promise((resolve, reject) => {
fs.readFile(path, opts, (err, data) => {
_this.fs.readFile(path, opts, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
} */
// Returns an array with each element containing a line of the file.
readLines (filename) {
return new Promise((resolve, reject) => {
try {
if (!filename || typeof filename !== 'string') {
throw new Error('filename must be a string')
}
// Throw an error if the file does not exist.
if (!_this.fs.existsSync(filename)) {
throw new Error('file does not exist')
}
const data = []
// let i = 0
lineReader.eachLine(filename, function (line, last) {
_this.lineReader.eachLine(filename, function (line, last) {
try {
data.push(JSON.parse(line))
@@ -183,7 +197,8 @@ class LogsApi {
if (last) return resolve(data)
} catch (err) {
console.log('err: ', err)
// console.log('err: ', err)
if (last) return resolve(data)
}
})
} catch (err) {
+15 -7
View File
@@ -47,15 +47,14 @@ class UserController {
* }
*/
async createUser (ctx) {
const user = new _this.User(ctx.request.body.user)
const userObj = ctx.request.body.user
try {
/*
* ERROR HANDLERS
*
*/
// Required property
if (!user.email || typeof user.email !== 'string') {
if (!userObj.email || typeof userObj.email !== 'string') {
throw new Error("Property 'email' must be a string!")
}
@@ -66,14 +65,15 @@ class UserController {
// throw new Error("Property 'email' must be email format!")
// }
if (!user.password || typeof user.password !== 'string') {
if (!userObj.password || typeof userObj.password !== 'string') {
throw new Error("Property 'password' must be a string!")
}
if (user.name && typeof user.name !== 'string') {
if (userObj.name && typeof userObj.name !== 'string') {
throw new Error("Property 'name' must be a string!")
}
const user = new _this.User(userObj)
// Enforce default value of 'user'
user.type = 'user'
@@ -160,6 +160,7 @@ class UserController {
*
* @apiUse TokenError
*/
async getUser (ctx, next) {
try {
const user = await _this.User.findById(ctx.params.id, '-password')
@@ -171,12 +172,18 @@ class UserController {
user
}
} catch (err) {
if (err === 404 || err.name === 'CastError') {
// Handle different error types.
if (
err === 404 ||
err.name === 'CastError' ||
err.message.toString().includes('Not Found')
) {
ctx.throw(404)
}
ctx.throw(500)
}
if (next) {
return next()
}
@@ -239,7 +246,8 @@ class UserController {
if (userObj.email && typeof userObj.email !== 'string') {
throw new Error("Property 'email' must be a string!")
}
const isEmail = await _this.validateEmail(user.email)
const isEmail = await _this.validateEmail(userObj.email)
if (userObj.email && !isEmail) {
throw new Error("Property 'email' must be email format!")
}
+3 -1
View File
@@ -1,4 +1,6 @@
const validator = require('../../middleware/validators')
const VALIDATOR = require('../../middleware/validators')
const validator = new VALIDATOR()
const CONTROLLER = require('./controller')
const controller = new CONTROLLER()
+324 -131
View File
@@ -2,6 +2,7 @@ const testUtils = require('./utils')
const assert = require('chai').assert
const config = require('../config')
const axios = require('axios').default
const sinon = require('sinon')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
@@ -10,6 +11,12 @@ const LOCALHOST = `http://localhost:${config.port}`
const context = {}
const UserController = require('../src/modules/users/controller')
let uut
let sandbox
const mockContext = require('./mocks/ctx-mock').context
describe('Users', () => {
before(async () => {
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
@@ -38,6 +45,14 @@ describe('Users', () => {
// console.log(`admin: ${JSON.stringify(admin, null, 2)}`)
})
beforeEach(() => {
uut = new UserController()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('POST /users', () => {
it('should reject signup when data is incomplete', async () => {
try {
@@ -49,11 +64,11 @@ describe('Users', () => {
}
}
const result = await axios(options)
await axios(options)
console.log(
/* console.log(
`result stringified: ${JSON.stringify(result.data, null, 2)}`
)
) */
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 422, 'Error code 422 expected.')
@@ -128,6 +143,28 @@ describe('Users', () => {
}
})
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(false, 'Unexpected result')
} catch (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',
@@ -251,6 +288,22 @@ describe('Users', () => {
assert.hasAnyKeys(users[0], ['type', '_id', 'email'])
assert.isNumber(users.length)
})
it('should catch and handle errors', async () => {
try {
// Force an error
sandbox.stub(uut.User, 'find').rejects(new Error('test error'))
// Mock the context object.
const ctx = mockContext()
await uut.getUsers(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'Not Found')
}
})
})
describe('GET /users/:id', () => {
@@ -321,6 +374,40 @@ describe('Users', () => {
'Password property should not be returned'
)
})
it('should catch and handle errors', async () => {
try {
// Force an error
sandbox.stub(uut.User, 'findById').rejects(new Error('test error'))
// Mock the context object.
const ctx = mockContext()
await uut.getUser(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'Internal Server Error')
}
})
it('should handle user not found', async () => {
try {
// Force an error
sandbox.stub(uut.User, 'findById').resolves(false)
// Mock the context object.
const ctx = mockContext()
ctx.params = { id: 1 }
await uut.getUser(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert.include(err.message, 'Not Found')
}
})
})
describe('PUT /users/:id', () => {
@@ -362,6 +449,240 @@ describe('Users', () => {
}
})
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: {
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: {}
}
}
}
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
}
}
}
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 project property is not array', 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: {
projects: 'projects'
}
}
}
await axios(options)
assert.equal(true, false, 'Unexpected behavior')
} catch (err) {
assert.equal(err.response.status, 422)
assert.include(
err.response.data,
"Property 'projects' must be a Array!"
)
}
})
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 if email is wrong format', 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: 'badEmailFormat'
}
}
}
await axios(options)
} catch (err) {
assert.equal(err.response.status, 422)
assert.include(
err.response.data,
"Property 'email' must be email format!"
)
}
})
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
}
}
}
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'
}
}
}
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
@@ -435,134 +756,6 @@ describe('Users', () => {
assert.equal(user.email, 'testToUpdate@test.com')
assert.equal(user.username, 'myUsername')
})
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: {
name: 'new name',
type: 'test'
}
}
}
const result = 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'
}
}
}
const result = 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 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'
}
}
}
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 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: {}
}
}
}
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 email is wrong format', 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: 'badEmailFormat'
}
}
}
await axios(options)
} catch (err) {
assert.equal(err.response.status, 422)
assert.include(err.response.data, 'not a valid Email format')
}
})
})
describe('DELETE /users/:id', () => {
+51 -18
View File
@@ -1,9 +1,23 @@
const assert = require('chai').assert
const NodeMailer = require('../src/lib/nodemailer')
const nodemailer = new NodeMailer()
const sinon = require('sinon')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
let sandbox
let uut
describe('NodeMailer', () => {
beforeEach(() => {
uut = new NodeMailer()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('sendEmail()', () => {
it('should throw error if email property is not provided', async () => {
try {
@@ -13,7 +27,7 @@ describe('NodeMailer', () => {
subject: 'test subject',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'email\' must be a string!')
@@ -28,7 +42,7 @@ describe('NodeMailer', () => {
subject: 'test subject',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'email\' must be email format!')
@@ -42,7 +56,7 @@ describe('NodeMailer', () => {
subject: 'test subject',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'message\' must be a string!')
@@ -55,7 +69,7 @@ describe('NodeMailer', () => {
name: 'test name',
subject: 'test subject'
}
await nodemailer.sendEmail(data)
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'to\' must be a array!')
@@ -71,7 +85,7 @@ describe('NodeMailer', () => {
subject: 'test subject',
to: ['test']
}
await nodemailer.sendEmail(data)
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Array must contain emails format!')
@@ -86,7 +100,7 @@ describe('NodeMailer', () => {
name: 'test name',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'subject\' must be a string!')
@@ -101,34 +115,53 @@ describe('NodeMailer', () => {
subject: 'test subject',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'payloadTitle\' must be a string!')
}
})
it('should throw error if payloadTitle property is not string', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com'],
payloadTitle: true
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'payloadTitle\' must be a string!')
}
})
/* it('should send email', async () => {
it('should send email', async () => {
try {
sandbox.stub(uut.transporter, 'sendMail').resolves({ messageId: 'messageId' })
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
to: 'test2@email.com',
to: ['test2@email.com'],
subject: 'test subject',
payloadTitle: 'test title'
}
await nodemailer.sendEmail(data)
assert(false, 'Unexpected result')
const info = await uut.sendEmail(data)
assert.isObject(info)
assert.isString(info.messageId)
} catch (err) {
assert.include(err.message, 'Property \'subject\' must be a string!')
assert(false, 'Unexpected result')
}
}) */
})
})
describe('validateEmailArray()', () => {
it('should throw error if email list is not provided ', async () => {
try {
await nodemailer.validateEmailArray()
await uut.validateEmailArray()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'emailList\' must be a array!')
@@ -137,7 +170,7 @@ describe('NodeMailer', () => {
it('should throw error if email list is empty', async () => {
try {
const emailList = []
await nodemailer.validateEmailArray(emailList)
await uut.validateEmailArray(emailList)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'emailList\' cant be empty!')
@@ -149,7 +182,7 @@ describe('NodeMailer', () => {
'wrongEmail',
'bad format'
]
await nodemailer.validateEmailArray(emailList)
await uut.validateEmailArray(emailList)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Array must contain emails format!')
@@ -161,7 +194,7 @@ describe('NodeMailer', () => {
'test@email.com',
'simple@email.com'
]
const result = await nodemailer.validateEmailArray(emailList)
const result = await uut.validateEmailArray(emailList)
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
+142 -17
View File
@@ -1,13 +1,27 @@
const config = require('../config')
const axios = require('axios').default
const assert = require('chai').assert
const sinon = require('sinon')
// Mock data
// const mockData = require('./mocks/contact-mocks')
const LOCALHOST = `http://localhost:${config.port}`
const mockContext = require('./mocks/ctx-mock').context
const ContactController = require('../src/modules/contact/controller')
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 {
@@ -28,10 +42,11 @@ describe('Contact', () => {
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 500)
assert.equal(err.response.status, 422)
assert.include(err.response.data, "Property 'email' must be a string!")
}
})
it('should throw error if email property is wrong format', async () => {
try {
const options = {
@@ -52,13 +67,14 @@ describe('Contact', () => {
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 500)
assert.equal(err.response.status, 422)
assert.include(
err.response.data,
"Property 'email' must be email format!"
)
}
})
it('should throw error if formMessage property is not provided', async () => {
try {
const options = {
@@ -78,13 +94,14 @@ describe('Contact', () => {
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 500)
assert.equal(err.response.status, 422)
assert.include(
err.response.data,
"Property 'message' must be a string!"
)
}
})
it('should throw error if payloadTitle property is not provided', async () => {
try {
const options = {
@@ -105,19 +122,15 @@ describe('Contact', () => {
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 500)
assert.equal(err.response.status, 422)
assert.include(
err.response.data,
"Property 'payloadTitle' must be a string!"
)
}
})
/* I presented problems here trying to use sandbox in this test */
/* it('should send email with all input', async () => {
// Mock live network calls.
sandbox.stub(
contactController.transporter, 'sendMail')
.resolves(mockData)
it('should throw error if payloadTitle property is not string', async () => {
try {
const options = {
method: 'POST',
@@ -125,18 +138,130 @@ describe('Contact', () => {
data: {
obj: {
email: 'email@email.com',
formMessage: 'test email',
name: 'test name',
payloadtitle: 'test title'
formMessage: 'test message',
payloadTitle: 1
}
}
}
const result = await axios(options)
console.log('RESULT', result.data)
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 'payloadTitle' 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',
payloadTitle: 'title',
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',
payloadTitle: 'title',
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.nodemailer, 'sendEmail').resolves(true)
// Mock the context object.
const ctx = mockContext()
ctx.request = {
body: {
obj: {
email: 'email@email.com',
formMessage: 'test message',
payloadTitle: 'title'
}
}
}
await uut.email(ctx)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should send email with all input', async () => {
try {
// Mock live network calls.
sandbox.stub(uut.nodemailer, 'sendEmail').resolves(true)
// Mock the context object.
const ctx = mockContext()
ctx.request = {
body: {
obj: {
email: 'email@email.com',
formMessage: 'test message',
payloadTitle: 'title',
emailList: ['email@email.com']
}
}
}
await uut.email(ctx)
} catch (err) {
// console.log(err)
assert(false, 'Unexpected result')
}
}) */
})
})
})
+18 -12
View File
@@ -1,17 +1,24 @@
const assert = require('chai').assert
const PassportLib = require('../src/lib/passport')
describe('#passport.js', () => {
let passportLib
const sinon = require('sinon')
beforeEach(async () => {
passportLib = new PassportLib()
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 passportLib.authUser()
await uut.authUser()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'ctx is required')
@@ -20,17 +27,16 @@ describe('#passport.js', () => {
it('Should throw error if the passport library fails', async () => {
try {
// This is a mock to handle the callback error
// when the passport library fails or throws error
const error = new Error('cant auth user')
const user = null
const authMock = (value, callback) => {
callback(error, user)
}
passportLib.passport.authenticate = authMock
// Mock calls
// https://sinonjs.org/releases/latest/stubs/
// About yields
sandbox.stub(uut.passport, 'authenticate').yields(error, user)
const ctx = {}
await passportLib.authUser(ctx)
await uut.authUser(ctx)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'cant auth user')
+251
View File
@@ -0,0 +1,251 @@
const config = require('../config')
const assert = require('chai').assert
const axios = require('axios').default
const sinon = require('sinon')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const LOCALHOST = `http://localhost:${config.port}`
const LogsController = require('../src/modules/logapi/controller')
const mockContext = require('./mocks/ctx-mock').context
const mockData = require('./mocks/log-api-mock')
const context = {}
let sandbox
let uut
describe('LogsApi', () => {
beforeEach(() => {
uut = new LogsController()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('POST /logapi', () => {
it('should return false if password is not provided', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/logapi`,
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}/logapi`,
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, 'generateFileName').resolves('bad router')
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.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.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')
}
})
})
describe('#filterLogs()', () => {
it('should throw error if data is not provided', async () => {
try {
await uut.filterLogs()
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'Data must be array')
}
})
it('should throw error if data provided is not an array', async () => {
try {
const data = 'data'
await uut.filterLogs(data)
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'Data must be array')
}
})
it('should sort the log data', async () => {
try {
const data = mockData.data
const result = await uut.filterLogs(data)
assert.isArray(result)
assert.property(result[1], 'message')
assert.property(result[1], 'level')
assert.property(result[1], 'timestamp')
} catch (err) {
assert.fail('Unexpected result')
}
})
it('should sort the log data with a limit', async () => {
try {
const data = mockData.data
const limit = 1
const result = await uut.filterLogs(data, limit)
assert.isArray(result)
assert.equal(result.length, limit)
assert.property(result[0], 'message')
assert.property(result[0], 'level')
assert.property(result[0], 'timestamp')
} catch (err) {
assert.fail('Unexpected result')
}
})
})
describe('#generateFileName()', () => {
it('should return file name', async () => {
try {
const fileName = await uut.generateFileName()
assert.isString(fileName)
context.fileName = fileName
} catch (err) {
assert.fail('Unexpected result')
}
})
it('should throw error if something fails', async () => {
try {
uut.config = null
await uut.generateFileName()
assert.fail('Unexpected result')
} catch (err) {
assert.exists(err)
assert.isString(err.message)
}
})
})
describe('#readLines()', () => {
it('should throw error if fileName is not provided', async () => {
try {
await uut.readLines()
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'filename must be a string')
}
})
it('should throw error if fileName provided is not string', async () => {
try {
const fileName = true
await uut.readLines(fileName)
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'filename must be a string')
}
})
it('should throw error if the file does not exist', async () => {
try {
const fileName = 'test/logs/'
await uut.readLines(fileName)
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'file does not exist')
}
})
it('should ignore fileReader callback errors', async () => {
try {
// https://sinonjs.org/releases/latest/stubs/
// About yields
sandbox.stub(uut.lineReader, 'eachLine').yieldsRight({}, true)
const fileName = context.fileName
const result = await uut.readLines(fileName)
assert.isArray(result)
} catch (err) {
assert.fail('Unexpected result')
}
})
it('should return data', async () => {
try {
const fileName = context.fileName
const result = await uut.readLines(fileName)
assert.isArray(result)
assert.property(result[1], 'message')
assert.property(result[1], 'level')
assert.property(result[1], 'timestamp')
} catch (err) {
assert.fail('Unexpected result')
}
})
})
})
+139
View File
@@ -0,0 +1,139 @@
const assert = require('chai').assert
const fs = require('fs')
const sinon = require('sinon')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const JsonFiles = require('../src/lib/utils/json-files')
const JSON_FILE = 'test-json-file.json'
const JSON_PATH = `${__dirname}/${JSON_FILE}`
const deleteFile = filepath => {
try {
// Delete state if exist
fs.unlinkSync(filepath)
} catch (error) {}
}
let sandbox
let uut
describe('JsonFiles', () => {
const obj = {
json: 'file'
}
beforeEach(() => {
uut = new JsonFiles()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
after(() => {
deleteFile(JSON_PATH)
})
describe('writeJSON()', () => {
it('should throw error if inputs is not provided', async () => {
try {
await uut.writeJSON()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'obj property is required')
}
})
it('should throw error if filename property is not provided', async () => {
try {
await uut.writeJSON(obj)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'fileName property must be a string')
}
})
it('should throw error if filename property is not string', async () => {
try {
await uut.writeJSON(obj, 1)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'fileName property must be a string')
}
})
it('should throw error if fs library return an error', async () => {
try {
// https://sinonjs.org/releases/latest/stubs/
// About yields
sandbox.stub(uut.fs, 'writeFile').yields(new Error('test error'))
await uut.writeJSON(obj, JSON_PATH)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should write a json file', async () => {
try {
await uut.writeJSON(obj, JSON_PATH)
assert.isTrue(fs.existsSync(JSON_PATH))
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('readJSON()', () => {
it('should throw error if filename property is not provided', async () => {
try {
await uut.readJSON(obj)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'fileName property must be a string')
}
})
it('should throw error if filename property is not string', async () => {
try {
await uut.readJSON(obj, 1)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'fileName property must be a string')
}
})
it('should throw error if fs library return an error', async () => {
try {
// https://sinonjs.org/releases/latest/stubs/
// About yields
sandbox.stub(uut.fs, 'readFile').yields(new Error('test error'))
await uut.readJSON(JSON_PATH)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should throw error if file not found', async () => {
try {
const testError = new Error('test error')
testError.code = 'ENOENT'
sandbox.stub(uut.fs, 'readFile').yields(testError)
await uut.readJSON(JSON_PATH)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should read a json file', async () => {
try {
const result = await uut.readJSON(JSON_PATH)
const objKeys = Object.keys(obj)
const resultKeys = Object.keys(result)
assert.isObject(result)
assert.equal(objKeys.length, resultKeys.length)
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
})
+317
View File
@@ -0,0 +1,317 @@
const assert = require('chai').assert
const testUtils = require('./utils')
const Validators = require('../src/middleware/validators')
const sinon = require('sinon')
const mockContext = require('./mocks/ctx-mock').context
const util = require('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: 'test2@test.com',
password: 'pass2'
}
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 trigger the "next" function if user is admin', async () => {
try {
// Mock the context object.
const ctx = mockContext()
ctx.params = { id: context.id }
ctx.request = {
header: {
authorization: `Bearer ${context.adminJWT}`
}
}
// Function that execute if the validations
// are successful
const next = () => { return 'next function' }
const result = await uut.ensureUser(ctx, next)
assert.isString(result)
assert.equal(result, 'next function')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
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 trigger the "next" function if user is admin', async () => {
try {
// Mock the context object.
const ctx = mockContext()
ctx.request = {
header: {
authorization: `Bearer ${context.adminJWT}`
}
}
// Function that execute if the validations
// are successful
const next = () => { return 'next function' }
const result = await uut.ensureAdmin(ctx, next)
assert.isString(result)
assert.equal(result, 'next function')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
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 trigger the "next" function if user is admin', async () => {
try {
// Mock the context object.
const ctx = mockContext()
ctx.params = { id: context.id }
ctx.request = {
header: {
authorization: `Bearer ${context.adminJWT}`
}
}
// Function that execute if the validations
// are successful
const next = () => { return 'next function' }
const result = await uut.ensureTargetUserOrAdmin(ctx, next)
assert.isString(result)
assert.equal(result, 'next function')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
})
+116
View File
@@ -0,0 +1,116 @@
const assert = require('chai').assert
const Admin = require('../src/lib/admin')
const sinon = require('sinon')
const util = require('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')
}
})
})
})
+50
View File
@@ -0,0 +1,50 @@
// Ripped from https://github.com/koajs/koa/blob/master/test/helpers/context.js
// Solution courtesy of user @fl0w. See: https://github.com/koajs/koa/issues/999#issuecomment-309270599
// Take from this gist: https://gist.github.com/emmanuelnk/f1254eed8f947a81e8d715476d9cc92c
// if you want more comprehensive Koa Context object to test stuff like Cookies etc
// then use https://www.npmjs.com/package/@shopify/jest-koa-mocks (requires Jest)
// INSTRUCTIONS:
// Import in test file as below:
//
// const mockContext = require('./mocks/ctx-mock').context
// const ctx = mockContext()
// ...
const Stream = require('stream')
const Koa = require('koa')
const context = (req, res, app) => {
const socket = new Stream.Duplex()
req = Object.assign(
{ headers: {}, socket },
Stream.Readable.prototype,
req || {}
)
res = Object.assign(
{ _headers: {}, socket },
Stream.Writable.prototype,
res || {}
)
req.socket.remoteAddress = req.socket.remoteAddress || '127.0.0.1'
app = app || new Koa()
res.getHeader = k => res._headers[k.toLowerCase()]
res.setHeader = (k, v) => (res._headers[k.toLowerCase()] = v)
res.removeHeader = (k, v) => delete res._headers[k.toLowerCase()]
const retApp = app.createContext(req, res)
return retApp
}
const request = (req, res, app) => context(req, res, app).request
const response = (req, res, app) => context(req, res, app).response
module.exports = {
context,
request,
response
}
+28
View File
@@ -0,0 +1,28 @@
// Mocks representing an array of logs for the
// Unit tests of logapi
const data = [
{
message: 'Error in lib/nodemailer.js/validateEmailArray()',
level: 'error',
timestamp: '2020-11-14T12:15:55.230Z'
},
{
message: 'Error in lib/nodemailer.js/validateEmailArray()',
level: 'error',
timestamp: '2020-11-14T12:15:55.231Z'
},
{
message: 'Error in lib/nodemailer.js/validateEmailArray()',
level: 'error',
timestamp: '2020-11-14T12:15:55.230Z'
},
{
message: 'Error in lib/nodemailer.js/validateEmailArray()',
level: 'error',
timestamp: '2020-11-14T12:15:55.231Z'
}
]
module.exports = {
data
}