Files
app-padel/backend/tests/integration/routes/booking.routes.test.ts
Ivan Alcaraz dd10891432
Some checks failed
CI/CD Pipeline / 🧪 Tests (push) Has been cancelled
CI/CD Pipeline / 🏗️ Build (push) Has been cancelled
CI/CD Pipeline / 🚀 Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / 🚀 Deploy to Production (push) Has been cancelled
CI/CD Pipeline / 🏷️ Create Release (push) Has been cancelled
CI/CD Pipeline / 🧹 Cleanup (push) Has been cancelled
FASE 7 COMPLETADA: Testing y Lanzamiento - PROYECTO FINALIZADO
Implementados 4 módulos con agent swarm:

1. TESTING FUNCIONAL (Jest)
   - Configuración Jest + ts-jest
   - Tests unitarios: auth, booking, court (55 tests)
   - Tests integración: routes (56 tests)
   - Factories y utilidades de testing
   - Coverage configurado (70% servicios)
   - Scripts: test, test:watch, test:coverage

2. TESTING DE USUARIO (Beta)
   - Sistema de beta testers
   - Feedback con categorías y severidad
   - Beta issues tracking
   - 8 testers de prueba creados
   - API completa para gestión de feedback

3. DOCUMENTACIÓN COMPLETA
   - API.md - 150+ endpoints documentados
   - SETUP.md - Guía de instalación
   - DEPLOY.md - Deploy en VPS
   - ARCHITECTURE.md - Arquitectura del sistema
   - APP_STORE.md - Material para stores
   - Postman Collection completa
   - PM2 ecosystem config
   - Nginx config con SSL

4. GO LIVE Y PRODUCCIÓN
   - Sistema de monitoreo (logs, health checks)
   - Servicio de alertas multi-canal
   - Pre-deploy check script
   - Docker + docker-compose producción
   - Backup automatizado
   - CI/CD GitHub Actions
   - Launch checklist completo

ESTADÍSTICAS FINALES:
- Fases completadas: 7/7
- Archivos creados: 250+
- Líneas de código: 60,000+
- Endpoints API: 150+
- Tests: 110+
- Documentación: 5,000+ líneas

PROYECTO COMPLETO Y LISTO PARA PRODUCCIÓN
2026-01-31 22:30:44 +00:00

429 lines
13 KiB
TypeScript

import request from 'supertest';
import app from '../../../src/app';
import { setupTestDb, teardownTestDb, resetDatabase } from '../../utils/testDb';
import { createUser, createCourtWithSchedules, createBooking } from '../../utils/factories';
import { generateTokens } from '../../utils/auth';
import { UserRole, BookingStatus } from '../../../src/utils/constants';
describe('Booking Routes Integration Tests', () => {
let testUser: any;
let testCourt: any;
let userToken: string;
let adminToken: string;
beforeAll(async () => {
await setupTestDb();
});
afterAll(async () => {
await teardownTestDb();
});
beforeEach(async () => {
await resetDatabase();
// Setup test data
testUser = await createUser({
email: 'bookinguser@example.com',
firstName: 'Booking',
lastName: 'User',
});
testCourt = await createCourtWithSchedules({
name: 'Test Court',
pricePerHour: 2000,
});
const tokens = generateTokens({
userId: testUser.id,
email: testUser.email,
role: testUser.role,
});
userToken = tokens.accessToken;
const adminUser = await createUser({
email: 'admin@example.com',
role: UserRole.ADMIN,
});
const adminTokens = generateTokens({
userId: adminUser.id,
email: adminUser.email,
role: adminUser.role,
});
adminToken = adminTokens.accessToken;
});
describe('POST /api/v1/bookings', () => {
const getTomorrowDate = () => {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
return tomorrow.toISOString().split('T')[0];
};
const getValidBookingData = () => ({
courtId: testCourt.id,
date: getTomorrowDate(),
startTime: '10:00',
endTime: '11:00',
notes: 'Test booking notes',
});
it('should create a booking successfully', async () => {
// Act
const response = await request(app)
.post('/api/v1/bookings')
.set('Authorization', `Bearer ${userToken}`)
.send(getValidBookingData());
// Assert
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('id');
expect(response.body.data).toHaveProperty('courtId', testCourt.id);
expect(response.body.data).toHaveProperty('userId', testUser.id);
expect(response.body.data).toHaveProperty('status', BookingStatus.PENDING);
expect(response.body.data).toHaveProperty('benefitsApplied');
});
it('should return 401 when not authenticated', async () => {
// Act
const response = await request(app)
.post('/api/v1/bookings')
.send(getValidBookingData());
// Assert
expect(response.status).toBe(401);
});
it('should return 400 with invalid court ID', async () => {
// Act
const response = await request(app)
.post('/api/v1/bookings')
.set('Authorization', `Bearer ${userToken}`)
.send({ ...getValidBookingData(), courtId: 'invalid-uuid' });
// Assert
expect(response.status).toBe(400);
expect(response.body).toHaveProperty('success', false);
});
it('should return 400 with invalid date format', async () => {
// Act
const response = await request(app)
.post('/api/v1/bookings')
.set('Authorization', `Bearer ${userToken}`)
.send({ ...getValidBookingData(), date: 'invalid-date' });
// Assert
expect(response.status).toBe(400);
});
it('should return 400 with invalid time format', async () => {
// Act
const response = await request(app)
.post('/api/v1/bookings')
.set('Authorization', `Bearer ${userToken}`)
.send({ ...getValidBookingData(), startTime: '25:00' });
// Assert
expect(response.status).toBe(400);
});
it('should return 404 when court is not found', async () => {
// Act
const response = await request(app)
.post('/api/v1/bookings')
.set('Authorization', `Bearer ${userToken}`)
.send({ ...getValidBookingData(), courtId: '00000000-0000-0000-0000-000000000000' });
// Assert
expect(response.status).toBe(404);
expect(response.body).toHaveProperty('message', 'Cancha no encontrada o inactiva');
});
it('should return 409 when time slot is already booked', async () => {
// Arrange - Create existing booking
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
await createBooking({
userId: testUser.id,
courtId: testCourt.id,
date: tomorrow,
startTime: '10:00',
endTime: '11:00',
status: BookingStatus.CONFIRMED,
});
// Act
const response = await request(app)
.post('/api/v1/bookings')
.set('Authorization', `Bearer ${userToken}`)
.send(getValidBookingData());
// Assert
expect(response.status).toBe(409);
expect(response.body).toHaveProperty('message', 'La cancha no está disponible en ese horario');
});
it('should return 400 with past date', async () => {
// Arrange
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
// Act
const response = await request(app)
.post('/api/v1/bookings')
.set('Authorization', `Bearer ${userToken}`)
.send({
...getValidBookingData(),
date: yesterday.toISOString().split('T')[0],
});
// Assert
expect(response.status).toBe(400);
expect(response.body).toHaveProperty('message', 'No se pueden hacer reservas en fechas pasadas');
});
});
describe('GET /api/v1/bookings/my-bookings', () => {
it('should return user bookings', async () => {
// Arrange
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
await createBooking({
userId: testUser.id,
courtId: testCourt.id,
date: tomorrow,
startTime: '10:00',
endTime: '11:00',
});
// Act
const response = await request(app)
.get('/api/v1/bookings/my-bookings')
.set('Authorization', `Bearer ${userToken}`);
// Assert
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toBeInstanceOf(Array);
expect(response.body.data.length).toBeGreaterThan(0);
});
it('should return 401 when not authenticated', async () => {
// Act
const response = await request(app)
.get('/api/v1/bookings/my-bookings');
// Assert
expect(response.status).toBe(401);
});
});
describe('GET /api/v1/bookings/:id', () => {
it('should return booking by id', async () => {
// Arrange
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const booking = await createBooking({
userId: testUser.id,
courtId: testCourt.id,
date: tomorrow,
startTime: '10:00',
endTime: '11:00',
});
// Act
const response = await request(app)
.get(`/api/v1/bookings/${booking.id}`)
.set('Authorization', `Bearer ${userToken}`);
// Assert
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('id', booking.id);
expect(response.body.data).toHaveProperty('court');
expect(response.body.data).toHaveProperty('user');
});
it('should return 404 when booking not found', async () => {
// Act
const response = await request(app)
.get('/api/v1/bookings/00000000-0000-0000-0000-000000000000')
.set('Authorization', `Bearer ${userToken}`);
// Assert
expect(response.status).toBe(404);
expect(response.body).toHaveProperty('message', 'Reserva no encontrada');
});
});
describe('PUT /api/v1/bookings/:id', () => {
it('should update booking notes', async () => {
// Arrange
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const booking = await createBooking({
userId: testUser.id,
courtId: testCourt.id,
date: tomorrow,
startTime: '10:00',
endTime: '11:00',
});
// Act
const response = await request(app)
.put(`/api/v1/bookings/${booking.id}`)
.set('Authorization', `Bearer ${userToken}`)
.send({ notes: 'Updated notes' });
// Assert
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('notes', 'Updated notes');
});
});
describe('DELETE /api/v1/bookings/:id', () => {
it('should cancel booking successfully', async () => {
// Arrange
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const booking = await createBooking({
userId: testUser.id,
courtId: testCourt.id,
date: tomorrow,
startTime: '10:00',
endTime: '11:00',
status: BookingStatus.PENDING,
});
// Act
const response = await request(app)
.delete(`/api/v1/bookings/${booking.id}`)
.set('Authorization', `Bearer ${userToken}`);
// Assert
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('status', BookingStatus.CANCELLED);
});
it('should return 403 when trying to cancel another user booking', async () => {
// Arrange
const otherUser = await createUser({ email: 'other@example.com' });
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const booking = await createBooking({
userId: otherUser.id,
courtId: testCourt.id,
date: tomorrow,
startTime: '10:00',
endTime: '11:00',
});
// Act
const response = await request(app)
.delete(`/api/v1/bookings/${booking.id}`)
.set('Authorization', `Bearer ${userToken}`);
// Assert
expect(response.status).toBe(403);
expect(response.body).toHaveProperty('message', 'No tienes permiso para cancelar esta reserva');
});
it('should return 400 when booking is already cancelled', async () => {
// Arrange
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const booking = await createBooking({
userId: testUser.id,
courtId: testCourt.id,
date: tomorrow,
startTime: '10:00',
endTime: '11:00',
status: BookingStatus.CANCELLED,
});
// Act
const response = await request(app)
.delete(`/api/v1/bookings/${booking.id}`)
.set('Authorization', `Bearer ${userToken}`);
// Assert
expect(response.status).toBe(400);
expect(response.body).toHaveProperty('message', 'La reserva ya está cancelada');
});
});
describe('GET /api/v1/bookings (Admin)', () => {
it('should return all bookings for admin', async () => {
// Arrange
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
await createBooking({
userId: testUser.id,
courtId: testCourt.id,
date: tomorrow,
startTime: '10:00',
endTime: '11:00',
});
// Act
const response = await request(app)
.get('/api/v1/bookings')
.set('Authorization', `Bearer ${adminToken}`);
// Assert
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toBeInstanceOf(Array);
});
it('should return 403 for non-admin user', async () => {
// Act
const response = await request(app)
.get('/api/v1/bookings')
.set('Authorization', `Bearer ${userToken}`);
// Assert
expect(response.status).toBe(403);
});
});
describe('PUT /api/v1/bookings/:id/confirm (Admin)', () => {
it('should confirm booking for admin', async () => {
// Arrange
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const booking = await createBooking({
userId: testUser.id,
courtId: testCourt.id,
date: tomorrow,
startTime: '10:00',
endTime: '11:00',
status: BookingStatus.PENDING,
});
// Act
const response = await request(app)
.put(`/api/v1/bookings/${booking.id}/confirm`)
.set('Authorization', `Bearer ${adminToken}`);
// Assert
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('status', BookingStatus.CONFIRMED);
});
});
});