import request from 'supertest'; import app from '../../../src/app'; import { setupTestDb, teardownTestDb, resetDatabase } from '../../utils/testDb'; import { createUser, createCourt, createCourtWithSchedules, createBooking } from '../../utils/factories'; import { generateTokens } from '../../utils/auth'; import { UserRole, BookingStatus } from '../../../src/utils/constants'; describe('Courts Routes Integration Tests', () => { let testUser: any; let adminUser: any; let userToken: string; let adminToken: string; beforeAll(async () => { await setupTestDb(); }); afterAll(async () => { await teardownTestDb(); }); beforeEach(async () => { await resetDatabase(); testUser = await createUser({ email: 'courtuser@example.com', firstName: 'Court', lastName: 'User', }); adminUser = await createUser({ email: 'courtadmin@example.com', role: UserRole.ADMIN, }); const userTokens = generateTokens({ userId: testUser.id, email: testUser.email, role: testUser.role, }); userToken = userTokens.accessToken; const adminTokens = generateTokens({ userId: adminUser.id, email: adminUser.email, role: adminUser.role, }); adminToken = adminTokens.accessToken; }); describe('GET /api/v1/courts', () => { it('should return all active courts', async () => { // Arrange await createCourt({ name: 'Cancha 1', isActive: true }); await createCourt({ name: 'Cancha 2', isActive: true }); await createCourt({ name: 'Cancha 3', isActive: false }); // Act const response = await request(app) .get('/api/v1/courts'); // Assert expect(response.status).toBe(200); expect(response.body).toHaveProperty('success', true); expect(response.body.data).toBeInstanceOf(Array); expect(response.body.data.length).toBe(2); // Only active courts }); it('should return courts with schedules', async () => { // Arrange await createCourtWithSchedules({ name: 'Cancha Con Horario' }); // Act const response = await request(app) .get('/api/v1/courts'); // Assert expect(response.status).toBe(200); expect(response.body.data[0]).toHaveProperty('schedules'); expect(response.body.data[0].schedules).toBeInstanceOf(Array); }); it('should return courts with booking counts', async () => { // Arrange const court = await createCourtWithSchedules({ name: 'Cancha Con Reservas' }); const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); await createBooking({ userId: testUser.id, courtId: court.id, date: tomorrow, status: BookingStatus.CONFIRMED, }); // Act const response = await request(app) .get('/api/v1/courts'); // Assert expect(response.status).toBe(200); expect(response.body.data[0]).toHaveProperty('_count'); expect(response.body.data[0]._count).toHaveProperty('bookings'); }); }); describe('GET /api/v1/courts/:id', () => { it('should return court by id', async () => { // Arrange const court = await createCourtWithSchedules({ name: 'Cancha Específica', description: 'Descripción de prueba', pricePerHour: 2500, }); // Act const response = await request(app) .get(`/api/v1/courts/${court.id}`); // Assert expect(response.status).toBe(200); expect(response.body).toHaveProperty('success', true); expect(response.body.data).toHaveProperty('id', court.id); expect(response.body.data).toHaveProperty('name', 'Cancha Específica'); expect(response.body.data).toHaveProperty('description', 'Descripción de prueba'); expect(response.body.data).toHaveProperty('pricePerHour', 2500); expect(response.body.data).toHaveProperty('schedules'); }); it('should return 404 when court not found', async () => { // Act const response = await request(app) .get('/api/v1/courts/00000000-0000-0000-0000-000000000000'); // Assert expect(response.status).toBe(404); expect(response.body).toHaveProperty('success', false); expect(response.body).toHaveProperty('message', 'Cancha no encontrada'); }); it('should return 400 for invalid court id format', async () => { // Act const response = await request(app) .get('/api/v1/courts/invalid-id'); // Assert expect(response.status).toBe(400); }); }); describe('GET /api/v1/courts/:id/availability', () => { it('should return availability for a court', async () => { // Arrange const court = await createCourtWithSchedules({ name: 'Cancha Disponible', pricePerHour: 2000, }); const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); const formattedDate = tomorrow.toISOString().split('T')[0]; // Act const response = await request(app) .get(`/api/v1/courts/${court.id}/availability`) .query({ date: formattedDate }); // Assert expect(response.status).toBe(200); expect(response.body).toHaveProperty('success', true); expect(response.body.data).toHaveProperty('courtId', court.id); expect(response.body.data).toHaveProperty('date'); expect(response.body.data).toHaveProperty('openTime'); expect(response.body.data).toHaveProperty('closeTime'); expect(response.body.data).toHaveProperty('slots'); expect(response.body.data.slots).toBeInstanceOf(Array); expect(response.body.data.slots.length).toBeGreaterThan(0); }); it('should mark booked slots as unavailable', async () => { // Arrange const court = await createCourtWithSchedules({ name: 'Cancha Con Reserva', pricePerHour: 2000, }); const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(0, 0, 0, 0); // Create a booking at 10:00 await createBooking({ userId: testUser.id, courtId: court.id, date: tomorrow, startTime: '10:00', endTime: '11:00', status: BookingStatus.CONFIRMED, }); const formattedDate = tomorrow.toISOString().split('T')[0]; // Act const response = await request(app) .get(`/api/v1/courts/${court.id}/availability`) .query({ date: formattedDate }); // Assert expect(response.status).toBe(200); const tenAmSlot = response.body.data.slots.find((s: any) => s.time === '10:00'); expect(tenAmSlot).toBeDefined(); expect(tenAmSlot.available).toBe(false); }); it('should return 404 when court not found', async () => { // Arrange const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); const formattedDate = tomorrow.toISOString().split('T')[0]; // Act const response = await request(app) .get('/api/v1/courts/00000000-0000-0000-0000-000000000000/availability') .query({ date: formattedDate }); // Assert expect(response.status).toBe(404); }); it('should return 400 when date is missing', async () => { // Arrange const court = await createCourtWithSchedules({ name: 'Test Court' }); // Act const response = await request(app) .get(`/api/v1/courts/${court.id}/availability`); // Assert expect(response.status).toBe(400); }); }); describe('POST /api/v1/courts (Admin only)', () => { it('should create a new court as admin', async () => { // Arrange const newCourtData = { name: 'Nueva Cancha Admin', description: 'Cancha creada por admin', type: 'PANORAMIC', isIndoor: false, hasLighting: true, hasParking: true, pricePerHour: 3000, }; // Act const response = await request(app) .post('/api/v1/courts') .set('Authorization', `Bearer ${adminToken}`) .send(newCourtData); // Assert expect(response.status).toBe(201); expect(response.body).toHaveProperty('success', true); expect(response.body.data).toHaveProperty('id'); expect(response.body.data).toHaveProperty('name', newCourtData.name); expect(response.body.data).toHaveProperty('schedules'); expect(response.body.data.schedules).toBeInstanceOf(Array); expect(response.body.data.schedules.length).toBe(7); // One for each day }); it('should return 401 when not authenticated', async () => { // Act const response = await request(app) .post('/api/v1/courts') .send({ name: 'Unauthorized Court' }); // Assert expect(response.status).toBe(401); }); it('should return 403 when not admin', async () => { // Act const response = await request(app) .post('/api/v1/courts') .set('Authorization', `Bearer ${userToken}`) .send({ name: 'Forbidden Court' }); // Assert expect(response.status).toBe(403); }); it('should return 409 when court name already exists', async () => { // Arrange await createCourt({ name: 'Duplicate Court' }); // Act const response = await request(app) .post('/api/v1/courts') .set('Authorization', `Bearer ${adminToken}`) .send({ name: 'Duplicate Court' }); // Assert expect(response.status).toBe(409); expect(response.body).toHaveProperty('message', 'Ya existe una cancha con ese nombre'); }); }); describe('PUT /api/v1/courts/:id (Admin only)', () => { it('should update court as admin', async () => { // Arrange const court = await createCourt({ name: 'Court To Update' }); const updateData = { name: 'Updated Court Name', pricePerHour: 3500, }; // Act const response = await request(app) .put(`/api/v1/courts/${court.id}`) .set('Authorization', `Bearer ${adminToken}`) .send(updateData); // Assert expect(response.status).toBe(200); expect(response.body).toHaveProperty('success', true); expect(response.body.data).toHaveProperty('name', updateData.name); expect(response.body.data).toHaveProperty('pricePerHour', updateData.pricePerHour); }); it('should return 404 when court not found', async () => { // Act const response = await request(app) .put('/api/v1/courts/00000000-0000-0000-0000-000000000000') .set('Authorization', `Bearer ${adminToken}`) .send({ name: 'New Name' }); // Assert expect(response.status).toBe(404); }); it('should return 403 for non-admin user', async () => { // Arrange const court = await createCourt({ name: 'Protected Court' }); // Act const response = await request(app) .put(`/api/v1/courts/${court.id}`) .set('Authorization', `Bearer ${userToken}`) .send({ name: 'Hacked Name' }); // Assert expect(response.status).toBe(403); }); }); describe('DELETE /api/v1/courts/:id (Admin only)', () => { it('should deactivate court as admin', async () => { // Arrange const court = await createCourt({ name: 'Court To Delete', isActive: true }); // Act const response = await request(app) .delete(`/api/v1/courts/${court.id}`) .set('Authorization', `Bearer ${adminToken}`); // Assert expect(response.status).toBe(200); expect(response.body).toHaveProperty('success', true); expect(response.body.data).toHaveProperty('isActive', false); }); it('should return 404 when court not found', async () => { // Act const response = await request(app) .delete('/api/v1/courts/00000000-0000-0000-0000-000000000000') .set('Authorization', `Bearer ${adminToken}`); // Assert expect(response.status).toBe(404); }); it('should return 403 for non-admin user', async () => { // Arrange const court = await createCourt({ name: 'Protected Court' }); // Act const response = await request(app) .delete(`/api/v1/courts/${court.id}`) .set('Authorization', `Bearer ${userToken}`); // Assert expect(response.status).toBe(403); }); }); });