✨ Características: - 45 ejercicios universitarios (Basic → Advanced) - Renderizado LaTeX profesional - IA generativa (Z.ai/DashScope) - Docker 9 servicios - Tests 123/123 pasando - Seguridad enterprise (JWT, XSS, Rate limiting) 🐳 Infraestructura: - Next.js 14 + Node.js 20 - PostgreSQL 15 + Redis 7 - Docker Compose completo - Nginx + SSL ready 📚 Documentación: - 5 informes técnicos completos - README profesional - Scripts de deployment automatizados Estado: Producción lista ✅
266 lines
7.4 KiB
TypeScript
266 lines
7.4 KiB
TypeScript
/**
|
|
* 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);
|
|
});
|
|
});
|
|
});
|