Skip to content

Commit

Permalink
[finishes #187845784] writing auth controllers tests
Browse files Browse the repository at this point in the history
  • Loading branch information
amin-leon committed Jun 27, 2024
1 parent 9da70d9 commit 8a12157
Show file tree
Hide file tree
Showing 13 changed files with 846 additions and 49 deletions.
49 changes: 24 additions & 25 deletions src/controllers/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const authenticateViaGoogle = (req: Request, res: Response, next: NextFun
})(req, res, next);
};
// calculate password expiration
const calculatePasswordExpirationDate = (user: UserAttributes): Date | null => {
export const calculatePasswordExpirationDate = (user: UserAttributes): Date | null => {
const expirationMinutes = parseInt(process.env.PASSWORD_EXPIRATION_MINUTES as string);
let expirationDate: Date | null = null;

Expand All @@ -47,23 +47,19 @@ const calculatePasswordExpirationDate = (user: UserAttributes): Date | null => {

return expirationDate;
};
const redirectToPasswordUpdate = (res: Response): void => {
export const redirectToPasswordUpdate = (res: Response): void => {
res.redirect('/api/user/passwordUpdate');
};
// login function
export const login = async (req: Request, res: Response): Promise<void> => {
try {
const { email, password } = req.body;

const requiredFields = ['email', 'password'];
const missingFields = validateFields(req, requiredFields);

// Field validation
if (missingFields.length > 0) {
logger.error(`Adding User:Required fields are missing:${missingFields.join(', ')}`);
if (!email || !password) {
res.status(400).json({
ok: false,
message: `Required fields are missing: ${missingFields.join(', ')}`,
message: 'Required fields are missing',
});
return;
}
Expand Down Expand Up @@ -124,24 +120,27 @@ export const verifyOTP = async (req: Request, res: Response) => {
};
// Function to create OTP Token, Save it Postgres,
export const sendOTP = async (req: Request, res: Response, email: string) => {
const userInfo = await User.findOne({ where: { email } });
if (userInfo) {
const { id, email, firstName } = userInfo.dataValues;

const token = await createOTPToken(id, email, firstName);

const otpSaved = await saveOTPDB(id, token);

if (otpSaved) {
/**
* The token used for comparing the received OTP via email with the
* generated token, which contains the user's ID.
*/
const accessToken = jwt.sign({ id, FAEnabled: true }, process.env.SECRET_KEY as string, {
expiresIn: process.env.JWT_EXPIRATION as string,
});
res.status(200).json({ ok: true, token: accessToken });
try {
const userInfo = await User.findOne({ where: { email } });

if (userInfo) {
const { id, email, firstName } = userInfo.dataValues;
const token = await createOTPToken(id, email, firstName);
const otpSaved = await saveOTPDB(id, token);

if (otpSaved) {
const accessToken = jwt.sign({ id, FAEnabled: true }, process.env.SECRET_KEY as string, {
expiresIn: process.env.JWT_EXPIRATION as string,
});
return res.status(200).json({ ok: true, token: accessToken });
} else {
return res.status(500).json({ ok: false, message: 'Failed to save OTP' });
}
} else {
return res.status(404).json({ ok: false, message: 'User not found' });
}
} catch (error) {
return res.status(500).json({ ok: false, message: 'Internal Server Error' });
}
};

Expand Down
3 changes: 0 additions & 3 deletions src/controllers/testController.ts

This file was deleted.

263 changes: 263 additions & 0 deletions src/test/auth/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import { Request } from 'express';
import bcrypt from 'bcrypt';
import dotenv from 'dotenv';
import User from '../../database/models/user';
import Role from '../../database/models/role';
import * as authController from '../../controllers/authController';
import { sendErrorResponse } from '../../helpers/helper';
import { sendInternalErrorResponse } from '../../validations';

dotenv.config();

const mockResponse = () => {
const res = {} as any;
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
res.send = jest.fn().mockReturnValue(res);
return res;
};

jest.mock('../../database/models/user', () => ({
__esModule: true,
default: {
findOne: jest.fn(),
findByPk: jest.fn(),
hasMany: jest.fn(),
},
}));

jest.mock('../../database/models/role', () => ({
__esModule: true,
default: {
findOne: jest.fn(),
},
}));

jest.mock('../../database/models/otp', () => ({
__esModule: true,
default: {
belongsTo: jest.fn(),
},
}));

jest.mock('bcrypt', () => ({
compare: jest.fn(() => Promise.resolve(true)),
}));

jest.mock('jsonwebtoken', () => ({
sign: jest.fn(() => 'dgshdgshdgshgdhs-hghgashagsh-jhj'),
}));

jest.mock('../../helpers/helper', () => ({
sendErrorResponse: jest.fn(),
}));

jest.mock('../../validations', () => ({
sendInternalErrorResponse: jest.fn(),
}));

jest.mock('../../controllers/authController', () => ({
...jest.requireActual('../../controllers/authController'),
calculatePasswordExpirationDate: jest.fn(),
redirectToPasswordUpdate: jest.fn(),
}));

describe('AuthController', () => {
afterEach(() => {
jest.clearAllMocks();
});

describe('POST/ User login route', () => {
it('should return 400 if required fields are missing', async () => {
const req = {
body: {
email: '[email protected]',
},
} as Request;
const res = mockResponse();

await authController.login(req, res);

expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
ok: false,
message: 'Required fields are missing',
});

expect(User.findOne).not.toHaveBeenCalled();
expect(User.findByPk).not.toHaveBeenCalled();
expect(Role.findOne).not.toHaveBeenCalled();
expect(bcrypt.compare).not.toHaveBeenCalled();
});

it('should return 401 if user status is inactive', async () => {
const req = {
body: {
email: '[email protected]',
password: 'correctPassword',
},
} as Request;
const res = mockResponse();

const mockUser = {
id: 1,
email: '[email protected]',
password: 'hashedPassword',
status: 'inactive',
verified: true,
dataValues: {
RoleId: 1,
enable2FA: false,
email: '[email protected]',
},
};

(User.findOne as jest.Mock).mockResolvedValueOnce(mockUser);
(bcrypt.compare as jest.Mock).mockResolvedValueOnce(true);

await authController.login(req, res);

expect(User.findOne).toHaveBeenCalledWith({ where: { email: '[email protected]' } });
expect(sendErrorResponse).toHaveBeenCalledWith(res, 'inactiveUser');
});

it('should return 401 if user is not verified', async () => {
const req = {
body: {
email: '[email protected]',
password: 'correctPassword',
},
} as Request;
const res = mockResponse();

const mockUser = {
id: 1,
email: '[email protected]',
password: 'hashedPassword',
status: 'active',
verified: false,
dataValues: {
RoleId: 1,
enable2FA: false,
email: '[email protected]',
},
};

(User.findOne as jest.Mock).mockResolvedValueOnce(mockUser);
(bcrypt.compare as jest.Mock).mockResolvedValueOnce(true);

await authController.login(req, res);

expect(User.findOne).toHaveBeenCalledWith({ where: { email: '[email protected]' } });
expect(sendErrorResponse).toHaveBeenCalledWith(res, 'unverifiedUser');
});

it('should handle internal server error', async () => {
const req = {
body: {
email: '[email protected]',
password: 'correctPassword',
},
} as Request;
const res = mockResponse();

const mockError = new Error('Database connection failed');
(User.findOne as jest.Mock).mockRejectedValueOnce(mockError);

await authController.login(req, res);

expect(User.findOne).toHaveBeenCalledWith({ where: { email: '[email protected]' } });
expect(sendInternalErrorResponse).toHaveBeenCalledWith(res, mockError);
});

it('should return 401 if user is not found', async () => {
const req = {
body: {
email: '[email protected]',
password: 'myPassword',
},
} as Request;
const res = mockResponse();

(User.findOne as jest.Mock).mockResolvedValueOnce(null);

await authController.login(req, res);

expect(User.findOne).toHaveBeenCalledWith({ where: { email: '[email protected]' } });
expect(sendErrorResponse).toHaveBeenCalledWith(res, 'invalidCredentials');
});

it('should return 401 if password is invalid', async () => {
const req = {
body: {
email: '[email protected]',
password: 'wrongPassword',
},
} as Request;
const res = mockResponse();

const mockUser = {
id: 1,
email: '[email protected]',
password: 'hashedPassword',
status: 'active',
verified: true,
dataValues: {
RoleId: 1,
enable2FA: false,
email: '[email protected]',
},
};

(User.findOne as jest.Mock).mockResolvedValueOnce(mockUser);
(bcrypt.compare as jest.Mock).mockResolvedValueOnce(false);

await authController.login(req, res);

expect(User.findOne).toHaveBeenCalledWith({ where: { email: '[email protected]' } });
expect(bcrypt.compare).toHaveBeenCalledWith('wrongPassword', 'hashedPassword');
});

it('should return 200 if login is successful', async () => {
const req = {
body: {
email: '[email protected]',
password: 'correctPassword',
},
} as Request;
const res = mockResponse();

const mockUser = {
id: 1,
email: '[email protected]',
password: 'hashedPassword',
status: 'active',
verified: true,
dataValues: {
RoleId: 1,
enable2FA: false,
email: '[email protected]',
},
};

const mockRole = {
dataValues: {
name: 'buyer',
},
};

(User.findOne as jest.Mock).mockResolvedValueOnce(mockUser);
(User.findByPk as jest.Mock).mockResolvedValueOnce(mockUser);
(Role.findOne as jest.Mock).mockResolvedValueOnce(mockRole);
(bcrypt.compare as jest.Mock).mockResolvedValueOnce(true);

await authController.login(req, res);

expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
ok: true,
token: 'dgshdgshdgshgdhs-hghgashagsh-jhj',
});
});
});
});
Loading

0 comments on commit 8a12157

Please sign in to comment.