diff --git a/package.json b/package.json index 434ebae..31b2952 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,16 @@ "main": "index.js", "scripts": { "start": "node index.js", - "test": "export KOA_ENV=test && npm run prep-test && nyc --reporter=text mocha --exit --timeout 15000 test/unit/", - "test:unit:lib": "export KOA_ENV=test && npm run prep-test && nyc --reporter=text mocha --exit --timeout 15000 test/unit/biz-logic/", - "test:unit:rest": "export KOA_ENV=test && npm run prep-test && nyc --reporter=text mocha --exit --timeout 15000 test/unit/rest-api/", - "test:e2e:auto": "export KOA_ENV=test && npm run prep-test && nyc --reporter=text mocha --exit --timeout 15000 test/e2e/automated/", + "test": "npm run test:all", + "test:all": "npm run set-env && nyc --reporter=text mocha --exit --timeout 15000 test/unit/biz-logic/ test/unit/rest-api/ test/e2e/automated/", + "test:unit:lib": "npm run set-env && mocha --exit --timeout 15000 test/unit/biz-logic/", + "test:unit:rest": "npm run set-env && mocha --exit --timeout 15000 test/unit/rest-api/", + "test:e2e:auto": "npm run set-env && mocha --exit --timeout 15000 test/e2e/automated/", + "set-env": "export KOA_ENV=test", "lint": "standard --env mocha --fix", "docs": "./node_modules/.bin/apidoc -i src/ -o docs", - "coverage": "export KOA_ENV=test && npm run prep-test && nyc report --reporter=text-lcov | coveralls", - "coverage:report": "export KOA_ENV=test && npm run prep-test && nyc --reporter=html mocha --exit --timeout 15000 test/unit/", - "prep-test": "node util/users/delete-all-test-users.js" + "coverage": "nyc report --reporter=text-lcov | coveralls", + "coverage:report": "npm run set-env && nyc --reporter=html mocha --exit --timeout 15000 test/unit/biz-logic/ test/unit/rest-api/ test/e2e/automated/" }, "keywords": [ "koa-api-boilerplate", diff --git a/src/lib/admin.js b/src/lib/admin.js index e6bfdc8..fbd0964 100644 --- a/src/lib/admin.js +++ b/src/lib/admin.js @@ -49,11 +49,14 @@ class Admin { data: { user: { email: 'system@system.com', - password: context.password + password: context.password, + name: 'admin' } } } const result = await _this.axios.request(options) + // console.log('admin.data: ', result.data) + context.email = result.data.user.email context.id = result.data.user._id context.token = result.data.token @@ -81,6 +84,7 @@ class Admin { // Handle existing system user. if (err.response.status === 422) { try { + console.log('ping03') // Delete the existing user await _this.deleteExistingSystemUser() diff --git a/src/lib/users.js b/src/lib/users.js index bf5d3e9..111aeb1 100644 --- a/src/lib/users.js +++ b/src/lib/users.js @@ -12,6 +12,45 @@ class UserLib { this.UserModel = UserModel } + // Create a new user model and add it to the Mongo database. + async createUser (userObj) { + try { + // Input Validation + if (!userObj.email || typeof userObj.email !== 'string') { + throw new Error("Property 'email' must be a string!") + } + if (!userObj.password || typeof userObj.password !== 'string') { + throw new Error("Property 'password' must be a string!") + } + if (!userObj.name || typeof userObj.name !== 'string') { + throw new Error("Property 'name' must be a string!") + } + + const user = new this.UserModel(userObj) + + // Enforce default value of 'user' + user.type = 'user' + + // Save the new user model to the database. + await user.save() + + // Generate a JWT token for the user. + const token = user.generateToken() + + // Convert the database model to a JSON object. + const userData = user.toJSON() + + // Delete the password property. + delete userData.password + + return { userData, token } + } catch (err) { + // console.log('createUser() error: ', err) + wlogger.error('Error in lib/users.js/createUser()') + throw err + } + } + // Returns an array of all user models in the Mongo database. async getAllUsers () { try { @@ -51,44 +90,6 @@ class UserLib { throw err } } - - // Create a new user model and add it to the Mongo database. - async createUser (userObj) { - try { - // Input Validation - if (!userObj.email || typeof userObj.email !== 'string') { - throw new Error("Property 'email' must be a string!") - } - if (!userObj.password || typeof userObj.password !== 'string') { - throw new Error("Property 'password' must be a string!") - } - if (!userObj.name && typeof userObj.name !== 'string') { - throw new Error("Property 'name' must be a string!") - } - - const user = new this.UserModel(userObj) - - // Enforce default value of 'user' - user.type = 'user' - - // Save the new user model to the database. - await user.save() - - // Generate a JWT token for the user. - const token = user.generateToken() - - // Convert the database model to a JSON object. - const userData = user.toJSON() - - // Delete the password property. - delete userData.password - - return { userData, token } - } catch (err) { - wlogger.error('Error in lib/users.js/createUser()') - throw err - } - } } module.exports = UserLib diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index 1384280..8c5b481 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -9,10 +9,11 @@ const wlogger = require('../../lib/wlogger') let _this class UserController { constructor () { - _this = this - + // Encapsulate dependencies this.User = User this.userLib = new UserLib() + + _this = this } /** @@ -58,35 +59,9 @@ class UserController { try { const userObj = ctx.request.body.user - // /* - // * Input Validation - // */ - // // Required property - // if (!userObj.email || typeof userObj.email !== 'string') { - // throw new Error("Property 'email' must be a string!") - // } - // - // if (!userObj.password || typeof userObj.password !== 'string') { - // throw new Error("Property 'password' must be a string!") - // } - // - // if (userObj.name && typeof userObj.name !== 'string') { - // throw new Error("Property 'name' must be a string!") - // } - // - // const user = new _this.User(userObj) - // - // // Enforce default value of 'user' - // user.type = 'user' - // - // await user.save() - // - // const token = user.generateToken() - // const response = user.toJSON() - // - // delete response.password - - const { userData, token } = this.userLib.createUser(userObj) + const { userData, token } = await _this.userLib.createUser(userObj) + // console.log('userData: ', userData) + // console.log('token: ', token) ctx.body = { user: userData, diff --git a/test/e2e/automated/a01-auth.rest-e2e.js b/test/e2e/automated/a01-auth.rest-e2e.js index 709b5a3..a6a877f 100644 --- a/test/e2e/automated/a01-auth.rest-e2e.js +++ b/test/e2e/automated/a01-auth.rest-e2e.js @@ -1,10 +1,18 @@ -const app = require('../../../bin/server') -const utils = require('../../utils/test-utils') -const config = require('../../../config') -const assert = require('chai').assert +/* + 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 request = supertest.agent(app.listen()) const context = {} @@ -15,12 +23,16 @@ describe('Auth', () => { // 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() + const userObj = { email: 'test@test.com', - password: 'pass' + password: 'pass', + name: 'test' } - const testUser = await utils.createUser(userObj) - console.log('TestUser: ', testUser) + const testUser = await testUtils.createUser(userObj) + // console.log('TestUser: ', testUser) context.user = testUser.user context.token = testUser.token diff --git a/test/e2e/automated/a02-users.rest-e2e.js b/test/e2e/automated/a02-users.rest-e2e.js index 51fdff5..d239866 100644 --- a/test/e2e/automated/a02-users.rest-e2e.js +++ b/test/e2e/automated/a02-users.rest-e2e.js @@ -24,7 +24,8 @@ describe('Users', () => { // Create a second test user. const userObj = { email: 'test2@test.com', - password: 'pass2' + password: 'pass2', + name: 'test2' } const testUser = await testUtils.createUser(userObj) // console.log(`testUser2: ${JSON.stringify(testUser, null, 2)}`) @@ -134,8 +135,9 @@ describe('Users', () => { } await axios(options) - assert(false, 'Unexpected result') + 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") } @@ -148,7 +150,8 @@ describe('Users', () => { data: { user: { email: 'test3@test.com', - password: 'supersecretpassword' + password: 'supersecretpassword', + name: 'test3' } } } @@ -381,443 +384,443 @@ describe('Users', () => { ) }) }) - - 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) - }) - }) + // + // 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) + // }) + // }) }) diff --git a/test/unit/biz-logic/a02-users.lib-unit.js b/test/unit/biz-logic/a02-users.lib-unit.js index 81c47f3..c58aa96 100644 --- a/test/unit/biz-logic/a02-users.lib-unit.js +++ b/test/unit/biz-logic/a02-users.lib-unit.js @@ -9,6 +9,7 @@ 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') @@ -26,6 +27,9 @@ describe('#users', () => { useUnifiedTopology: true, useNewUrlParser: true }) + + // Delete all previous users in the database. + await testUtils.deleteAllUsers() }) beforeEach(() => { diff --git a/test/unit/rest-api/README.md b/test/unit/rest-api/README.md index fe7d1d6..eeab4e9 100644 --- a/test/unit/rest-api/README.md +++ b/test/unit/rest-api/README.md @@ -4,6 +4,6 @@ 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 to network errors? +- 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? diff --git a/test/unit/rest-api/a02-users.rest-unit.js b/test/unit/rest-api/a02-users.rest-unit.js index e6dd2e9..985c2fe 100644 --- a/test/unit/rest-api/a02-users.rest-unit.js +++ b/test/unit/rest-api/a02-users.rest-unit.js @@ -1,16 +1,15 @@ -// const testUtils = require('../../utils/test-utils') +/* + Unit tests for the REST API handler for the /users endpoints. +*/ + +// Public npm libraries const assert = require('chai').assert -const config = require('../../../config') -// const axios = require('axios').default const sinon = require('sinon') const mongoose = require('mongoose') -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// const LOCALHOST = `http://localhost:${config.port}` - -// const context = {} +// Local support libraries +const config = require('../../../config') +const testUtils = require('../../utils/test-utils') const UserController = require('../../../src/modules/users/controller') let uut @@ -29,6 +28,9 @@ describe('Users', () => { 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. @@ -70,6 +72,39 @@ describe('Users', () => { 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') + }) + }) + describe('GET /users', () => { it('should return 422 status on arbitrary biz logic error', async () => { try { diff --git a/test/utils/test-utils.js b/test/utils/test-utils.js index 91cc79a..31b26b0 100644 --- a/test/utils/test-utils.js +++ b/test/utils/test-utils.js @@ -1,7 +1,15 @@ +/* + Utility functions used to prepare the environment for tests. +*/ + +// Public NPM libraries const mongoose = require('mongoose') -const config = require('../../config') 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. @@ -17,6 +25,24 @@ async function cleanDb () { } } +// Delete all users in the database. This ensures there is no previous state +// to confuse tests. +async function deleteAllUsers () { + try { + // Get all the users in the DB. + const users = await User.find({}, '-password') + // console.log(`users: ${JSON.stringify(users, null, 2)}`) + + // Delete each user. + for (let i = 0; i < users.length; i++) { + const thisUser = users[i] + await thisUser.remove() + } + } catch (err) { + console.error('Error in test-utils.js/deleteAllUsers()') + } +} + // This function is used to create new users. // userObj = { // username, @@ -30,7 +56,8 @@ async function createUser (userObj) { data: { user: { email: userObj.email, - password: userObj.password + password: userObj.password, + name: userObj.name } } } @@ -44,7 +71,9 @@ async function createUser (userObj) { return retObj } catch (err) { - console.log('Error in utils.js/createUser(): ' + JSON.stringify(err, null, 2)) + console.log( + 'Error in utils.js/createUser(): ' + JSON.stringify(err, null, 2) + ) throw err } } @@ -72,7 +101,9 @@ async function loginTestUser () { return retObj } catch (err) { - console.log('Error authenticating test user: ' + JSON.stringify(err, null, 2)) + console.log( + 'Error authenticating test user: ' + JSON.stringify(err, null, 2) + ) throw err } } @@ -88,7 +119,8 @@ async function loginAdminUser () { url: `${LOCALHOST}/auth`, data: { email: adminUserData.email, - password: adminUserData.password + password: adminUserData.password, + name: 'admin' } } @@ -104,7 +136,9 @@ async function loginAdminUser () { return retObj } catch (err) { - console.log('Error authenticating test admin user: ' + JSON.stringify(err, null, 2)) + console.log( + 'Error authenticating test admin user: ' + JSON.stringify(err, null, 2) + ) throw err } } @@ -131,5 +165,6 @@ module.exports = { createUser, loginTestUser, loginAdminUser, - getAdminJWT + getAdminJWT, + deleteAllUsers }