Skip to content

Commit f3d4eab

Browse files
authored
fix: POST v2/bookings - recurrenceCount (with eventType reoccuring enabled) causes lengthInMinutes to be ignored (calcom#23023)
* fix * added tests * chore
1 parent 866b6cc commit f3d4eab

2 files changed

Lines changed: 80 additions & 2 deletions

File tree

apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/variable-length-bookings.e2e-spec.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ import { randomString } from "test/utils/randomString";
2121
import { withApiAuth } from "test/utils/withApiAuth";
2222

2323
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
24-
import { CreateBookingInput_2024_08_13, BookingOutput_2024_08_13 } from "@calcom/platform-types";
24+
import {
25+
CreateBookingInput_2024_08_13,
26+
BookingOutput_2024_08_13,
27+
CreateRecurringBookingInput_2024_08_13,
28+
RecurringBookingOutput_2024_08_13,
29+
} from "@calcom/platform-types";
2530
import { PlatformOAuthClient, Team } from "@calcom/prisma/client";
2631

2732
describe("Bookings Endpoints 2024-08-13", () => {
@@ -44,6 +49,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
4449
const VARIABLE_LENGTH_EVENT_TYPE_SLUG = `variable-length-bookings-2024-08-13-event-type-${randomString()}`;
4550
let normalEventType: EventType;
4651
const NORMAL_EVENT_TYPE_SLUG = `variable-length-bookings-2024-08-13-event-type-${randomString()}`;
52+
let variableLengthRecurringEventType: EventType;
53+
const VARIABLE_LENGTH_RECURRING_EVENT_TYPE_SLUG = `variable-length-recurring-bookings-2024-08-13-event-type-${randomString()}`;
4754

4855
beforeAll(async () => {
4956
const moduleRef = await withApiAuth(
@@ -105,6 +112,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
105112
user.id
106113
);
107114

115+
variableLengthRecurringEventType = await eventTypesRepositoryFixture.create(
116+
{
117+
title: `variable-length-recurring-bookings-2024-08-13-event-type-${randomString()}`,
118+
slug: VARIABLE_LENGTH_RECURRING_EVENT_TYPE_SLUG,
119+
length: 15,
120+
metadata: { multipleDuration: [15, 30, 60] },
121+
recurringEvent: { freq: 2, count: 3, interval: 1 }, // Weekly, 3 times, every 1 week
122+
},
123+
user.id
124+
);
125+
108126
app = moduleRef.createNestApplication();
109127
bootstrap(app as NestExpressApplication);
110128

@@ -246,6 +264,63 @@ describe("Bookings Endpoints 2024-08-13", () => {
246264
});
247265
});
248266

267+
describe("create recurring bookings", () => {
268+
it("should create recurring bookings with custom lengthInMinutes", async () => {
269+
const lengthInMinutes = 30;
270+
const body: CreateRecurringBookingInput_2024_08_13 = {
271+
start: new Date(Date.UTC(2030, 1, 1, 14, 0, 0)).toISOString(),
272+
eventTypeId: variableLengthRecurringEventType.id,
273+
lengthInMinutes,
274+
recurrenceCount: 2,
275+
attendee: {
276+
name: "Mr Recurring",
277+
email: "mr_recurring@gmail.com",
278+
timeZone: "Europe/Rome",
279+
language: "it",
280+
},
281+
};
282+
283+
return request(app.getHttpServer())
284+
.post("/v2/bookings")
285+
.send(body)
286+
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
287+
.expect(201)
288+
.then(async (response) => {
289+
const responseBody: { status: string; data: RecurringBookingOutput_2024_08_13[] } = response.body;
290+
expect(responseBody.status).toEqual(SUCCESS_STATUS);
291+
expect(responseBody.data).toBeDefined();
292+
expect(Array.isArray(responseBody.data)).toBe(true);
293+
expect(responseBody.data).toHaveLength(2);
294+
295+
const firstBooking = responseBody.data[0];
296+
const secondBooking = responseBody.data[1];
297+
expect(firstBooking.duration).toEqual(lengthInMinutes);
298+
expect(secondBooking.duration).toEqual(lengthInMinutes);
299+
});
300+
});
301+
302+
it("should reject recurring booking with invalid lengthInMinutes", async () => {
303+
const body: CreateRecurringBookingInput_2024_08_13 = {
304+
start: new Date(Date.UTC(2030, 3, 1, 10, 0, 0)).toISOString(),
305+
eventTypeId: variableLengthRecurringEventType.id,
306+
lengthInMinutes: 45, // Not in allowed [15, 30, 60]
307+
recurrenceCount: 2,
308+
attendee: {
309+
name: "Mr Invalid",
310+
email: "mr_invalid@gmail.com",
311+
timeZone: "Europe/Rome",
312+
language: "it",
313+
},
314+
};
315+
316+
return request(app.getHttpServer())
317+
.post("/v2/bookings")
318+
.send(body)
319+
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
320+
.expect(400);
321+
});
322+
});
323+
249324
afterAll(async () => {
250325
await oauthClientRepositoryFixture.delete(oAuthClient.id);
251326
await teamRepositoryFixture.delete(organization.id);

apps/api/v2/src/ee/bookings/2024-08-13/services/input.service.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ export class InputBookingsService_2024_08_13 {
420420
throw new NotFoundException(`Event type with id=${inputBooking.eventTypeId} is not a recurring event`);
421421
}
422422

423+
this.validateBookingLengthInMinutes(inputBooking, eventType);
424+
const lengthInMinutes = inputBooking.lengthInMinutes ?? eventType.length;
425+
423426
const occurrence = recurringEventSchema.parse(eventType.recurringEvent);
424427
const repeatsEvery = occurrence.interval;
425428

@@ -456,7 +459,7 @@ export class InputBookingsService_2024_08_13 {
456459
const location = inputLocation ? this.transformLocation(inputLocation) : undefined;
457460

458461
for (let i = 0; i < repeatsTimes; i++) {
459-
const endTime = startTime.plus({ minutes: eventType.length });
462+
const endTime = startTime.plus({ minutes: lengthInMinutes });
460463

461464
events.push({
462465
start: startTime.toISO(),

0 commit comments

Comments
 (0)