import prismock from "@calcom/testing/lib/__mocks__/prisma"; import { createBookingScenario, getGoogleCalendarCredential, getOrganizer, getScenarioData, mockCalendarToHaveNoBusySlots, mockNoTranslations, mockSuccessfulVideoMeetingCreation, TestData, } from "@calcom/testing/lib/bookingScenario/bookingScenario"; import { BookingStatus } from "@calcom/prisma/enums"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { getInstantBookingCreateService } from "../../di/InstantBookingCreateService.container"; import type { CreateInstantBookingData } from "../dto/types"; // Mock calendar services map to prevent real calendar service modules (feishu, lark, etc.) from being // imported. Their top-level imports trigger async fetch calls (getAppAccessToken) that cause // "Closing rpc while fetch was pending" errors when the test worker shuts down. // This vi.mock must be in the test file itself (not just in bookingScenario.ts) to guarantee // Vitest hoists it before any transitive imports resolve the real module. vi.mock("@calcom/app-store/calendar.services.generated", () => ({ CalendarServiceMap: new Proxy( {}, { get(_target: Record, prop: string) { if (typeof prop === "symbol") return undefined; return Promise.resolve({ default: vi.fn() }); }, } ), })); // Mock OrganizationRepository container to prevent a deep transitive import chain // (InstantBookingCreateService → getBookingFields → workflows/types → routing-forms → // webhooks DI → eventTypes → OrganizationRepository.container) from triggering a Vitest // module-resolution RPC that is still in flight when the worker shuts down, causing // "Closing rpc while fetch was pending" errors. vi.mock("@calcom/features/ee/organizations/di/OrganizationRepository.container", () => ({ getOrganizationRepository: vi.fn().mockReturnValue({}), })); vi.mock("@calcom/features/notifications/sendNotification", () => ({ sendNotification: vi.fn(), })); vi.mock("@calcom/features/conferencing/lib/videoClient", () => ({ createInstantMeetingWithCalVideo: vi.fn().mockResolvedValue({ type: "daily_video", id: "MOCK_INSTANT_MEETING_ID", password: "MOCK_INSTANT_PASS", url: "http://mock-dailyvideo.example.com/instant-meeting-url", }), })); describe("handleInstantMeeting", () => { beforeEach(() => { mockNoTranslations(); }); describe("team event instant meeting", () => { it("should successfully create instant meeting for team event", async () => { const instantBookingCreateService = getInstantBookingCreateService(); const organizer = getOrganizer({ name: "Organizer", email: "organizer@example.com", id: 101, schedules: [TestData.schedules.IstWorkHours], credentials: [getGoogleCalendarCredential()], selectedCalendars: [TestData.selectedCalendars.google], }); const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); await createBookingScenario( getScenarioData({ eventTypes: [ { id: 1, slotInterval: 45, length: 45, users: [ { id: 101, }, ], team: { id: 1, }, instantMeetingExpiryTimeOffsetInSeconds: 90, }, ], organizer, apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]], }) ); mockSuccessfulVideoMeetingCreation({ metadataLookupKey: "dailyvideo", videoMeetingData: { id: "MOCK_ID", password: "MOCK_PASS", url: `http://mock-dailyvideo.example.com/meeting-1`, }, }); mockCalendarToHaveNoBusySlots("googlecalendar", { create: { uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID", }, }); const mockBookingData: CreateInstantBookingData = { eventTypeId: 1, timeZone: "UTC", language: "en", start: `${plus1DateString}T04:00:00.000Z`, end: `${plus1DateString}T04:45:00.000Z`, responses: { name: "Test User", email: "test@example.com", attendeePhoneNumber: "+918888888888", }, metadata: {}, instant: true, }; const result = await instantBookingCreateService.createBooking({ bookingData: mockBookingData, }); expect(result.message).toBe("Success"); expect(result.bookingId).toBeDefined(); expect(result.bookingUid).toBeDefined(); expect(result.meetingTokenId).toBeDefined(); expect(result.expires).toBeInstanceOf(Date); const booking = await prismock.booking.findUnique({ where: { id: result.bookingId }, select: { status: true, attendees: true, references: true }, }); expect(booking).toBeDefined(); expect(booking?.status).toBe(BookingStatus.AWAITING_HOST); expect(booking?.attendees).toHaveLength(1); expect(booking?.attendees[0].email).toBe("test@example.com"); expect(booking?.attendees[0].phoneNumber).toBe("+918888888888"); expect(booking?.references).toHaveLength(1); expect(booking?.references[0].type).toBe("daily_video"); }); it("should throw error for non-team event types", async () => { const instantBookingCreateService = getInstantBookingCreateService(); const organizer = getOrganizer({ name: "Organizer", email: "organizer@example.com", id: 101, schedules: [TestData.schedules.IstWorkHours], credentials: [getGoogleCalendarCredential()], selectedCalendars: [TestData.selectedCalendars.google], }); const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); await createBookingScenario( getScenarioData({ eventTypes: [ { id: 1, slotInterval: 45, length: 45, users: [ { id: 101, }, ], }, ], organizer, apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]], }) ); const mockBookingData: CreateInstantBookingData = { eventTypeId: 1, timeZone: "UTC", language: "en", start: `${plus1DateString}T04:00:00.000Z`, end: `${plus1DateString}T04:45:00.000Z`, responses: { name: "Test User", email: "test@example.com", attendeePhoneNumber: "+918888888888", }, metadata: {}, instant: true, }; await expect( instantBookingCreateService.createBooking({ bookingData: mockBookingData, }) ).rejects.toThrow("Only Team Event Types are supported for Instant Meeting"); }); it("should fire booking audit event with correct data when org has booking-audit feature", async () => { const { BookingEventHandlerService } = await import("../onBookingEvents/BookingEventHandlerService"); const onBookingCreatedSpy = vi .spyOn(BookingEventHandlerService.prototype, "onBookingCreated") .mockResolvedValue(undefined); const instantBookingCreateService = getInstantBookingCreateService(); const organizer = getOrganizer({ name: "Organizer", email: "organizer@example.com", id: 101, schedules: [TestData.schedules.IstWorkHours], credentials: [getGoogleCalendarCredential()], selectedCalendars: [TestData.selectedCalendars.google], }); const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); await createBookingScenario( getScenarioData({ eventTypes: [ { id: 1, slotInterval: 45, length: 45, users: [{ id: 101 }], team: { id: 1 }, instantMeetingExpiryTimeOffsetInSeconds: 90, }, ], organizer, apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]], }) ); mockSuccessfulVideoMeetingCreation({ metadataLookupKey: "dailyvideo", videoMeetingData: { id: "MOCK_ID", password: "MOCK_PASS", url: `http://mock-dailyvideo.example.com/meeting-1`, }, }); mockCalendarToHaveNoBusySlots("googlecalendar", { create: { uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID" }, }); const mockBookingData: CreateInstantBookingData = { eventTypeId: 1, timeZone: "UTC", language: "en", start: `${plus1DateString}T04:00:00.000Z`, end: `${plus1DateString}T04:45:00.000Z`, responses: { name: "Test User", email: "test@example.com", attendeePhoneNumber: "+918888888888", }, metadata: {}, instant: true, }; const result = await instantBookingCreateService.createBooking({ bookingData: mockBookingData, }); expect(result.message).toBe("Success"); expect(result.bookingId).toBeDefined(); expect(onBookingCreatedSpy).toHaveBeenCalledTimes(1); const callArgs = onBookingCreatedSpy.mock.calls[0][0]; expect(callArgs.payload.booking.uid).toBe(result.bookingUid); expect(callArgs.payload.config.isDryRun).toBe(false); expect(callArgs.actor).toEqual(expect.objectContaining({ identifiedBy: expect.any(String) })); expect(callArgs.auditData).toEqual( expect.objectContaining({ startTime: expect.any(Number), endTime: expect.any(Number), status: expect.any(String), }) ); expect(callArgs.source).toEqual(expect.any(String)); onBookingCreatedSpy.mockRestore(); }); it("should not throw when booking audit event fails", async () => { const { BookingEventHandlerService } = await import("../onBookingEvents/BookingEventHandlerService"); const onBookingCreatedSpy = vi .spyOn(BookingEventHandlerService.prototype, "onBookingCreated") .mockRejectedValue(new Error("Audit event handler failure")); const instantBookingCreateService = getInstantBookingCreateService(); const organizer = getOrganizer({ name: "Organizer", email: "organizer@example.com", id: 101, schedules: [TestData.schedules.IstWorkHours], credentials: [getGoogleCalendarCredential()], selectedCalendars: [TestData.selectedCalendars.google], }); const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); await createBookingScenario( getScenarioData({ eventTypes: [ { id: 1, slotInterval: 45, length: 45, users: [{ id: 101 }], team: { id: 1 }, instantMeetingExpiryTimeOffsetInSeconds: 90, }, ], organizer, apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]], }) ); mockSuccessfulVideoMeetingCreation({ metadataLookupKey: "dailyvideo", videoMeetingData: { id: "MOCK_ID", password: "MOCK_PASS", url: `http://mock-dailyvideo.example.com/meeting-1`, }, }); mockCalendarToHaveNoBusySlots("googlecalendar", { create: { uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID" }, }); const mockBookingData: CreateInstantBookingData = { eventTypeId: 1, timeZone: "UTC", language: "en", start: `${plus1DateString}T04:00:00.000Z`, end: `${plus1DateString}T04:45:00.000Z`, responses: { name: "Test User", email: "test@example.com", attendeePhoneNumber: "+918888888888", }, metadata: {}, instant: true, }; const result = await instantBookingCreateService.createBooking({ bookingData: mockBookingData, }); expect(result.message).toBe("Success"); expect(result.bookingId).toBeDefined(); expect(onBookingCreatedSpy).toHaveBeenCalled(); onBookingCreatedSpy.mockRestore(); }); }); }); function getDate(param: { dateIncrement?: number } = {}) { const { dateIncrement = 0 } = param; const date = new Date(); date.setDate(date.getDate() + dateIncrement); return { date, dateString: date.toISOString().split("T")[0], }; }