Files
calendar/packages/features/bookings/lib/handleNewBooking/test/team-bookings/collective-reschedule-destination-calendar.test.ts
52a5afeba5 fix: different calendar hosts (#24000)
* fis: pass the missing `platformClientId` to `handleNewBooking` in api/v2

* Update formatCalendarEvent.ts

* only add the externald in case of same calendar, otherwise add if from the .ics file sent in email

* Revert "fis: pass the missing `platformClientId` to `handleNewBooking` in api/v2"

This reverts commit 030ac0f0981c1135d4973fbdafa62d58e8985831.

* fix: alow the host change logic to run for collective envets as well

* fix: run the changedOrganizer logic for both collective and round-robin events

* Update handleNewBooking.ts

* fix: failing unit test

---------

Co-authored-by: Lauris Skraucis <[email protected]>
2025-10-01 12:15:34 +03:00

239 lines
8.7 KiB
TypeScript

import {
createBookingScenario,
getGoogleCalendarCredential,
TestData,
getOrganizer,
getBooker,
getScenarioData,
mockSuccessfulVideoMeetingCreation,
mockCalendarToHaveNoBusySlots,
getDate,
BookingLocations,
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
import { expectBookingToBeInDatabase } from "@calcom/web/test/utils/bookingScenario/expects";
import { getMockRequestDataForBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking";
import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown";
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { describe, expect, beforeEach } from "vitest";
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
import { resetTestEmails } from "@calcom/lib/testEmails";
import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "../getNewBookingHandler";
export type CustomNextApiRequest = NextApiRequest & Request;
export type CustomNextApiResponse = NextApiResponse & Response;
const timeout = process.env.CI ? 5000 : 20000;
describe("handleNewBooking", () => {
setupAndTeardown();
beforeEach(() => {
resetTestEmails();
});
describe("Collective Event Reschedule with Destination Calendar Bug", () => {
test(
`should reschedule a collective event booking and ensure both hosts have the booking in their calendars when a second host is added
1. Create a collective event with 1 host and book it
2. Add a second host to the event type
3. Reschedule the booking
4. Verify both hosts have the booking in their calendars
`,
async ({ emails }) => {
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "[email protected]",
name: "Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "[email protected]",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
destinationCalendar: {
integration: "google_calendar",
externalId: "[email protected]",
primaryEmail: "[email protected]",
},
});
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
const uidOfBookingToBeRescheduled = "n5Wv3eHgconAED2j4gcVhP";
const iCalUID = `${uidOfBookingToBeRescheduled}@Cal.com`;
await createBookingScenario(
getScenarioData({
eventTypes: [
{
id: 1,
slotInterval: 15,
length: 30,
schedulingType: SchedulingType.COLLECTIVE,
users: [
{
id: 101, // organizer
},
{
id: 102, // second host (will be added to event type)
},
],
destinationCalendar: {
integration: "google_calendar",
externalId: "[email protected]",
primaryEmail: "[email protected]",
},
},
],
users: [
{
id: 101,
name: "Organizer",
email: "[email protected]",
username: "organizer",
timeZone: "Asia/Kolkata",
defaultScheduleId: null,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
destinationCalendar: {
integration: "google_calendar",
externalId: "[email protected]",
primaryEmail: "[email protected]",
},
},
{
id: 102,
name: "Second Host",
email: "[email protected]",
username: "second-host",
timeZone: "Asia/Kolkata",
defaultScheduleId: null,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
destinationCalendar: {
integration: "google_calendar",
externalId: "[email protected]",
primaryEmail: "[email protected]",
},
},
],
bookings: [
{
uid: uidOfBookingToBeRescheduled,
eventTypeId: 1,
status: BookingStatus.ACCEPTED,
startTime: `${plus1DateString}T05:00:00.000Z`,
endTime: `${plus1DateString}T05:30:00.000Z`,
userId: 101,
references: [
{
type: appStoreMetadata.dailyvideo.type,
uid: "MOCK_ID_1",
meetingId: "MOCK_ID_1",
meetingPassword: "MOCK_PASS_1",
meetingUrl: "http://mock-dailyvideo.example.com/meeting-1",
credentialId: null,
},
{
type: appStoreMetadata.googlecalendar.type,
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID_1",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID_1",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
externalCalendarId: "[email protected]",
credentialId: undefined,
},
],
iCalUID,
},
],
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
})
);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: appStoreMetadata.dailyvideo.dirName,
videoMeetingData: {
id: "MOCK_ID_1", // Video meeting ID is not updated during reschedule
password: "MOCK_PASS_1",
url: `http://mock-dailyvideo.example.com/meeting-1`,
},
});
const organizerCalendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
update: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID_2",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID_2",
},
});
const rescheduleBookingData = getMockRequestDataForBooking({
data: {
start: `${plus2DateString}T11:00:00.000Z`,
end: `${plus2DateString}T11:30:00.000Z`,
eventTypeId: 1,
rescheduleUid: uidOfBookingToBeRescheduled,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
rescheduledBy: organizer.email,
},
});
const rescheduledBooking = await handleNewBooking({
bookingData: rescheduleBookingData,
});
await expectBookingToBeInDatabase({
description: "",
location: BookingLocations.CalVideo,
responses: expect.objectContaining({
email: booker.email,
name: booker.name,
}),
uid: rescheduledBooking.uid!,
eventTypeId: rescheduleBookingData.eventTypeId,
status: BookingStatus.ACCEPTED,
references: [
{
type: appStoreMetadata.dailyvideo.type,
uid: "MOCK_ID_1",
meetingId: "MOCK_ID_1",
meetingPassword: "MOCK_PASS_1",
meetingUrl: "http://mock-dailyvideo.example.com/meeting-1",
},
{
type: appStoreMetadata.googlecalendar.type,
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID_1",
externalCalendarId: "[email protected]",
},
],
});
expect(organizerCalendarMock.updateEventCalls.length).toBe(1);
expect(organizerCalendarMock.createEventCalls.length).toBe(0);
// Check availability calls for both hosts
expect(organizerCalendarMock.getAvailabilityCalls.length).toBe(2);
},
timeout
);
});
});