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); }); }); });