backend unit test coverage to 80%

This commit is contained in:
jackiettran
2026-01-19 19:22:01 -05:00
parent d4362074f5
commit 1923ffc251
8 changed files with 3183 additions and 7 deletions

View File

@@ -1,4 +1,4 @@
const { authenticateToken, requireVerifiedEmail } = require('../../../middleware/auth');
const { authenticateToken, optionalAuth, requireVerifiedEmail, requireAdmin } = require('../../../middleware/auth');
const jwt = require('jsonwebtoken');
jest.mock('jsonwebtoken');
@@ -348,4 +348,393 @@ describe('requireVerifiedEmail Middleware', () => {
});
});
});
});
describe('optionalAuth Middleware', () => {
let req, res, next;
beforeEach(() => {
req = {
cookies: {}
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
next = jest.fn();
jest.clearAllMocks();
process.env.JWT_ACCESS_SECRET = 'test-secret';
});
describe('No token present', () => {
it('should set req.user to null when no token present', async () => {
req.cookies = {};
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
expect(res.status).not.toHaveBeenCalled();
});
it('should set req.user to null when cookies is undefined', async () => {
req.cookies = undefined;
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
});
it('should set req.user to null for empty string token', async () => {
req.cookies.accessToken = '';
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
});
});
describe('Valid token present', () => {
it('should set req.user when valid token present', async () => {
const mockUser = { id: 1, email: 'test@test.com', jwtVersion: 1 };
req.cookies.accessToken = 'validtoken';
jwt.verify.mockReturnValue({ id: 1, jwtVersion: 1 });
User.findByPk.mockResolvedValue(mockUser);
await optionalAuth(req, res, next);
expect(jwt.verify).toHaveBeenCalledWith('validtoken', process.env.JWT_ACCESS_SECRET);
expect(User.findByPk).toHaveBeenCalledWith(1);
expect(req.user).toEqual(mockUser);
expect(next).toHaveBeenCalled();
});
});
describe('Invalid token handling', () => {
it('should set req.user to null for invalid token (no error returned)', async () => {
req.cookies.accessToken = 'invalidtoken';
jwt.verify.mockImplementation(() => {
throw new Error('Invalid token');
});
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
expect(res.status).not.toHaveBeenCalled();
});
it('should set req.user to null for expired token (no error returned)', async () => {
req.cookies.accessToken = 'expiredtoken';
const error = new Error('jwt expired');
error.name = 'TokenExpiredError';
jwt.verify.mockImplementation(() => {
throw error;
});
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
expect(res.status).not.toHaveBeenCalled();
});
it('should set req.user to null when token has no user id', async () => {
req.cookies.accessToken = 'tokenwithnoid';
jwt.verify.mockReturnValue({ email: 'test@test.com' }); // Missing id
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
});
});
describe('User state handling', () => {
it('should set req.user to null for banned user', async () => {
const mockUser = { id: 1, email: 'banned@test.com', isBanned: true, jwtVersion: 1 };
req.cookies.accessToken = 'validtoken';
jwt.verify.mockReturnValue({ id: 1, jwtVersion: 1 });
User.findByPk.mockResolvedValue(mockUser);
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
expect(res.status).not.toHaveBeenCalled();
});
it('should set req.user to null for JWT version mismatch', async () => {
const mockUser = { id: 1, email: 'test@test.com', jwtVersion: 2 };
req.cookies.accessToken = 'validtoken';
jwt.verify.mockReturnValue({ id: 1, jwtVersion: 1 }); // Old version
User.findByPk.mockResolvedValue(mockUser);
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
expect(res.status).not.toHaveBeenCalled();
});
it('should set req.user to null when user not found', async () => {
req.cookies.accessToken = 'validtoken';
jwt.verify.mockReturnValue({ id: 999, jwtVersion: 1 });
User.findByPk.mockResolvedValue(null);
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
});
});
describe('Edge cases', () => {
it('should handle database error gracefully', async () => {
req.cookies.accessToken = 'validtoken';
jwt.verify.mockReturnValue({ id: 1, jwtVersion: 1 });
User.findByPk.mockRejectedValue(new Error('Database error'));
await optionalAuth(req, res, next);
expect(req.user).toBeNull();
expect(next).toHaveBeenCalled();
expect(res.status).not.toHaveBeenCalled();
});
});
});
describe('requireAdmin Middleware', () => {
let req, res, next;
beforeEach(() => {
req = {
user: null
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
next = jest.fn();
jest.clearAllMocks();
});
describe('Admin users', () => {
it('should call next() for admin user', () => {
req.user = {
id: 1,
email: 'admin@test.com',
role: 'admin'
};
requireAdmin(req, res, next);
expect(next).toHaveBeenCalled();
expect(res.status).not.toHaveBeenCalled();
expect(res.json).not.toHaveBeenCalled();
});
it('should call next() for admin user with additional properties', () => {
req.user = {
id: 1,
email: 'admin@test.com',
role: 'admin',
firstName: 'Admin',
lastName: 'User',
isVerified: true
};
requireAdmin(req, res, next);
expect(next).toHaveBeenCalled();
});
});
describe('Non-admin users', () => {
it('should return 403 for non-admin user', () => {
req.user = {
id: 1,
email: 'user@test.com',
role: 'user'
};
requireAdmin(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(res.json).toHaveBeenCalledWith({
error: 'Admin access required',
code: 'INSUFFICIENT_PERMISSIONS'
});
expect(next).not.toHaveBeenCalled();
});
it('should return 403 for host role', () => {
req.user = {
id: 1,
email: 'host@test.com',
role: 'host'
};
requireAdmin(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(res.json).toHaveBeenCalledWith({
error: 'Admin access required',
code: 'INSUFFICIENT_PERMISSIONS'
});
});
it('should return 403 for user with no role property', () => {
req.user = {
id: 1,
email: 'user@test.com'
// role is missing
};
requireAdmin(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(res.json).toHaveBeenCalledWith({
error: 'Admin access required',
code: 'INSUFFICIENT_PERMISSIONS'
});
expect(next).not.toHaveBeenCalled();
});
it('should return 403 for user with empty string role', () => {
req.user = {
id: 1,
email: 'user@test.com',
role: ''
};
requireAdmin(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
});
});
describe('No user', () => {
it('should return 401 when user is null', () => {
req.user = null;
requireAdmin(req, res, next);
expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({
error: 'Authentication required',
code: 'NO_AUTH'
});
expect(next).not.toHaveBeenCalled();
});
it('should return 401 when user is undefined', () => {
req.user = undefined;
requireAdmin(req, res, next);
expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({
error: 'Authentication required',
code: 'NO_AUTH'
});
expect(next).not.toHaveBeenCalled();
});
});
describe('Edge cases', () => {
it('should handle case-sensitive role comparison', () => {
req.user = {
id: 1,
email: 'user@test.com',
role: 'Admin' // Capital A
};
requireAdmin(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(next).not.toHaveBeenCalled();
});
it('should handle role with whitespace', () => {
req.user = {
id: 1,
email: 'user@test.com',
role: ' admin ' // With spaces
};
requireAdmin(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(next).not.toHaveBeenCalled();
});
});
});
describe('authenticateToken - Additional Tests', () => {
let req, res, next;
beforeEach(() => {
req = {
cookies: {},
id: 'request-123'
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
next = jest.fn();
jest.clearAllMocks();
process.env.JWT_ACCESS_SECRET = 'test-secret';
});
describe('Banned user', () => {
it('should return 403 USER_BANNED for banned user', async () => {
const mockUser = { id: 1, email: 'banned@test.com', isBanned: true, jwtVersion: 1 };
req.cookies.accessToken = 'validtoken';
jwt.verify.mockReturnValue({ id: 1, jwtVersion: 1 });
User.findByPk.mockResolvedValue(mockUser);
await authenticateToken(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(res.json).toHaveBeenCalledWith({
error: 'Your account has been suspended. Please contact support for more information.',
code: 'USER_BANNED'
});
expect(next).not.toHaveBeenCalled();
});
});
describe('JWT version mismatch', () => {
it('should return 401 JWT_VERSION_MISMATCH for version mismatch', async () => {
const mockUser = { id: 1, email: 'test@test.com', jwtVersion: 2 };
req.cookies.accessToken = 'validtoken';
jwt.verify.mockReturnValue({ id: 1, jwtVersion: 1 }); // Old version in token
User.findByPk.mockResolvedValue(mockUser);
await authenticateToken(req, res, next);
expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({
error: 'Session expired due to password change. Please log in again.',
code: 'JWT_VERSION_MISMATCH'
});
expect(next).not.toHaveBeenCalled();
});
it('should pass when JWT version matches', async () => {
const mockUser = { id: 1, email: 'test@test.com', jwtVersion: 5 };
req.cookies.accessToken = 'validtoken';
jwt.verify.mockReturnValue({ id: 1, jwtVersion: 5 });
User.findByPk.mockResolvedValue(mockUser);
await authenticateToken(req, res, next);
expect(next).toHaveBeenCalled();
expect(req.user).toEqual(mockUser);
});
});
});