const request = require('supertest'); const express = require('express'); const { v4: uuidv4 } = require('uuid'); const authRoutes = require('../routes/auth.routes'); const { User, GuestSession, QuizSession, sequelize } = require('../models'); // Create Express app for testing const app = express(); app.use(express.json()); app.use('/api/auth', authRoutes); describe('Authentication Endpoints', () => { let testUser; let authToken; beforeAll(async () => { // Sync database await sequelize.sync({ force: true }); }); afterAll(async () => { // Clean up await User.destroy({ where: {}, force: true }); await sequelize.close(); }); describe('POST /api/auth/register', () => { it('should register a new user successfully', async () => { const userData = { username: 'testuser', email: 'test@example.com', password: 'Test@123' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(201); expect(response.body.success).toBe(true); expect(response.body.message).toBe('User registered successfully'); expect(response.body.data).toHaveProperty('user'); expect(response.body.data).toHaveProperty('token'); expect(response.body.data.user.email).toBe(userData.email); expect(response.body.data.user.username).toBe(userData.username); expect(response.body.data.user).not.toHaveProperty('password'); testUser = response.body.data.user; authToken = response.body.data.token; }); it('should reject registration with duplicate email', async () => { const userData = { username: 'anotheruser', email: 'test@example.com', // Same email password: 'Test@123' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body.success).toBe(false); expect(response.body.message).toBe('Email already registered'); }); it('should reject registration with duplicate username', async () => { const userData = { username: 'testuser', // Same username email: 'another@example.com', password: 'Test@123' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body.success).toBe(false); expect(response.body.message).toBe('Username already taken'); }); it('should reject registration with invalid email', async () => { const userData = { username: 'newuser', email: 'invalid-email', password: 'Test@123' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body.success).toBe(false); expect(response.body.message).toBe('Validation failed'); }); it('should reject registration with weak password', async () => { const userData = { username: 'newuser', email: 'new@example.com', password: 'weak' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body.success).toBe(false); expect(response.body.message).toBe('Validation failed'); }); it('should reject registration with username too short', async () => { const userData = { username: 'ab', // Only 2 characters email: 'new@example.com', password: 'Test@123' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body.success).toBe(false); expect(response.body.message).toBe('Validation failed'); }); it('should reject registration with invalid username characters', async () => { const userData = { username: 'test-user!', // Contains invalid characters email: 'new@example.com', password: 'Test@123' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(400); expect(response.body.success).toBe(false); expect(response.body.message).toBe('Validation failed'); }); }); describe('POST /api/auth/register with guest migration', () => { let guestSession; beforeAll(async () => { // Create a guest session with quiz data guestSession = await GuestSession.create({ id: uuidv4(), guest_id: `guest_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, session_token: 'test-guest-token', expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000), max_quizzes: 3, quizzes_attempted: 2, is_converted: false }); // Create quiz sessions for the guest await QuizSession.create({ id: uuidv4(), guest_session_id: guestSession.id, category_id: uuidv4(), quiz_type: 'practice', difficulty: 'easy', status: 'completed', questions_count: 5, questions_answered: 5, correct_answers: 4, score: 40, percentage: 80, is_passed: true, started_at: new Date(), completed_at: new Date() }); }); it('should register user and migrate guest data', async () => { const userData = { username: 'guestconvert', email: 'guestconvert@example.com', password: 'Test@123', guestSessionId: guestSession.guest_id }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(201); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('migratedData'); expect(response.body.data.migratedData).toHaveProperty('quizzes'); expect(response.body.data.migratedData).toHaveProperty('stats'); // Verify guest session is marked as converted await guestSession.reload(); expect(guestSession.is_converted).toBe(true); expect(guestSession.converted_user_id).toBe(response.body.data.user.id); }); }); describe('POST /api/auth/login', () => { it('should login with valid credentials', async () => { const credentials = { email: 'test@example.com', password: 'Test@123' }; const response = await request(app) .post('/api/auth/login') .send(credentials) .expect(200); expect(response.body.success).toBe(true); expect(response.body.message).toBe('Login successful'); expect(response.body.data).toHaveProperty('user'); expect(response.body.data).toHaveProperty('token'); expect(response.body.data.user).not.toHaveProperty('password'); }); it('should reject login with invalid email', async () => { const credentials = { email: 'nonexistent@example.com', password: 'Test@123' }; const response = await request(app) .post('/api/auth/login') .send(credentials) .expect(401); expect(response.body.success).toBe(false); expect(response.body.message).toBe('Invalid email or password'); }); it('should reject login with invalid password', async () => { const credentials = { email: 'test@example.com', password: 'WrongPassword123' }; const response = await request(app) .post('/api/auth/login') .send(credentials) .expect(401); expect(response.body.success).toBe(false); expect(response.body.message).toBe('Invalid email or password'); }); it('should reject login with missing fields', async () => { const credentials = { email: 'test@example.com' // Missing password }; const response = await request(app) .post('/api/auth/login') .send(credentials) .expect(400); expect(response.body.success).toBe(false); expect(response.body.message).toBe('Validation failed'); }); }); describe('GET /api/auth/verify', () => { it('should verify valid token', async () => { const response = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${authToken}`) .expect(200); expect(response.body.success).toBe(true); expect(response.body.message).toBe('Token valid'); expect(response.body.data).toHaveProperty('user'); expect(response.body.data.user.email).toBe('test@example.com'); }); it('should reject request without token', async () => { const response = await request(app) .get('/api/auth/verify') .expect(401); expect(response.body.success).toBe(false); expect(response.body.message).toContain('No token provided'); }); it('should reject request with invalid token', async () => { const response = await request(app) .get('/api/auth/verify') .set('Authorization', 'Bearer invalid-token') .expect(401); expect(response.body.success).toBe(false); expect(response.body.message).toContain('Invalid token'); }); }); describe('POST /api/auth/logout', () => { it('should logout successfully', async () => { const response = await request(app) .post('/api/auth/logout') .expect(200); expect(response.body.success).toBe(true); expect(response.body.message).toContain('Logout successful'); }); }); });