diff --git a/requirements/logincontroller/getUser-usecase.md b/requirements/logincontroller/getUser-usecase.md new file mode 100644 index 000000000..aa1dfd50c --- /dev/null +++ b/requirements/logincontroller/getUser-usecase.md @@ -0,0 +1,9 @@ +Check mark: ✅ +Cross Mark: ❌ + +# GetUser + +> ## Positive case + +1. ❌ Receives a POST request in the **/api/userProfile** route +2. ✅ Returns **200**, with the requestor body diff --git a/requirements/logincontroller/login-usecase.md b/requirements/logincontroller/login-usecase.md new file mode 100644 index 000000000..6c0188caf --- /dev/null +++ b/requirements/logincontroller/login-usecase.md @@ -0,0 +1,21 @@ +Check mark: ✅ +Cross Mark: ❌ + +# login + +> ## Positive case + +1. ❌ Receives a POST request in the **/api/userProfile** route +2. ✅ Returns 200, if the user is a new user and there is a password match +3. ✅ Returns 200, if the user already exists and the password is a match + +## Negative case + +1. ✅ Returns error 400 if there is no email or password +2. ✅ Returns error 403 if there is no user +3. ✅ Returns error 403 if the user exists but is not active +4. ✅ Returns error 403 if the password is not a match and if the user already exists - in progress + +## Edge case + +1. ✅ Returns the error if the try block fails - in progress \ No newline at end of file diff --git a/src/controllers/logincontroller.js b/src/controllers/logincontroller.js index 809c5892f..3ba0203aa 100644 --- a/src/controllers/logincontroller.js +++ b/src/controllers/logincontroller.js @@ -23,7 +23,10 @@ const logincontroller = function () { if (!user) { res.status(403).send({ message: 'Username not found.' }); } else if (user.isActive === false) { - res.status(403).send({ message: 'Sorry, this account is no longer active. If you feel this is in error, please contact your Manager and/or Administrator.' }); + res.status(403).send({ + message: + 'Sorry, this account is no longer active. If you feel this is in error, please contact your Manager and/or Administrator.', + }); } else { let isPasswordMatch = false; let isNewUser = false; @@ -34,42 +37,42 @@ const logincontroller = function () { isPasswordMatch = await bcrypt.compare(_password, user.password); if (!isPasswordMatch && user.resetPwd !== '') { - isPasswordMatch = (_password === user.resetPwd); + isPasswordMatch = _password === user.resetPwd; isNewUser = true; } - if (isNewUser && isPasswordMatch) { - const result = { - new: true, - userId: user._id, - }; - res.status(200).send(result); - } else if (isPasswordMatch && !isNewUser) { - const jwtPayload = { - userid: user._id, - role: user.role, - permissions: user.permissions, - access: { - canAccessBMPortal: false, - }, - email: user.email, - expiryTimestamp: moment().add(config.TOKEN.Lifetime, config.TOKEN.Units), - }; + if (isNewUser && isPasswordMatch) { + const result = { + new: true, + userId: user._id, + }; + res.status(200).send(result); + } else if (isPasswordMatch && !isNewUser) { + const jwtPayload = { + userid: user._id, + role: user.role, + permissions: user.permissions, + access: { + canAccessBMPortal: false, + }, + email: user.email, + expiryTimestamp: moment().add(config.TOKEN.Lifetime, config.TOKEN.Units), + }; - const token = jwt.sign(jwtPayload, JWT_SECRET); + const token = jwt.sign(jwtPayload, JWT_SECRET); - res.status(200).send({ token }); - } else { - res.status(403).send({ - message: 'Invalid password.', - }); - } + res.status(200).send({ token }); + } else { + res.status(403).send({ + message: 'Invalid password.', + }); + } } - } catch (err) { - console.log(err); - res.json(err); - } -}; + } catch (err) { + console.log(err); + res.json(err); + } + }; const getUser = function (req, res) { const { requestor } = req.body; @@ -78,7 +81,6 @@ const logincontroller = function () { }; return { - login, getUser, }; diff --git a/src/controllers/logincontroller.spec.js b/src/controllers/logincontroller.spec.js new file mode 100644 index 000000000..595bfe77b --- /dev/null +++ b/src/controllers/logincontroller.spec.js @@ -0,0 +1,211 @@ +const path = require('path'); +require('dotenv').config({ path: path.resolve(__dirname, '../../.env') }); +const bcrypt = require('bcryptjs'); +const logincontroller = require('./logincontroller'); +const { mockReq, mockRes, assertResMock, mockUser } = require('../test'); +const userProfile = require('../models/userProfile'); + +const makeSut = () => { + const { login, getUser } = logincontroller(); + return { + login, + getUser, + }; +}; + +describe('logincontroller module', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('login', () => { + test('Ensure login returns error 400 if there is no email or password', async () => { + const { login } = makeSut(); + const mockReqModified = { + ...mockReq, + ...{ + body: { + email: '', + password: '', + }, + }, + }; + const res = await login(mockReqModified, mockRes); + assertResMock(400, { error: 'Invalid request' }, res, mockRes); + }); + + test('Ensure login returns error 403 if there is no user', async () => { + const { login } = makeSut(); + const mockReqModified = { + ...mockReq, + ...{ + body: { + email: 'example@test.com', + password: 'exampletest', + }, + }, + }; + const findOneSpy = jest + .spyOn(userProfile, 'findOne') + .mockImplementation(() => Promise.resolve(null)); + + const res = await login(mockReqModified, mockRes); + expect(findOneSpy).toHaveBeenCalledWith({ email: mockReqModified.body.email }); + assertResMock(403, { message: 'Username not found.' }, res, mockRes); + }); + + test('Ensure login returns error 403 if the user exists but is not active', async () => { + const { login } = makeSut(); + const mockReqModified = { + ...mockReq, + ...{ + body: { + email: 'example@test.com', + password: 'exampletest', + }, + }, + }; + const mockUserModified = { + ...mockUser, + ...{ + isActive: false, + }, + }; + + const findOneSpy = jest + .spyOn(userProfile, 'findOne') + .mockImplementation(() => Promise.resolve(mockUserModified)); + + const res = await login(mockReqModified, mockRes); + expect(findOneSpy).toHaveBeenCalledWith({ email: mockReqModified.body.email }); + assertResMock( + 403, + { + message: + 'Sorry, this account is no longer active. If you feel this is in error, please contact your Manager and/or Administrator.', + }, + res, + mockRes, + ); + }); + + test('Ensure login returns error 403 if the password is not a match and if the user already exists', async () => { + const { login } = makeSut(); + const mockReqModified = { + ...mockReq, + ...{ + body: { + email: 'example@test.com', + password: 'SuperSecretPassword@', + }, + }, + }; + + const findOneSpy = jest + .spyOn(userProfile, 'findOne') + .mockImplementation(() => Promise.resolve(mockUser)); + jest.spyOn(bcrypt, 'compare').mockResolvedValue(false); + + const res = await login(mockReqModified, mockRes); + expect(findOneSpy).toHaveBeenCalledWith({ email: mockReqModified.body.email }); + + assertResMock( + 403, + { + message: 'Invalid password.', + }, + res, + mockRes, + ); + }); + + test('Ensure login returns the error if the try block fails', async () => { + const { login } = makeSut(); + const error = new Error('Try block failed'); + const mockReqModified = { + ...mockReq, + ...{ + body: { + email: 'example@test.com', + password: 'exampletest', + }, + }, + }; + + jest.spyOn(userProfile, 'findOne').mockImplementation(() => Promise.reject(error)); + + await login(mockReqModified, mockRes); + expect(mockRes.json).toHaveBeenCalledWith(error); + }); + + test('Ensure login returns 200, if the user is a new user and there is a password match', async () => { + const { login } = makeSut(); + const mockReqModified = { + ...mockReq, + ...{ + body: { + email: 'example@example.com', + password: '123Welcome!', + }, + }, + }; + + const mockUserModified = { + _id: 'user123', + email: 'example@example.com', + password: 'hashedPassword', + resetPwd: 'newUserPassword', + isActive: true, + }; + + jest + .spyOn(userProfile, 'findOne') + .mockImplementation(() => Promise.resolve(mockUserModified)); + + jest.spyOn(bcrypt, 'compare').mockResolvedValue(true); + + const res = await login(mockReqModified, mockRes); + assertResMock(200, { new: true, userId: 'user123' }, res, mockRes); + }); + + test('Ensure login returns 200, if the user already exists and the password is a match', async () => { + const { login } = makeSut(); + const mockReqModified = { + ...mockReq, + body: { + email: 'existing@example.com', + password: 'existingUserPassword', + }, + }; + const mockUserModified = { + _id: 'user123', + email: 'existing@example.com', + password: 'hashedPassword', + resetPwd: 'newUserPassword', + isActive: true, + role: 'Volunteer', + permissions: ['read', 'write'], + }; + + const findOneSpy = jest + .spyOn(userProfile, 'findOne') + .mockImplementation(() => Promise.resolve(mockUserModified)); + + jest.spyOn(bcrypt, 'compare').mockResolvedValue(true); + + const res = await login(mockReqModified, mockRes); + expect(findOneSpy).toHaveBeenCalledWith({ email: mockReqModified.body.email }); + + assertResMock(200, { token: expect.any(String) }, res, mockRes); + }); + }); + + describe('getUser', () => { + it('Ensure getUser returns 200, with the requestor body', () => { + const { getUser } = makeSut(); + + const res = getUser(mockReq, mockRes); + assertResMock(200, mockReq.body.requestor, res, mockRes); + }); + }); +}); diff --git a/src/test/mock-response.js b/src/test/mock-response.js index 057ee45d8..336e64057 100644 --- a/src/test/mock-response.js +++ b/src/test/mock-response.js @@ -1,6 +1,7 @@ const mockRes = { status: jest.fn().mockReturnThis(), send: jest.fn(), + json: jest.fn(), }; module.exports = mockRes;