Porting code from chris-troutner to PSF GitHub Repo

This commit is contained in:
Chris Troutner
2021-04-09 08:39:53 -07:00
commit 10f028175f
88 changed files with 31642 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
# 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).
+119
View File
@@ -0,0 +1,119 @@
/*
End-to-end tests for /auth endpoints.
This test sets up the environment for other e2e tests.
*/
// Public npm libraries
const assert = require('chai').assert
const axios = require('axios').default
// Local support libraries
const config = require('../../../config')
const app = require('../../../bin/server')
const testUtils = require('../../utils/test-utils')
const AdminLib = require('../../../src/lib/admin')
const adminLib = new AdminLib()
// const request = supertest.agent(app.listen())
const context = {}
const LOCALHOST = `http://localhost:${config.port}`
describe('Auth', () => {
before(async () => {
// This should be the first instruction. It starts the REST API server.
await app.startServer()
// 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
})
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
}
})
})
})
+786
View File
@@ -0,0 +1,786 @@
const testUtils = require('../../utils/test-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 }
const LOCALHOST = `http://localhost:${config.port}`
const context = {}
const UserController = require('../../../src/modules/users/controller')
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: ${admi nJWT}`)
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 UserController()
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)
/* 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.')
}
})
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.userLib, '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) {
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)
})
})
})
+178
View File
@@ -0,0 +1,178 @@
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('../../unit/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 {
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')
}
})
})
})
+126
View File
@@ -0,0 +1,126 @@
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('../../unit/mocks/ctx-mock').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.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')
}
})
})
})
+8
View File
@@ -0,0 +1,8 @@
# Unit Tests
Unit tests are defined as testing the smallest possible unit of a function. They also do not make any live network calls.
Unit tests are broken up by directory:
- [biz-logic](./biz-logic) tests the business logic libraries.
- [rest-api](./rest-api) tests the REST API specific handling of the router.
- json-rpc (coming soon) tests the JSON-RPC routing using ipfs-coord library.
+3
View File
@@ -0,0 +1,3 @@
# Business Logic Unit Tests
The unit tests in this directly are concerned with business logic libraries in the /src/lib folder. These are the methods that should be triggered by REST API endpoints. These tests are not concerned with the handling of the REST API request/response, but by the code that is triggered by those endpoints. It also tests any business logic that is not directly associated with a REST API endpoint.
+396
View File
@@ -0,0 +1,396 @@
/*
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
const mongoose = require('mongoose')
const assert = require('chai').assert
const sinon = require('sinon')
// Local support libraries
const config = require('../../../config')
const testUtils = require('../../utils/test-utils')
// Unit under test (uut)
const UserLib = require('../../../src/lib/users')
describe('#users', () => {
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
}
)
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
})
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new UserLib()
})
afterEach(() => sandbox.restore())
after(() => {
mongoose.connection.close()
})
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, 'Cannot read property')
}
})
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
// 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, 'eyJ')
})
})
describe('#getAllUsers', () => {
it('should return all users from the database', async () => {
const users = 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 {
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 () => {
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 property')
}
})
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'
}
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 () => {
const user = 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 {
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 {
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 property')
}
})
it('should delete the user from the database', async () => {
await uut.deleteUser(testUser)
assert.isOk('Not throwing an error is a pass!')
})
})
})
@@ -0,0 +1,230 @@
/*
Unit tests for the nodemailer.js library.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const NodeMailer = require('../../../src/lib/nodemailer')
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 {
const data = {
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'email' must be a string!")
}
})
it('should throw error if formMessage property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'formMessage' must be a string!")
}
})
it('should throw error if <to> property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
name: 'test name',
subject: 'test subject'
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'to' must be a array!")
}
})
it('should throw error if <to> is wrong type', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: 'test'
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'to' must be a array!")
}
})
it('should throw error if subject Property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'subject' must be a string!")
}
})
it('should send email with default html data', async () => {
try {
sandbox
.stub(uut.transporter, 'sendMail')
.resolves({ messageId: 'messageId' })
const data = {
email: 'test@email.com',
formMessage: 'test msg',
to: ['test2@email.com'],
subject: 'test subject'
}
const info = await uut.sendEmail(data)
assert.isObject(info)
assert.isString(info.messageId)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should send email if htmlData is provided', 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'],
subject: 'test subject',
htmlData: '<p> Unit test </p>'
}
const info = await uut.sendEmail(data)
assert.isObject(info)
assert.isString(info.messageId)
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('validateEmailArray()', () => {
it('should throw error if email list is not provided ', async () => {
try {
await uut.validateEmailArray()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'emailList' must be a array!")
}
})
it('should throw error if email list is empty', async () => {
try {
const emailList = []
await uut.validateEmailArray(emailList)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'emailList' cant be empty!")
}
})
it('should return true ', async () => {
try {
const emailList = ['test@email.com', 'simple@email.com']
const result = await uut.validateEmailArray(emailList)
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('getHtmlFromObject()', () => {
it('should throw error if the input is not provided ', async () => {
try {
await uut.getHtmlFromObject()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'objectData' must be a object!")
}
})
it('should throw error if the object is empty', async () => {
try {
await uut.getHtmlFromObject({})
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'subject' must be a string!")
}
})
it('should throw error if "formMessage" property is not provided', async () => {
try {
const obj = {
subject: 'unit'
}
await uut.getHtmlFromObject(obj)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'formMessage' must be a string!")
}
})
it('should return the html', async () => {
try {
const obj = {
subject: 'unit ',
formMessage: 'test',
value1: 'value1',
value2: 'value2',
value3: 'value3'
}
const result = await uut.getHtmlFromObject(obj)
assert.isString(result)
assert.include(result, '<p>', 'expect html tag')
assert.include(result, '</p>', 'expect html tag')
assert.include(
result,
'value1',
'Expect value 1 is included in the html'
)
assert.include(result, 'value2', 'expect is included in the html')
assert.include(result, 'value3', 'expect is included in the html')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
})
+121
View File
@@ -0,0 +1,121 @@
const assert = require('chai').assert
const sinon = require('sinon')
const ContactLib = require('../../../src/lib/contact')
let uut
let sandbox
describe('Contact', () => {
beforeEach(() => {
uut = new ContactLib()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('sendEmail()', () => {
it('should throw error if email property is not provided', async () => {
try {
const data = {
formMessage: 'test msg'
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'email' must be a string!")
}
})
it('should throw error if formMessage property is not provided', async () => {
try {
const data = {
email: 'test@email.com'
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'formMessage' must be a string!")
}
})
it('should throw error if email list provided is not a array', async () => {
try {
sandbox.stub(uut.nodemailer, 'sendEmail').resolves(true)
const data = {
formMessage: 'test msg',
email: 'test@email.com',
emailList: 'test@email.com'
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'emailList' must be a array of emails!")
}
})
it('should throw error if email list provided is a empty array', async () => {
try {
sandbox.stub(uut.nodemailer, 'sendEmail').resolves(true)
const data = {
formMessage: 'test msg',
email: 'test@email.com',
emailList: []
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'emailList' must be a array of emails!")
}
})
it('should send email to default server email', async () => {
try {
sandbox.stub(uut.nodemailer, 'sendEmail').resolves(true)
const data = {
formMessage: 'test msg',
email: 'test@email.com'
}
const result = await uut.sendEmail(data)
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should catch and throw nodemailer lib error', async () => {
try {
// Force an error with the database.
sandbox.stub(uut.nodemailer, 'sendEmail').throws(new Error('test error'))
const data = {
formMessage: 'test msg',
email: 'test@email.com'
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should send email to specifics email list', async () => {
try {
sandbox.stub(uut.nodemailer, 'sendEmail').resolves(true)
const data = {
formMessage: 'test msg',
email: 'test@email.com',
emailList: ['testcontact@email.com']
}
const result = await uut.sendEmail(data)
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
})
@@ -0,0 +1,46 @@
const assert = require('chai').assert
const PassportLib = require('../../../src/lib/passport')
const sinon = require('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')
}
})
})
})
+231
View File
@@ -0,0 +1,231 @@
const assert = require('chai').assert
const sinon = require('sinon')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const LogsApiLib = require('../../../src/lib/logapi')
const mockData = require('../mocks/log-api-mock')
const context = {}
let sandbox
let uut
describe('#LogsApiLib', () => {
beforeEach(() => {
uut = new LogsApiLib()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('#getLogs()', () => {
it('should return false if password is not provided', async () => {
try {
const result = await uut.getLogs()
assert.property(result, 'success')
assert.isFalse(result.success)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should return log', async () => {
try {
const pass = 'test'
const result = await uut.getLogs(pass)
// console.log('result', result)
assert.isTrue(result.success)
assert.isArray(result.data)
assert.property(result.data[0], 'message')
assert.property(result.data[0], 'level')
assert.property(result.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 password = 'test'
const result = await uut.getLogs(password)
// console.log(result)
assert.isFalse(result.success)
assert.include(result.data, 'file does not exist')
} catch (err) {
console.log('ERRROR', 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'))
const password = 'test'
await uut.getLogs(password)
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('Unhandled error'))
const password = 'test'
await uut.getLogs(password)
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')
}
})
})
})
+31
View File
@@ -0,0 +1,31 @@
/*
Unit tests for the rpc/index.js library.
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
const JSONRPC = require('../../../src/rpc')
describe('#JSON RPC', () => {
let uut
beforeEach(() => {
uut = new JSONRPC()
})
describe('#router', () => {
it('should do something', async () => {
// const request = {
// 'users', // id
// 'getAll', // method
// {}
// }
const json = jsonrpc.request('users', 'getAll', {})
const str = JSON.stringify(json)
await uut.router(str)
})
})
})
+107
View File
@@ -0,0 +1,107 @@
/*
Unit tests for the rpc/index.js library.
*/
// Public npm libraries
const assert = require('chai').assert
const jsonrpc = require('jsonrpc-lite')
const sinon = require('sinon')
const { v4: uid } = require('uuid')
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries.
const JSONRPC = require('../../../src/rpc')
describe('#JSON RPC', () => {
let uut
let sandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new JSONRPC()
})
afterEach(() => sandbox.restore())
describe('#router', () => {
it('should exit quietly if given a random string', async () => {
const str = 'random string message'
await uut.router(str)
assert.isOk('Not throwing an error is a pass.')
})
it('should exit quietly if invalid JSON RPC message received', async () => {
const malformedRpc = '{"jsonrpc":"2.0"}'
await uut.router(malformedRpc, 'peerA')
assert.isOk('Not throwing an error is a pass.')
})
it('should return default response if routing is not possible', async () => {
const id = uid()
const json = jsonrpc.request(id, 'unknownMethod', {})
const str = JSON.stringify(json)
const result = await uut.router(str, 'peerA')
// console.log('result: ', result)
const jsonObj = jsonrpc.parse(result.retStr)
// console.log(`jsonObj: ${JSON.stringify(jsonObj, null, 2)}`)
// Assert the expected properties exist on the returned object.
assert.property(jsonObj, 'payload')
assert.property(jsonObj, 'type')
assert.property(jsonObj.payload, 'jsonrpc')
assert.property(jsonObj.payload, 'id')
assert.property(jsonObj.payload, 'result')
assert.property(jsonObj.payload.result, 'reciever')
assert.property(jsonObj.payload.result.value, 'success')
assert.property(jsonObj.payload.result.value, 'message')
// Assert the expected values exist.
assert.equal(jsonObj.payload.id, id)
assert.equal(jsonObj.payload.result.value.success, false)
assert.equal(jsonObj.payload.result.value.status, 422)
assert.equal(
jsonObj.payload.result.value.message,
'Input does not match routing rules.'
)
})
it('should catch and handle errors', async () => {
// Force an error
sandbox.stub(uut.jsonrpc, 'parse').throws(new Error('test error'))
const malformedRpc = '{"jsonrpc":"2.0"}'
await uut.router(malformedRpc, 'peerA')
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)
})
})
})
+187
View File
@@ -0,0 +1,187 @@
/*
Unit tests for the rpc/auth/index.js file.
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
const mongoose = require('mongoose')
const sinon = require('sinon')
const assert = require('chai').assert
const { v4: uid } = require('uuid')
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries
const config = require('../../../config')
const AuthRPC = require('../../../src/rpc/auth')
const UserLib = require('../../../src/lib/users')
const userLib = new UserLib()
describe('#AuthRPC', () => {
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
}
)
// Create a test user.
testUser = await userLib.createUser({
email: 'test543@test.com',
name: 'tester543',
password: 'password'
})
})
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new AuthRPC()
})
afterEach(() => sandbox.restore())
after(async () => {
// Delete the test user.
testUser = await userLib.getUser({ id: testUser.userData._id })
await userLib.deleteUser(testUser)
mongoose.connection.close()
})
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)
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)
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)
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')
})
})
})
+270
View File
@@ -0,0 +1,270 @@
/*
Unit tests for the JSON RPC validator middleware.
TODO: ensureTargetUserOrAdmin: it should exit quietly if user is an admin.
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
const mongoose = require('mongoose')
const sinon = require('sinon')
const assert = require('chai').assert
const { v4: uid } = require('uuid')
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries
const config = require('../../../config')
const Validators = require('../../../src/rpc/validators')
const UserLib = require('../../../src/lib/users')
const userLib = new UserLib()
describe('#validators', () => {
let testUser
let uut
let sandbox
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
}
)
// Create a test user.
testUser = await userLib.createUser({
email: 'test544@test.com',
name: 'tester544',
password: 'password'
})
// console.log('testUser: ', testUser)
})
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new Validators()
})
afterEach(() => sandbox.restore())
after(async () => {
// Delete the test user.
testUser = await userLib.getUser({ id: testUser.userData._id })
await userLib.deleteUser(testUser)
mongoose.connection.close()
})
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: testUser.token
})
const jsonStr = JSON.stringify(userCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const user = await uut.ensureUser(rpcData)
// console.log('user: ', user)
assert.property(user, 'type')
assert.property(user, '_id')
assert.property(user, 'email')
assert.property(user, 'name')
})
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)
// 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: testUser.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, '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: testUser.token,
userId: testUser.userData._id.toString()
})
const jsonStr = JSON.stringify(userCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const user = await uut.ensureTargetUserOrAdmin(rpcData)
assert.property(user, 'type')
assert.property(user, '_id')
assert.property(user, 'email')
assert.property(user, 'name')
})
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: testUser.token
})
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: testUser.userData._id.toString()
})
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 {
// Force an error
sandbox.stub(uut.UserModel, 'findById').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: 'deleteUser',
apiToken: testUser.token,
userId: testUser.userData._id.toString()
})
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, 'test error')
}
})
// TODO: it should exit quietly if user is an admin.
})
})
+378
View File
@@ -0,0 +1,378 @@
/*
Unit tests for the rpc/users/index.js file.
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
const mongoose = require('mongoose')
const sinon = require('sinon')
const assert = require('chai').assert
const { v4: uid } = require('uuid')
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries
const config = require('../../../config')
const UserRPC = require('../../../src/rpc/users')
const UserModel = require('../../../src/models/users')
describe('#UserRPC', () => {
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
}
)
})
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new UserRPC()
})
afterEach(() => sandbox.restore())
after(() => {
mongoose.connection.close()
})
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)
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)
const result = await uut.userRouter(rpcData)
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: testUser.token,
userId: testUser.userData._id
})
const jsonStr = JSON.stringify(userCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
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)
const result = await uut.userRouter(rpcData)
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: testUser.token,
userId: testUser.userData._id
})
const jsonStr = JSON.stringify(userCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
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)
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')
assert.isArray(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(),
name: 'test777'
})
const jsonStr = JSON.stringify(userCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const result = await uut.updateUser(rpcData, testUserModel)
// 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 property')
})
})
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: testUser.userData._id.toString()
})
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 property')
})
})
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({}, testUserModel)
// 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 by not specifying an user ID.
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')
})
})
})
+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
}
+103
View File
@@ -0,0 +1,103 @@
const app = require('../../bin/server')
const utils = require('./utils')
const config = require('../../config')
const assert = require('chai').assert
const axios = require('axios').default
// const request = supertest.agent(app.listen())
const context = {}
const LOCALHOST = `http://localhost:${config.port}`
describe('Auth', () => {
before(async () => {
// await utils.cleanDb() // This should be first instruction.
await app.startServer() // This should be second instruction.
const userObj = {
email: 'test@test.com',
password: 'pass'
}
const testUser = await utils.createUser(userObj)
console.log(`TestUser : ${testUser}`)
context.user = testUser.user
context.token = testUser.token
})
describe('POST /auth', () => {
it('should throw 401 if credentials are incorrect', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'test@test.com',
password: 'wrongpassword'
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
console.log(
`result stringified: ${JSON.stringify(result.data, null, 2)}`
)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
}
})
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
}
})
})
})
+851
View File
@@ -0,0 +1,851 @@
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 }
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)}`)
// 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.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(() => {
uut = new UserController()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('POST /users', () => {
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)
/* 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.')
}
})
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 email property provided in wrong format', async () => {
// try {
// const options = {
// method: 'POST',
// url: `${LOCALHOST}/users`,
// data: {
// user: {
// email: 'badEmailFormat',
// password: 'test'
// }
// }
// }
// await axios(options)
//
// assert(false, 'Unexpected result')
// } catch (err) {
// assert.equal(err.response.status, 422)
// assert.include(
// err.response.data,
// "Property 'email' must be email format"
// )
// }
// })
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(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',
url: `${LOCALHOST}/users`,
data: {
user: {
email: 'test3@test.com',
password: 'supersecretpassword'
}
}
}
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 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', () => {
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/1`,
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 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'
)
})
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', () => {
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: {
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
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)
})
})
})
+204
View File
@@ -0,0 +1,204 @@
const assert = require('chai').assert
const NodeMailer = require('../../src/lib/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 {
const data = {
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'email\' must be a string!')
}
})
it('should throw error if email property is wrong format', async () => {
try {
const data = {
email: 'test',
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'email\' must be email format!')
}
})
it('should throw error if formMessage property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'message\' must be a string!')
}
})
it('should throw error if <to> property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
name: 'test name',
subject: 'test subject'
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'to\' must be a array!')
}
})
it('should throw error if <to> is wrong format', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Array must contain emails format!')
}
})
it('should throw error if subject Property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'subject\' must be a string!')
}
})
it('should throw error if payloadTitle property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
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 () => {
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'],
subject: 'test subject',
payloadTitle: 'test title'
}
const info = await uut.sendEmail(data)
assert.isObject(info)
assert.isString(info.messageId)
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('validateEmailArray()', () => {
it('should throw error if email list is not provided ', async () => {
try {
await uut.validateEmailArray()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'emailList\' must be a array!')
}
})
it('should throw error if email list is empty', async () => {
try {
const emailList = []
await uut.validateEmailArray(emailList)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'emailList\' cant be empty!')
}
})
it('should throw error if email list contain wrong format', async () => {
try {
const emailList = [
'wrongEmail',
'bad format'
]
await uut.validateEmailArray(emailList)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Array must contain emails format!')
}
})
it('should return true if email list contain email format', async () => {
try {
const emailList = [
'test@email.com',
'simple@email.com'
]
const result = await uut.validateEmailArray(emailList)
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
})
+267
View File
@@ -0,0 +1,267 @@
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 {
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 email property is wrong format', async () => {
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/contact/email`,
data: {
obj: {
email: 'email',
formMessage: 'test 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 email format!"
)
}
})
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 'message' must be a string!"
)
}
})
it('should throw error if payloadTitle property is not provided', async () => {
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/contact/email`,
data: {
obj: {
email: 'email@email.com',
formMessage: 'test 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 'payloadTitle' must be a string!"
)
}
})
it('should throw error if payloadTitle property is not string', async () => {
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/contact/email`,
data: {
obj: {
email: 'email@email.com',
formMessage: 'test message',
payloadTitle: 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 '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) {
assert(false, 'Unexpected result')
}
})
})
})
+46
View File
@@ -0,0 +1,46 @@
const assert = require('chai').assert
const PassportLib = require('../../src/lib/passport')
const sinon = require('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')
}
})
})
})
+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.toString()}/${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')
}
})
})
})
+135
View File
@@ -0,0 +1,135 @@
const mongoose = require('mongoose')
const config = require('../../config')
const axios = require('axios').default
const LOCALHOST = `http://localhost:${config.port}`
// Remove all collections from the DB.
async function cleanDb () {
for (const collection in mongoose.connection.collections) {
const collections = mongoose.connection.collections
if (collections.collection) {
// const thisCollection = mongoose.connection.collections[collection]
// console.log(`thisCollection: ${JSON.stringify(thisCollection, null, 2)}`)
await collection.deleteMany()
}
}
}
// This function is used to create new users.
// userObj = {
// username,
// password
// }
async function createUser (userObj) {
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/users`,
data: {
user: {
email: userObj.email,
password: userObj.password
}
}
}
const result = await axios(options)
const retObj = {
user: result.data.user,
token: result.data.token
}
return retObj
} catch (err) {
console.log('Error in utils.js/createUser(): ' + JSON.stringify(err, null, 2))
throw err
}
}
async function loginTestUser () {
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)}`)
const retObj = {
token: result.data.token,
user: result.data.user.username,
id: result.data.user._id.toString()
}
return retObj
} catch (err) {
console.log('Error authenticating test user: ' + JSON.stringify(err, null, 2))
throw err
}
}
async function loginAdminUser () {
try {
const FILENAME = `../../config/system-user-${config.env}.json`
const adminUserData = require(FILENAME)
console.log(`adminUserData: ${JSON.stringify(adminUserData, null, 2)}`)
const options = {
method: 'POST',
url: `${LOCALHOST}/auth`,
data: {
email: adminUserData.email,
password: adminUserData.password
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
const retObj = {
token: result.data.token,
user: result.data.user.username,
id: result.data.user._id.toString()
}
return retObj
} catch (err) {
console.log('Error authenticating test admin user: ' + JSON.stringify(err, null, 2))
throw err
}
}
// Retrieve the admin user JWT token from the JSON file it's saved at.
async function getAdminJWT () {
try {
// process.env.KOA_ENV = process.env.KOA_ENV || 'dev'
// console.log(`env: ${process.env.KOA_ENV}`)
const FILENAME = `../../config/system-user-${config.env}.json`
const adminUserData = require(FILENAME)
// console.log(`adminUserData: ${JSON.stringify(adminUserData, null, 2)}`)
return adminUserData.token
} catch (err) {
console.error('Error in test/utils.js/getAdminJWT()')
throw err
}
}
module.exports = {
cleanDb,
createUser,
loginTestUser,
loginAdminUser,
getAdminJWT
}
+9
View File
@@ -0,0 +1,9 @@
# REST API Unit Tests
The tests in this directory are unit tests of REST API. These tests are not
concerned with the business logic behind the endpoints. They are only concerned
with the handling of the REST API endpoint. These tests answer questions like:
- Is the endpoint responding properly when the business logic throws an error?
- When returning an error, is it returning the proper HTTP response?
- When returning success, is it returning the correct payload?
+254
View File
@@ -0,0 +1,254 @@
/*
Unit tests for the REST API handler for the /users endpoints.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const mongoose = require('mongoose')
// Local support libraries
const config = require('../../../config')
const testUtils = require('../../utils/test-utils')
const User = require('../../../src/models/users')
const UserController = require('../../../src/modules/users/controller')
let uut
let sandbox
let ctx
const mockContext = require('../../unit/mocks/ctx-mock').context
describe('Users', () => {
let testUser = {}
before(async () => {
// 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
})
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
// 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.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(() => {
uut = new UserController()
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = mockContext()
})
afterEach(() => sandbox.restore())
after(() => {
mongoose.connection.close()
})
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 property')
}
})
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.userLib, '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.userLib, '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.userLib, '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.userLib, '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 property')
}
})
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: existingUser
}
ctx.request.body = {
user: testUser
}
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 property')
}
})
it('should return 200 status on success', async () => {
// Replace the testUser variable with an actual model from the DB.
const existingUser = await User.findById(testUser._id)
ctx.body = {
user: existingUser
}
await uut.deleteUser(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
})
@@ -0,0 +1,62 @@
/*
Unit tests for the REST API handler for the /users endpoints.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const ContactController = require('../../../src/modules/contact/controller')
let uut
let sandbox
let ctx
const mockContext = require('../../unit/mocks/ctx-mock').context
describe('Contact', () => {
before(async () => {
})
beforeEach(() => {
uut = new ContactController()
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#POST /contact', () => {
it('should return 422 status on biz logic error', async () => {
try {
await uut.email(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.contactLib, 'sendEmail').resolves(true)
ctx.request.body = {
email: 'test02@test.com',
formMessage: 'test'
}
await uut.email(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, 'success')
assert.isTrue(ctx.response.body.success)
})
})
})
@@ -0,0 +1,75 @@
/*
Unit tests for the REST API handler for the /users endpoints.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const LogsApiController = require('../../../src/modules/logapi/controller')
let uut
let sandbox
let ctx
const mockContext = require('../../unit/mocks/ctx-mock').context
describe('Logapi', () => {
before(async () => {
})
beforeEach(() => {
uut = new LogsApiController()
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#POST /logapi', () => {
it('should return 422 status on biz logic error', async () => {
try {
await uut.getLogs(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
}
})
it('should return 500 status on biz logic Unhandled error', async () => {
try {
// eslint-disable
sandbox.stub(uut.logsApiLib, 'getLogs').returns(Promise.reject(new Error()))
ctx.request.body = {
password: 'test'
}
await uut.getLogs(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 500)
assert.include(err.message, 'Unhandled error')
}
})
it('should return 200 status on success', async () => {
ctx.request.body = {
password: 'test'
}
await uut.getLogs(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, 'success')
assert.isTrue(ctx.response.body.success)
})
})
})
+170
View File
@@ -0,0 +1,170 @@
/*
Utility functions used to prepare the environment for tests.
*/
// Public NPM libraries
const mongoose = require('mongoose')
const axios = require('axios').default
// Local libraries
const config = require('../../config')
const User = require('../../src/models/users')
const LOCALHOST = `http://localhost:${config.port}`
// Remove all collections from the DB.
async function cleanDb () {
for (const collection in mongoose.connection.collections) {
const collections = mongoose.connection.collections
if (collections.collection) {
// const thisCollection = mongoose.connection.collections[collection]
// console.log(`thisCollection: ${JSON.stringify(thisCollection, null, 2)}`)
await collection.deleteMany()
}
}
}
// Delete all users in the database. This ensures there is no previous state
// to confuse tests.
async function deleteAllUsers () {
try {
// Get all the users in the DB.
const users = await User.find({}, '-password')
// console.log(`users: ${JSON.stringify(users, null, 2)}`)
// Delete each user.
for (let i = 0; i < users.length; i++) {
const thisUser = users[i]
await thisUser.remove()
}
} catch (err) {
console.error('Error in test-utils.js/deleteAllUsers()')
}
}
// This function is used to create new users.
// userObj = {
// username,
// password
// }
async function createUser (userObj) {
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/users`,
data: {
user: {
email: userObj.email,
password: userObj.password,
name: userObj.name
}
}
}
const result = await axios(options)
const retObj = {
user: result.data.user,
token: result.data.token
}
return retObj
} catch (err) {
console.log(
'Error in utils.js/createUser(): ' + JSON.stringify(err, null, 2)
)
throw err
}
}
async function loginTestUser () {
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)}`)
const retObj = {
token: result.data.token,
user: result.data.user.username,
id: result.data.user._id.toString()
}
return retObj
} catch (err) {
console.log(
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
)
throw err
}
}
async function loginAdminUser () {
try {
const FILENAME = `../../config/system-user-${config.env}.json`
const adminUserData = require(FILENAME)
console.log(`adminUserData: ${JSON.stringify(adminUserData, null, 2)}`)
const options = {
method: 'POST',
url: `${LOCALHOST}/auth`,
data: {
email: adminUserData.email,
password: adminUserData.password,
name: 'admin'
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
const retObj = {
token: result.data.token,
user: result.data.user.username,
id: result.data.user._id.toString()
}
return retObj
} catch (err) {
console.log(
'Error authenticating test admin user: ' + JSON.stringify(err, null, 2)
)
throw err
}
}
// Retrieve the admin user JWT token from the JSON file it's saved at.
async function getAdminJWT () {
try {
// process.env.KOA_ENV = process.env.KOA_ENV || 'dev'
// console.log(`env: ${process.env.KOA_ENV}`)
const FILENAME = `../../config/system-user-${config.env}.json`
const adminUserData = require(FILENAME)
// console.log(`adminUserData: ${JSON.stringify(adminUserData, null, 2)}`)
return adminUserData.token
} catch (err) {
console.error('Error in test/utils.js/getAdminJWT()')
throw err
}
}
module.exports = {
cleanDb,
createUser,
loginTestUser,
loginAdminUser,
getAdminJWT,
deleteAllUsers
}