/** * Integration Tests - Auth API * * Tests for: * - User registration * - User login * - JWT token validation * - Protected routes * - Password strength validation */ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; import request from 'supertest'; import express, { ErrorRequestHandler } from 'express'; import { prisma } from '../../src/shared/database/prisma.client'; import { authRoutes } from '../../src/modules/auth/auth.routes'; // Simple error handler for tests const testErrorHandler: ErrorRequestHandler = (err, req, res, _next) => { const statusCode = err.statusCode || err.status || 500; const code = err.code || 'INTERNAL_ERROR'; const message = err.message || 'An unexpected error occurred'; res.status(statusCode).json({ success: false, error: { code, message, }, meta: { timestamp: new Date().toISOString(), }, }); }; describe('Auth API Integration', () => { let app: express.Application; beforeAll(async () => { // Ensure test database is clean await prisma.$connect(); }); afterAll(async () => { await prisma.$disconnect(); }); beforeEach(async () => { // Clean up test data before each test await prisma.refreshToken.deleteMany({}); await prisma.passwordResetToken.deleteMany({}); await prisma.exerciseAttempt.deleteMany({}); await prisma.progress.deleteMany({}); await prisma.user.deleteMany({ where: { email: { contains: 'test@', }, }, }); // Create fresh app instance app = express(); app.use(express.json()); app.use('/api/auth', authRoutes); // Add error handler last app.use(testErrorHandler); }); // ============================================ // REGISTRATION TESTS // ============================================ describe('POST /api/auth/register', () => { it('should register a new user successfully', async () => { const response = await request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'SecurePass123!', username: 'testuser', }); expect(response.status).toBe(201); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('token'); expect(response.body.data.user).toHaveProperty('id'); expect(response.body.data.user.email).toBe('test@example.com'); expect(response.body.data.user).not.toHaveProperty('passwordHash'); }); it('should reject weak passwords', async () => { const response = await request(app) .post('/api/auth/register') .send({ email: 'test2@example.com', password: '123', username: 'testuser2', }); expect(response.status).toBe(400); expect(response.body.success).toBe(false); }); it('should reject duplicate emails', async () => { // Create first user await request(app) .post('/api/auth/register') .send({ email: 'duplicate@example.com', password: 'SecurePass123!', username: 'user1', }); // Try to create second user with same email const response = await request(app) .post('/api/auth/register') .send({ email: 'duplicate@example.com', password: 'SecurePass123!', username: 'user2', }); expect(response.status).toBe(409); expect(response.body.success).toBe(false); }); it('should reject duplicate usernames', async () => { // Create first user await request(app) .post('/api/auth/register') .send({ email: 'user1@example.com', password: 'SecurePass123!', username: 'uniqueuser', }); // Try to create second user with same username const response = await request(app) .post('/api/auth/register') .send({ email: 'user2@example.com', password: 'SecurePass123!', username: 'uniqueuser', }); expect(response.status).toBe(409); expect(response.body.success).toBe(false); }); }); // ============================================ // LOGIN TESTS // ============================================ describe('POST /api/auth/login', () => { beforeEach(async () => { // Register a user for login tests await request(app) .post('/api/auth/register') .send({ email: 'login@example.com', password: 'SecurePass123!', username: 'loginuser', }); }); it('should login with correct credentials', async () => { const response = await request(app) .post('/api/auth/login') .send({ email: 'login@example.com', password: 'SecurePass123!', }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('token'); expect(response.body.data.user.email).toBe('login@example.com'); }); it('should reject incorrect password', async () => { const response = await request(app) .post('/api/auth/login') .send({ email: 'login@example.com', password: 'WrongPass123!', }); expect(response.status).toBe(401); expect(response.body.success).toBe(false); }); it('should reject non-existent user', async () => { const response = await request(app) .post('/api/auth/login') .send({ email: 'nonexistent@example.com', password: 'SecurePass123!', }); expect(response.status).toBe(401); expect(response.body.success).toBe(false); }); }); // ============================================ // PROFILE TESTS // ============================================ describe('GET /api/auth/profile', () => { let authToken: string; let userId: string; beforeEach(async () => { // Clean up any existing profile test user await prisma.refreshToken.deleteMany({}); await prisma.exerciseAttempt.deleteMany({}); await prisma.progress.deleteMany({}); await prisma.user.deleteMany({ where: { email: 'profile@example.com', }, }); const registerResponse = await request(app) .post('/api/auth/register') .send({ email: 'profile@example.com', password: 'SecurePass123!', username: 'profileuser', }); authToken = registerResponse.body.data.token; userId = registerResponse.body.data.user.id; }); it('should get user profile with valid token', async () => { const response = await request(app) .get('/api/auth/me') .set('Authorization', `Bearer ${authToken}`); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.id).toBe(userId); expect(response.body.data.email).toBe('profile@example.com'); }); it('should reject request without token', async () => { const response = await request(app) .get('/api/auth/me'); expect(response.status).toBe(401); }); it('should reject request with invalid token', async () => { const response = await request(app) .get('/api/auth/me') .set('Authorization', 'Bearer invalid-token'); expect(response.status).toBe(401); }); }); });