test: More tests in booking flow (#11510)
This commit is contained in:
@@ -10,7 +10,7 @@ import "vitest-fetch-mock";
|
||||
|
||||
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
|
||||
import { handleStripePaymentSuccess } from "@calcom/features/ee/payments/api/webhook";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import type { HttpError } from "@calcom/lib/http-error";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type { SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { BookingStatus } from "@calcom/prisma/enums";
|
||||
@@ -74,6 +74,7 @@ type InputUser = typeof TestData.users.example & { id: number } & {
|
||||
}[];
|
||||
timeZone: string;
|
||||
}[];
|
||||
destinationCalendar?: Prisma.DestinationCalendarCreateInput;
|
||||
};
|
||||
|
||||
export type InputEventType = {
|
||||
@@ -92,6 +93,7 @@ export type InputEventType = {
|
||||
beforeEventBuffer?: number;
|
||||
afterEventBuffer?: number;
|
||||
requiresConfirmation?: boolean;
|
||||
destinationCalendar?: Prisma.DestinationCalendarCreateInput;
|
||||
} & Partial<Omit<Prisma.EventTypeCreateInput, "users">>;
|
||||
|
||||
type InputBooking = {
|
||||
@@ -124,17 +126,31 @@ const Timezones = {
|
||||
logger.setSettings({ minLevel: "silly" });
|
||||
|
||||
async function addEventTypesToDb(
|
||||
eventTypes: (Prisma.EventTypeCreateInput & {
|
||||
eventTypes: (Omit<Prisma.EventTypeCreateInput, "users" | "worflows" | "destinationCalendar"> & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
users?: any[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
workflows?: any[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
destinationCalendar?: any;
|
||||
})[]
|
||||
) {
|
||||
logger.silly("TestData: Add EventTypes to DB", JSON.stringify(eventTypes));
|
||||
await prismock.eventType.createMany({
|
||||
data: eventTypes,
|
||||
});
|
||||
logger.silly(
|
||||
"TestData: All EventTypes in DB are",
|
||||
JSON.stringify({
|
||||
eventTypes: await prismock.eventType.findMany({
|
||||
include: {
|
||||
users: true,
|
||||
workflows: true,
|
||||
destinationCalendar: true,
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser[]) {
|
||||
@@ -174,6 +190,11 @@ async function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser
|
||||
...eventType,
|
||||
workflows: [],
|
||||
users,
|
||||
destinationCalendar: eventType.destinationCalendar
|
||||
? {
|
||||
create: eventType.destinationCalendar,
|
||||
}
|
||||
: eventType.destinationCalendar,
|
||||
};
|
||||
});
|
||||
logger.silly("TestData: Creating EventType", JSON.stringify(eventTypesWithUsers));
|
||||
@@ -266,6 +287,9 @@ async function addUsersToDb(users: (Prisma.UserCreateInput & { schedules: Prisma
|
||||
await prismock.user.createMany({
|
||||
data: users,
|
||||
});
|
||||
logger.silly("Added users to Db", {
|
||||
allUsers: await prismock.user.findMany(),
|
||||
});
|
||||
}
|
||||
|
||||
async function addUsers(users: InputUser[]) {
|
||||
@@ -298,6 +322,15 @@ async function addUsers(users: InputUser[]) {
|
||||
},
|
||||
};
|
||||
}
|
||||
if (user.selectedCalendars) {
|
||||
newUser.selectedCalendars = {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
createMany: {
|
||||
data: user.selectedCalendars,
|
||||
},
|
||||
};
|
||||
}
|
||||
return newUser;
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -569,6 +602,7 @@ export function getOrganizer({
|
||||
schedules,
|
||||
credentials,
|
||||
selectedCalendars,
|
||||
destinationCalendar,
|
||||
}: {
|
||||
name: string;
|
||||
email: string;
|
||||
@@ -576,6 +610,7 @@ export function getOrganizer({
|
||||
schedules: InputUser["schedules"];
|
||||
credentials?: InputCredential[];
|
||||
selectedCalendars?: InputSelectedCalendar[];
|
||||
destinationCalendar?: Prisma.DestinationCalendarCreateInput;
|
||||
}) {
|
||||
return {
|
||||
...TestData.users.example,
|
||||
@@ -585,6 +620,7 @@ export function getOrganizer({
|
||||
schedules,
|
||||
credentials,
|
||||
selectedCalendars,
|
||||
destinationCalendar,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -599,7 +635,7 @@ export function getScenarioData({
|
||||
{
|
||||
organizer: ReturnType<typeof getOrganizer>;
|
||||
eventTypes: ScenarioData["eventTypes"];
|
||||
apps: ScenarioData["apps"];
|
||||
apps?: ScenarioData["apps"];
|
||||
usersApartFromOrganizer?: ScenarioData["users"];
|
||||
webhooks?: ScenarioData["webhooks"];
|
||||
bookings?: ScenarioData["bookings"];
|
||||
@@ -624,7 +660,19 @@ export function getScenarioData({
|
||||
description: `It's a test event type - ${index + 1}`,
|
||||
};
|
||||
}),
|
||||
users,
|
||||
users: users.map((user) => {
|
||||
const newUser = {
|
||||
...user,
|
||||
};
|
||||
if (user.destinationCalendar) {
|
||||
newUser.destinationCalendar = {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
create: user.destinationCalendar,
|
||||
};
|
||||
}
|
||||
return newUser;
|
||||
}),
|
||||
apps: [...apps],
|
||||
webhooks,
|
||||
bookings: bookings || [],
|
||||
@@ -651,15 +699,23 @@ export function mockNoTranslations() {
|
||||
});
|
||||
}
|
||||
|
||||
export function mockCalendarToHaveNoBusySlots(
|
||||
/**
|
||||
* @param metadataLookupKey
|
||||
* @param calendarData Specify uids and other data to be faked to be returned by createEvent and updateEvent
|
||||
*/
|
||||
export function mockCalendar(
|
||||
metadataLookupKey: keyof typeof appStoreMetadata,
|
||||
calendarData?: {
|
||||
create: {
|
||||
uid: string;
|
||||
create?: {
|
||||
uid?: string;
|
||||
};
|
||||
update?: {
|
||||
uid: string;
|
||||
};
|
||||
busySlots?: { start: `${string}Z`; end: `${string}Z` }[];
|
||||
creationCrash?: boolean;
|
||||
updationCrash?: boolean;
|
||||
getAvailabilityCrash?: boolean;
|
||||
}
|
||||
) {
|
||||
const appStoreLookupKey = metadataLookupKey;
|
||||
@@ -685,17 +741,17 @@ export function mockCalendarToHaveNoBusySlots(
|
||||
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] = rest;
|
||||
logger.silly(
|
||||
"mockCalendarToHaveNoBusySlots.createEvent",
|
||||
JSON.stringify({ calEvent, credentialId })
|
||||
);
|
||||
logger.silly("mockCalendar.createEvent", JSON.stringify({ calEvent, credentialId }));
|
||||
createEventCalls.push(rest);
|
||||
return Promise.resolve({
|
||||
type: app.type,
|
||||
additionalInfo: {},
|
||||
uid: "PROBABLY_UNUSED_UID",
|
||||
id: normalizedCalendarData.create.uid,
|
||||
id: normalizedCalendarData.create?.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",
|
||||
@@ -703,11 +759,11 @@ export function mockCalendarToHaveNoBusySlots(
|
||||
},
|
||||
// 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;
|
||||
logger.silly(
|
||||
"mockCalendarToHaveNoBusySlots.updateEvent",
|
||||
JSON.stringify({ uid, event, externalCalendarId })
|
||||
);
|
||||
logger.silly("mockCalendar.updateEvent", JSON.stringify({ uid, event, externalCalendarId }));
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
updateEventCalls.push(rest);
|
||||
return Promise.resolve({
|
||||
@@ -715,15 +771,18 @@ export function mockCalendarToHaveNoBusySlots(
|
||||
additionalInfo: {},
|
||||
uid: "PROBABLY_UNUSED_UID",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
id: normalizedCalendarData.update!.uid!,
|
||||
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",
|
||||
});
|
||||
},
|
||||
getAvailability: (): Promise<EventBusyDate[]> => {
|
||||
getAvailability: async (): Promise<EventBusyDate[]> => {
|
||||
if (calendarData?.getAvailabilityCrash) {
|
||||
throw new Error("MockCalendarService.getAvailability fake error");
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
resolve([]);
|
||||
resolve(calendarData?.busySlots || []);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -736,10 +795,42 @@ export function mockCalendarToHaveNoBusySlots(
|
||||
};
|
||||
}
|
||||
|
||||
export function mockSuccessfulVideoMeetingCreation({
|
||||
export function mockCalendarToHaveNoBusySlots(
|
||||
metadataLookupKey: keyof typeof appStoreMetadata,
|
||||
calendarData?: {
|
||||
create: {
|
||||
uid?: string;
|
||||
};
|
||||
update?: {
|
||||
uid: string;
|
||||
};
|
||||
}
|
||||
) {
|
||||
calendarData = calendarData || {
|
||||
create: {
|
||||
uid: "MOCK_ID",
|
||||
},
|
||||
update: {
|
||||
uid: "UPDATED_MOCK_ID",
|
||||
},
|
||||
};
|
||||
return mockCalendar(metadataLookupKey, { ...calendarData, busySlots: [] });
|
||||
}
|
||||
|
||||
export function mockCalendarToCrashOnCreateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
|
||||
return mockCalendar(metadataLookupKey, { creationCrash: true });
|
||||
}
|
||||
|
||||
export function mockCalendarToCrashOnUpdateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
|
||||
return mockCalendar(metadataLookupKey, { updationCrash: true });
|
||||
}
|
||||
|
||||
export function mockVideoApp({
|
||||
metadataLookupKey,
|
||||
appStoreLookupKey,
|
||||
videoMeetingData,
|
||||
creationCrash,
|
||||
updationCrash,
|
||||
}: {
|
||||
metadataLookupKey: string;
|
||||
appStoreLookupKey?: string;
|
||||
@@ -748,6 +839,8 @@ export function mockSuccessfulVideoMeetingCreation({
|
||||
id: string;
|
||||
url: string;
|
||||
};
|
||||
creationCrash?: boolean;
|
||||
updationCrash?: boolean;
|
||||
}) {
|
||||
appStoreLookupKey = appStoreLookupKey || metadataLookupKey;
|
||||
videoMeetingData = videoMeetingData || {
|
||||
@@ -772,7 +865,11 @@ export function mockSuccessfulVideoMeetingCreation({
|
||||
VideoApiAdapter: () => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
createMeeting: (...rest: any[]) => {
|
||||
if (creationCrash) {
|
||||
throw new Error("MockVideoApiAdapter.createMeeting fake error");
|
||||
}
|
||||
createMeetingCalls.push(rest);
|
||||
|
||||
return Promise.resolve({
|
||||
type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type,
|
||||
...videoMeetingData,
|
||||
@@ -780,6 +877,9 @@ export function mockSuccessfulVideoMeetingCreation({
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
updateMeeting: async (...rest: any[]) => {
|
||||
if (updationCrash) {
|
||||
throw new Error("MockVideoApiAdapter.updateMeeting fake error");
|
||||
}
|
||||
const [bookingRef, calEvent] = rest;
|
||||
updateMeetingCalls.push(rest);
|
||||
if (!bookingRef.type) {
|
||||
@@ -808,6 +908,40 @@ export function mockSuccessfulVideoMeetingCreation({
|
||||
};
|
||||
}
|
||||
|
||||
export function mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey,
|
||||
appStoreLookupKey,
|
||||
videoMeetingData,
|
||||
}: {
|
||||
metadataLookupKey: string;
|
||||
appStoreLookupKey?: string;
|
||||
videoMeetingData?: {
|
||||
password: string;
|
||||
id: string;
|
||||
url: string;
|
||||
};
|
||||
}) {
|
||||
return mockVideoApp({
|
||||
metadataLookupKey,
|
||||
appStoreLookupKey,
|
||||
videoMeetingData,
|
||||
});
|
||||
}
|
||||
|
||||
export function mockVideoAppToCrashOnCreateMeeting({
|
||||
metadataLookupKey,
|
||||
appStoreLookupKey,
|
||||
}: {
|
||||
metadataLookupKey: string;
|
||||
appStoreLookupKey?: string;
|
||||
}) {
|
||||
return mockVideoApp({
|
||||
metadataLookupKey,
|
||||
appStoreLookupKey,
|
||||
creationCrash: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function mockPaymentApp({
|
||||
metadataLookupKey,
|
||||
appStoreLookupKey,
|
||||
@@ -885,11 +1019,8 @@ export async function mockPaymentSuccessWebhookFromStripe({ externalId }: { exte
|
||||
try {
|
||||
await handleStripePaymentSuccess(getMockedStripePaymentEvent({ paymentIntentId: externalId }));
|
||||
} catch (e) {
|
||||
if (!(e instanceof HttpError)) {
|
||||
logger.silly("mockPaymentSuccessWebhookFromStripe:catch", JSON.stringify(e));
|
||||
} else {
|
||||
logger.error("mockPaymentSuccessWebhookFromStripe:catch", JSON.stringify(e));
|
||||
}
|
||||
logger.silly("mockPaymentSuccessWebhookFromStripe:catch", JSON.stringify(e));
|
||||
|
||||
webhookResponse = e as HttpError;
|
||||
}
|
||||
return { webhookResponse };
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import prismaMock from "../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import type { Booking, BookingReference } from "@prisma/client";
|
||||
import type { WebhookTriggerEvents } from "@prisma/client";
|
||||
import type { WebhookTriggerEvents, Booking, BookingReference } from "@prisma/client";
|
||||
import { expect } from "vitest";
|
||||
import "vitest-fetch-mock";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import type { Fixtures } from "@calcom/web/test/fixtures/fixtures";
|
||||
|
||||
import type { InputEventType } from "./bookingScenario";
|
||||
@@ -30,7 +31,9 @@ expect.extend({
|
||||
to: string
|
||||
) {
|
||||
const testEmail = emails.get().find((email) => email.to.includes(to));
|
||||
const emailsToLog = emails.get().map((email) => ({ to: email.to, html: email.html }));
|
||||
if (!testEmail) {
|
||||
logger.silly("All Emails", JSON.stringify({ numEmails: emailsToLog.length, emailsToLog }));
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `No email sent to ${to}`,
|
||||
@@ -43,6 +46,10 @@ expect.extend({
|
||||
}
|
||||
isToAddressExpected = expectedEmail.to === testEmail.to;
|
||||
|
||||
if (!isHtmlContained || !isToAddressExpected) {
|
||||
logger.silly("All Emails", JSON.stringify({ numEmails: emailsToLog.length, emailsToLog }));
|
||||
}
|
||||
|
||||
return {
|
||||
pass: isHtmlContained && isToAddressExpected,
|
||||
message: () => {
|
||||
@@ -150,6 +157,59 @@ export function expectSuccessfulBookingCreationEmails({
|
||||
);
|
||||
}
|
||||
|
||||
export function expectBrokenIntegrationEmails({
|
||||
emails,
|
||||
organizer,
|
||||
booker,
|
||||
}: {
|
||||
emails: Fixtures["emails"];
|
||||
organizer: { email: string; name: string };
|
||||
booker: { email: string; name: string };
|
||||
}) {
|
||||
// Broken Integration email is only sent to the Organizer
|
||||
expect(emails).toHaveEmail(
|
||||
{
|
||||
htmlToContain: "<title>broken_integration</title>",
|
||||
to: `${organizer.email}`,
|
||||
},
|
||||
`${organizer.email}`
|
||||
);
|
||||
|
||||
// expect(emails).toHaveEmail(
|
||||
// {
|
||||
// htmlToContain: "<title>confirmed_event_type_subject</title>",
|
||||
// to: `${booker.name} <${booker.email}>`,
|
||||
// },
|
||||
// `${booker.name} <${booker.email}>`
|
||||
// );
|
||||
}
|
||||
|
||||
export function expectCalendarEventCreationFailureEmails({
|
||||
emails,
|
||||
organizer,
|
||||
booker,
|
||||
}: {
|
||||
emails: Fixtures["emails"];
|
||||
organizer: { email: string; name: string };
|
||||
booker: { email: string; name: string };
|
||||
}) {
|
||||
expect(emails).toHaveEmail(
|
||||
{
|
||||
htmlToContain: "<title>broken_integration</title>",
|
||||
to: `${organizer.email}`,
|
||||
},
|
||||
`${organizer.email}`
|
||||
);
|
||||
|
||||
expect(emails).toHaveEmail(
|
||||
{
|
||||
htmlToContain: "<title>calendar_event_creation_failure_subject</title>",
|
||||
to: `${booker.name} <${booker.email}>`,
|
||||
},
|
||||
`${booker.name} <${booker.email}>`
|
||||
);
|
||||
}
|
||||
|
||||
export function expectSuccessfulBookingRescheduledEmails({
|
||||
emails,
|
||||
organizer,
|
||||
@@ -394,20 +454,28 @@ export function expectSuccessfulCalendarEventCreationInCalendar(
|
||||
updateEventCalls: any[];
|
||||
},
|
||||
expected: {
|
||||
externalCalendarId: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
calEvent: any;
|
||||
uid: string;
|
||||
calendarId: string | null;
|
||||
videoCallUrl: string;
|
||||
}
|
||||
) {
|
||||
expect(calendarMock.createEventCalls.length).toBe(1);
|
||||
const call = calendarMock.createEventCalls[0];
|
||||
const uid = call[0];
|
||||
const calendarEvent = call[1];
|
||||
const externalId = call[2];
|
||||
expect(uid).toBe(expected.uid);
|
||||
expect(calendarEvent).toEqual(expect.objectContaining(expected.calEvent));
|
||||
expect(externalId).toBe(expected.externalCalendarId);
|
||||
const calEvent = call[0];
|
||||
|
||||
expect(calEvent).toEqual(
|
||||
expect.objectContaining({
|
||||
destinationCalendar: expected.calendarId
|
||||
? [
|
||||
expect.objectContaining({
|
||||
externalId: expected.calendarId,
|
||||
}),
|
||||
]
|
||||
: null,
|
||||
videoCallData: expect.objectContaining({
|
||||
url: expected.videoCallUrl,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function expectSuccessfulCalendarEventUpdationInCalendar(
|
||||
@@ -419,8 +487,7 @@ export function expectSuccessfulCalendarEventUpdationInCalendar(
|
||||
},
|
||||
expected: {
|
||||
externalCalendarId: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
calEvent: any;
|
||||
calEvent: Partial<CalendarEvent>;
|
||||
uid: string;
|
||||
}
|
||||
) {
|
||||
@@ -443,8 +510,7 @@ export function expectSuccessfulVideoMeetingCreationInCalendar(
|
||||
},
|
||||
expected: {
|
||||
externalCalendarId: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
calEvent: any;
|
||||
calEvent: Partial<CalendarEvent>;
|
||||
uid: string;
|
||||
}
|
||||
) {
|
||||
@@ -468,8 +534,7 @@ export function expectSuccessfulVideoMeetingUpdationInCalendar(
|
||||
expected: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
bookingRef: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
calEvent: any;
|
||||
calEvent: Partial<CalendarEvent>;
|
||||
}
|
||||
) {
|
||||
expect(videoMock.updateMeetingCalls.length).toBe(1);
|
||||
@@ -479,3 +544,19 @@ export function expectSuccessfulVideoMeetingUpdationInCalendar(
|
||||
expect(bookingRef).toEqual(expect.objectContaining(expected.bookingRef));
|
||||
expect(calendarEvent).toEqual(expect.objectContaining(expected.calEvent));
|
||||
}
|
||||
|
||||
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
|
||||
await expectBookingToBeInDatabase({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
...to,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import dayjs from "@calcom/dayjs";
|
||||
import { getUid } from "@calcom/lib/CalEventParser";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getPiiFreeCalendarEvent } from "@calcom/lib/piiFreeData";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { performance } from "@calcom/lib/server/perfObserver";
|
||||
import type {
|
||||
CalendarEvent,
|
||||
@@ -103,6 +104,8 @@ export const getConnectedCalendars = async (
|
||||
}
|
||||
}
|
||||
|
||||
log.error("getConnectedCalendars failed", safeStringify({ error, item }));
|
||||
|
||||
return {
|
||||
integration: cleanIntegrationKeys(item.integration),
|
||||
credentialId: item.credential.id,
|
||||
@@ -211,7 +214,7 @@ export const getBusyCalendarTimes = async (
|
||||
const endDate = dayjs(dateTo).endOf("month").add(14, "hours").format();
|
||||
results = await getCalendarsEvents(withCredentials, startDate, endDate, selectedCalendars);
|
||||
} catch (e) {
|
||||
logger.warn(e);
|
||||
log.warn(safeStringify(e));
|
||||
}
|
||||
return results.reduce((acc, availability) => acc.concat(availability), []);
|
||||
};
|
||||
@@ -292,7 +295,7 @@ export const updateEvent = async (
|
||||
let calWarnings: string[] | undefined = [];
|
||||
log.debug(
|
||||
"Updating calendar event",
|
||||
JSON.stringify({
|
||||
safeStringify({
|
||||
bookingRefUid,
|
||||
calEvent: getPiiFreeCalendarEvent(calEvent),
|
||||
})
|
||||
@@ -301,7 +304,7 @@ export const updateEvent = async (
|
||||
log.error(
|
||||
"updateEvent failed",
|
||||
"bookingRefUid is empty",
|
||||
JSON.stringify({ calEvent: getPiiFreeCalendarEvent(calEvent) })
|
||||
safeStringify({ calEvent: getPiiFreeCalendarEvent(calEvent) })
|
||||
);
|
||||
}
|
||||
const updatedResult: NewCalendarEventType | NewCalendarEventType[] | undefined =
|
||||
@@ -318,7 +321,7 @@ export const updateEvent = async (
|
||||
// await sendBrokenIntegrationEmail(calEvent, "calendar");
|
||||
log.error(
|
||||
"updateEvent failed",
|
||||
JSON.stringify({ e, calEvent: getPiiFreeCalendarEvent(calEvent) })
|
||||
safeStringify({ e, calEvent: getPiiFreeCalendarEvent(calEvent) })
|
||||
);
|
||||
if (e?.calError) {
|
||||
calError = e.calError;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Booking, DestinationCalendar } from "@prisma/client";
|
||||
import type { DestinationCalendar } from "@prisma/client";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { cloneDeep, merge } from "lodash";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
@@ -10,6 +10,7 @@ import { appKeysSchema as calVideoKeysSchema } from "@calcom/app-store/dailyvide
|
||||
import { getEventLocationTypeFromApp, MeetLocationType } from "@calcom/app-store/locations";
|
||||
import getApps from "@calcom/app-store/utils";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
import { createdEventSchema } from "@calcom/prisma/zod-utils";
|
||||
@@ -403,7 +404,7 @@ export default class EventManager {
|
||||
} else {
|
||||
logger.silly(
|
||||
"No destination Calendar found, falling back to first connected calendar",
|
||||
JSON.stringify({
|
||||
safeStringify({
|
||||
calendarCredentials: this.calendarCredentials,
|
||||
})
|
||||
);
|
||||
@@ -659,26 +660,4 @@ export default class EventManager {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event to set a cancelled event placeholder on users calendar
|
||||
* remove if virtual calendar is already done and user availability its read from there
|
||||
* and not only in their calendars
|
||||
* @param event
|
||||
* @param booking
|
||||
* @public
|
||||
*/
|
||||
public async updateAndSetCancelledPlaceholder(event: CalendarEvent, booking: PartialBooking) {
|
||||
await this.updateAllCalendarEvents(event, booking);
|
||||
}
|
||||
|
||||
public async rescheduleBookingWithSeats(
|
||||
originalBooking: Booking,
|
||||
newTimeSlotBooking?: Booking,
|
||||
owner?: boolean
|
||||
) {
|
||||
// Get originalBooking
|
||||
// If originalBooking has only one attendee we should do normal reschedule
|
||||
// Change current event attendees in everyone calendar
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import type { SelectedCalendar } from "@prisma/client";
|
||||
|
||||
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getPiiFreeCredential, getPiiFreeSelectedCalendar } from "@calcom/lib/piiFreeData";
|
||||
import { performance } from "@calcom/lib/server/perfObserver";
|
||||
import type { EventBusyDate } from "@calcom/types/Calendar";
|
||||
import type { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
const log = logger.getChildLogger({ prefix: ["getCalendarsEvents"] });
|
||||
const getCalendarsEvents = async (
|
||||
withCredentials: CredentialPayload[],
|
||||
dateFrom: string,
|
||||
@@ -15,6 +18,7 @@ const getCalendarsEvents = async (
|
||||
.filter((credential) => credential.type.endsWith("_calendar"))
|
||||
// filter out invalid credentials - these won't work.
|
||||
.filter((credential) => !credential.invalid);
|
||||
|
||||
const calendars = await Promise.all(calendarCredentials.map((credential) => getCalendar(credential)));
|
||||
performance.mark("getBusyCalendarTimesStart");
|
||||
const results = calendars.map(async (c, i) => {
|
||||
@@ -48,6 +52,11 @@ const getCalendarsEvents = async (
|
||||
"getBusyCalendarTimesStart",
|
||||
"getBusyCalendarTimesEnd"
|
||||
);
|
||||
log.debug({
|
||||
calendarCredentials: calendarCredentials.map(getPiiFreeCredential),
|
||||
selectedCalendars: selectedCalendars.map(getPiiFreeSelectedCalendar),
|
||||
calendarEvents: awaitedResults,
|
||||
});
|
||||
return awaitedResults;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { sendBrokenIntegrationEmail } from "@calcom/emails";
|
||||
import { getUid } from "@calcom/lib/CalEventParser";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getPiiFreeCalendarEvent } from "@calcom/lib/piiFreeData";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { GetRecordingsResponseSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { CalendarEvent, EventBusyDate } from "@calcom/types/Calendar";
|
||||
@@ -99,7 +100,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv
|
||||
returnObject = { ...returnObject, createdEvent: createdMeeting, success: true };
|
||||
} catch (err) {
|
||||
await sendBrokenIntegrationEmail(calEvent, "video");
|
||||
log.error("createMeeting failed", JSON.stringify({ err, calEvent: getPiiFreeCalendarEvent(calEvent) }));
|
||||
log.error("createMeeting failed", safeStringify({ err, calEvent: getPiiFreeCalendarEvent(calEvent) }));
|
||||
|
||||
// Default to calVideo
|
||||
const defaultMeeting = await createMeetingWithCalVideo(calEvent);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,6 +60,7 @@ import { HttpError } from "@calcom/lib/http-error";
|
||||
import isOutOfBounds, { BookingDateInPastError } from "@calcom/lib/isOutOfBounds";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { handlePayment } from "@calcom/lib/payment/handlePayment";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { checkBookingLimits, checkDurationLimits, getLuckyUser } from "@calcom/lib/server";
|
||||
import { getBookerUrl } from "@calcom/lib/server/getBookerUrl";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
@@ -213,7 +214,7 @@ function checkForConflicts(busyTimes: BufferedBusyTimes, time: dayjs.ConfigType,
|
||||
// Check if time is between start and end times
|
||||
if (dayjs(time).isBetween(startTime, endTime, null, "[)")) {
|
||||
log.error(
|
||||
`NAUF: start between a busy time slot ${JSON.stringify({
|
||||
`NAUF: start between a busy time slot ${safeStringify({
|
||||
...busyTime,
|
||||
time: dayjs(time).format(),
|
||||
})}`
|
||||
@@ -223,7 +224,7 @@ function checkForConflicts(busyTimes: BufferedBusyTimes, time: dayjs.ConfigType,
|
||||
// Check if slot end time is between start and end time
|
||||
if (dayjs(time).add(length, "minutes").isBetween(startTime, endTime)) {
|
||||
log.error(
|
||||
`NAUF: Ends between a busy time slot ${JSON.stringify({
|
||||
`NAUF: Ends between a busy time slot ${safeStringify({
|
||||
...busyTime,
|
||||
time: dayjs(time).add(length, "minutes").format(),
|
||||
})}`
|
||||
@@ -2123,10 +2124,7 @@ async function handler(
|
||||
message: "Booking Rescheduling failed",
|
||||
};
|
||||
|
||||
loggerWithEventDetails.error(
|
||||
`Booking ${organizerUser.name} failed`,
|
||||
JSON.stringify({ error, results })
|
||||
);
|
||||
loggerWithEventDetails.error(`Booking ${organizerUser.name} failed`, safeStringify({ error, results }));
|
||||
} else {
|
||||
const metadata: AdditionalInformation = {};
|
||||
const calendarResult = results.find((result) => result.type.includes("_calendar"));
|
||||
@@ -2180,7 +2178,7 @@ async function handler(
|
||||
|
||||
loggerWithEventDetails.error(
|
||||
`Booking ${organizerUser.username} failed`,
|
||||
JSON.stringify({ error, results })
|
||||
safeStringify({ error, results })
|
||||
);
|
||||
} else {
|
||||
const metadata: AdditionalInformation = {};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Credential, SelectedCalendar } from "@prisma/client";
|
||||
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
export function getPiiFreeCalendarEvent(calEvent: CalendarEvent) {
|
||||
@@ -44,3 +46,30 @@ export function getPiiFreeBooking(booking: {
|
||||
// .... Add all other props here that we don't want to be logged. It prevents those properties from being logged accidentally
|
||||
};
|
||||
}
|
||||
|
||||
export function getPiiFreeCredential(credential: Partial<Credential>) {
|
||||
return {
|
||||
id: credential.id,
|
||||
invalid: credential.invalid,
|
||||
appId: credential.appId,
|
||||
userId: credential.userId,
|
||||
type: credential.type,
|
||||
teamId: credential.teamId,
|
||||
/**
|
||||
* Let's just get a boolean value for PII sensitive fields so that we atleast know if it's present or not
|
||||
*/
|
||||
key: !!credential.key,
|
||||
};
|
||||
}
|
||||
|
||||
export function getPiiFreeSelectedCalendar(selectedCalendar: Partial<SelectedCalendar>) {
|
||||
return {
|
||||
integration: selectedCalendar.integration,
|
||||
userId: selectedCalendar.userId,
|
||||
/**
|
||||
* Let's just get a boolean value for PII sensitive fields so that we atleast know if it's present or not
|
||||
*/
|
||||
externalId: !!selectedCalendar.externalId,
|
||||
credentialId: !!selectedCalendar.credentialId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export function safeStringify(obj: unknown) {
|
||||
try {
|
||||
// Avoid crashing on circular references
|
||||
return JSON.stringify(obj);
|
||||
} catch (e) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user