backend unit test coverage to 80%
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
355
backend/tests/unit/middleware/stepUpAuth.test.js
Normal file
355
backend/tests/unit/middleware/stepUpAuth.test.js
Normal file
@@ -0,0 +1,355 @@
|
||||
const { requireStepUpAuth } = require('../../../middleware/stepUpAuth');
|
||||
|
||||
// Mock TwoFactorService
|
||||
jest.mock('../../../services/TwoFactorService', () => ({
|
||||
validateStepUpSession: jest.fn(),
|
||||
getRemainingRecoveryCodesCount: jest.fn()
|
||||
}));
|
||||
|
||||
// Mock logger
|
||||
jest.mock('../../../utils/logger', () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn()
|
||||
}));
|
||||
|
||||
const TwoFactorService = require('../../../services/TwoFactorService');
|
||||
const logger = require('../../../utils/logger');
|
||||
|
||||
describe('stepUpAuth Middleware', () => {
|
||||
let req, res, next;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
user: {
|
||||
id: 1,
|
||||
email: 'test@test.com',
|
||||
twoFactorEnabled: true,
|
||||
twoFactorMethod: 'totp',
|
||||
recoveryCodesHash: null
|
||||
}
|
||||
};
|
||||
res = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn()
|
||||
};
|
||||
next = jest.fn();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('requireStepUpAuth', () => {
|
||||
describe('2FA Disabled Path', () => {
|
||||
it('should call next() when user has 2FA disabled', async () => {
|
||||
req.user.twoFactorEnabled = false;
|
||||
const middleware = requireStepUpAuth('sensitive_action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(TwoFactorService.validateStepUpSession).not.toHaveBeenCalled();
|
||||
expect(res.status).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call next() when twoFactorEnabled is null/falsy', async () => {
|
||||
req.user.twoFactorEnabled = null;
|
||||
const middleware = requireStepUpAuth('sensitive_action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(res.status).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call next() when twoFactorEnabled is undefined', async () => {
|
||||
delete req.user.twoFactorEnabled;
|
||||
const middleware = requireStepUpAuth('sensitive_action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(res.status).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Valid Step-Up Session', () => {
|
||||
it('should call next() when validateStepUpSession returns true', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(true);
|
||||
const middleware = requireStepUpAuth('password_change');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(TwoFactorService.validateStepUpSession).toHaveBeenCalledWith(req.user);
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(res.status).not.toHaveBeenCalled();
|
||||
expect(logger.info).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should validate step-up session for different actions', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(true);
|
||||
|
||||
const actions = ['password_change', 'delete_account', 'change_email', 'export_data'];
|
||||
|
||||
for (const action of actions) {
|
||||
jest.clearAllMocks();
|
||||
const middleware = requireStepUpAuth(action);
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(TwoFactorService.validateStepUpSession).toHaveBeenCalledWith(req.user);
|
||||
expect(next).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid/Expired Session', () => {
|
||||
it('should return 403 with STEP_UP_REQUIRED code when session is invalid', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
const middleware = requireStepUpAuth('password_change');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
||||
error: 'Multi-factor authentication required',
|
||||
code: 'STEP_UP_REQUIRED',
|
||||
action: 'password_change'
|
||||
}));
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should include action name in response', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
const actionName = 'delete_account';
|
||||
const middleware = requireStepUpAuth(actionName);
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
||||
action: actionName
|
||||
}));
|
||||
});
|
||||
|
||||
it('should log step-up requirement with user ID and action', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
const middleware = requireStepUpAuth('export_data');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
`Step-up authentication required for user ${req.user.id}, action: export_data`
|
||||
);
|
||||
});
|
||||
|
||||
it('should include methods array in response', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
const middleware = requireStepUpAuth('password_change');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
||||
methods: expect.any(Array)
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Available Methods Logic', () => {
|
||||
beforeEach(() => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
});
|
||||
|
||||
it('should return totp and email for TOTP users', async () => {
|
||||
req.user.twoFactorMethod = 'totp';
|
||||
req.user.recoveryCodesHash = null;
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
const response = res.json.mock.calls[0][0];
|
||||
expect(response.methods).toContain('totp');
|
||||
expect(response.methods).toContain('email');
|
||||
});
|
||||
|
||||
it('should return email only for email-2FA users', async () => {
|
||||
req.user.twoFactorMethod = 'email';
|
||||
req.user.recoveryCodesHash = null;
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
const response = res.json.mock.calls[0][0];
|
||||
expect(response.methods).not.toContain('totp');
|
||||
expect(response.methods).toContain('email');
|
||||
});
|
||||
|
||||
it('should include recovery when recovery codes remain (count > 0)', async () => {
|
||||
req.user.twoFactorMethod = 'totp';
|
||||
req.user.recoveryCodesHash = JSON.stringify({ codes: ['code1', 'code2'] });
|
||||
TwoFactorService.getRemainingRecoveryCodesCount.mockReturnValue(2);
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
const response = res.json.mock.calls[0][0];
|
||||
expect(response.methods).toContain('recovery');
|
||||
expect(TwoFactorService.getRemainingRecoveryCodesCount).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should exclude recovery when all codes used (count = 0)', async () => {
|
||||
req.user.twoFactorMethod = 'totp';
|
||||
req.user.recoveryCodesHash = JSON.stringify({ codes: [] });
|
||||
TwoFactorService.getRemainingRecoveryCodesCount.mockReturnValue(0);
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
const response = res.json.mock.calls[0][0];
|
||||
expect(response.methods).not.toContain('recovery');
|
||||
});
|
||||
|
||||
it('should exclude recovery when recoveryCodesHash is null', async () => {
|
||||
req.user.twoFactorMethod = 'totp';
|
||||
req.user.recoveryCodesHash = null;
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
const response = res.json.mock.calls[0][0];
|
||||
expect(response.methods).not.toContain('recovery');
|
||||
expect(TwoFactorService.getRemainingRecoveryCodesCount).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return correct methods for TOTP user with recovery codes', async () => {
|
||||
req.user.twoFactorMethod = 'totp';
|
||||
req.user.recoveryCodesHash = JSON.stringify({ codes: ['abc1-def2', 'ghi3-jkl4'] });
|
||||
TwoFactorService.getRemainingRecoveryCodesCount.mockReturnValue(2);
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
const response = res.json.mock.calls[0][0];
|
||||
expect(response.methods).toEqual(['totp', 'email', 'recovery']);
|
||||
});
|
||||
|
||||
it('should return correct methods for email-2FA user with recovery codes', async () => {
|
||||
req.user.twoFactorMethod = 'email';
|
||||
req.user.recoveryCodesHash = JSON.stringify({ codes: ['abc1-def2'] });
|
||||
TwoFactorService.getRemainingRecoveryCodesCount.mockReturnValue(1);
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
const response = res.json.mock.calls[0][0];
|
||||
expect(response.methods).toEqual(['email', 'recovery']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should return 500 when TwoFactorService.validateStepUpSession throws', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockImplementation(() => {
|
||||
throw new Error('Service error');
|
||||
});
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(500);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: 'An error occurred during authentication'
|
||||
});
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
'Step-up auth middleware error:',
|
||||
expect.any(Error)
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 500 when TwoFactorService.getRemainingRecoveryCodesCount throws', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
req.user.recoveryCodesHash = JSON.stringify({ codes: [] });
|
||||
TwoFactorService.getRemainingRecoveryCodesCount.mockImplementation(() => {
|
||||
throw new Error('Service error');
|
||||
});
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(500);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: 'An error occurred during authentication'
|
||||
});
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle malformed recoveryCodesHash JSON', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
req.user.recoveryCodesHash = 'invalid json {{{';
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(500);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: 'An error occurred during authentication'
|
||||
});
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should log error details when exception occurs', async () => {
|
||||
const testError = new Error('Test error message');
|
||||
TwoFactorService.validateStepUpSession.mockImplementation(() => {
|
||||
throw testError;
|
||||
});
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
'Step-up auth middleware error:',
|
||||
testError
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle user with empty string twoFactorMethod', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
req.user.twoFactorMethod = '';
|
||||
const middleware = requireStepUpAuth('action');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
const response = res.json.mock.calls[0][0];
|
||||
expect(response.methods).toContain('email');
|
||||
expect(response.methods).not.toContain('totp');
|
||||
});
|
||||
|
||||
it('should handle action parameter as empty string', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
const middleware = requireStepUpAuth('');
|
||||
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
||||
action: ''
|
||||
}));
|
||||
});
|
||||
|
||||
it('should handle various user ID types', async () => {
|
||||
TwoFactorService.validateStepUpSession.mockReturnValue(false);
|
||||
|
||||
// Test with string ID
|
||||
req.user.id = 'user-uuid-123';
|
||||
const middleware = requireStepUpAuth('action');
|
||||
await middleware(req, res, next);
|
||||
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
'Step-up authentication required for user user-uuid-123, action: action'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be a factory function that returns middleware', () => {
|
||||
const middleware = requireStepUpAuth('test_action');
|
||||
expect(typeof middleware).toBe('function');
|
||||
expect(middleware.length).toBe(3); // Should accept req, res, next
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -46,7 +46,16 @@ const {
|
||||
validateLogin,
|
||||
validateGoogleAuth,
|
||||
validateProfileUpdate,
|
||||
validatePasswordChange
|
||||
validatePasswordChange,
|
||||
validateForgotPassword,
|
||||
validateResetPassword,
|
||||
validateVerifyResetToken,
|
||||
validateFeedback,
|
||||
validateCoordinatesQuery,
|
||||
validateCoordinatesBody,
|
||||
validateTotpCode,
|
||||
validateEmailOtp,
|
||||
validateRecoveryCode
|
||||
} = require('../../../middleware/validation');
|
||||
|
||||
describe('Validation Middleware', () => {
|
||||
@@ -2066,4 +2075,392 @@ describe('Validation Middleware', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Two-Factor Authentication Validation', () => {
|
||||
describe('validateTotpCode', () => {
|
||||
it('should be an array ending with handleValidationErrors', () => {
|
||||
expect(Array.isArray(validateTotpCode)).toBe(true);
|
||||
expect(validateTotpCode.length).toBeGreaterThan(1);
|
||||
expect(validateTotpCode[validateTotpCode.length - 1]).toBe(handleValidationErrors);
|
||||
});
|
||||
|
||||
it('should validate 6-digit numeric format', () => {
|
||||
const validCodes = ['123456', '000000', '999999', '012345'];
|
||||
const invalidCodes = ['12345', '1234567', 'abcdef', '12345a', '12 345', ''];
|
||||
const totpRegex = /^\d{6}$/;
|
||||
|
||||
validCodes.forEach(code => {
|
||||
expect(totpRegex.test(code)).toBe(true);
|
||||
});
|
||||
|
||||
invalidCodes.forEach(code => {
|
||||
expect(totpRegex.test(code)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have at least 2 middleware functions', () => {
|
||||
expect(validateTotpCode.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateEmailOtp', () => {
|
||||
it('should be an array ending with handleValidationErrors', () => {
|
||||
expect(Array.isArray(validateEmailOtp)).toBe(true);
|
||||
expect(validateEmailOtp.length).toBeGreaterThan(1);
|
||||
expect(validateEmailOtp[validateEmailOtp.length - 1]).toBe(handleValidationErrors);
|
||||
});
|
||||
|
||||
it('should validate 6-digit numeric format', () => {
|
||||
const validCodes = ['123456', '000000', '999999', '654321'];
|
||||
const invalidCodes = ['12345', '1234567', 'abcdef', '12345a', '', ' '];
|
||||
const otpRegex = /^\d{6}$/;
|
||||
|
||||
validCodes.forEach(code => {
|
||||
expect(otpRegex.test(code)).toBe(true);
|
||||
});
|
||||
|
||||
invalidCodes.forEach(code => {
|
||||
expect(otpRegex.test(code)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have at least 2 middleware functions', () => {
|
||||
expect(validateEmailOtp.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateRecoveryCode', () => {
|
||||
it('should be an array ending with handleValidationErrors', () => {
|
||||
expect(Array.isArray(validateRecoveryCode)).toBe(true);
|
||||
expect(validateRecoveryCode.length).toBeGreaterThan(1);
|
||||
expect(validateRecoveryCode[validateRecoveryCode.length - 1]).toBe(handleValidationErrors);
|
||||
});
|
||||
|
||||
it('should validate XXXX-XXXX format', () => {
|
||||
const validCodes = ['ABCD-1234', 'abcd-efgh', '1234-5678', 'A1B2-C3D4', 'aaaa-bbbb'];
|
||||
const invalidCodes = ['ABCD1234', 'ABCD-12345', 'ABC-1234', 'ABCD-123', '', 'ABCD--1234', 'ABCD_1234'];
|
||||
const recoveryRegex = /^[A-Za-z0-9]{4}-[A-Za-z0-9]{4}$/i;
|
||||
|
||||
validCodes.forEach(code => {
|
||||
expect(recoveryRegex.test(code)).toBe(true);
|
||||
});
|
||||
|
||||
invalidCodes.forEach(code => {
|
||||
expect(recoveryRegex.test(code)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have at least 2 middleware functions', () => {
|
||||
expect(validateRecoveryCode.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Password Reset Validation', () => {
|
||||
describe('validateForgotPassword', () => {
|
||||
it('should be an array ending with handleValidationErrors', () => {
|
||||
expect(Array.isArray(validateForgotPassword)).toBe(true);
|
||||
expect(validateForgotPassword.length).toBeGreaterThan(1);
|
||||
expect(validateForgotPassword[validateForgotPassword.length - 1]).toBe(handleValidationErrors);
|
||||
});
|
||||
|
||||
it('should validate email format', () => {
|
||||
const validEmails = ['user@example.com', 'test.user@domain.co.uk', 'email@test.org'];
|
||||
const invalidEmails = ['invalid-email', '@domain.com', 'user@', 'user.domain.com'];
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
validEmails.forEach(email => {
|
||||
expect(emailRegex.test(email)).toBe(true);
|
||||
});
|
||||
|
||||
invalidEmails.forEach(email => {
|
||||
expect(emailRegex.test(email)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should enforce email length limits', () => {
|
||||
const longEmail = 'a'.repeat(250) + '@example.com';
|
||||
expect(longEmail.length).toBeGreaterThan(255);
|
||||
|
||||
const validEmail = 'user@example.com';
|
||||
expect(validEmail.length).toBeLessThanOrEqual(255);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateResetPassword', () => {
|
||||
it('should be an array ending with handleValidationErrors', () => {
|
||||
expect(Array.isArray(validateResetPassword)).toBe(true);
|
||||
expect(validateResetPassword.length).toBeGreaterThan(1);
|
||||
expect(validateResetPassword[validateResetPassword.length - 1]).toBe(handleValidationErrors);
|
||||
});
|
||||
|
||||
it('should validate 64-character token format', () => {
|
||||
const valid64CharToken = 'a'.repeat(64);
|
||||
const shortToken = 'a'.repeat(63);
|
||||
const longToken = 'a'.repeat(65);
|
||||
|
||||
expect(valid64CharToken.length).toBe(64);
|
||||
expect(shortToken.length).toBe(63);
|
||||
expect(longToken.length).toBe(65);
|
||||
});
|
||||
|
||||
it('should validate password strength requirements', () => {
|
||||
const passwordRegex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z])(?=.*[-@$!%*?&#^]).{8,}$/;
|
||||
|
||||
const strongPasswords = ['Password123!', 'MyStr0ng@Pass', 'Secure1#Test'];
|
||||
const weakPasswords = ['password', 'PASSWORD123', 'Password', '12345678'];
|
||||
|
||||
strongPasswords.forEach(password => {
|
||||
expect(passwordRegex.test(password)).toBe(true);
|
||||
});
|
||||
|
||||
weakPasswords.forEach(password => {
|
||||
expect(passwordRegex.test(password)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have multiple middleware functions for token and password', () => {
|
||||
expect(validateResetPassword.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateVerifyResetToken', () => {
|
||||
it('should be an array ending with handleValidationErrors', () => {
|
||||
expect(Array.isArray(validateVerifyResetToken)).toBe(true);
|
||||
expect(validateVerifyResetToken.length).toBeGreaterThan(1);
|
||||
expect(validateVerifyResetToken[validateVerifyResetToken.length - 1]).toBe(handleValidationErrors);
|
||||
});
|
||||
|
||||
it('should validate 64-character token format', () => {
|
||||
const valid64CharToken = 'abcdef1234567890'.repeat(4);
|
||||
expect(valid64CharToken.length).toBe(64);
|
||||
|
||||
const shortToken = 'abc123'.repeat(10);
|
||||
expect(shortToken.length).toBe(60);
|
||||
|
||||
const longToken = 'a'.repeat(65);
|
||||
expect(longToken.length).toBe(65);
|
||||
});
|
||||
|
||||
it('should have at least 2 middleware functions', () => {
|
||||
expect(validateVerifyResetToken.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Feedback Validation', () => {
|
||||
describe('validateFeedback', () => {
|
||||
it('should be an array ending with handleValidationErrors', () => {
|
||||
expect(Array.isArray(validateFeedback)).toBe(true);
|
||||
expect(validateFeedback.length).toBeGreaterThan(1);
|
||||
expect(validateFeedback[validateFeedback.length - 1]).toBe(handleValidationErrors);
|
||||
});
|
||||
|
||||
it('should validate text length (5-5000 chars)', () => {
|
||||
const tooShort = 'abcd'; // 4 chars
|
||||
const minValid = 'abcde'; // 5 chars
|
||||
const maxValid = 'a'.repeat(5000);
|
||||
const tooLong = 'a'.repeat(5001);
|
||||
|
||||
expect(tooShort.length).toBe(4);
|
||||
expect(minValid.length).toBe(5);
|
||||
expect(maxValid.length).toBe(5000);
|
||||
expect(tooLong.length).toBe(5001);
|
||||
|
||||
// Validate boundaries
|
||||
expect(tooShort.length).toBeLessThan(5);
|
||||
expect(minValid.length).toBeGreaterThanOrEqual(5);
|
||||
expect(maxValid.length).toBeLessThanOrEqual(5000);
|
||||
expect(tooLong.length).toBeGreaterThan(5000);
|
||||
});
|
||||
|
||||
it('should have at least 2 middleware functions', () => {
|
||||
expect(validateFeedback.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('should include optional URL validation', () => {
|
||||
// The feedback validation should include url field as optional
|
||||
expect(validateFeedback.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Coordinates Validation', () => {
|
||||
describe('validateCoordinatesQuery', () => {
|
||||
it('should be an array ending with handleValidationErrors', () => {
|
||||
expect(Array.isArray(validateCoordinatesQuery)).toBe(true);
|
||||
expect(validateCoordinatesQuery.length).toBeGreaterThan(1);
|
||||
expect(validateCoordinatesQuery[validateCoordinatesQuery.length - 1]).toBe(handleValidationErrors);
|
||||
});
|
||||
|
||||
it('should validate latitude range (-90 to 90)', () => {
|
||||
const validLatitudes = [0, 45, -45, 90, -90, 37.7749];
|
||||
const invalidLatitudes = [91, -91, 180, -180, 1000];
|
||||
|
||||
validLatitudes.forEach(lat => {
|
||||
expect(lat).toBeGreaterThanOrEqual(-90);
|
||||
expect(lat).toBeLessThanOrEqual(90);
|
||||
});
|
||||
|
||||
invalidLatitudes.forEach(lat => {
|
||||
expect(lat < -90 || lat > 90).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate longitude range (-180 to 180)', () => {
|
||||
const validLongitudes = [0, 90, -90, 180, -180, -122.4194];
|
||||
const invalidLongitudes = [181, -181, 360, -360];
|
||||
|
||||
validLongitudes.forEach(lng => {
|
||||
expect(lng).toBeGreaterThanOrEqual(-180);
|
||||
expect(lng).toBeLessThanOrEqual(180);
|
||||
});
|
||||
|
||||
invalidLongitudes.forEach(lng => {
|
||||
expect(lng < -180 || lng > 180).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate radius range (0.1 to 100)', () => {
|
||||
const validRadii = [0.1, 1, 50, 100, 0.5, 99.9];
|
||||
const invalidRadii = [0, 0.05, 100.1, 200, -1];
|
||||
|
||||
validRadii.forEach(radius => {
|
||||
expect(radius).toBeGreaterThanOrEqual(0.1);
|
||||
expect(radius).toBeLessThanOrEqual(100);
|
||||
});
|
||||
|
||||
invalidRadii.forEach(radius => {
|
||||
expect(radius < 0.1 || radius > 100).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have middleware for lat, lng, and radius', () => {
|
||||
expect(validateCoordinatesQuery.length).toBeGreaterThanOrEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateCoordinatesBody', () => {
|
||||
it('should be an array with validation middleware', () => {
|
||||
expect(Array.isArray(validateCoordinatesBody)).toBe(true);
|
||||
expect(validateCoordinatesBody.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should validate body latitude range (-90 to 90)', () => {
|
||||
const validLatitudes = [0, 45.5, -89.99, 90, -90];
|
||||
const invalidLatitudes = [90.1, -90.1, 100, -100];
|
||||
|
||||
validLatitudes.forEach(lat => {
|
||||
expect(lat).toBeGreaterThanOrEqual(-90);
|
||||
expect(lat).toBeLessThanOrEqual(90);
|
||||
});
|
||||
|
||||
invalidLatitudes.forEach(lat => {
|
||||
expect(lat < -90 || lat > 90).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate body longitude range (-180 to 180)', () => {
|
||||
const validLongitudes = [0, 179.99, -179.99, 180, -180];
|
||||
const invalidLongitudes = [180.1, -180.1, 200, -200];
|
||||
|
||||
validLongitudes.forEach(lng => {
|
||||
expect(lng).toBeGreaterThanOrEqual(-180);
|
||||
expect(lng).toBeLessThanOrEqual(180);
|
||||
});
|
||||
|
||||
invalidLongitudes.forEach(lng => {
|
||||
expect(lng < -180 || lng > 180).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have middleware for latitude and longitude', () => {
|
||||
expect(validateCoordinatesBody.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Module Exports Completeness', () => {
|
||||
it('should export all validators from the module', () => {
|
||||
const validationModule = require('../../../middleware/validation');
|
||||
|
||||
// Core middleware
|
||||
expect(validationModule).toHaveProperty('sanitizeInput');
|
||||
expect(validationModule).toHaveProperty('handleValidationErrors');
|
||||
|
||||
// Auth validators
|
||||
expect(validationModule).toHaveProperty('validateRegistration');
|
||||
expect(validationModule).toHaveProperty('validateLogin');
|
||||
expect(validationModule).toHaveProperty('validateGoogleAuth');
|
||||
|
||||
// Profile validators
|
||||
expect(validationModule).toHaveProperty('validateProfileUpdate');
|
||||
expect(validationModule).toHaveProperty('validatePasswordChange');
|
||||
|
||||
// Password reset validators
|
||||
expect(validationModule).toHaveProperty('validateForgotPassword');
|
||||
expect(validationModule).toHaveProperty('validateResetPassword');
|
||||
expect(validationModule).toHaveProperty('validateVerifyResetToken');
|
||||
|
||||
// Feedback validator
|
||||
expect(validationModule).toHaveProperty('validateFeedback');
|
||||
|
||||
// Coordinate validators
|
||||
expect(validationModule).toHaveProperty('validateCoordinatesQuery');
|
||||
expect(validationModule).toHaveProperty('validateCoordinatesBody');
|
||||
|
||||
// 2FA validators
|
||||
expect(validationModule).toHaveProperty('validateTotpCode');
|
||||
expect(validationModule).toHaveProperty('validateEmailOtp');
|
||||
expect(validationModule).toHaveProperty('validateRecoveryCode');
|
||||
});
|
||||
|
||||
it('should export functions and arrays with correct types', () => {
|
||||
const validationModule = require('../../../middleware/validation');
|
||||
|
||||
// Functions
|
||||
expect(typeof validationModule.sanitizeInput).toBe('function');
|
||||
expect(typeof validationModule.handleValidationErrors).toBe('function');
|
||||
|
||||
// Arrays (validation chains)
|
||||
expect(Array.isArray(validationModule.validateRegistration)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateLogin)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateGoogleAuth)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateProfileUpdate)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validatePasswordChange)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateForgotPassword)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateResetPassword)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateVerifyResetToken)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateFeedback)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateCoordinatesQuery)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateCoordinatesBody)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateTotpCode)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateEmailOtp)).toBe(true);
|
||||
expect(Array.isArray(validationModule.validateRecoveryCode)).toBe(true);
|
||||
});
|
||||
|
||||
it('should have all validation arrays end with handleValidationErrors', () => {
|
||||
const validationModule = require('../../../middleware/validation');
|
||||
const validatorsWithHandler = [
|
||||
'validateRegistration',
|
||||
'validateLogin',
|
||||
'validateGoogleAuth',
|
||||
'validateProfileUpdate',
|
||||
'validatePasswordChange',
|
||||
'validateForgotPassword',
|
||||
'validateResetPassword',
|
||||
'validateVerifyResetToken',
|
||||
'validateFeedback',
|
||||
'validateCoordinatesQuery',
|
||||
'validateTotpCode',
|
||||
'validateEmailOtp',
|
||||
'validateRecoveryCode'
|
||||
];
|
||||
|
||||
validatorsWithHandler.forEach(validatorName => {
|
||||
const validator = validationModule[validatorName];
|
||||
expect(validator[validator.length - 1]).toBe(validationModule.handleValidationErrors);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user