feat: add usernameInOrg field to webhook organizer payload for organization users (#23246)

* feat: add usernameInOrg field to webhook organizer payload for organization users

- Add usernameInOrg field to CalendarEventBuilder organizer interface
- Update handleNewBooking to pass organizerOrganizationProfile.username as usernameInOrg
- Include usernameInOrg in webhook payload generation (sendPayload.ts)
- Add webhook form variable for usernameInOrg with translation
- Update Person type to include usernameInOrg field
- Add test case for organization user webhook verification
- Maintain backward compatibility by keeping existing username field unchanged

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* feat: add usernameInOrg to additional webhook sending locations

- Update handleCancelBooking.ts to include usernameInOrg in organizer payload
- Update confirm.handler.ts to include usernameInOrg for booking confirmations
- Update getBooking.ts to include usernameInOrg in payment-related webhooks
- Maintain backward compatibility with existing username field
- All webhook sending locations now include organization profile username

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fixes

* Add subteam event test for usernameInOrg

* fix eslitn issues

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Hariom BalharaandDevin AI authored and GitHub committed 2025-10-06 19:51:04 +05:30
1 parent 1e226a40d1
commit 9c97b2aa6d
14 files changed
+581 -243

No files matched your search

@@ -3677,6 +3677,8 @@
"webhook_organizer_email": "Email of the organizer",
"webhook_organizer_timezone": "Timezone of the organizer (e.g., 'America/New_York', 'Asia/Kolkata')",
"webhook_organizer_locale": "Locale of the organizer (e.g., 'en', 'fr')",
"webhook_organizer_username": "Username of the organizer (e.g., 'john.doe')",
"webhook_organizer_username_in_org": "Username of the organizer in their organization (e.g., 'john.doe')",
"webhook_attendee_name": "Name of the first attendee",
"webhook_attendee_email": "Email of the first attendee",
"webhook_attendee_timezone": "Timezone of the first attendee",
@@ -29,18 +29,22 @@ import type { BookingStatus } from "@calcom/prisma/enums";
import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import type { userMetadataType } from "@calcom/prisma/zod-utils";
import type { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { AppMeta } from "@calcom/types/App";
import type {
Calendar,
CalendarEvent,
IntegrationCalendar,
NewCalendarEventType,
EventBusyDate,
} from "@calcom/types/Calendar";
import type { CredentialForCalendarService } from "@calcom/types/Credential";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { VideoApiAdapter } from "@calcom/types/VideoApiAdapter";
import { getMockPaymentService } from "./MockPaymentService";
import type { getMockRequestDataForBooking } from "./getMockRequestDataForBooking";
type NonNullableVideoApiAdapter = NonNullable<VideoApiAdapter>;
vi.mock("@calcom/app-store/calendar.services.generated", () => ({
CalendarServiceMap: {
googlecalendar: Promise.resolve({ default: vi.fn() }),
@@ -50,7 +54,7 @@ vi.mock("@calcom/app-store/calendar.services.generated", () => ({
},
}));
const mockVideoAdapterRegistry: Record<string, any> = {};
const mockVideoAdapterRegistry: Record<string, unknown> = {};
vi.mock("@calcom/app-store/video.adapters.generated", () => ({
VideoApiAdapterMap: new Proxy(
@@ -72,9 +76,7 @@ vi.mock("@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic", () => ({
}));
vi.mock("@calcom/lib/crypto", async (importOriginal) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const actual = await importOriginal<any>();
const actual = await importOriginal<typeof import("@calcom/lib/crypto")>();
return {
...actual,
symmetricEncrypt: vi.fn((serviceAccountKey) => serviceAccountKey),
@@ -116,6 +118,17 @@ type InputWorkflow = {
verifiedAt?: Date;
};
type PaymentData = {
// Common payment data fields based on Stripe and other payment providers
paymentIntent?: string;
paymentMethodId?: string;
clientSecret?: string;
customerId?: string;
subscriptionId?: string;
metadata?: Record<string, string>;
[key: string]: unknown; // Allow additional provider-specific fields
};
type InputPayment = {
id?: number;
uid: string;
@@ -126,7 +139,7 @@ type InputPayment = {
currency: string;
success: boolean;
refunded: boolean;
data: Record<string, any>;
data: PaymentData;
externalId: string;
paymentOption?: PaymentOption;
};
@@ -258,7 +271,7 @@ export type InputEventType = {
bookingLimits?: IntervalLimit;
durationLimits?: IntervalLimit;
owner?: number;
metadata?: any;
metadata?: z.infer<typeof EventTypeMetaDataSchema>;
rescheduleWithSameRoundRobinHost?: boolean;
restrictionSchedule?: {
create: {
@@ -296,6 +309,7 @@ type WhiteListedBookingProps = {
email: string;
phoneNumber?: string;
bookingSeat?: AttendeeBookingSeatInput | null;
timeZone?: string;
}[];
references?: (Omit<ReturnType<typeof getMockBookingReference>, "credentialId"> & {
// TODO: Make sure that all references start providing credentialId and then remove this intersection of optional credentialId
@@ -370,13 +384,18 @@ export async function addEventTypesToDb(
"users" | "workflows" | "destinationCalendar" | "schedule"
> & {
id?: number;
users?: any[];
users?: ({ id: number } | undefined)[];
userId?: number;
hosts?: any[];
workflows?: any[];
destinationCalendar?: any;
schedule?: any;
metadata?: any;
hosts?: {
user: InputUser | undefined;
id: number;
}[];
workflows?: Prisma.WorkflowCreateInput[];
destinationCalendar?: {
create: Prisma.DestinationCalendarCreateInput;
};
schedule?: { create: Prisma.ScheduleCreateInput } | null | undefined;
metadata?: z.infer<typeof EventTypeMetaDataSchema>;
team?: { id?: number | null; bookingLimits?: IntervalLimit; includeManagedEventsInLimits?: boolean };
restrictionSchedule?: {
create: {
@@ -559,7 +578,8 @@ export async function addEventTypes(eventTypes: InputEventType[], usersStore: In
};
});
log.silly("TestData: Creating EventType", JSON.stringify(eventTypesWithUsers));
return await addEventTypesToDb(eventTypesWithUsers);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Pretty complex type here
return await addEventTypesToDb(eventTypesWithUsers as unknown as any);
}
function addBookingReferencesToDB(bookingReferences: Prisma.BookingReferenceCreateManyInput[]) {
@@ -941,7 +961,6 @@ export async function addUsers(users: InputUser[]) {
}
if (user.profiles) {
newUser.profiles = {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error Not sure why this is not working
createMany: {
data: user.profiles,
@@ -1062,7 +1081,7 @@ export async function createOrganization(orgData: {
export async function createCredentials(
credentialData: {
type: string;
key: any;
key: Prisma.JsonValue;
id?: number;
userId?: number | null;
teamId?: number | null;
@@ -1575,7 +1594,7 @@ export function getOrganizer({
completedOnboarding,
locked,
emailVerified,
};
};
}
export function getScenarioData(
@@ -1603,7 +1622,7 @@ export function getScenarioData(
bookings?: ScenarioData["bookings"];
payment?: ScenarioData["payment"];
},
org?: { id: number | null } | undefined | null
org?: { id: number | null; profileUsername?: string } | undefined | null
) {
if (_users && (usersApartFromOrganizer.length || organizer)) {
throw new Error("When users are provided, usersApartFromOrganizer and organizer should not be provided");
@@ -1617,11 +1636,13 @@ export function getScenarioData(
if (!orgId) {
throw new Error("If org is specified org.id is required");
}
users.forEach((user) => {
const profileUsername = org.profileUsername ?? user.username ?? "";
user.profiles = [
{
organizationId: orgId,
username: user.username || "",
username: profileUsername,
uid: ProfileRepository.generateProfileUid(),
},
];
@@ -1694,7 +1715,7 @@ export const enum BookingLocations {
export type CalendarServiceMethodMockCallBase = {
calendarServiceConstructorArgs: {
credential: CredentialForCalendarService;
credential: CredentialPayload;
};
};
@@ -1710,7 +1731,7 @@ type UpdateEventMethodMockCall = CalendarServiceMethodMockCallBase & {
args: {
uid: string;
event: CalendarEvent;
externalCalendarId?: string;
externalCalendarId?: string | null;
};
};
@@ -1718,7 +1739,7 @@ type DeleteEventMethodMockCall = CalendarServiceMethodMockCallBase & {
args: {
uid: string;
event: CalendarEvent;
externalCalendarId?: string;
externalCalendarId?: string | null;
};
};
@@ -1800,156 +1821,161 @@ export async function mockCalendar(
const calendarServicePromise = CalendarServiceMap[calendarServiceKey];
if (calendarServicePromise) {
const resolvedService = await calendarServicePromise;
vi.mocked(resolvedService.default as any).mockImplementation(function MockCalendarService(
credential: any
) {
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createEvent: async function (...rest: any[]): Promise<NewCalendarEventType> {
if (calendarData?.creationCrash) {
throw new Error("MockCalendarService.createEvent fake error");
}
const [calEvent, credentialId, externalCalendarId] = rest;
log.debug(
"mockCalendar.createEvent",
JSON.stringify({ calEvent, credentialId, externalCalendarId })
);
createEventCalls.push({
args: {
calEvent,
credentialId,
externalCalendarId,
},
calendarServiceConstructorArgs: {
credential,
},
});
const isGoogleMeetLocation = calEvent?.location === BookingLocations.GoogleMeet;
if (app.type === "google_calendar") {
return Promise.resolve({
type: app.type,
additionalInfo: {
vi.mocked(resolvedService.default).mockImplementation(
// @ts-expect-error - Mock implementation satisfies Calendar interface but TypeScript expects specific calendar service types
function MockCalendarService(credential) {
return {
createEvent: async function (
...rest: Parameters<Calendar["createEvent"]>
): Promise<NewCalendarEventType> {
if (calendarData?.creationCrash) {
throw new Error("MockCalendarService.createEvent fake error");
}
const [calEvent, credentialId, externalCalendarId] = rest;
log.debug(
"mockCalendar.createEvent",
JSON.stringify({ calEvent, credentialId, externalCalendarId })
);
createEventCalls.push({
args: {
calEvent,
credentialId,
externalCalendarId,
},
calendarServiceConstructorArgs: {
credential,
},
});
const isGoogleMeetLocation = calEvent?.location === BookingLocations.GoogleMeet;
if (app.type === "google_calendar") {
return Promise.resolve({
type: app.type,
additionalInfo: {
hangoutLink:
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
hangoutLink:
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
uid: normalizedCalendarData.create?.uid || "GOOGLE_CALENDAR_EVENT_ID",
id: normalizedCalendarData.create?.id || "GOOGLE_CALENDAR_EVENT_ID",
iCalUID:
normalizedCalendarData.create?.iCalUID || calEvent.iCalUID || "GOOGLE_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url:
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
});
} else if (app.type === "office365_calendar") {
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: normalizedCalendarData.create?.uid || "OFFICE_365_CALENDAR_EVENT_ID",
id: normalizedCalendarData.create?.id || "OFFICE_365_CALENDAR_EVENT_ID",
iCalUID:
normalizedCalendarData.create?.iCalUID ||
calEvent.iCalUID ||
"OFFICE_365_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url:
normalizedCalendarData.create?.appSpecificData?.office365Calendar?.url ||
"https://UNUSED_URL",
});
} else {
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: "PROBABLY_UNUSED_UID",
hangoutLink:
(isGoogleMeetLocation
? normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink
: null) || "https://UNUSED_URL",
// A Calendar is always expected to return an id.
id: normalizedCalendarData.create?.id || "FALLBACK_MOCK_CALENDAR_EVENT_ID",
iCalUID: normalizedCalendarData.create?.iCalUID,
// Password and URL seems useless for CalendarService, plan to remove them if that's the case
password: "MOCK_PASSWORD",
url: "https://UNUSED_URL",
});
}
},
updateEvent: async function (
...rest: Parameters<Calendar["updateEvent"]>
): Promise<NewCalendarEventType> {
if (calendarData?.updationCrash) {
throw new Error("MockCalendarService.updateEvent fake error");
}
const [uid, event, externalCalendarId] = rest;
log.silly("mockCalendar.updateEvent", JSON.stringify({ uid, event, externalCalendarId }));
updateEventCalls.push({
args: {
uid,
event,
externalCalendarId,
},
calendarServiceConstructorArgs: {
credential,
},
hangoutLink:
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
uid: normalizedCalendarData.create?.uid || "GOOGLE_CALENDAR_EVENT_ID",
id: normalizedCalendarData.create?.id || "GOOGLE_CALENDAR_EVENT_ID",
iCalUID:
normalizedCalendarData.create?.iCalUID || calEvent.iCalUID || "GOOGLE_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url:
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
});
} else if (app.type === "office365_calendar") {
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: normalizedCalendarData.create?.uid || "OFFICE_365_CALENDAR_EVENT_ID",
id: normalizedCalendarData.create?.id || "OFFICE_365_CALENDAR_EVENT_ID",
iCalUID:
normalizedCalendarData.create?.iCalUID || calEvent.iCalUID || "OFFICE_365_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url:
normalizedCalendarData.create?.appSpecificData?.office365Calendar?.url ||
"https://UNUSED_URL",
});
} else {
const isGoogleMeetLocation = event.location === BookingLocations.GoogleMeet;
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: "PROBABLY_UNUSED_UID",
hangoutLink:
(isGoogleMeetLocation
? normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink
: null) || "https://UNUSED_URL",
// A Calendar is always expected to return an id.
id: normalizedCalendarData.create?.id || "FALLBACK_MOCK_CALENDAR_EVENT_ID",
iCalUID: normalizedCalendarData.create?.iCalUID,
iCalUID: normalizedCalendarData.update?.iCalUID,
id: normalizedCalendarData.update?.uid || "FALLBACK_MOCK_ID",
// Password and URL seems useless for CalendarService, plan to remove them if that's the case
password: "MOCK_PASSWORD",
url: "https://UNUSED_URL",
location: isGoogleMeetLocation ? "https://UNUSED_URL" : undefined,
hangoutLink:
(isGoogleMeetLocation
? normalizedCalendarData.update?.appSpecificData?.googleCalendar?.hangoutLink
: null) || "https://UNUSED_URL",
conferenceData: isGoogleMeetLocation ? event.conferenceData : undefined,
});
}
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
updateEvent: async function (...rest: any[]): Promise<NewCalendarEventType> {
if (calendarData?.updationCrash) {
throw new Error("MockCalendarService.updateEvent fake error");
}
const [uid, event, externalCalendarId] = rest;
log.silly("mockCalendar.updateEvent", JSON.stringify({ uid, event, externalCalendarId }));
// eslint-disable-next-line prefer-rest-params
updateEventCalls.push({
args: {
uid,
event,
externalCalendarId,
},
calendarServiceConstructorArgs: {
credential,
},
});
const isGoogleMeetLocation = event.location === BookingLocations.GoogleMeet;
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: "PROBABLY_UNUSED_UID",
iCalUID: normalizedCalendarData.update?.iCalUID,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: normalizedCalendarData.update?.uid || "FALLBACK_MOCK_ID",
// Password and URL seems useless for CalendarService, plan to remove them if that's the case
password: "MOCK_PASSWORD",
url: "https://UNUSED_URL",
location: isGoogleMeetLocation ? "https://UNUSED_URL" : undefined,
hangoutLink:
(isGoogleMeetLocation
? normalizedCalendarData.update?.appSpecificData?.googleCalendar?.hangoutLink
: null) || "https://UNUSED_URL",
conferenceData: isGoogleMeetLocation ? event.conferenceData : undefined,
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deleteEvent: async (...rest: any[]) => {
log.silly("mockCalendar.deleteEvent", JSON.stringify({ rest }));
// eslint-disable-next-line prefer-rest-params
deleteEventCalls.push({
args: {
uid: rest[0],
event: rest[1],
externalCalendarId: rest[2],
},
calendarServiceConstructorArgs: {
credential,
},
});
},
getAvailability: async (...rest: any[]): Promise<EventBusyDate[]> => {
if (calendarData?.getAvailabilityCrash) {
throw new Error("MockCalendarService.getAvailability fake error");
}
const [dateFrom, dateTo, selectedCalendars, shouldServeCache] = rest;
getAvailabilityCalls.push({
args: {
dateFrom,
dateTo,
selectedCalendars,
shouldServeCache,
},
calendarServiceConstructorArgs: {
credential,
},
});
return new Promise((resolve) => {
resolve(calendarData?.busySlots || []);
});
},
};
});
},
deleteEvent: async (...rest: Parameters<Calendar["deleteEvent"]>) => {
log.silly("mockCalendar.deleteEvent", JSON.stringify({ rest }));
deleteEventCalls.push({
args: {
uid: rest[0],
event: rest[1],
externalCalendarId: rest[2],
},
calendarServiceConstructorArgs: {
credential,
},
});
},
getAvailability: async (
dateFrom: string,
dateTo: string,
selectedCalendars: IntegrationCalendar[],
shouldServeCache?: boolean
): Promise<EventBusyDate[]> => {
if (calendarData?.getAvailabilityCrash) {
throw new Error("MockCalendarService.getAvailability fake error");
}
getAvailabilityCalls.push({
args: {
dateFrom,
dateTo,
selectedCalendars,
shouldServeCache,
},
calendarServiceConstructorArgs: {
credential,
},
});
return new Promise((resolve) => {
resolve(calendarData?.busySlots || []);
});
},
} as Calendar;
}
);
}
return {
@@ -2014,10 +2040,9 @@ export function mockVideoApp({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deleteMeetingCalls: any[] = [];
const mockVideoAdapter = (credential: any) => {
const mockVideoAdapter = (credential: unknown) => {
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createMeeting: (...rest: any[]) => {
createMeeting: (...rest: Parameters<NonNullableVideoApiAdapter["createMeeting"]>) => {
if (creationCrash) {
throw new Error("MockVideoApiAdapter.createMeeting fake error");
}
@@ -2031,8 +2056,7 @@ export function mockVideoApp({
...videoMeetingData,
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
updateMeeting: async (...rest: any[]) => {
updateMeeting: async (...rest: Parameters<NonNullableVideoApiAdapter["updateMeeting"]>) => {
if (updationCrash) {
throw new Error("MockVideoApiAdapter.updateMeeting fake error");
}
@@ -2053,8 +2077,7 @@ export function mockVideoApp({
...videoMeetingData,
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deleteMeeting: async (...rest: any[]) => {
deleteMeeting: async (...rest: Parameters<NonNullableVideoApiAdapter["deleteMeeting"]>) => {
log.silly("MockVideoApiAdapter.deleteMeeting", JSON.stringify(rest));
deleteMeetingCalls.push({
credential,
@@ -2116,7 +2139,7 @@ export function mockPaymentApp({
metadataLookupKey: string;
appStoreLookupKey?: string;
}) {
appStoreLookupKey = appStoreLookupKey || metadataLookupKey;
const _appStoreLookupKey = appStoreLookupKey || metadataLookupKey;
const { paymentUid, externalId } = getMockPaymentService();
return {
@@ -2366,7 +2389,7 @@ export const getDefaultBookingFields = ({
required: true,
defaultLabel: "your_name",
},
!!emailField
emailField
? emailField
: {
name: "email",
+20 -7
View File
@@ -306,13 +306,13 @@ export function expectWebhookToHaveBeenCalledWith(
if (parsedBody.payload) {
if (data.payload) {
if (!!data.payload.metadata) {
if (data.payload.metadata) {
expect(parsedBody.payload.metadata).toEqual(expect.objectContaining(data.payload.metadata));
}
if (!!data.payload.responses)
if (data.payload.responses)
expect(parsedBody.payload.responses).toEqual(expect.objectContaining(data.payload.responses));
if (!!data.payload.organizer)
if (data.payload.organizer)
expect(parsedBody.payload.organizer).toEqual(expect.objectContaining(data.payload.organizer));
const { responses: _1, metadata: _2, organizer: _3, ...remainingPayload } = data.payload;
@@ -1031,6 +1031,7 @@ export function expectBookingRequestedWebhookToHaveBeenFired({
}
export function expectBookingCreatedWebhookToHaveBeenFired({
organizer,
booker,
location,
subscriberUrl,
@@ -1039,7 +1040,7 @@ export function expectBookingCreatedWebhookToHaveBeenFired({
isEmailHidden = false,
isAttendeePhoneNumberHidden = false,
}: {
organizer: { email: string; name: string };
organizer: { email: string; name: string; username?: string; usernameInOrg?: string };
booker: { email: string; name: string; attendeePhoneNumber?: string };
subscriberUrl: string;
location: string;
@@ -1048,6 +1049,11 @@ export function expectBookingCreatedWebhookToHaveBeenFired({
isEmailHidden?: boolean;
isAttendeePhoneNumberHidden?: boolean;
}) {
const organizerPayload = {
username: organizer.username,
...(organizer.usernameInOrg ? { usernameInOrg: organizer.usernameInOrg } : null),
};
if (!paidEvent) {
expectWebhookToHaveBeenCalledWith(subscriberUrl, {
triggerEvent: "BOOKING_CREATED",
@@ -1073,6 +1079,7 @@ export function expectBookingCreatedWebhookToHaveBeenFired({
isHidden: false,
},
},
organizer: organizerPayload,
},
});
} else {
@@ -1103,6 +1110,7 @@ export function expectBookingCreatedWebhookToHaveBeenFired({
value: { optionValue: "", value: location },
},
},
organizer: organizerPayload,
},
});
}
@@ -1144,21 +1152,28 @@ export function expectBookingRescheduledWebhookToHaveBeenFired({
}
export function expectBookingCancelledWebhookToHaveBeenFired({
organizer,
booker,
location,
subscriberUrl,
payload,
}: {
organizer: { email: string; name: string };
organizer: { email: string; name: string; username?: string; usernameInOrg?: string };
booker: { email: string; name: string };
subscriberUrl: string;
location: string;
payload?: Record<string, unknown>;
}) {
const organizerPayload = {
username: organizer.username,
...(organizer.usernameInOrg ? { usernameInOrg: organizer.usernameInOrg } : null),
};
expectWebhookToHaveBeenCalledWith(subscriberUrl, {
triggerEvent: "BOOKING_CANCELLED",
payload: {
...payload,
organizer: organizerPayload,
metadata: null,
responses: {
name: {
@@ -1387,14 +1402,12 @@ export function expectSuccessfulVideoMeetingDeletionInCalendar(
export async function expectBookingInDBToBeRescheduledFromTo({ from, to }: { from: any; to: any }) {
// Expect previous booking to be cancelled
await expectBookingToBeInDatabase({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...from,
status: BookingStatus.CANCELLED,
});
// Expect new booking to be created but status would depend on whether the new booking requires confirmation or not.
await expectBookingToBeInDatabase({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...to,
});
}