From aa53b5469814ed3158cbb3f2436369dadb5a4e07 Mon Sep 17 00:00:00 2001 From: Alejandro Saenz Date: Wed, 15 Jul 2020 22:01:22 -0400 Subject: [PATCH] test(user controller): user controller unit test test #122 --- package-lock.json | 153 ++++++++++++++++++++ package.json | 1 + src/app.ts | 1 - src/entity/User.ts | 22 ++- src/routes/user.controller.spec.ts | 224 +++++++++++++++++++++++++++++ src/routes/user.controller.ts | 39 +---- 6 files changed, 396 insertions(+), 44 deletions(-) create mode 100644 src/routes/user.controller.spec.ts diff --git a/package-lock.json b/package-lock.json index f6fe3319..8fb67a44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3252,6 +3252,16 @@ } } }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "optional": true, + "requires": { + "inherits": "~2.0.0" + } + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -6330,6 +6340,31 @@ "dev": true, "optional": true }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -10606,6 +10641,68 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "optional": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "optional": true, + "requires": { + "abbrev": "1" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "dev": true, + "optional": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12381,6 +12478,62 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, + "sqlite3": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.0.tgz", + "integrity": "sha512-rjvqHFUaSGnzxDy2AHCwhHy6Zp6MNJzCPGYju4kD8yi6bze4d1/zMTg6C7JI49b7/EM7jKMTvyfN/4ylBKdwfw==", + "dev": true, + "requires": { + "node-addon-api": "2.0.0", + "node-gyp": "3.x", + "node-pre-gyp": "^0.11.0" + }, + "dependencies": { + "node-addon-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", + "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==", + "dev": true + }, + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "dev": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", diff --git a/package.json b/package.json index 67a42882..4fd7b84c 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "jest": "^26.1.0", "nodemon": "^2.0.4", "rimraf": "^3.0.2", + "sqlite3": "^5.0.0", "standard-version": "^8.0.2", "ts-jest": "^26.1.2", "tslint": "^6.1.2", diff --git a/src/app.ts b/src/app.ts index a28d0cc6..fdde8997 100644 --- a/src/app.ts +++ b/src/app.ts @@ -43,7 +43,6 @@ app.listen(serverPort, () => console.info(`Server running on ${serverIpAddress}: // create typeorm connection createConnection().then((_) => { // register routes - app.post('/api/user/create', jwtMiddleware.checkToken, userController.create); app.post('/api/user/register', userController.register); app.post('/api/user/invite', jwtMiddleware.checkToken, userController.invite); app.patch('/api/user', jwtMiddleware.checkToken, userController.patch); diff --git a/src/entity/User.ts b/src/entity/User.ts index e2b0f799..b0aa16e1 100644 --- a/src/entity/User.ts +++ b/src/entity/User.ts @@ -3,25 +3,35 @@ import { IsEmail, IsUUID, IsAlpha, IsOptional } from 'class-validator'; @Entity() export class User { - @PrimaryGeneratedColumn() + @PrimaryGeneratedColumn({}) id: number; @Column({ unique: true }) @IsEmail() email: string; - @Column() + @Column({ + nullable: true + }) password: string; @Column() active: boolean; - @Column() + @Column({ + nullable: true + }) @IsOptional() @IsUUID() uuid: string; - @Column() + @Column({ + nullable: true + }) firstName: string; - @Column() + @Column({ + nullable: true + }) lastName: string; - @Column() + @Column({ + nullable: true + }) title: string; } diff --git a/src/routes/user.controller.spec.ts b/src/routes/user.controller.spec.ts new file mode 100644 index 00000000..2de3d2d5 --- /dev/null +++ b/src/routes/user.controller.spec.ts @@ -0,0 +1,224 @@ +import { createConnection, getConnection, Entity, getRepository } from 'typeorm'; +const userController = require('./user.controller'); +import { User } from '../entity/user'; +import { v4 as uuidv4 } from 'uuid'; +describe('User Controller', () => { + // Mocks the Request Object that is returned + const mockRequest = () => { + const req = { + body: {}, + user: Function + }; + req.user = jest.fn().mockReturnValue(req); + return req; + }; + // Mocks the Response Object that is returned + const mockResponse = () => { + const res = { + status: Function, + json: Function + }; + res.status = jest.fn().mockReturnValue(res); + res.json = jest.fn().mockReturnValue(res); + return res; + }; + beforeEach(() => { + return createConnection({ + type: 'sqlite', + database: ':memory:', + dropSchema: true, + entities: [User], + synchronize: true, + logging: false + }); + }); + + afterEach(() => { + const conn = getConnection(); + return conn.close(); + }); + test('invite user success', async () => { + const req = mockRequest(); + req.body = { + email: 'testing@jest.com' + }; + const res = mockResponse(); + await userController.invite(req, res); + expect(res.status).toHaveBeenCalledWith(200); + }); + test('invite user failure missing email', async () => { + const req = mockRequest(); + req.body = {}; + const res = mockResponse(); + await userController.invite(req, res); + expect(res.status).toHaveBeenCalledWith(400); + }); + test('invite user failure user already exists', async () => { + const req = mockRequest(); + req.body = { + email: 'testing@jest.com' + }; + const res = mockResponse(); + const existUser = new User(); + existUser.firstName = 'master'; + existUser.lastName = 'chief'; + existUser.email = 'testing@jest.com'; + existUser.active = true; + await getConnection().getRepository(User).insert(existUser); + await userController.invite(req, res); + expect(res.status).toHaveBeenCalledWith(400); + }); + test('register user failure missing first name', async () => { + const req = mockRequest(); + req.body = { + email: 'testing@jest.com', + title: 'Spartan 117', + lastName: 'Chief', + password: 'notSecure123' + }; + const res = mockResponse(); + await userController.register(req, res); + expect(res.status).toHaveBeenCalledWith(400); + }); + test('register user failure missing last name', async () => { + const req = mockRequest(); + req.body = { + email: 'testing@jest.com', + title: 'Spartan 117', + firstName: 'Master', + password: 'notSecure123' + }; + const res = mockResponse(); + await userController.register(req, res); + expect(res.status).toHaveBeenCalledWith(400); + }); + test('register user failure missing title', async () => { + const req = mockRequest(); + req.body = { + email: 'testing@jest.com', + lastName: 'Chief', + firstName: 'Master', + password: 'notSecure123' + }; + const res = mockResponse(); + await userController.register(req, res); + expect(res.status).toHaveBeenCalledWith(400); + }); + test('register user failure passwords do not match', async () => { + const req = mockRequest(); + req.body = { + email: 'testing@jest.com', + lastName: 'Chief', + firstName: 'Master', + password: 'notSecure123', + confirmPassword: 'notSecureAbc', + title: 'Spartan 117' + }; + const res = mockResponse(); + await userController.register(req, res); + expect(res.status).toHaveBeenCalledWith(400); + }); + test('register user failure password validation', async () => { + const req = mockRequest(); + req.body = { + email: 'testing@jest.com', + lastName: 'Chief', + firstName: 'Master', + password: '123', + confirmPassword: '123', + title: 'Spartan 117' + }; + const res = mockResponse(); + await userController.register(req, res); + expect(res.status).toHaveBeenCalledWith(400); + }); + test('register user success', async () => { + const req = mockRequest(); + const invReq = mockRequest(); + invReq.body = { + email: 'testing@jest.com' + }; + const res = mockResponse(); + await userController.invite(invReq, res); + const invUser = await getConnection() + .getRepository(User) + .find({ where: { email: 'testing@jest.com' } }); + req.body = { + email: invUser[0].email, + lastName: 'Chief', + firstName: 'Master', + password: '&3x1GqpeFO61*HJ', + confirmPassword: '&3x1GqpeFO61*HJ', + title: 'Spartan 117', + uuid: invUser[0].uuid + }; + await userController.register(req, res); + expect(res.status).toHaveBeenCalledWith(200); + }); + test('verify user failure no uuid', async () => { + const mRequest = () => { + const req = { + params: {}, + user: Function + }; + req.user = jest.fn().mockReturnValue(req); + return req; + }; + const vReq = mRequest(); + const res = mockResponse(); + vReq.params = {}; + await userController.verify(vReq, res); + expect(res.status).toHaveBeenCalledWith(400); + }); + test('verify user success', async () => { + const res = mockResponse(); + const mRequest = () => { + const r = { + params: {}, + user: Function + }; + r.user = jest.fn().mockReturnValue(r); + return r; + }; + const existUser = new User(); + existUser.firstName = 'master'; + existUser.lastName = 'chief'; + existUser.email = 'testing@jest.com'; + existUser.active = false; + const uuid = uuidv4(); + existUser.uuid = uuid; + await getConnection().getRepository(User).insert(existUser); + const verifyReq = mRequest(); + verifyReq.params = { + uuid + }; + await userController.verify(verifyReq, res); + expect(res.status).toHaveBeenCalledWith(200); + }); + test('verify user failure user does not exist', async () => { + const res = mockResponse(); + const mRequest = () => { + const r = { + params: {}, + user: Function + }; + r.user = jest.fn().mockReturnValue(r); + return r; + }; + const existUser = new User(); + existUser.firstName = 'master'; + existUser.lastName = 'chief'; + existUser.email = 'testing@jest.com'; + existUser.active = false; + const uuid = uuidv4(); + existUser.uuid = uuid; + await getConnection().getRepository(User).insert(existUser); + const verifyReq = mRequest(); + const uuid2 = uuidv4(); + verifyReq.params = { + uuid: uuid2 + }; + await userController.verify(verifyReq, res); + expect(res.status).toHaveBeenCalledWith(400); + }); +}); diff --git a/src/routes/user.controller.ts b/src/routes/user.controller.ts index c7e42ddc..a9b1862f 100644 --- a/src/routes/user.controller.ts +++ b/src/routes/user.controller.ts @@ -9,41 +9,7 @@ import { generateHash, passwordSchema, updatePassword } from '../utilities/passw // tslint:disable-next-line: no-var-requires const emailService = require('../services/email.service'); -/** - * @description Create user - * @param {UserRequest} req - * @param {Response} res - * @returns success message - */ -const create = async (req: UserRequest, res: Response) => { - const user = new User(); - const { password, confirmPassword, email } = req.body; - if (!email) { - return res.status(400).json('Email is invalid'); - } - const existUser = await getConnection().getRepository(User).find({ where: { email } }); - if (existUser.length) { - return res.status(400).json('A user associated to that email already exists'); - } - user.email = email; - if (password !== confirmPassword) { - return res.status(400).json('Passwords do not match'); - } - if (!passwordSchema.validate(password)) { - return res.status(400).json(passwordRequirement); - } - user.password = await generateHash(password); - user.active = false; - user.uuid = uuidv4(); - const errors = await validate(user); - if (errors.length > 0) { - return res.status(400).json('User validation failed'); - } else { - await getConnection().getRepository(User).save(user); - emailService.sendVerificationEmail(user.uuid, user.email); - return res.status(200).json('User created successfully'); - } -}; + /** * @description Register user * @param {UserRequest} req @@ -125,7 +91,7 @@ const verify = async (req: UserRequest, res: Response) => { if (user) { user.active = true; user.uuid = null; - getConnection().getRepository(User).save(user); + await getConnection().getRepository(User).save(user); return res.status(200).json('Email verification successful'); } else { return res.status(400).json('Email verification failed. User does not exist.'); @@ -240,7 +206,6 @@ const getUsersById = async (userIds: number[]) => { return userArray; }; module.exports = { - create, verify, updateUserPassword, invite,