fix(calendar): sync Cal.diy iCalUID events (#29580)
Co-authored-by: Kartik <103111467+kartik-212004@users.noreply.github.com>
This commit is contained in:
@@ -1,14 +1,25 @@
|
||||
import type { CreateRegularBookingData } from "@calcom/features/bookings/lib/dto/types";
|
||||
import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService";
|
||||
import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking";
|
||||
import type { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
||||
import type { CalendarSubscriptionEventItem } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionPort.interface";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import type { SelectedCalendar } from "@calcom/prisma/client";
|
||||
import { metrics } from "@sentry/nextjs";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["CalendarSyncService"] });
|
||||
const CAL_MANAGED_ICAL_UID_SUFFIXES: ReadonlySet<string> = new Set(
|
||||
["cal.com", "cal.diy", APP_NAME].map((suffix) => suffix.toLowerCase())
|
||||
);
|
||||
|
||||
const isCalManagedICalUID = (iCalUID?: string | null): boolean => {
|
||||
const suffix = iCalUID?.split("@").at(-1)?.toLowerCase();
|
||||
if (!suffix) return false;
|
||||
|
||||
return CAL_MANAGED_ICAL_UID_SUFFIXES.has(suffix);
|
||||
};
|
||||
|
||||
/**
|
||||
* Service to handle synchronization of calendar events.
|
||||
@@ -42,10 +53,7 @@ export class CalendarSyncService {
|
||||
},
|
||||
});
|
||||
|
||||
// only process cal.com calendar events
|
||||
const calEvents = calendarSubscriptionEvents.filter((e) =>
|
||||
e.iCalUID?.toLowerCase()?.endsWith("@cal.com")
|
||||
);
|
||||
const calEvents = calendarSubscriptionEvents.filter((e) => isCalManagedICalUID(e.iCalUID));
|
||||
|
||||
metrics.distribution("calendar.sync.handleEvents.events_count", calEvents.length, {
|
||||
attributes: {
|
||||
|
||||
+28
-7
@@ -1,9 +1,7 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { CalendarSubscriptionEventItem } from "@calcom/features/calendar-subscription/lib/CalendarSubscriptionPort.interface";
|
||||
import type { BookingRepository } from "@calcom/lib/server/repository/booking";
|
||||
import type { SelectedCalendar } from "@calcom/prisma/client";
|
||||
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { CalendarSyncService } from "../CalendarSyncService";
|
||||
|
||||
const { mockHandleCancelBooking, mockCreateBooking } = vi.hoisted(() => ({
|
||||
@@ -206,6 +204,21 @@ describe("CalendarSyncService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should handle default Cal.diy iCalUID", async () => {
|
||||
const eventWithCalDiyUID: CalendarSubscriptionEventItem = {
|
||||
...mockCalComEvent,
|
||||
iCalUID: "test-booking-uid@Cal.diy",
|
||||
};
|
||||
|
||||
mockBookingRepository.findBookingByUidWithEventType = vi.fn().mockResolvedValue(mockBooking);
|
||||
|
||||
await service.handleEvents(mockSelectedCalendar, [eventWithCalDiyUID]);
|
||||
|
||||
expect(mockBookingRepository.findBookingByUidWithEventType).toHaveBeenCalledWith({
|
||||
bookingUid: "test-booking-uid",
|
||||
});
|
||||
});
|
||||
|
||||
test("should handle events with null iCalUID", async () => {
|
||||
const eventWithNullUID: CalendarSubscriptionEventItem = {
|
||||
...mockCalComEvent,
|
||||
@@ -279,7 +292,9 @@ describe("CalendarSyncService", () => {
|
||||
mockHandleCancelBooking.mockRejectedValue(new Error("Cancellation failed"));
|
||||
|
||||
// Should not throw - errors are caught and logged
|
||||
await expect(service.cancelBooking(mockCancelledEvent, mockSelectedCalendar.userId)).resolves.not.toThrow();
|
||||
await expect(
|
||||
service.cancelBooking(mockCancelledEvent, mockSelectedCalendar.userId)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
expect(mockBookingRepository.findBookingByUidWithEventType).toHaveBeenCalled();
|
||||
expect(mockHandleCancelBooking).toHaveBeenCalled();
|
||||
@@ -290,7 +305,9 @@ describe("CalendarSyncService", () => {
|
||||
.fn()
|
||||
.mockRejectedValue(new Error("DB connection failed"));
|
||||
|
||||
await expect(service.cancelBooking(mockCancelledEvent, mockSelectedCalendar.userId)).resolves.not.toThrow();
|
||||
await expect(
|
||||
service.cancelBooking(mockCancelledEvent, mockSelectedCalendar.userId)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
expect(mockBookingRepository.findBookingByUidWithEventType).toHaveBeenCalled();
|
||||
expect(mockHandleCancelBooking).not.toHaveBeenCalled();
|
||||
@@ -489,7 +506,9 @@ describe("CalendarSyncService", () => {
|
||||
mockCreateBooking.mockRejectedValue(new Error("Rescheduling failed"));
|
||||
|
||||
// Should not throw - errors are caught and logged
|
||||
await expect(service.rescheduleBooking(eventWithDifferentStart, mockSelectedCalendar.userId)).resolves.not.toThrow();
|
||||
await expect(
|
||||
service.rescheduleBooking(eventWithDifferentStart, mockSelectedCalendar.userId)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
expect(mockBookingRepository.findBookingByUidWithEventType).toHaveBeenCalled();
|
||||
expect(mockCreateBooking).toHaveBeenCalled();
|
||||
@@ -500,7 +519,9 @@ describe("CalendarSyncService", () => {
|
||||
.fn()
|
||||
.mockRejectedValue(new Error("DB connection failed"));
|
||||
|
||||
await expect(service.rescheduleBooking(mockCalComEvent, mockSelectedCalendar.userId)).resolves.not.toThrow();
|
||||
await expect(
|
||||
service.rescheduleBooking(mockCalComEvent, mockSelectedCalendar.userId)
|
||||
).resolves.not.toThrow();
|
||||
|
||||
expect(mockBookingRepository.findBookingByUidWithEventType).toHaveBeenCalled();
|
||||
expect(mockCreateBooking).not.toHaveBeenCalled();
|
||||
|
||||
Reference in New Issue
Block a user