2023-09-06 19:23:53 +00:00
/ * *
2023-09-30 13:28:52 +00:00
* These tests are integration tests that test the flow from receiving a api / book / event request and then verifying
* - database entries created in In - MEMORY DB using prismock
* - emails sent by checking the testEmails global variable
* - webhooks fired by mocking fetch
* - APIs of various apps called by mocking those apps ' module s
*
* They don ' t intend to test what the apps logic should do , but rather test if the apps are called with the correct data . For testing that , once should write tests within each app .
2023-09-06 19:23:53 +00:00
* /
2023-09-30 04:52:32 +00:00
import prismaMock from "../../../../tests/libs/__mocks__/prisma" ;
2023-09-06 19:23:53 +00:00
import type { Request , Response } from "express" ;
import type { NextApiRequest , NextApiResponse } from "next" ;
import { createMocks } from "node-mocks-http" ;
import { describe , expect , beforeEach } from "vitest" ;
import { WEBAPP_URL } from "@calcom/lib/constants" ;
2023-09-30 04:52:32 +00:00
import logger from "@calcom/lib/logger" ;
2023-09-06 19:23:53 +00:00
import { BookingStatus } from "@calcom/prisma/enums" ;
import { test } from "@calcom/web/test/fixtures/fixtures" ;
import {
createBookingScenario ,
getDate ,
getGoogleCalendarCredential ,
TestData ,
getOrganizer ,
getBooker ,
getScenarioData ,
getZoomAppCredential ,
2023-09-30 04:52:32 +00:00
enableEmailFeature ,
2023-09-06 19:23:53 +00:00
mockNoTranslations ,
mockErrorOnVideoMeetingCreation ,
mockSuccessfulVideoMeetingCreation ,
mockCalendarToHaveNoBusySlots ,
2023-09-30 04:52:32 +00:00
getStripeAppCredential ,
2023-09-06 19:23:53 +00:00
MockError ,
2023-09-30 04:52:32 +00:00
mockPaymentApp ,
mockPaymentSuccessWebhookFromStripe ,
2023-09-30 13:28:52 +00:00
mockCalendar ,
mockCalendarToCrashOnCreateEvent ,
mockVideoAppToCrashOnCreateMeeting ,
mockCalendarToCrashOnUpdateEvent ,
2023-09-30 04:52:32 +00:00
} from "@calcom/web/test/utils/bookingScenario/bookingScenario" ;
import {
expectWorkflowToBeTriggered ,
expectSuccessfulBookingCreationEmails ,
expectBookingToBeInDatabase ,
expectAwaitingPaymentEmails ,
expectBookingRequestedEmails ,
expectBookingRequestedWebhookToHaveBeenFired ,
expectBookingCreatedWebhookToHaveBeenFired ,
expectBookingPaymentIntiatedWebhookToHaveBeenFired ,
expectBookingRescheduledWebhookToHaveBeenFired ,
expectSuccessfulBookingRescheduledEmails ,
expectSuccessfulCalendarEventUpdationInCalendar ,
expectSuccessfulVideoMeetingUpdationInCalendar ,
2023-09-30 13:28:52 +00:00
expectBrokenIntegrationEmails ,
expectSuccessfulCalendarEventCreationInCalendar ,
expectBookingInDBToBeRescheduledFromTo ,
2023-09-30 04:52:32 +00:00
} from "@calcom/web/test/utils/bookingScenario/expects" ;
2023-09-06 19:23:53 +00:00
type CustomNextApiRequest = NextApiRequest & Request ;
2023-09-30 04:52:32 +00:00
2023-09-06 19:23:53 +00:00
type CustomNextApiResponse = NextApiResponse & Response ;
// Local test runs sometime gets too slow
const timeout = process . env . CI ? 5000 : 20000 ;
2023-09-30 04:52:32 +00:00
describe ( "handleNewBooking" , ( ) = > {
2023-09-06 19:23:53 +00:00
beforeEach ( ( ) = > {
// Required to able to generate token in email in some cases
2023-09-30 04:52:32 +00:00
process . env . CALENDSO_ENCRYPTION_KEY = "abcdefghjnmkljhjklmnhjklkmnbhjui" ;
process . env . STRIPE_WEBHOOK_SECRET = "MOCK_STRIPE_WEBHOOK_SECRET" ;
2023-09-06 19:23:53 +00:00
mockNoTranslations ( ) ;
2023-09-30 04:52:32 +00:00
// mockEnableEmailFeature();
enableEmailFeature ( ) ;
2023-09-06 19:23:53 +00:00
globalThis . testEmails = [ ] ;
fetchMock . resetMocks ( ) ;
} ) ;
2023-09-30 04:52:32 +00:00
describe ( "Fresh Booking:" , ( ) = > {
2023-09-06 19:23:53 +00:00
test (
` should create a successful booking with Cal Video(Daily Video) if no explicit location is provided
2023-09-30 04:52:32 +00:00
1 . Should create a booking in the database
2 . Should send emails to the booker as well as organizer
2023-09-30 13:28:52 +00:00
3 . Should create a booking in the event ' s destination calendar
2023-09-30 04:52:32 +00:00
3 . Should trigger BOOKING_CREATED webhook
2023-09-06 19:23:53 +00:00
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
2023-09-30 13:28:52 +00:00
destinationCalendar : {
integration : "google_calendar" ,
externalId : "organizer@google-calendar.com" ,
} ,
2023-09-06 19:23:53 +00:00
} ) ;
2023-09-30 04:52:32 +00:00
await createBookingScenario (
getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl : "http://my-webhook.example.com" ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
2023-09-30 13:28:52 +00:00
destinationCalendar : {
integration : "google_calendar" ,
externalId : "event-type-1@google-calendar.com" ,
} ,
2023-09-30 04:52:32 +00:00
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} )
) ;
mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
videoMeetingData : {
id : "MOCK_ID" ,
password : "MOCK_PASS" ,
2023-09-30 13:28:52 +00:00
url : ` http://mock-dailyvideo.example.com/meeting-1 ` ,
2023-09-30 04:52:32 +00:00
} ,
} ) ;
2023-09-30 13:28:52 +00:00
const calendarMock = mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
2023-09-30 04:52:32 +00:00
create : {
2023-10-02 10:51:04 +00:00
id : "MOCKED_GOOGLE_CALENDAR_EVENT_ID" ,
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
2023-09-30 04:52:32 +00:00
} ,
} ) ;
2023-09-06 19:23:53 +00:00
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
expect ( createdBooking ) . toContain ( {
location : "integrations:daily" ,
} ) ;
2023-09-30 04:52:32 +00:00
await expectBookingToBeInDatabase ( {
2023-09-06 19:23:53 +00:00
description : "" ,
2023-09-30 04:52:32 +00:00
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
2023-09-06 19:23:53 +00:00
status : BookingStatus.ACCEPTED ,
2023-09-30 04:52:32 +00:00
references : [
{
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
2023-09-30 13:28:52 +00:00
meetingUrl : "http://mock-dailyvideo.example.com/meeting-1" ,
2023-09-30 04:52:32 +00:00
} ,
{
type : "google_calendar" ,
2023-10-02 10:51:04 +00:00
uid : "MOCKED_GOOGLE_CALENDAR_EVENT_ID" ,
meetingId : "MOCKED_GOOGLE_CALENDAR_EVENT_ID" ,
2023-09-30 04:52:32 +00:00
meetingPassword : "MOCK_PASSWORD" ,
meetingUrl : "https://UNUSED_URL" ,
} ,
] ,
2023-09-06 19:23:53 +00:00
} ) ;
expectWorkflowToBeTriggered ( ) ;
2023-09-30 13:28:52 +00:00
expectSuccessfulCalendarEventCreationInCalendar ( calendarMock , {
calendarId : "event-type-1@google-calendar.com" ,
videoCallUrl : "http://mock-dailyvideo.example.com/meeting-1" ,
} ) ;
2023-09-06 19:23:53 +00:00
2023-10-02 10:51:04 +00:00
expectSuccessfulBookingCreationEmails ( {
booker ,
organizer ,
emails ,
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ) ;
2023-09-30 04:52:32 +00:00
expectBookingCreatedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl : "http://my-webhook.example.com" ,
videoCallUrl : ` ${ WEBAPP_URL } /video/DYNAMIC_UID ` ,
2023-09-06 19:23:53 +00:00
} ) ;
} ,
timeout
) ;
2023-09-30 13:28:52 +00:00
describe ( "Calendar events should be created in the appropriate calendar" , ( ) = > {
2023-09-30 04:52:32 +00:00
test (
2023-09-30 13:28:52 +00:00
` should create a successful booking in the first connected calendar i.e. using the first credential(in the scenario when there is no event-type or organizer destination calendar)
1 . Should create a booking in the database
2 . Should send emails to the booker as well as organizer
3 . Should fallback to creating the booking in the first connected Calendar when neither event nor organizer has a destination calendar - This doesn ' t practically happen because organizer is always required to have a schedule set
3 . Should trigger BOOKING_CREATED webhook
2023-09-06 19:23:53 +00:00
` ,
2023-09-30 04:52:32 +00:00
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
2023-09-30 13:28:52 +00:00
await createBookingScenario (
getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl : "http://my-webhook.example.com" ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} )
) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
2023-09-30 13:28:52 +00:00
videoMeetingData : {
id : "MOCK_ID" ,
password : "MOCK_PASS" ,
url : ` http://mock-dailyvideo.example.com/meeting-1 ` ,
} ,
2023-09-30 04:52:32 +00:00
} ) ;
2023-09-06 19:23:53 +00:00
2023-10-02 10:51:04 +00:00
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
2023-09-30 13:28:52 +00:00
const calendarMock = mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
create : {
2023-10-02 10:51:04 +00:00
id : "GOOGLE_CALENDAR_EVENT_ID" ,
2023-09-30 13:28:52 +00:00
uid : "MOCK_ID" ,
2023-10-02 10:51:04 +00:00
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
2023-09-30 13:28:52 +00:00
} ,
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
const mockBookingData = getMockRequestDataForBooking ( {
data : {
2023-09-06 19:23:53 +00:00
eventTypeId : 1 ,
2023-09-30 04:52:32 +00:00
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
2023-09-06 19:23:53 +00:00
} ,
2023-09-30 04:52:32 +00:00
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
expect ( createdBooking ) . toContain ( {
location : "integrations:daily" ,
} ) ;
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
2023-09-30 13:28:52 +00:00
status : BookingStatus.ACCEPTED ,
references : [
{
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
meetingUrl : "http://mock-dailyvideo.example.com/meeting-1" ,
} ,
{
type : "google_calendar" ,
2023-10-02 10:51:04 +00:00
uid : "GOOGLE_CALENDAR_EVENT_ID" ,
meetingId : "GOOGLE_CALENDAR_EVENT_ID" ,
2023-09-30 13:28:52 +00:00
meetingPassword : "MOCK_PASSWORD" ,
meetingUrl : "https://UNUSED_URL" ,
} ,
] ,
2023-09-30 04:52:32 +00:00
} ) ;
expectWorkflowToBeTriggered ( ) ;
2023-09-30 13:28:52 +00:00
expectSuccessfulCalendarEventCreationInCalendar ( calendarMock , {
videoCallUrl : "http://mock-dailyvideo.example.com/meeting-1" ,
// We won't be sending evt.destinationCalendar in this case.
// Google Calendar in this case fallbacks to the "primary" calendar - https://github.com/calcom/cal.com/blob/7d5dad7fea78ff24dddbe44f1da5d7e08e1ff568/packages/app-store/googlecalendar/lib/CalendarService.ts#L217
// Not sure if it's the correct behaviour. Right now, it isn't possible to have an organizer with connected calendar but no destination calendar - As soon as the Google Calendar app is installed, a destination calendar is created.
calendarId : null ,
2023-09-30 04:52:32 +00:00
} ) ;
2023-10-02 10:51:04 +00:00
expectSuccessfulBookingCreationEmails ( {
booker ,
organizer ,
emails ,
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ) ;
2023-09-30 13:28:52 +00:00
expectBookingCreatedWebhookToHaveBeenFired ( {
2023-09-30 04:52:32 +00:00
booker ,
organizer ,
location : "integrations:daily" ,
2023-09-30 13:28:52 +00:00
subscriberUrl : "http://my-webhook.example.com" ,
videoCallUrl : ` ${ WEBAPP_URL } /video/DYNAMIC_UID ` ,
2023-09-30 04:52:32 +00:00
} ) ;
} ,
timeout
) ;
test (
2023-09-30 13:28:52 +00:00
` should create a successful booking in the organizer calendar(in the scenario when event type doesn't have destination calendar)
1 . Should create a booking in the database
2 . Should send emails to the booker as well as organizer
3 . Should fallback to create a booking in the Organizer Calendar if event doesn ' t have destination calendar
3 . Should trigger BOOKING_CREATED webhook
2023-09-30 04:52:32 +00:00
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
2023-09-30 13:28:52 +00:00
destinationCalendar : {
integration : "google_calendar" ,
externalId : "organizer@google-calendar.com" ,
} ,
2023-09-30 04:52:32 +00:00
} ) ;
await createBookingScenario (
getScenarioData ( {
webhooks : [
2023-09-06 19:23:53 +00:00
{
2023-09-30 04:52:32 +00:00
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
2023-09-30 13:28:52 +00:00
subscriberUrl : "http://my-webhook.example.com" ,
2023-09-30 04:52:32 +00:00
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
2023-09-06 19:23:53 +00:00
} ,
] ,
2023-09-30 04:52:32 +00:00
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} )
) ;
mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
2023-09-30 13:28:52 +00:00
videoMeetingData : {
id : "MOCK_ID" ,
password : "MOCK_PASS" ,
url : ` http://mock-dailyvideo.example.com/meeting-1 ` ,
} ,
2023-09-30 04:52:32 +00:00
} ) ;
2023-09-30 13:28:52 +00:00
const calendarMock = mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
create : {
uid : "MOCK_ID" ,
2023-10-02 10:51:04 +00:00
id : "GOOGLE_CALENDAR_EVENT_ID" ,
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
2023-09-30 13:28:52 +00:00
} ,
} ) ;
2023-09-30 04:52:32 +00:00
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
2023-09-06 19:23:53 +00:00
} ,
2023-09-30 04:52:32 +00:00
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
expect ( createdBooking ) . toContain ( {
location : "integrations:daily" ,
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.ACCEPTED ,
2023-09-30 13:28:52 +00:00
references : [
{
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
meetingUrl : "http://mock-dailyvideo.example.com/meeting-1" ,
} ,
{
type : "google_calendar" ,
2023-10-02 10:51:04 +00:00
uid : "GOOGLE_CALENDAR_EVENT_ID" ,
meetingId : "GOOGLE_CALENDAR_EVENT_ID" ,
2023-09-30 13:28:52 +00:00
meetingPassword : "MOCK_PASSWORD" ,
meetingUrl : "https://UNUSED_URL" ,
} ,
] ,
2023-09-30 04:52:32 +00:00
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
expectWorkflowToBeTriggered ( ) ;
2023-09-30 13:28:52 +00:00
expectSuccessfulCalendarEventCreationInCalendar ( calendarMock , {
calendarId : "organizer@google-calendar.com" ,
videoCallUrl : "http://mock-dailyvideo.example.com/meeting-1" ,
} ) ;
2023-09-06 19:23:53 +00:00
2023-10-02 10:51:04 +00:00
expectSuccessfulBookingCreationEmails ( {
booker ,
organizer ,
emails ,
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ) ;
2023-09-30 04:52:32 +00:00
expectBookingCreatedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
2023-09-30 13:28:52 +00:00
subscriberUrl : "http://my-webhook.example.com" ,
2023-09-30 04:52:32 +00:00
videoCallUrl : ` ${ WEBAPP_URL } /video/DYNAMIC_UID ` ,
} ) ;
} ,
timeout
) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
test (
2023-09-30 13:28:52 +00:00
` an error in creating a calendar event should not stop the booking creation - Current behaviour is wrong as the booking is created but no-one is notified of it ` ,
2023-10-02 10:51:04 +00:00
async ( { } ) = > {
2023-09-30 04:52:32 +00:00
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
2023-09-30 13:28:52 +00:00
destinationCalendar : {
integration : "google_calendar" ,
externalId : "organizer@google-calendar.com" ,
} ,
2023-09-30 04:52:32 +00:00
} ) ;
2023-09-30 13:28:52 +00:00
await createBookingScenario (
getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl : "http://my-webhook.example.com" ,
active : true ,
eventTypeId : 1 ,
appId : null ,
2023-09-30 04:52:32 +00:00
} ,
2023-09-30 13:28:52 +00:00
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} )
) ;
2023-09-30 04:52:32 +00:00
2023-10-02 10:51:04 +00:00
const _calendarMock = mockCalendarToCrashOnCreateEvent ( "googlecalendar" ) ;
2023-09-30 04:52:32 +00:00
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
2023-09-30 13:28:52 +00:00
location : { optionValue : "" , value : "New York" } ,
2023-09-06 19:23:53 +00:00
} ,
} ,
2023-09-30 04:52:32 +00:00
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
expect ( createdBooking ) . toContain ( {
2023-09-30 13:28:52 +00:00
location : "New York" ,
2023-09-30 04:52:32 +00:00
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 04:52:32 +00:00
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
2023-09-30 13:28:52 +00:00
status : BookingStatus.ACCEPTED ,
references : [
{
type : "google_calendar" ,
// A reference is still created in case of event creation failure, with nullish values. Not sure what's the purpose for this.
uid : "" ,
meetingId : null ,
meetingPassword : null ,
meetingUrl : null ,
} ,
] ,
2023-09-30 04:52:32 +00:00
} ) ;
expectWorkflowToBeTriggered ( ) ;
2023-09-30 13:28:52 +00:00
// FIXME: We should send Broken Integration emails on calendar event creation failure
// expectCalendarEventCreationFailureEmails({ booker, organizer, emails });
2023-09-30 04:52:32 +00:00
2023-09-30 13:28:52 +00:00
expectBookingCreatedWebhookToHaveBeenFired ( {
2023-09-30 04:52:32 +00:00
booker ,
organizer ,
2023-09-30 13:28:52 +00:00
location : "New York" ,
subscriberUrl : "http://my-webhook.example.com" ,
2023-09-30 04:52:32 +00:00
} ) ;
} ,
timeout
) ;
} ) ;
2023-09-30 13:28:52 +00:00
describe ( "Video Meeting Creation" , ( ) = > {
test (
` should create a successful booking with Zoom if used ` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const subscriberUrl = "http://my-webhook.example.com" ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
2023-09-06 19:23:53 +00:00
2023-09-30 13:28:52 +00:00
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getZoomAppCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
await createBookingScenario (
getScenarioData ( {
organizer ,
eventTypes : [
2023-09-06 19:23:53 +00:00
{
2023-09-30 13:28:52 +00:00
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
apps : [ TestData . apps [ "zoomvideo" ] ] ,
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
} )
) ;
mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "zoomvideo" ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:zoom" } ,
} ,
} ,
} ) ,
} ) ;
2023-10-02 10:51:04 +00:00
const createdBooking = await handleNewBooking ( req ) ;
2023-09-30 13:28:52 +00:00
2023-10-02 10:51:04 +00:00
expectSuccessfulBookingCreationEmails ( {
booker ,
organizer ,
emails ,
// Because no calendar was involved, we don't have an ics UID
iCalUID : createdBooking.uid ,
} ) ;
2023-09-30 13:28:52 +00:00
expectBookingCreatedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:zoom" ,
subscriberUrl ,
videoCallUrl : "http://mock-zoomvideo.example.com" ,
} ) ;
} ,
timeout
) ;
test (
` Booking should still be created if booking with Zoom errors ` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const subscriberUrl = "http://my-webhook.example.com" ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getZoomAppCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
await createBookingScenario (
getScenarioData ( {
organizer ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
apps : [ TestData . apps [ "zoomvideo" ] ] ,
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
} )
) ;
mockVideoAppToCrashOnCreateMeeting ( {
metadataLookupKey : "zoomvideo" ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:zoom" } ,
} ,
} ,
} ) ,
} ) ;
await handleNewBooking ( req ) ;
expectBrokenIntegrationEmails ( { booker , organizer , emails } ) ;
expectBookingCreatedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:zoom" ,
subscriberUrl ,
videoCallUrl : null ,
} ) ;
} ,
timeout
) ;
} ) ;
describe (
"Availability Check during booking" ,
( ) = > {
test (
` should fail a booking if there is already a Cal.com booking overlapping the time ` ,
async ( { } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
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 } ) ;
const uidOfOverlappingBooking = "harWv3eHgconAED2j4gcVhP" ;
await createBookingScenario (
getScenarioData ( {
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
bookings : [
{
uid : uidOfOverlappingBooking ,
eventTypeId : 1 ,
userId : 101 ,
status : BookingStatus.ACCEPTED ,
startTime : ` ${ plus1DateString } T05:00:00.000Z ` ,
endTime : ` ${ plus1DateString } T05:15:00.000Z ` ,
} ,
] ,
organizer ,
} )
) ;
const mockBookingData = getMockRequestDataForBooking ( {
data : {
start : ` ${ getDate ( { dateIncrement : 1 } ).dateString}T04:00:00.000Z ` ,
end : ` ${ getDate ( { dateIncrement : 1 } ).dateString}T05:30:00.000Z ` ,
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "New York" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
await expect ( async ( ) = > await handleNewBooking ( req ) ) . rejects . toThrowError (
"No available users found"
) ;
} ,
timeout
) ;
test (
` should fail a booking if there is already a booking in the organizer's selectedCalendars(Single Calendar) with the overlapping time ` ,
async ( ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const organizerId = 101 ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : organizerId ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
const { dateString : plus1DateString } = getDate ( { dateIncrement : 1 } ) ;
await createBookingScenario (
getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl : "http://my-webhook.example.com" ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] ] ,
} )
) ;
2023-10-02 10:51:04 +00:00
const _calendarMock = mockCalendar ( "googlecalendar" , {
2023-09-30 13:28:52 +00:00
create : {
uid : "MOCK_ID" ,
2023-10-02 10:51:04 +00:00
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
2023-09-30 13:28:52 +00:00
} ,
busySlots : [
{
start : ` ${ plus1DateString } T05:00:00.000Z ` ,
end : ` ${ plus1DateString } T05:15:00.000Z ` ,
} ,
] ,
} ) ;
const mockBookingData = getMockRequestDataForBooking ( {
data : {
start : ` ${ getDate ( { dateIncrement : 1 } ).dateString}T04:00:00.000Z ` ,
end : ` ${ getDate ( { dateIncrement : 1 } ).dateString}T05:30:00.000Z ` ,
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "New York" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
await expect ( async ( ) = > await handleNewBooking ( req ) ) . rejects . toThrowError (
"No available users found"
) ;
} ,
timeout
) ;
} ,
timeout
) ;
describe ( "Event Type that requires confirmation" , ( ) = > {
test (
` should create a booking request for event that requires confirmation
1 . Should create a booking in the database with status PENDING
2 . Should send emails to the booker as well as organizer for booking request and awaiting approval
3 . Should trigger BOOKING_REQUESTED webhook
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const subscriberUrl = "http://my-webhook.example.com" ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
const scenarioData = getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
requiresConfirmation : true ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} ) ;
await createBookingScenario ( scenarioData ) ;
mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
} ) ;
2023-10-02 10:51:04 +00:00
mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
create : {
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ,
} ) ;
2023-09-30 13:28:52 +00:00
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
expect ( createdBooking ) . toContain ( {
location : "integrations:daily" ,
} ) ;
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.PENDING ,
} ) ;
expectWorkflowToBeTriggered ( ) ;
expectBookingRequestedEmails ( {
booker ,
organizer ,
emails ,
} ) ;
expectBookingRequestedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl ,
eventType : scenarioData.eventTypes [ 0 ] ,
} ) ;
} ,
timeout
) ;
test (
` should create a booking for event that requires confirmation based on a booking notice duration threshold, if threshold is not met
1 . Should create a booking in the database with status ACCEPTED
2 . Should send emails to the booker as well as organizer
3 . Should trigger BOOKING_CREATED webhook
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const subscriberUrl = "http://my-webhook.example.com" ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
await createBookingScenario (
getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
requiresConfirmation : true ,
metadata : {
requiresConfirmationThreshold : {
time : 30 ,
unit : "minutes" ,
} ,
} ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} )
) ;
mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
} ) ;
2023-10-02 10:51:04 +00:00
mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
create : {
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ,
} ) ;
2023-09-30 13:28:52 +00:00
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
expect ( createdBooking ) . toContain ( {
location : "integrations:daily" ,
} ) ;
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.ACCEPTED ,
} ) ;
expectWorkflowToBeTriggered ( ) ;
2023-10-02 10:51:04 +00:00
expectSuccessfulBookingCreationEmails ( {
booker ,
organizer ,
emails ,
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ) ;
2023-09-30 13:28:52 +00:00
expectBookingCreatedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl ,
videoCallUrl : ` ${ WEBAPP_URL } /video/DYNAMIC_UID ` ,
} ) ;
} ,
timeout
) ;
test (
` should create a booking for event that requires confirmation based on a booking notice duration threshold, if threshold IS MET
1 . Should create a booking in the database with status PENDING
2 . Should send emails to the booker as well as organizer for booking request and awaiting approval
3 . Should trigger BOOKING_REQUESTED webhook
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const subscriberUrl = "http://my-webhook.example.com" ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
const scenarioData = getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
requiresConfirmation : true ,
metadata : {
requiresConfirmationThreshold : {
time : 120 ,
unit : "hours" ,
} ,
} ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} ) ;
await createBookingScenario ( scenarioData ) ;
mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
} ) ;
2023-10-02 10:51:04 +00:00
mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
create : {
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ,
} ) ;
2023-09-30 13:28:52 +00:00
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
expect ( createdBooking ) . toContain ( {
location : "integrations:daily" ,
} ) ;
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.PENDING ,
} ) ;
expectWorkflowToBeTriggered ( ) ;
expectBookingRequestedEmails ( { booker , organizer , emails } ) ;
expectBookingRequestedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl ,
eventType : scenarioData.eventTypes [ 0 ] ,
} ) ;
} ,
timeout
) ;
} ) ;
// FIXME: We shouldn't throw error here, the behaviour should be fixed.
test (
` if booking with Cal Video(Daily Video) fails, booking creation fails with uncaught error ` ,
async ( { } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.org" ,
name : "Booker" ,
} ) ;
const organizer = TestData . users . example ;
await createBookingScenario ( {
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
2023-09-06 19:23:53 +00:00
} ,
] ,
} ,
] ,
users : [
{
. . . organizer ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ,
] ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
2023-09-30 04:52:32 +00:00
} ) ;
2023-09-06 19:23:53 +00:00
mockErrorOnVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
} ) ;
2023-09-30 04:52:32 +00:00
2023-09-06 19:23:53 +00:00
mockCalendarToHaveNoBusySlots ( "googlecalendar" ) ;
2023-09-30 04:52:32 +00:00
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
} ,
} ) ,
} ) ;
2023-09-06 19:23:53 +00:00
try {
await handleNewBooking ( req ) ;
} catch ( e ) {
expect ( e ) . toBeInstanceOf ( MockError ) ;
expect ( ( e as { message : string } ) . message ) . toBe ( "Error creating Video meeting" ) ;
}
} ,
timeout
) ;
2023-09-13 04:19:01 +00:00
test (
` should create a successful booking when location is provided as label of an option(Done for Organizer Address)
1 . Should create a booking in the database
2 . Should send emails to the booker as well as organizer
3 . Should trigger BOOKING_CREATED webhook
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "New York" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const scenarioData = getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl : "http://my-webhook.example.com" ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} ) ;
2023-10-02 10:51:04 +00:00
mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
create : {
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ,
} ) ;
2023-09-30 04:52:32 +00:00
await createBookingScenario ( scenarioData ) ;
2023-09-13 04:19:01 +00:00
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
expect ( createdBooking ) . toContain ( {
location : "New York" ,
} ) ;
2023-09-30 04:52:32 +00:00
await expectBookingToBeInDatabase ( {
2023-09-13 04:19:01 +00:00
description : "" ,
2023-09-30 04:52:32 +00:00
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
2023-09-13 04:19:01 +00:00
status : BookingStatus.ACCEPTED ,
} ) ;
expectWorkflowToBeTriggered ( ) ;
2023-10-02 10:51:04 +00:00
expectSuccessfulBookingCreationEmails ( {
booker ,
organizer ,
emails ,
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ) ;
2023-09-30 04:52:32 +00:00
expectBookingCreatedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "New York" ,
subscriberUrl : "http://my-webhook.example.com" ,
2023-09-13 04:19:01 +00:00
} ) ;
2023-09-30 04:52:32 +00:00
} ,
timeout
) ;
describe ( "Paid Events" , ( ) = > {
test (
` Event Type that doesn't require confirmation
1 . Should create a booking in the database with status PENDING
2 . Should send email to the booker for Payment request
3 . Should trigger BOOKING_PAYMENT_INITIATED webhook
4 . Once payment is successful , should trigger BOOKING_CREATED webhook
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) , getStripeAppCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
const scenarioData = getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl : "http://my-webhook.example.com" ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
title : "Paid Event" ,
description : "It's a test Paid Event" ,
slotInterval : 45 ,
requiresConfirmation : false ,
metadata : {
apps : {
// EventType is connected to stripe.
stripe : {
price : 100 ,
enabled : true ,
currency : "inr" /*, credentialId: 57*/ ,
} ,
} ,
} ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
organizer ,
apps : [
TestData . apps [ "google-calendar" ] ,
TestData . apps [ "daily-video" ] ,
TestData . apps [ "stripe-payment" ] ,
] ,
} ) ;
await createBookingScenario ( scenarioData ) ;
mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
} ) ;
const { paymentUid , externalId } = mockPaymentApp ( {
metadataLookupKey : "stripe" ,
appStoreLookupKey : "stripepayment" ,
} ) ;
mockCalendarToHaveNoBusySlots ( "googlecalendar" ) ;
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
2023-09-13 04:19:01 +00:00
} ,
2023-09-30 04:52:32 +00:00
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
expect ( createdBooking ) . toContain ( {
location : "integrations:daily" ,
paymentUid : paymentUid ,
} ) ;
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.PENDING ,
} ) ;
expectWorkflowToBeTriggered ( ) ;
expectAwaitingPaymentEmails ( { organizer , booker , emails } ) ;
expectBookingPaymentIntiatedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl : "http://my-webhook.example.com" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
paymentId : createdBooking.paymentId ! ,
} ) ;
const { webhookResponse } = await mockPaymentSuccessWebhookFromStripe ( { externalId } ) ;
expect ( webhookResponse ? . statusCode ) . toBe ( 200 ) ;
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.ACCEPTED ,
} ) ;
expectBookingCreatedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl : "http://my-webhook.example.com" ,
videoCallUrl : ` ${ WEBAPP_URL } /video/DYNAMIC_UID ` ,
paidEvent : true ,
} ) ;
} ,
timeout
) ;
// TODO: We should introduce a new state BOOKING.PAYMENT_PENDING that can clearly differentiate b/w pending confirmation(stuck on Organizer) and pending payment(stuck on booker)
test (
` Event Type that requires confirmation
1 . Should create a booking in the database with status PENDING
2 . Should send email to the booker for Payment request
3 . Should trigger BOOKING_PAYMENT_INITIATED webhook
4 . Once payment is successful , should trigger BOOKING_REQUESTED webhook
5 . Booking should still stay in pending state
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const subscriberUrl = "http://my-webhook.example.com" ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) , getStripeAppCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
} ) ;
const scenarioData = getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl ,
active : true ,
eventTypeId : 1 ,
appId : null ,
2023-09-13 04:19:01 +00:00
} ,
2023-09-30 04:52:32 +00:00
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
requiresConfirmation : true ,
metadata : {
apps : {
stripe : {
price : 100 ,
enabled : true ,
currency : "inr" /*, credentialId: 57*/ ,
} ,
} ,
} ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
organizer ,
apps : [
TestData . apps [ "google-calendar" ] ,
TestData . apps [ "daily-video" ] ,
TestData . apps [ "stripe-payment" ] ,
] ,
} ) ;
await createBookingScenario ( scenarioData ) ;
mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
} ) ;
const { paymentUid , externalId } = mockPaymentApp ( {
metadataLookupKey : "stripe" ,
appStoreLookupKey : "stripepayment" ,
} ) ;
mockCalendarToHaveNoBusySlots ( "googlecalendar" ) ;
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
expect ( createdBooking . responses ) . toContain ( {
email : booker.email ,
name : booker.name ,
} ) ;
expect ( createdBooking ) . toContain ( {
location : "integrations:daily" ,
paymentUid : paymentUid ,
} ) ;
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.PENDING ,
} ) ;
expectWorkflowToBeTriggered ( ) ;
expectAwaitingPaymentEmails ( { organizer , booker , emails } ) ;
expectBookingPaymentIntiatedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl : "http://my-webhook.example.com" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
paymentId : createdBooking.paymentId ! ,
} ) ;
2023-09-30 13:28:52 +00:00
const { webhookResponse } = await mockPaymentSuccessWebhookFromStripe ( { externalId } ) ;
expect ( webhookResponse ? . statusCode ) . toBe ( 200 ) ;
await expectBookingToBeInDatabase ( {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.PENDING ,
} ) ;
expectBookingRequestedWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl ,
paidEvent : true ,
eventType : scenarioData.eventTypes [ 0 ] ,
} ) ;
} ,
timeout
) ;
} ) ;
} ) ;
describe ( "Team Events" , ( ) = > {
test . todo ( "Collective event booking" ) ;
test . todo ( "Round Robin booking" ) ;
} ) ;
describe ( "Team Plus Paid Events" , ( ) = > {
test . todo ( "Collective event booking" ) ;
test . todo ( "Round Robin booking" ) ;
} ) ;
test . todo ( "Calendar and video Apps installed on a Team Account" ) ;
test . todo ( "Managed Event Type booking" ) ;
test . todo ( "Dynamic Group Booking" ) ;
describe ( "Booking Limits" , ( ) = > {
test . todo ( "Test these cases that were failing earlier https://github.com/calcom/cal.com/pull/10480" ) ;
} ) ;
describe ( "Reschedule" , ( ) = > {
test (
` should rechedule an existing booking successfully with Cal Video(Daily Video)
1 . Should cancel the existing booking
2 . Should create a new booking in the database
3 . Should send emails to the booker as well as organizer
4 . Should trigger BOOKING_RESCHEDULED webhook
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
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 } ) ;
const uidOfBookingToBeRescheduled = "n5Wv3eHgconAED2j4gcVhP" ;
await createBookingScenario (
getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl : "http://my-webhook.example.com" ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
bookings : [
{
uid : uidOfBookingToBeRescheduled ,
eventTypeId : 1 ,
status : BookingStatus.ACCEPTED ,
startTime : ` ${ plus1DateString } T05:00:00.000Z ` ,
endTime : ` ${ plus1DateString } T05:15:00.000Z ` ,
references : [
{
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
meetingUrl : "http://mock-dailyvideo.example.com" ,
} ,
{
type : "google_calendar" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASSWORD" ,
meetingUrl : "https://UNUSED_URL" ,
externalCalendarId : "MOCK_EXTERNAL_CALENDAR_ID" ,
credentialId : undefined ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} )
) ;
const videoMock = mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
} ) ;
const calendarMock = mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
create : {
uid : "MOCK_ID" ,
} ,
update : {
uid : "UPDATED_MOCK_ID" ,
2023-10-02 10:51:04 +00:00
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
2023-09-30 13:28:52 +00:00
} ,
} ) ;
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
rescheduleUid : uidOfBookingToBeRescheduled ,
start : ` ${ plus1DateString } T04:00:00.000Z ` ,
end : ` ${ plus1DateString } T04:15:00.000Z ` ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
const previousBooking = await prismaMock . booking . findUnique ( {
where : {
uid : uidOfBookingToBeRescheduled ,
} ,
} ) ;
logger . silly ( {
previousBooking ,
allBookings : await prismaMock . booking . findMany ( ) ,
} ) ;
// Expect previous booking to be cancelled
await expectBookingToBeInDatabase ( {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : uidOfBookingToBeRescheduled ,
status : BookingStatus.CANCELLED ,
} ) ;
expect ( previousBooking ? . status ) . toBe ( BookingStatus . CANCELLED ) ;
/ * *
* Booking Time should be new time
* /
expect ( createdBooking . startTime ? . toISOString ( ) ) . toBe ( ` ${ plus1DateString } T04:00:00.000Z ` ) ;
expect ( createdBooking . endTime ? . toISOString ( ) ) . toBe ( ` ${ plus1DateString } T04:15:00.000Z ` ) ;
2023-09-30 04:52:32 +00:00
2023-09-30 13:28:52 +00:00
await expectBookingInDBToBeRescheduledFromTo ( {
from : {
uid : uidOfBookingToBeRescheduled ,
} ,
to : {
2023-09-30 04:52:32 +00:00
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
2023-09-30 13:28:52 +00:00
status : BookingStatus.ACCEPTED ,
2023-09-30 04:52:32 +00:00
location : "integrations:daily" ,
2023-09-30 13:28:52 +00:00
responses : expect.objectContaining ( {
email : booker.email ,
name : booker.name ,
} ) ,
references : [
{
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
meetingUrl : "http://mock-dailyvideo.example.com" ,
} ,
{
type : "google_calendar" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASSWORD" ,
meetingUrl : "https://UNUSED_URL" ,
externalCalendarId : "MOCK_EXTERNAL_CALENDAR_ID" ,
} ,
] ,
} ,
} ) ;
2023-09-30 04:52:32 +00:00
2023-09-30 13:28:52 +00:00
expectWorkflowToBeTriggered ( ) ;
expectSuccessfulVideoMeetingUpdationInCalendar ( videoMock , {
calEvent : {
location : "http://mock-dailyvideo.example.com" ,
} ,
bookingRef : {
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
meetingUrl : "http://mock-dailyvideo.example.com" ,
} ,
} ) ;
expectSuccessfulCalendarEventUpdationInCalendar ( calendarMock , {
externalCalendarId : "MOCK_EXTERNAL_CALENDAR_ID" ,
calEvent : {
videoCallData : expect.objectContaining ( {
url : "http://mock-dailyvideo.example.com" ,
} ) ,
} ,
uid : "MOCK_ID" ,
} ) ;
2023-10-02 10:51:04 +00:00
expectSuccessfulBookingRescheduledEmails ( {
booker ,
organizer ,
emails ,
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ) ;
2023-09-30 13:28:52 +00:00
expectBookingRescheduledWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl : "http://my-webhook.example.com" ,
videoCallUrl : ` ${ WEBAPP_URL } /video/DYNAMIC_UID ` ,
} ) ;
} ,
timeout
) ;
2023-09-30 04:52:32 +00:00
test (
2023-09-30 13:28:52 +00:00
` should rechedule a booking successfully and update the event in the same externalCalendarId as was used in the booking earlier.
1 . Should cancel the existing booking
2023-09-30 04:52:32 +00:00
2 . Should create a new booking in the database
3 . Should send emails to the booker as well as organizer
4 . Should trigger BOOKING_RESCHEDULED webhook
` ,
async ( { emails } ) = > {
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
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 } ) ;
const uidOfBookingToBeRescheduled = "n5Wv3eHgconAED2j4gcVhP" ;
await createBookingScenario (
getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl : "http://my-webhook.example.com" ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
2023-09-30 13:28:52 +00:00
destinationCalendar : {
integration : "google_calendar" ,
externalId : "event-type-1@example.com" ,
} ,
2023-09-30 04:52:32 +00:00
} ,
] ,
bookings : [
{
uid : uidOfBookingToBeRescheduled ,
eventTypeId : 1 ,
status : BookingStatus.ACCEPTED ,
startTime : ` ${ plus1DateString } T05:00:00.000Z ` ,
endTime : ` ${ plus1DateString } T05:15:00.000Z ` ,
references : [
{
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
meetingUrl : "http://mock-dailyvideo.example.com" ,
} ,
{
type : "google_calendar" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASSWORD" ,
meetingUrl : "https://UNUSED_URL" ,
2023-09-30 13:28:52 +00:00
externalCalendarId : "existing-event-type@example.com" ,
2023-09-30 04:52:32 +00:00
credentialId : undefined ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} )
) ;
const videoMock = mockSuccessfulVideoMeetingCreation ( {
metadataLookupKey : "dailyvideo" ,
} ) ;
const calendarMock = mockCalendarToHaveNoBusySlots ( "googlecalendar" , {
create : {
uid : "MOCK_ID" ,
} ,
update : {
2023-10-02 10:51:04 +00:00
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
2023-09-30 04:52:32 +00:00
uid : "UPDATED_MOCK_ID" ,
} ,
} ) ;
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
rescheduleUid : uidOfBookingToBeRescheduled ,
start : ` ${ plus1DateString } T04:00:00.000Z ` ,
end : ` ${ plus1DateString } T04:15:00.000Z ` ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "integrations:daily" } ,
2023-09-06 19:23:53 +00:00
} ,
} ,
} ) ;
2023-09-30 04:52:32 +00:00
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
/ * *
* Booking Time should be new time
* /
expect ( createdBooking . startTime ? . toISOString ( ) ) . toBe ( ` ${ plus1DateString } T04:00:00.000Z ` ) ;
expect ( createdBooking . endTime ? . toISOString ( ) ) . toBe ( ` ${ plus1DateString } T04:15:00.000Z ` ) ;
2023-09-30 13:28:52 +00:00
await expectBookingInDBToBeRescheduledFromTo ( {
from : {
uid : uidOfBookingToBeRescheduled ,
} ,
to : {
description : "" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.ACCEPTED ,
location : "integrations:daily" ,
responses : expect.objectContaining ( {
email : booker.email ,
name : booker.name ,
} ) ,
references : [
{
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
meetingUrl : "http://mock-dailyvideo.example.com" ,
} ,
{
type : "google_calendar" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASSWORD" ,
meetingUrl : "https://UNUSED_URL" ,
externalCalendarId : "existing-event-type@example.com" ,
} ,
] ,
} ,
2023-09-30 04:52:32 +00:00
} ) ;
expectWorkflowToBeTriggered ( ) ;
expectSuccessfulVideoMeetingUpdationInCalendar ( videoMock , {
calEvent : {
location : "http://mock-dailyvideo.example.com" ,
} ,
bookingRef : {
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
meetingUrl : "http://mock-dailyvideo.example.com" ,
} ,
} ) ;
2023-09-30 13:28:52 +00:00
// updateEvent uses existing booking's externalCalendarId to update the event in calendar.
// and not the event-type's organizer's which is event-type-1@example.com
2023-09-30 04:52:32 +00:00
expectSuccessfulCalendarEventUpdationInCalendar ( calendarMock , {
2023-09-30 13:28:52 +00:00
externalCalendarId : "existing-event-type@example.com" ,
2023-09-30 04:52:32 +00:00
calEvent : {
location : "http://mock-dailyvideo.example.com" ,
} ,
uid : "MOCK_ID" ,
} ) ;
2023-10-02 10:51:04 +00:00
expectSuccessfulBookingRescheduledEmails ( {
booker ,
organizer ,
emails ,
iCalUID : "MOCKED_GOOGLE_CALENDAR_ICS_ID" ,
} ) ;
2023-09-30 04:52:32 +00:00
expectBookingRescheduledWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "integrations:daily" ,
subscriberUrl : "http://my-webhook.example.com" ,
videoCallUrl : ` ${ WEBAPP_URL } /video/DYNAMIC_UID ` ,
} ) ;
2023-09-06 19:23:53 +00:00
} ,
timeout
) ;
2023-09-30 13:28:52 +00:00
test (
` an error in updating a calendar event should not stop the rescheduling - Current behaviour is wrong as the booking is resheduled but no-one is notified of it ` ,
2023-10-02 10:51:04 +00:00
async ( { } ) = > {
2023-09-30 13:28:52 +00:00
const handleNewBooking = ( await import ( "@calcom/features/bookings/lib/handleNewBooking" ) ) . default ;
const booker = getBooker ( {
email : "booker@example.com" ,
name : "Booker" ,
} ) ;
const organizer = getOrganizer ( {
name : "Organizer" ,
email : "organizer@example.com" ,
id : 101 ,
schedules : [ TestData . schedules . IstWorkHours ] ,
credentials : [ getGoogleCalendarCredential ( ) ] ,
selectedCalendars : [ TestData . selectedCalendars . google ] ,
destinationCalendar : {
integration : "google_calendar" ,
externalId : "organizer@google-calendar.com" ,
} ,
} ) ;
const uidOfBookingToBeRescheduled = "n5Wv3eHgconAED2j4gcVhP" ;
const { dateString : plus1DateString } = getDate ( { dateIncrement : 1 } ) ;
await createBookingScenario (
getScenarioData ( {
webhooks : [
{
userId : organizer.id ,
eventTriggers : [ "BOOKING_CREATED" ] ,
subscriberUrl : "http://my-webhook.example.com" ,
active : true ,
eventTypeId : 1 ,
appId : null ,
} ,
] ,
eventTypes : [
{
id : 1 ,
slotInterval : 45 ,
length : 45 ,
users : [
{
id : 101 ,
} ,
] ,
} ,
] ,
bookings : [
{
uid : uidOfBookingToBeRescheduled ,
eventTypeId : 1 ,
status : BookingStatus.ACCEPTED ,
startTime : ` ${ plus1DateString } T05:00:00.000Z ` ,
endTime : ` ${ plus1DateString } T05:15:00.000Z ` ,
references : [
{
type : "daily_video" ,
uid : "MOCK_ID" ,
meetingId : "MOCK_ID" ,
meetingPassword : "MOCK_PASS" ,
meetingUrl : "http://mock-dailyvideo.example.com" ,
} ,
{
type : "google_calendar" ,
uid : "ORIGINAL_BOOKING_UID" ,
meetingId : "ORIGINAL_MEETING_ID" ,
meetingPassword : "ORIGINAL_MEETING_PASSWORD" ,
meetingUrl : "https://ORIGINAL_MEETING_URL" ,
externalCalendarId : "existing-event-type@example.com" ,
credentialId : undefined ,
} ,
] ,
} ,
] ,
organizer ,
apps : [ TestData . apps [ "google-calendar" ] , TestData . apps [ "daily-video" ] ] ,
} )
) ;
2023-10-02 10:51:04 +00:00
const _calendarMock = mockCalendarToCrashOnUpdateEvent ( "googlecalendar" ) ;
2023-09-30 13:28:52 +00:00
const mockBookingData = getMockRequestDataForBooking ( {
data : {
eventTypeId : 1 ,
rescheduleUid : uidOfBookingToBeRescheduled ,
responses : {
email : booker.email ,
name : booker.name ,
location : { optionValue : "" , value : "New York" } ,
} ,
} ,
} ) ;
const { req } = createMockNextJsRequest ( {
method : "POST" ,
body : mockBookingData ,
} ) ;
const createdBooking = await handleNewBooking ( req ) ;
await expectBookingInDBToBeRescheduledFromTo ( {
from : {
uid : uidOfBookingToBeRescheduled ,
} ,
to : {
description : "" ,
location : "New York" ,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid : createdBooking.uid ! ,
eventTypeId : mockBookingData.eventTypeId ,
status : BookingStatus.ACCEPTED ,
responses : expect.objectContaining ( {
email : booker.email ,
name : booker.name ,
} ) ,
references : [
{
type : "google_calendar" ,
// A reference is still created in case of event creation failure, with nullish values. Not sure what's the purpose for this.
uid : "ORIGINAL_BOOKING_UID" ,
meetingId : "ORIGINAL_MEETING_ID" ,
meetingPassword : "ORIGINAL_MEETING_PASSWORD" ,
meetingUrl : "https://ORIGINAL_MEETING_URL" ,
} ,
] ,
} ,
} ) ;
expectWorkflowToBeTriggered ( ) ;
// FIXME: We should send Broken Integration emails on calendar event updation failure
// expectBrokenIntegrationEmails({ booker, organizer, emails });
expectBookingRescheduledWebhookToHaveBeenFired ( {
booker ,
organizer ,
location : "New York" ,
subscriberUrl : "http://my-webhook.example.com" ,
} ) ;
} ,
timeout
) ;
2023-09-06 19:23:53 +00:00
} ) ;
} ) ;
function createMockNextJsRequest ( . . . args : Parameters < typeof createMocks > ) {
return createMocks < CustomNextApiRequest , CustomNextApiResponse > ( . . . args ) ;
}
function getBasicMockRequestDataForBooking() {
return {
start : ` ${ getDate ( { dateIncrement : 1 } ).dateString}T04:00:00.000Z ` ,
end : ` ${ getDate ( { dateIncrement : 1 } ).dateString}T04:30:00.000Z ` ,
eventTypeSlug : "no-confirmation" ,
timeZone : "Asia/Calcutta" ,
language : "en" ,
user : "teampro" ,
metadata : { } ,
hasHashedBookingLink : false ,
hashedLink : null ,
} ;
}
function getMockRequestDataForBooking ( {
data ,
} : {
data : Partial < ReturnType < typeof getBasicMockRequestDataForBooking > > & {
eventTypeId : number ;
2023-09-30 04:52:32 +00:00
rescheduleUid? : string ;
bookingUid? : string ;
2023-09-06 19:23:53 +00:00
responses : {
email : string ;
name : string ;
location : { optionValue : "" ; value : string } ;
} ;
} ;
} ) {
return {
. . . getBasicMockRequestDataForBooking ( ) ,
. . . data ,
} ;
}