const request = require('supertest'); const app = require('../../src/app'); const User = require('../../src/models/User'); const dbConnection = require('../../src/database/connection'); describe('Authentication Integration Tests', () => { let testUser; let authToken; beforeAll(async () => { // Ensure database is clean before tests await dbConnection.query('DELETE FROM users WHERE email LIKE $1', ['%test%']); }); afterAll(async () => { // Clean up test data if (testUser) { await dbConnection.query('DELETE FROM users WHERE id = $1', [testUser.id]); } await dbConnection.end(); }); beforeEach(() => { testUser = null; authToken = null; }); describe('User Registration Flow', () => { it('should register a new user successfully', async () => { const userData = { email: 'integration-test@example.com', password: 'TestPassword123!' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(201); expect(response.body).toHaveProperty('message'); expect(response.body).toHaveProperty('user'); expect(response.body.user.email).toBe(userData.email); expect(response.body.user.is_verified).toBe(false); // Store test user for cleanup testUser = await User.findByEmail(userData.email); expect(testUser).toBeTruthy(); }); it('should reject registration with invalid email', async () => { const userData = { email: 'invalid-email', password: 'TestPassword123!' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body).toHaveProperty('error'); expect(response.body.code).toBe('REGISTRATION_FAILED'); }); it('should reject registration with weak password', async () => { const userData = { email: 'weak-password-test@example.com', password: 'weak' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body).toHaveProperty('error'); expect(response.body.code).toBe('REGISTRATION_FAILED'); }); it('should reject duplicate email registration', async () => { const userData = { email: 'duplicate-test@example.com', password: 'TestPassword123!' }; // First registration await request(app) .post('/api/auth/register') .send(userData) .expect(201); // Store for cleanup testUser = await User.findByEmail(userData.email); // Second registration with same email const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body).toHaveProperty('error'); expect(response.body.code).toBe('REGISTRATION_FAILED'); }); it('should reject registration with missing fields', async () => { const response = await request(app) .post('/api/auth/register') .send({ email: 'test@example.com' }) // Missing password .expect(400); expect(response.body.code).toBe('MISSING_FIELDS'); }); }); describe('Email Verification Flow', () => { beforeEach(async () => { // Create unverified user for testing testUser = await User.create({ email: 'verification-test@example.com', password: 'TestPassword123!' }); }); it('should verify email with valid token', async () => { const response = await request(app) .get(`/api/auth/verify/${testUser.verification_token}`) .expect(200); expect(response.body).toHaveProperty('message'); expect(response.body.message).toContain('verified successfully'); // Check that user is now verified const updatedUser = await User.findById(testUser.id); expect(updatedUser.is_verified).toBe(true); }); it('should reject verification with invalid token', async () => { const response = await request(app) .get('/api/auth/verify/invalid-token') .expect(400); expect(response.body).toHaveProperty('error'); expect(response.body.code).toBe('EMAIL_VERIFICATION_FAILED'); }); it('should handle already verified email', async () => { // First verification await request(app) .get(`/api/auth/verify/${testUser.verification_token}`) .expect(200); // Second verification attempt const response = await request(app) .get(`/api/auth/verify/${testUser.verification_token}`) .expect(200); expect(response.body.message).toContain('already verified'); }); }); describe('User Login Flow', () => { beforeEach(async () => { // Create verified user for testing testUser = await User.create({ email: 'login-test@example.com', password: 'TestPassword123!' }); await testUser.verifyEmail(); }); it('should login with valid credentials', async () => { const loginData = { email: 'login-test@example.com', password: 'TestPassword123!' }; const response = await request(app) .post('/api/auth/login') .send(loginData) .expect(200); expect(response.body).toHaveProperty('message'); expect(response.body).toHaveProperty('user'); expect(response.body.user.email).toBe(loginData.email); // Check that auth cookie is set const cookies = response.headers['set-cookie']; expect(cookies).toBeDefined(); expect(cookies.some(cookie => cookie.includes('authToken'))).toBe(true); // Extract token for further tests const authCookie = cookies.find(cookie => cookie.includes('authToken')); authToken = authCookie.split('authToken=')[1].split(';')[0]; }); it('should reject login with invalid credentials', async () => { const loginData = { email: 'login-test@example.com', password: 'WrongPassword123!' }; const response = await request(app) .post('/api/auth/login') .send(loginData) .expect(401); expect(response.body).toHaveProperty('error'); expect(response.body.code).toBe('INVALID_CREDENTIALS'); }); it('should reject login for unverified user', async () => { // Create unverified user const unverifiedUser = await User.create({ email: 'unverified-login-test@example.com', password: 'TestPassword123!' }); const loginData = { email: 'unverified-login-test@example.com', password: 'TestPassword123!' }; const response = await request(app) .post('/api/auth/login') .send(loginData) .expect(403); expect(response.body).toHaveProperty('error'); expect(response.body.code).toBe('EMAIL_NOT_VERIFIED'); // Cleanup await unverifiedUser.delete(); }); it('should reject login with missing credentials', async () => { const response = await request(app) .post('/api/auth/login') .send({ email: 'test@example.com' }) // Missing password .expect(400); expect(response.body.code).toBe('MISSING_CREDENTIALS'); }); }); describe('Password Reset Flow', () => { beforeEach(async () => { testUser = await User.create({ email: 'password-reset-test@example.com', password: 'TestPassword123!' }); await testUser.verifyEmail(); }); it('should request password reset for existing user', async () => { const response = await request(app) .post('/api/auth/forgot-password') .send({ email: 'password-reset-test@example.com' }) .expect(200); expect(response.body).toHaveProperty('message'); expect(response.body.message).toContain('password reset link has been sent'); // Check that reset token was set const updatedUser = await User.findById(testUser.id); expect(updatedUser.reset_token).toBeTruthy(); expect(updatedUser.reset_expires).toBeTruthy(); }); it('should not reveal if email does not exist', async () => { const response = await request(app) .post('/api/auth/forgot-password') .send({ email: 'nonexistent@example.com' }) .expect(200); expect(response.body.message).toContain('password reset link has been sent'); }); it('should reset password with valid token', async () => { // First request password reset await request(app) .post('/api/auth/forgot-password') .send({ email: 'password-reset-test@example.com' }); // Get the reset token const userWithToken = await User.findById(testUser.id); const resetToken = userWithToken.reset_token; // Reset password const newPassword = 'NewPassword123!'; const response = await request(app) .post('/api/auth/reset-password') .send({ token: resetToken, newPassword: newPassword }) .expect(200); expect(response.body).toHaveProperty('message'); expect(response.body.message).toContain('reset successfully'); // Verify user can login with new password const loginResponse = await request(app) .post('/api/auth/login') .send({ email: 'password-reset-test@example.com', password: newPassword }) .expect(200); expect(loginResponse.body.user.email).toBe('password-reset-test@example.com'); }); it('should reject password reset with invalid token', async () => { const response = await request(app) .post('/api/auth/reset-password') .send({ token: 'invalid-token', newPassword: 'NewPassword123!' }) .expect(400); expect(response.body).toHaveProperty('error'); expect(response.body.code).toBe('PASSWORD_RESET_FAILED'); }); }); describe('Logout Flow', () => { beforeEach(async () => { // Create verified user and login testUser = await User.create({ email: 'logout-test@example.com', password: 'TestPassword123!' }); await testUser.verifyEmail(); const loginResponse = await request(app) .post('/api/auth/login') .send({ email: 'logout-test@example.com', password: 'TestPassword123!' }); const cookies = loginResponse.headers['set-cookie']; const authCookie = cookies.find(cookie => cookie.includes('authToken')); authToken = authCookie.split('authToken=')[1].split(';')[0]; }); it('should logout successfully', async () => { const response = await request(app) .post('/api/auth/logout') .set('Cookie', `authToken=${authToken}`) .expect(200); expect(response.body).toHaveProperty('message'); expect(response.body.message).toContain('Logged out successfully'); // Check that auth cookie is cleared const cookies = response.headers['set-cookie']; expect(cookies).toBeDefined(); expect(cookies.some(cookie => cookie.includes('authToken=;'))).toBe(true); }); it('should require authentication for logout', async () => { const response = await request(app) .post('/api/auth/logout') .expect(401); expect(response.body).toHaveProperty('error'); }); }); describe('Token Refresh Flow', () => { beforeEach(async () => { // Create verified user and login testUser = await User.create({ email: 'refresh-test@example.com', password: 'TestPassword123!' }); await testUser.verifyEmail(); const loginResponse = await request(app) .post('/api/auth/login') .send({ email: 'refresh-test@example.com', password: 'TestPassword123!' }); const cookies = loginResponse.headers['set-cookie']; const authCookie = cookies.find(cookie => cookie.includes('authToken')); authToken = authCookie.split('authToken=')[1].split(';')[0]; }); it('should refresh token successfully', async () => { const response = await request(app) .post('/api/auth/refresh') .set('Cookie', `authToken=${authToken}`) .expect(200); expect(response.body).toHaveProperty('message'); expect(response.body).toHaveProperty('user'); expect(response.body.message).toContain('Token refreshed successfully'); // Check that new auth cookie is set const cookies = response.headers['set-cookie']; expect(cookies).toBeDefined(); expect(cookies.some(cookie => cookie.includes('authToken'))).toBe(true); }); it('should require valid token for refresh', async () => { const response = await request(app) .post('/api/auth/refresh') .set('Cookie', 'authToken=invalid-token') .expect(401); expect(response.body).toHaveProperty('error'); expect(response.body.code).toBe('TOKEN_REFRESH_FAILED'); }); }); describe('Rate Limiting', () => { it('should enforce rate limiting on login attempts', async () => { const loginData = { email: 'rate-limit-test@example.com', password: 'WrongPassword123!' }; // Make multiple failed login attempts const promises = []; for (let i = 0; i < 6; i++) { promises.push( request(app) .post('/api/auth/login') .send(loginData) ); } const responses = await Promise.all(promises); // First 5 should be 401 (invalid credentials) // 6th should be 429 (rate limited) const rateLimitedResponse = responses.find(res => res.status === 429); expect(rateLimitedResponse).toBeDefined(); expect(rateLimitedResponse.body.code).toBe('RATE_LIMIT_EXCEEDED'); }, 10000); // Increase timeout for this test it('should enforce rate limiting on registration attempts', async () => { const promises = []; for (let i = 0; i < 4; i++) { promises.push( request(app) .post('/api/auth/register') .send({ email: `rate-limit-register-${i}@example.com`, password: 'TestPassword123!' }) ); } const responses = await Promise.all(promises); // 4th registration should be rate limited const rateLimitedResponse = responses.find(res => res.status === 429); expect(rateLimitedResponse).toBeDefined(); expect(rateLimitedResponse.body.code).toBe('REGISTRATION_RATE_LIMIT'); // Cleanup created users for (let i = 0; i < 3; i++) { const user = await User.findByEmail(`rate-limit-register-${i}@example.com`); if (user) { await user.delete(); } } }, 10000); }); });