'use strict'; /** * Auth Controller Unit Tests * Tests for user authentication endpoints */ const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); process.env.JWT_SECRET = 'test-secret-for-unit-tests'; // Mock dependencies before requiring the controller const mockPool = { query: jest.fn(), connect: jest.fn(() => ({ query: jest.fn(), release: jest.fn() })) }; jest.mock('../db', () => ({ getPool: jest.fn(() => mockPool) })); jest.mock('../services/google.service', () => ({ verifyToken: jest.fn() })); // Now require the controller const authController = require('../controllers/auth.controller'); const { getPool } = require('../db'); describe('Auth Controller', () => { let mockReq; let mockRes; let mockPool; beforeEach(() => { mockReq = { body: {}, user: {}, headers: {} }; mockRes = { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis() }; mockPool = getPool(); jest.clearAllMocks(); }); describe('register', () => { it('should return 400 if email already exists', async () => { mockReq.body = { email: 'existing@example.com', password: 'Password123!', name: 'Test User' }; mockPool.query.mockResolvedValueOnce({ rows: [{ id: 'existing-id' }] }); await authController.register(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(400); expect(mockRes.json).toHaveBeenCalledWith({ error: 'Email already registered' }); }); it('should successfully register a new user', async () => { mockReq.body = { email: 'newuser@example.com', password: 'SecurePass123!', name: 'New User' }; // User doesn't exist mockPool.query.mockResolvedValueOnce({ rows: [] }); // Insert user returns new user const newUser = { id: 'new-user-id', email: 'newuser@example.com', name: 'New User', role: 'tourist' }; mockPool.query.mockResolvedValueOnce({ rows: [newUser] }); await authController.register(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(201); expect(mockRes.json).toHaveBeenCalledWith( expect.objectContaining({ user: expect.objectContaining({ email: 'newuser@example.com' }), tokens: expect.objectContaining({ accessToken: expect.any(String), refreshToken: expect.any(String) }) }) ); }); it('should return 500 on database error', async () => { mockReq.body = { email: 'test@test.com', password: 'pass', name: 'Test' }; mockPool.query.mockRejectedValueOnce(new Error('Database error')); await authController.register(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(500); }); }); describe('login', () => { it('should return 401 for non-existent user', async () => { mockReq.body = { email: 'nonexistent@example.com', password: 'password123' }; mockPool.query.mockResolvedValueOnce({ rows: [] }); await authController.login(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(401); expect(mockRes.json).toHaveBeenCalledWith({ error: 'Invalid credentials' }); }); it('should return 401 for invalid password', async () => { mockReq.body = { email: 'user@example.com', password: 'wrongpassword' }; // User exists with different password const user = { id: 'user-id', email: 'user@example.com', password_hash: await bcrypt.hash('correctpassword', 10), is_active: true }; mockPool.query.mockResolvedValueOnce({ rows: [user] }); await authController.login(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(401); }); it('should successfully login with valid credentials', async () => { const password = 'ValidPassword123!'; const hashedPassword = await bcrypt.hash(password, 10); mockReq.body = { email: 'user@example.com', password: password }; const user = { id: 'user-id', email: 'user@example.com', password_hash: hashedPassword, name: 'Test User', role: 'tourist', is_active: true }; mockPool.query.mockResolvedValueOnce({ rows: [user] }); await authController.login(mockReq, mockRes); expect(mockRes.json).toHaveBeenCalledWith( expect.objectContaining({ user: expect.objectContaining({ email: 'user@example.com' }), tokens: expect.objectContaining({ accessToken: expect.any(String), refreshToken: expect.any(String) }) }) ); }); }); describe('currentUser', () => { it('should return current user info', async () => { mockReq.user = { id: 'user-id', email: 'user@example.com', role: 'tourist' }; const fullUser = { id: 'user-id', email: 'user@example.com', name: 'Test User', role: 'tourist' }; mockPool.query.mockResolvedValueOnce({ rows: [fullUser] }); await authController.currentUser(mockReq, mockRes); expect(mockRes.json).toHaveBeenCalledWith( expect.objectContaining({ user: expect.objectContaining({ email: 'user@example.com' }) }) ); }); it('should return 404 if user not found', async () => { mockReq.user = { id: 'nonexistent-id' }; mockPool.query.mockResolvedValueOnce({ rows: [] }); await authController.currentUser(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(404); }); }); describe('logout', () => { it('should return success message', async () => { await authController.logout(mockReq, mockRes); expect(mockRes.json).toHaveBeenCalledWith({ success: true, message: 'Logged out successfully' }); }); }); describe('refresh', () => { it('should return 401 for invalid refresh token', async () => { mockReq.body = { refreshToken: 'invalid-token' }; await authController.refresh(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(401); }); it('should return 401 for token without refresh type', async () => { // Create a token without type: 'refresh' const invalidToken = jwt.sign({ id: 'user-id' }, process.env.JWT_SECRET, { expiresIn: '1h' }); mockReq.body = { refreshToken: invalidToken }; await authController.refresh(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(401); }); it('should refresh tokens successfully with valid refresh token', async () => { // Create a valid refresh token const validRefreshToken = jwt.sign( { id: 'user-id', type: 'refresh' }, process.env.JWT_SECRET, { expiresIn: '30d' } ); mockReq.body = { refreshToken: validRefreshToken }; const user = { id: 'user-id', email: 'user@test.com', name: 'Test', role: 'tourist' }; mockPool.query.mockResolvedValueOnce({ rows: [user] }); await authController.refresh(mockReq, mockRes); expect(mockRes.json).toHaveBeenCalledWith( expect.objectContaining({ user: expect.objectContaining({ email: 'user@test.com' }), tokens: expect.objectContaining({ accessToken: expect.any(String), refreshToken: expect.any(String) }) }) ); }); }); describe('changePassword', () => { it('should return 400 if passwords missing', async () => { mockReq.user = { id: 'user-id' }; mockReq.body = {}; await authController.changePassword(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(400); }); it('should return 401 if current password is incorrect', async () => { mockReq.user = { id: 'user-id' }; mockReq.body = { currentPassword: 'wrongpassword', newPassword: 'NewSecurePass123!' }; const user = { id: 'user-id', password_hash: await bcrypt.hash('correctpassword', 10) }; mockPool.query.mockResolvedValueOnce({ rows: [user] }); await authController.changePassword(mockReq, mockRes); expect(mockRes.status).toHaveBeenCalledWith(401); }); it('should successfully change password with correct current password', async () => { const currentPassword = 'CurrentPass123!'; mockReq.user = { id: 'user-id' }; mockReq.body = { currentPassword: currentPassword, newPassword: 'NewSecurePass123!' }; const user = { id: 'user-id', password_hash: await bcrypt.hash(currentPassword, 10) }; mockPool.query.mockResolvedValueOnce({ rows: [user] }); mockPool.query.mockResolvedValueOnce({}); // UPDATE query await authController.changePassword(mockReq, mockRes); expect(mockRes.json).toHaveBeenCalledWith({ success: true, message: 'Password changed successfully' }); }); }); });