Files
playzer/apps/backend/test/admin-booking/reschedule-admin-booking.handler.test.ts
Jose Selesan dce312d426 feat: add reschedule functionality for admin bookings
- Implemented rescheduleAdminBooking service to allow users to change court and time for confirmed bookings.
- Added validation for court availability, maintenance status, and overlapping bookings.
- Created reschedule-admin-booking handler to process rescheduling requests and send confirmation emails.
- Updated booking email service to include rescheduling notifications.
- Enhanced frontend components to support booking rescheduling, including a new dialog for selecting new court and time.
- Added tests for rescheduling logic, covering various scenarios including validation errors and successful reschedules.
- Updated Prisma schema to log previous court and time for audit purposes.
2026-06-24 08:38:10 -03:00

127 lines
3.5 KiB
TypeScript

import { beforeEach, expect, mock, test } from 'bun:test';
const rescheduleAdminBookingMock = mock(
async (_userId: string, _id: string, _input: unknown) => ({})
);
class MockAdminBookingServiceError extends Error {
status: 400 | 403 | 404 | 409;
constructor(message: string, status: 400 | 403 | 404 | 409 = 400) {
super(message);
this.name = 'AdminBookingServiceError';
this.status = status;
}
}
mock.module('@/modules/admin-booking/services/admin-booking.service', () => ({
__esModule: true,
AdminBookingServiceError: MockAdminBookingServiceError,
rescheduleAdminBooking: rescheduleAdminBookingMock,
}));
const { rescheduleAdminBookingHandler } = await import(
'@/modules/admin-booking/handlers/reschedule-admin-booking.handler'
);
type HandlerContext = {
get: (key: 'user' | 'requestId') => { id: string } | undefined;
req: {
valid: (type: string) => Record<string, unknown>;
};
json: ReturnType<typeof mock>;
};
function createContext(input: { userId: string; body: unknown }) {
const json = mock((payload: unknown, init?: unknown) => ({
payload,
init,
}));
const c = {
get: (key: 'user' | 'requestId') => {
if (key === 'user') return { id: input.userId };
return undefined;
},
req: {
valid: (type: string) => {
if (type === 'param') return { id: 'booking-1' };
if (type === 'json') return input.body as Record<string, unknown>;
return {};
},
},
json,
} as unknown as HandlerContext;
return { c, json };
}
beforeEach(() => {
rescheduleAdminBookingMock.mockReset();
});
test('returns 200 with the updated booking', async () => {
const updatedBooking = {
id: 'booking-1',
bookingCode: 'ABC123',
complexId: 'complex-1',
complexName: 'Complejo Test',
courtId: 'court-2',
courtName: 'Cancha 2',
sport: { id: 'sport-1', name: 'Tenis', slug: 'tenis' },
date: '2026-07-15',
startTime: '14:00',
endTime: '15:00',
customerName: 'Juan Perez',
customerPhone: '1144444444',
customerEmail: 'juan@example.com',
price: 15000,
status: 'CONFIRMED',
recurringGroupId: null,
createdAt: '2026-07-14T00:00:00.000Z',
updatedAt: '2026-07-14T00:00:00.000Z',
} as const;
rescheduleAdminBookingMock.mockResolvedValue(updatedBooking);
const { c, json } = createContext({
userId: 'user-1',
body: { courtId: 'court-2', startTime: '14:00' },
});
const response = await rescheduleAdminBookingHandler(c as never);
expect(rescheduleAdminBookingMock).toHaveBeenCalledWith('user-1', 'booking-1', {
courtId: 'court-2',
startTime: '14:00',
});
expect(json.mock.calls[0]?.[0]).toEqual(updatedBooking);
expect(json.mock.calls[0]?.[1]).toBeUndefined();
expect(response).toBeDefined();
});
test('maps AdminBookingServiceError to an error response', async () => {
rescheduleAdminBookingMock.mockRejectedValue(
new MockAdminBookingServiceError(
'Solo se pueden reprogramar reservas en estado confirmada.',
409
)
);
const { c, json } = createContext({
userId: 'user-1',
body: { startTime: '14:00' },
});
const response = await rescheduleAdminBookingHandler(c as never);
expect(rescheduleAdminBookingMock).toHaveBeenCalledWith('user-1', 'booking-1', {
startTime: '14:00',
});
expect(json.mock.calls[0]?.[0]).toEqual({
message: 'Solo se pueden reprogramar reservas en estado confirmada.',
});
expect(json.mock.calls[0]?.[1]).toBe(409);
expect(response).toBeDefined();
});