362 lines
13 KiB
JavaScript
362 lines
13 KiB
JavaScript
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();
|
|
});
|
|
});
|
|
}); |