WIP
This commit is contained in:
362
backend/tests/unit/authService.test.js
Normal file
362
backend/tests/unit/authService.test.js
Normal file
@ -0,0 +1,362 @@
|
||||
const AuthService = require('../../src/services/AuthService');
|
||||
const User = require('../../src/models/User');
|
||||
const emailService = require('../../src/services/EmailService');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('../../src/models/User');
|
||||
jest.mock('../../src/services/EmailService');
|
||||
jest.mock('jsonwebtoken');
|
||||
|
||||
describe('AuthService Unit Tests', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('generateToken', () => {
|
||||
it('should generate a valid JWT token', () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
is_verified: true
|
||||
};
|
||||
|
||||
const mockToken = 'mock-jwt-token';
|
||||
jwt.sign.mockReturnValue(mockToken);
|
||||
|
||||
const token = AuthService.generateToken(mockUser);
|
||||
|
||||
expect(jwt.sign).toHaveBeenCalledWith(
|
||||
{
|
||||
userId: 'user-123',
|
||||
email: 'test@example.com',
|
||||
isVerified: true
|
||||
},
|
||||
process.env.JWT_SECRET,
|
||||
{
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '24h',
|
||||
issuer: 'bookmark-manager',
|
||||
audience: 'bookmark-manager-users'
|
||||
}
|
||||
);
|
||||
expect(token).toBe(mockToken);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyToken', () => {
|
||||
it('should verify a valid token', () => {
|
||||
const mockPayload = { userId: 'user-123', email: 'test@example.com' };
|
||||
jwt.verify.mockReturnValue(mockPayload);
|
||||
|
||||
const result = AuthService.verifyToken('valid-token');
|
||||
|
||||
expect(jwt.verify).toHaveBeenCalledWith(
|
||||
'valid-token',
|
||||
process.env.JWT_SECRET,
|
||||
{
|
||||
issuer: 'bookmark-manager',
|
||||
audience: 'bookmark-manager-users'
|
||||
}
|
||||
);
|
||||
expect(result).toEqual(mockPayload);
|
||||
});
|
||||
|
||||
it('should return null for invalid token', () => {
|
||||
jwt.verify.mockImplementation(() => {
|
||||
throw new Error('Invalid token');
|
||||
});
|
||||
|
||||
const result = AuthService.verifyToken('invalid-token');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should successfully register a new user', async () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
toSafeObject: () => ({ id: 'user-123', email: 'test@example.com' })
|
||||
};
|
||||
|
||||
User.create.mockResolvedValue(mockUser);
|
||||
emailService.sendVerificationEmail.mockResolvedValue({ message: 'Email sent' });
|
||||
|
||||
const result = await AuthService.register('test@example.com', 'password123');
|
||||
|
||||
expect(User.create).toHaveBeenCalledWith({
|
||||
email: 'test@example.com',
|
||||
password: 'password123'
|
||||
});
|
||||
expect(emailService.sendVerificationEmail).toHaveBeenCalled();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toBe('User registered successfully. Please check your email for verification.');
|
||||
});
|
||||
|
||||
it('should handle registration failure', async () => {
|
||||
User.create.mockRejectedValue(new Error('Email already exists'));
|
||||
|
||||
const result = await AuthService.register('test@example.com', 'password123');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('Email already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('should successfully login a verified user', async () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
is_verified: true,
|
||||
toSafeObject: () => ({ id: 'user-123', email: 'test@example.com' })
|
||||
};
|
||||
|
||||
User.authenticate.mockResolvedValue(mockUser);
|
||||
jwt.sign.mockReturnValue('mock-token');
|
||||
|
||||
const result = await AuthService.login('test@example.com', 'password123');
|
||||
|
||||
expect(User.authenticate).toHaveBeenCalledWith('test@example.com', 'password123');
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.token).toBe('mock-token');
|
||||
expect(result.user).toEqual({ id: 'user-123', email: 'test@example.com' });
|
||||
});
|
||||
|
||||
it('should fail login for invalid credentials', async () => {
|
||||
User.authenticate.mockResolvedValue(null);
|
||||
|
||||
const result = await AuthService.login('test@example.com', 'wrongpassword');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('Invalid email or password');
|
||||
});
|
||||
|
||||
it('should fail login for unverified user', async () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
is_verified: false
|
||||
};
|
||||
|
||||
User.authenticate.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await AuthService.login('test@example.com', 'password123');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('Please verify your email before logging in');
|
||||
expect(result.requiresVerification).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyEmail', () => {
|
||||
it('should successfully verify email', async () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
is_verified: false,
|
||||
verifyEmail: jest.fn().mockResolvedValue(true)
|
||||
};
|
||||
|
||||
User.findByVerificationToken.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await AuthService.verifyEmail('valid-token');
|
||||
|
||||
expect(User.findByVerificationToken).toHaveBeenCalledWith('valid-token');
|
||||
expect(mockUser.verifyEmail).toHaveBeenCalled();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toBe('Email verified successfully');
|
||||
});
|
||||
|
||||
it('should handle invalid verification token', async () => {
|
||||
User.findByVerificationToken.mockResolvedValue(null);
|
||||
|
||||
const result = await AuthService.verifyEmail('invalid-token');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('Invalid or expired verification token');
|
||||
});
|
||||
|
||||
it('should handle already verified email', async () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
is_verified: true
|
||||
};
|
||||
|
||||
User.findByVerificationToken.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await AuthService.verifyEmail('valid-token');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toBe('Email already verified');
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestPasswordReset', () => {
|
||||
it('should send reset email for existing user', async () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
setResetToken: jest.fn().mockResolvedValue('reset-token')
|
||||
};
|
||||
|
||||
User.findByEmail.mockResolvedValue(mockUser);
|
||||
emailService.sendPasswordResetEmail.mockResolvedValue({ message: 'Email sent' });
|
||||
|
||||
const result = await AuthService.requestPasswordReset('test@example.com');
|
||||
|
||||
expect(User.findByEmail).toHaveBeenCalledWith('test@example.com');
|
||||
expect(mockUser.setResetToken).toHaveBeenCalled();
|
||||
expect(emailService.sendPasswordResetEmail).toHaveBeenCalledWith(mockUser, 'reset-token');
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should not reveal if email does not exist', async () => {
|
||||
User.findByEmail.mockResolvedValue(null);
|
||||
|
||||
const result = await AuthService.requestPasswordReset('nonexistent@example.com');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toBe('If an account with that email exists, a password reset link has been sent.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPassword', () => {
|
||||
it('should successfully reset password', async () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
updatePassword: jest.fn().mockResolvedValue(true)
|
||||
};
|
||||
|
||||
User.findByResetToken.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await AuthService.resetPassword('valid-token', 'newPassword123');
|
||||
|
||||
expect(User.findByResetToken).toHaveBeenCalledWith('valid-token');
|
||||
expect(mockUser.updatePassword).toHaveBeenCalledWith('newPassword123');
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toBe('Password reset successfully');
|
||||
});
|
||||
|
||||
it('should handle invalid reset token', async () => {
|
||||
User.findByResetToken.mockResolvedValue(null);
|
||||
|
||||
const result = await AuthService.resetPassword('invalid-token', 'newPassword123');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('Invalid or expired reset token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('changePassword', () => {
|
||||
it('should successfully change password', async () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
password_hash: 'hashed-password',
|
||||
updatePassword: jest.fn().mockResolvedValue(true)
|
||||
};
|
||||
|
||||
User.findById.mockResolvedValue(mockUser);
|
||||
User.verifyPassword.mockResolvedValue(true);
|
||||
|
||||
const result = await AuthService.changePassword('user-123', 'currentPassword', 'newPassword123');
|
||||
|
||||
expect(User.findById).toHaveBeenCalledWith('user-123');
|
||||
expect(User.verifyPassword).toHaveBeenCalledWith('currentPassword', 'hashed-password');
|
||||
expect(mockUser.updatePassword).toHaveBeenCalledWith('newPassword123');
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should fail with incorrect current password', async () => {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
password_hash: 'hashed-password'
|
||||
};
|
||||
|
||||
User.findById.mockResolvedValue(mockUser);
|
||||
User.verifyPassword.mockResolvedValue(false);
|
||||
|
||||
const result = await AuthService.changePassword('user-123', 'wrongPassword', 'newPassword123');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('Current password is incorrect');
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshToken', () => {
|
||||
it('should successfully refresh token', async () => {
|
||||
const mockPayload = { userId: 'user-123' };
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
toSafeObject: () => ({ id: 'user-123', email: 'test@example.com' })
|
||||
};
|
||||
|
||||
jwt.verify.mockReturnValue(mockPayload);
|
||||
User.findById.mockResolvedValue(mockUser);
|
||||
jwt.sign.mockReturnValue('new-token');
|
||||
|
||||
const result = await AuthService.refreshToken('old-token');
|
||||
|
||||
expect(jwt.verify).toHaveBeenCalledWith('old-token', process.env.JWT_SECRET, {
|
||||
issuer: 'bookmark-manager',
|
||||
audience: 'bookmark-manager-users'
|
||||
});
|
||||
expect(User.findById).toHaveBeenCalledWith('user-123');
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.token).toBe('new-token');
|
||||
});
|
||||
|
||||
it('should fail with invalid token', async () => {
|
||||
jwt.verify.mockImplementation(() => {
|
||||
throw new Error('Invalid token');
|
||||
});
|
||||
|
||||
const result = await AuthService.refreshToken('invalid-token');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('Invalid token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateAuthToken', () => {
|
||||
it('should validate token and return user', async () => {
|
||||
const mockPayload = { userId: 'user-123' };
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
is_verified: true
|
||||
};
|
||||
|
||||
jwt.verify.mockReturnValue(mockPayload);
|
||||
User.findById.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await AuthService.validateAuthToken('valid-token');
|
||||
|
||||
expect(result).toEqual(mockUser);
|
||||
});
|
||||
|
||||
it('should return null for invalid token', async () => {
|
||||
jwt.verify.mockImplementation(() => {
|
||||
throw new Error('Invalid token');
|
||||
});
|
||||
|
||||
const result = await AuthService.validateAuthToken('invalid-token');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for unverified user', async () => {
|
||||
const mockPayload = { userId: 'user-123' };
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
is_verified: false
|
||||
};
|
||||
|
||||
jwt.verify.mockReturnValue(mockPayload);
|
||||
User.findById.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await AuthService.validateAuthToken('valid-token');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user