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(); }); }); });