Files
calendar/packages/features/calendars/lib/CalendarManager.test.ts
Benny JooGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
6287026638 fix: prevent 500 error when deleting calendar events with empty uid (#27500)
* fix: prevent 500 error when deleting calendar events with empty uid

This fix addresses a 500 error that occurs when trying to delete a Google
Calendar event with an empty event ID, which results in a malformed API URL.

Root cause: When calendar event creation fails or returns without an ID,
booking references were being created with empty uids. Later, when trying
to delete these events (e.g., during reschedule), the empty uid caused
a 404 from Google Calendar API which was then thrown as a 500 error.

Changes:
1. CalendarManager.deleteEvent: Added validation to skip deletion if
   bookingRefUid is empty, with appropriate error logging. This is a
   safety net for existing bad data in the database.

2. EventManager.create: Added filtering to exclude booking references
   with empty uids from being stored, with error logging to help
   diagnose the root cause of missing event IDs.

3. EventManager.updateLocation: Same filtering applied for consistency.

Co-Authored-By: [email protected] <[email protected]>

* fix: revert EventManager filtering to preserve existing behavior

The test expects that booking references with empty uids are still created
when calendar event creation fails. This is intentional behavior to track
failed calendar syncs. The CalendarManager.deleteEvent fix handles the
deletion case gracefully by skipping the API call when uid is empty.

Co-Authored-By: [email protected] <[email protected]>

* fix: throw ErrorWithCode instead of silently returning when bookingRefUid is empty

Co-Authored-By: [email protected] <[email protected]>

* fix: validate uid before calling calendar.deleteEvent to prevent 500 error

- Add uid check in lastAttendeeDeleteBooking.ts before calling calendar.deleteEvent
- Add uid check in EventManager.updateAllCalendarEvents before calling calendar.deleteEvent
- Revert CalendarManager.deleteEvent changes (validation at call sites is the proper fix)

Co-Authored-By: [email protected] <[email protected]>

* fix: add error logging safeguard in CalendarManager.deleteEvent for empty bookingRefUid

Co-Authored-By: [email protected] <[email protected]>

* fix: move bookingRefUid check before getCalendar call

Co-Authored-By: [email protected] <[email protected]>

* fix: prevent storing booking references with empty uid when calendar event creation fails

Root cause fix: Change the check from 'if (createdEvent)' to 'if (createdEvent.createdEvent)'
in createAllCalendarEvents to prevent failed calendar events from being added to results.

This prevents empty uids from being stored in the database in the first place, which
was causing 500 errors when trying to delete non-existent calendar events later.

Co-Authored-By: [email protected] <[email protected]>

* test: update test to expect no calendar reference when calendar event creation fails

The root cause fix changes behavior so that failed calendar events no longer
create booking references with empty uids. This test now expects an empty
references array when calendar event creation fails, which is the correct
behavior that prevents 500 errors later when trying to delete non-existent
calendar events.

Co-Authored-By: [email protected] <[email protected]>

* refactor: use createdEvent.success instead of createdEvent.createdEvent

Using success is more semantically clear since it explicitly checks the
success status rather than checking if the nested object exists.

Co-Authored-By: [email protected] <[email protected]>

* refactor: use optional chaining for createdEvent?.success

Using optional chaining is safer in case createdEvent is somehow undefined.

Co-Authored-By: [email protected] <[email protected]>

* revert: remove root cause fix to preserve backfilling script compatibility

The root cause fix (checking createdEvent?.success instead of createdEvent)
would break the backfilling script that relies on booking references with
empty uids to identify which bookings need backfilling.

This PR now only includes defensive checks to prevent 500 errors when
deleting calendar events with empty uids.

Co-Authored-By: [email protected] <[email protected]>

* revert

* fix

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-09 08:42:14 -03:00

602 lines
18 KiB
TypeScript

import { prisma } from "@calcom/prisma/__mocks__/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
deduplicateCredentialsBasedOnSelectedCalendars,
deleteEvent,
getCalendarCredentials,
processEvent,
} from "./CalendarManager";
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
vi.mock("@calcom/prisma", () => ({
prisma,
}));
vi.mock("@calcom/app-store/_utils/getCalendar", () => ({
getCalendar: vi.fn(),
}));
vi.mock("@calcom/lib/constants", () => ({
ORGANIZER_EMAIL_EXEMPT_DOMAINS: "",
IS_PRODUCTION: false,
}));
vi.mock("@calcom/app-store/locations", () => ({
MeetLocationType: "integrations:google:meet",
}));
vi.mock("@calcom/lib/CalEventParser", () => ({
getRichDescription: vi.fn(() => "Test description"),
}));
function buildCredential(data: {
type: string;
appId: string;
id: number;
delegatedToId: string | null;
user: { email: string } | null;
}) {
return {
...data,
teamId: null,
invalid: false,
key: { access_token: "DONT_MATTER" },
userId: 10000,
delegatedTo: {
serviceAccountKey: {
client_email: "DONT_MATTER",
tenant_id: "DONT_MATTER",
client_id: "DONT_MATTER",
private_key: "DONT_MATTER",
},
},
};
}
function buildCalendarEvent(overrides = {}) {
return {
type: "test-event",
title: "Test Event",
startTime: "2024-01-01T10:00:00Z",
endTime: "2024-01-01T11:00:00Z",
organizer: {
name: "Organizer",
email: "[email protected]",
timeZone: "UTC",
language: { translate: (x: string) => x, locale: "en" },
},
attendees: [
{
name: "Attendee 1",
email: "[email protected]",
timeZone: "UTC",
language: { translate: (x: string) => x, locale: "en" },
},
{
name: "Attendee 2",
email: "[email protected]",
timeZone: "UTC",
language: { translate: (x: string) => x, locale: "en" },
},
],
destinationCalendar: null,
hideOrganizerEmail: false,
location: null,
...overrides,
};
}
describe("CalendarManager tests", () => {
describe("fn: processEvent", () => {
it("should clear attendees when hideOrganizerEmail is true and no Zoho Calendar destination", () => {
const calEvent = buildCalendarEvent({
hideOrganizerEmail: true,
destinationCalendar: [
{
integration: "google_calendar",
externalId: "calendar-1",
},
],
});
const result = processEvent(calEvent as any);
expect(result.attendees).toEqual([]);
});
it("should NOT clear attendees when hideOrganizerEmail is true and destination is Zoho Calendar", () => {
const calEvent = buildCalendarEvent({
hideOrganizerEmail: true,
destinationCalendar: [
{
integration: "zoho_calendar",
externalId: "calendar-1",
},
],
});
const result = processEvent(calEvent as any);
// Zoho Calendar requires at least one attendee, so attendees should NOT be cleared
expect(result.attendees).toHaveLength(2);
expect(result.attendees[0].email).toBe("[email protected]");
expect(result.attendees[1].email).toBe("[email protected]");
});
it("should NOT clear attendees when hideOrganizerEmail is true and one of multiple destinations is Zoho Calendar", () => {
const calEvent = buildCalendarEvent({
hideOrganizerEmail: true,
destinationCalendar: [
{
integration: "google_calendar",
externalId: "google-calendar-1",
},
{
integration: "zoho_calendar",
externalId: "zoho-calendar-1",
},
],
});
const result = processEvent(calEvent as any);
// Zoho Calendar is in the list, so attendees should NOT be cleared
expect(result.attendees).toHaveLength(2);
});
it("should NOT clear attendees when hideOrganizerEmail is false", () => {
const calEvent = buildCalendarEvent({
hideOrganizerEmail: false,
destinationCalendar: [
{
integration: "google_calendar",
externalId: "calendar-1",
},
],
});
const result = processEvent(calEvent as any);
expect(result.attendees).toHaveLength(2);
});
it("should NOT clear attendees when location is MeetLocationType even with hideOrganizerEmail true", () => {
const calEvent = buildCalendarEvent({
hideOrganizerEmail: true,
location: "integrations:google:meet",
destinationCalendar: [
{
integration: "google_calendar",
externalId: "calendar-1",
},
],
});
const result = processEvent(calEvent as any);
expect(result.attendees).toHaveLength(2);
});
it("should handle null destinationCalendar with hideOrganizerEmail true", () => {
const calEvent = buildCalendarEvent({
hideOrganizerEmail: true,
destinationCalendar: null,
});
const result = processEvent(calEvent as any);
expect(result.attendees).toEqual([]);
});
it("should handle empty destinationCalendar array with hideOrganizerEmail true", () => {
const calEvent = buildCalendarEvent({
hideOrganizerEmail: true,
destinationCalendar: [],
});
const result = processEvent(calEvent as any);
expect(result.attendees).toEqual([]);
});
it("should include calendarDescription from getRichDescription", () => {
const calEvent = buildCalendarEvent();
const result = processEvent(calEvent as any);
expect(result.calendarDescription).toBe("Test description");
});
it("should clear responses for seatsPerTimeSlot events", () => {
const calEvent = buildCalendarEvent({
seatsPerTimeSlot: 5,
responses: { field1: { label: "Field 1", value: "test" } },
userFieldsResponses: { field1: { label: "Field 1", value: "test" } },
additionalNotes: "Test notes",
customInputs: { input1: "value1" },
});
const result = processEvent(calEvent as any);
expect(result.responses).toBeNull();
expect(result.userFieldsResponses).toBeNull();
expect(result.additionalNotes).toBeNull();
expect(result.customInputs).toBeNull();
});
});
describe("fn: getCalendarCredentials", () => {
it("should only return credentials for calendar apps", async () => {
const googleCalendarCredentials = {
id: "1",
appId: "google-calendar",
type: "google_calendar",
userId: "3",
key: {
access_token: "google_calendar_key",
},
invalid: false,
delegatedTo: null,
};
const credentials = [
googleCalendarCredentials,
{
id: "2",
appId: "office365-video",
type: "office365_video",
userId: "4",
key: {
access_token: "office365_video_key",
},
invalid: false,
},
];
const calendarCredentials = getCalendarCredentials(credentials);
expect(calendarCredentials).toHaveLength(1);
expect(calendarCredentials[0].credential).toEqual(googleCalendarCredentials);
});
});
describe("fn: deduplicateCredentialsBasedOnSelectedCalendars", () => {
it("should remove a regular credential for which a delegation credential exists i.e. both are fetching events for same externalId", () => {
const calcomUser = {
email: "[email protected]",
};
const externalIdSameAsCalcomUserEmail = calcomUser.email;
const externalIdDifferentFromCalcomUserEmail = "[email protected]";
// Delegation Credential(Calendar)
const delegationCredentialCalendarForCalcomEmail = buildCredential({
type: "google_calendar",
appId: "google-calendar",
id: -1,
delegatedToId: "45fa8f04-e891-417b-98e1-abf48e8ae18a",
user: calcomUser,
});
// Delegation Credential(Conferencing)
const delegationCredentialConferencingForCalcomEmail = buildCredential({
type: "google_video",
appId: "google-meet",
id: -1,
delegatedToId: "45fa8f04-e891-417b-98e1-abf48e8ae18a",
user: calcomUser,
});
// Regular Credential for [email protected]
const regularCredentialForCalcomEmail = buildCredential({
id: 2,
appId: "google-calendar",
type: "google_calendar",
delegatedToId: null,
user: calcomUser,
});
// Regular Credential for [email protected]
const regularCredentialForSomeOtherEmail = buildCredential({
id: 3,
appId: "google-calendar",
type: "google_calendar",
delegatedToId: null,
user: calcomUser,
});
const credentials = [
delegationCredentialCalendarForCalcomEmail,
delegationCredentialConferencingForCalcomEmail,
// This one is duplicate
regularCredentialForCalcomEmail,
// Regular Credential for [email protected]
regularCredentialForSomeOtherEmail,
];
const selectedCalendars = [
{
id: "62f4f3b7-c65b-4199-8052-25b91ee25ff2",
userId: 23,
integration: "google_calendar",
externalId: externalIdDifferentFromCalcomUserEmail,
credentialId: regularCredentialForSomeOtherEmail.id,
delegationCredentialId: null,
eventTypeId: null,
},
{
id: "418e1bd0-7bde-4ea6-b03a-b2b6de6af497",
userId: 23,
integration: "google_calendar",
externalId: externalIdSameAsCalcomUserEmail,
credentialId: regularCredentialForCalcomEmail.id,
delegationCredentialId: null,
eventTypeId: null,
},
];
const uniqueCredentials = deduplicateCredentialsBasedOnSelectedCalendars({
credentials,
selectedCalendars,
});
expect(uniqueCredentials).toHaveLength(3);
expect(uniqueCredentials).toEqual([
delegationCredentialCalendarForCalcomEmail,
delegationCredentialConferencingForCalcomEmail,
regularCredentialForSomeOtherEmail,
]);
});
it("should not remove a regular credential for which a delegation credential exists i.e. both are fetching events for same externalId if integration is different", () => {
const calcomUser = {
email: "[email protected]",
};
const externalIdSameAsCalcomUserEmail = calcomUser.email;
const externalIdDifferentFromCalcomUserEmail = "[email protected]";
// Delegation Credential(Calendar) - wrong integration type
const delegationCredentialCalendarForCalcomEmail = buildCredential({
type: "google_calendar_wrong_type",
appId: "google-calendar",
id: -1,
delegatedToId: "45fa8f04-e891-417b-98e1-abf48e8ae18a",
user: calcomUser,
});
// Delegation Credential(Conferencing)
const delegationCredentialConferencingForCalcomEmail = buildCredential({
type: "google_video",
appId: "google-meet",
id: -1,
delegatedToId: "45fa8f04-e891-417b-98e1-abf48e8ae18a",
user: calcomUser,
});
// Regular Credential for [email protected]
const regularCredentialForCalcomEmail = buildCredential({
id: 2,
appId: "google-calendar",
type: "google_calendar",
delegatedToId: null,
user: calcomUser,
});
// Regular Credential for [email protected]
const regularCredentialForSomeOtherEmail = buildCredential({
id: 3,
appId: "google-calendar",
type: "google_calendar",
delegatedToId: null,
user: calcomUser,
});
const credentials = [
delegationCredentialCalendarForCalcomEmail,
delegationCredentialConferencingForCalcomEmail,
// This one won't be removed as integration is different
regularCredentialForCalcomEmail,
// Regular Credential for [email protected]
regularCredentialForSomeOtherEmail,
];
const selectedCalendars = [
{
id: "62f4f3b7-c65b-4199-8052-25b91ee25ff2",
userId: 23,
integration: "google_calendar",
externalId: externalIdDifferentFromCalcomUserEmail,
credentialId: regularCredentialForSomeOtherEmail.id,
delegationCredentialId: null,
eventTypeId: null,
},
{
id: "418e1bd0-7bde-4ea6-b03a-b2b6de6af497",
userId: 23,
integration: "google_calendar",
externalId: externalIdSameAsCalcomUserEmail,
credentialId: regularCredentialForCalcomEmail.id,
delegationCredentialId: null,
eventTypeId: null,
},
];
const uniqueCredentials = deduplicateCredentialsBasedOnSelectedCalendars({
credentials,
selectedCalendars,
});
expect(uniqueCredentials).toHaveLength(4);
expect(uniqueCredentials).toEqual([
delegationCredentialCalendarForCalcomEmail,
delegationCredentialConferencingForCalcomEmail,
regularCredentialForCalcomEmail,
regularCredentialForSomeOtherEmail,
]);
});
it("should return empty array when credentials array is empty", () => {
const selectedCalendars = [
{
id: "calendar-1",
userId: 23,
integration: "google_calendar",
externalId: "[email protected]",
credentialId: 1,
delegationCredentialId: null,
eventTypeId: null,
},
];
const uniqueCredentials = deduplicateCredentialsBasedOnSelectedCalendars({
credentials: [],
selectedCalendars,
});
expect(uniqueCredentials).toHaveLength(0);
});
it("should return original credentials when user email is not present", () => {
const credentialsWithoutUserEmail = [
buildCredential({
type: "google_calendar",
appId: "google-calendar",
id: 1,
delegatedToId: null,
user: null,
}),
buildCredential({
type: "google_calendar",
appId: "google-calendar",
id: -1,
delegatedToId: "delegation-1",
user: null,
}),
];
const selectedCalendars = [
{
id: "calendar-1",
userId: 23,
integration: "google_calendar",
externalId: "[email protected]",
credentialId: 1,
delegationCredentialId: null,
eventTypeId: null,
},
];
const uniqueCredentials = deduplicateCredentialsBasedOnSelectedCalendars({
credentials: credentialsWithoutUserEmail,
selectedCalendars,
});
expect(uniqueCredentials).toEqual(credentialsWithoutUserEmail);
});
it("should return original credentials when no delegation credentials exist", () => {
const regularCredentials = [
buildCredential({
type: "google_calendar",
appId: "google-calendar",
id: 1,
delegatedToId: null,
user: { email: "[email protected]" },
}),
buildCredential({
type: "google_calendar",
appId: "google-calendar",
id: 2,
delegatedToId: null,
user: { email: "[email protected]" },
}),
];
const selectedCalendars = [
{
id: "calendar-1",
userId: 23,
integration: "google_calendar",
externalId: "[email protected]",
credentialId: 1,
delegationCredentialId: null,
eventTypeId: null,
},
];
const uniqueCredentials = deduplicateCredentialsBasedOnSelectedCalendars({
credentials: regularCredentials,
selectedCalendars,
});
expect(uniqueCredentials).toEqual(regularCredentials);
});
it("should return original credentials when no matching selected calendars exist", () => {
const credentials = [
buildCredential({
type: "google_calendar",
appId: "google-calendar",
id: 1,
delegatedToId: "delegation-1",
user: { email: "[email protected]" },
}),
buildCredential({
type: "google_calendar",
appId: "google-calendar",
id: 2,
delegatedToId: null,
user: { email: "[email protected]" },
}),
];
const selectedCalendars = [
{
id: "calendar-1",
userId: 23,
integration: "google_calendar",
externalId: "[email protected]", // Different email than the credentials
credentialId: 1,
delegationCredentialId: null,
eventTypeId: null,
},
];
const uniqueCredentials = deduplicateCredentialsBasedOnSelectedCalendars({
credentials,
selectedCalendars,
});
expect(uniqueCredentials).toEqual(credentials);
});
});
describe("fn: deleteEvent", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should return early when bookingRefUid is empty string", async () => {
vi.mocked(getCalendar).mockResolvedValue({ deleteEvent: vi.fn() } as any);
const credential = {
id: 1,
type: "google_calendar",
key: { access_token: "test_token" },
encryptedKey: null,
userId: 1,
user: { email: "[email protected]" },
teamId: null,
appId: "google-calendar",
invalid: false,
delegationCredentialId: null,
delegatedTo: null,
};
const result = await deleteEvent({
credential,
bookingRefUid: "",
event: buildCalendarEvent(),
externalCalendarId: "calendar-id",
});
expect(result).toEqual({});
});
});
});