import { describe, expect, it, vi, beforeEach } from "vitest"; import type { ICalendarCacheEventRepository } from "@calcom/features/calendar-subscription/lib/cache/CalendarCacheEventRepository.interface"; import type { Calendar, CalendarEvent, CalendarServiceEvent, EventBusyDate, IntegrationCalendar, NewCalendarEventType, } from "@calcom/types/Calendar"; import { CalendarCacheWrapper } from "../CalendarCacheWrapper"; describe("CalendarCacheWrapper", () => { let mockOriginalCalendar: Calendar; let mockRepository: ICalendarCacheEventRepository; let wrapper: CalendarCacheWrapper; const createMockCalendar = (): Calendar => ({ getAvailability: vi.fn().mockResolvedValue([]), getAvailabilityWithTimeZones: vi.fn().mockResolvedValue([]), createEvent: vi.fn(), updateEvent: vi.fn(), deleteEvent: vi.fn(), listCalendars: vi.fn(), }); const createMockRepository = (): ICalendarCacheEventRepository => ({ findAllBySelectedCalendarIdsBetween: vi.fn().mockResolvedValue([]), }); beforeEach(() => { mockOriginalCalendar = createMockCalendar(); mockRepository = createMockRepository(); wrapper = new CalendarCacheWrapper({ originalCalendar: mockOriginalCalendar, calendarCacheEventRepository: mockRepository, }); }); describe("getAvailability", () => { it("should return empty array when no calendars provided", async () => { const result = await wrapper.getAvailability({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars: [], mode: "slots", }); expect(result).toEqual([]); expect(mockRepository.findAllBySelectedCalendarIdsBetween).not.toHaveBeenCalled(); expect(mockOriginalCalendar.getAvailability).not.toHaveBeenCalled(); }); it("should fetch only from cache when all calendars have syncToken and syncSubscribedAt", async () => { const selectedCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", syncToken: "token-1", syncSubscribedAt: new Date(), }, { id: "cal-2", externalId: "ext-2", integration: "google_calendar", syncToken: "token-2", syncSubscribedAt: new Date(), }, ]; const cachedEvents: EventBusyDate[] = [ { start: new Date("2025-01-01T10:00:00Z"), end: new Date("2025-01-01T11:00:00Z") }, { start: new Date("2025-01-01T14:00:00Z"), end: new Date("2025-01-01T15:00:00Z") }, ]; vi.mocked(mockRepository.findAllBySelectedCalendarIdsBetween).mockResolvedValue(cachedEvents); const result = await wrapper.getAvailability({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", }); expect(result).toEqual(cachedEvents); expect(mockRepository.findAllBySelectedCalendarIdsBetween).toHaveBeenCalledWith( ["cal-1", "cal-2"], new Date("2025-01-01"), new Date("2025-01-02") ); expect(mockOriginalCalendar.getAvailability).not.toHaveBeenCalled(); }); it("should fetch only from original calendar when all calendars lack syncToken or syncSubscribedAt", async () => { const selectedCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", syncToken: null, syncSubscribedAt: null, }, { id: "cal-2", externalId: "ext-2", integration: "google_calendar", syncToken: "token-2", syncSubscribedAt: null, // Missing syncSubscribedAt }, ]; const originalEvents: EventBusyDate[] = [ { start: new Date("2025-01-01T09:00:00Z"), end: new Date("2025-01-01T10:00:00Z") }, ]; vi.mocked(mockOriginalCalendar.getAvailability).mockResolvedValue(originalEvents); const result = await wrapper.getAvailability({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", }); expect(result).toEqual(originalEvents); expect(mockRepository.findAllBySelectedCalendarIdsBetween).not.toHaveBeenCalled(); expect(mockOriginalCalendar.getAvailability).toHaveBeenCalledWith({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", fallbackToPrimary: undefined, }); }); it("should fetch from both cache and original calendar when calendars are mixed", async () => { const selectedCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", syncToken: "token-1", syncSubscribedAt: new Date(), }, { id: "cal-2", externalId: "ext-2", integration: "google_calendar", syncToken: null, syncSubscribedAt: null, }, ]; const cachedEvents: EventBusyDate[] = [ { start: new Date("2025-01-01T10:00:00Z"), end: new Date("2025-01-01T11:00:00Z") }, ]; const originalEvents: EventBusyDate[] = [ { start: new Date("2025-01-01T14:00:00Z"), end: new Date("2025-01-01T15:00:00Z") }, ]; vi.mocked(mockRepository.findAllBySelectedCalendarIdsBetween).mockResolvedValue(cachedEvents); vi.mocked(mockOriginalCalendar.getAvailability).mockResolvedValue(originalEvents); const result = await wrapper.getAvailability({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", }); expect(result).toEqual([...cachedEvents, ...originalEvents]); expect(mockRepository.findAllBySelectedCalendarIdsBetween).toHaveBeenCalledWith( ["cal-1"], new Date("2025-01-01"), new Date("2025-01-02") ); expect(mockOriginalCalendar.getAvailability).toHaveBeenCalledWith({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars: [selectedCalendars[1]], mode: "slots", fallbackToPrimary: undefined, }); }); it("should filter out calendars without id when fetching from cache", async () => { const selectedCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", syncToken: "token-1", syncSubscribedAt: new Date(), }, { id: null, externalId: "ext-2", integration: "google_calendar", syncToken: "token-2", syncSubscribedAt: new Date(), }, ]; await wrapper.getAvailability({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", }); expect(mockRepository.findAllBySelectedCalendarIdsBetween).toHaveBeenCalledWith( ["cal-1"], // Only cal-1 has a valid id new Date("2025-01-01"), new Date("2025-01-02") ); }); }); describe("getAvailabilityWithTimeZones", () => { it("should return empty array when no calendars provided", async () => { const result = await wrapper.getAvailabilityWithTimeZones({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars: [], mode: "slots", }); expect(result).toEqual([]); expect(mockRepository.findAllBySelectedCalendarIdsBetween).not.toHaveBeenCalled(); expect(mockOriginalCalendar.getAvailabilityWithTimeZones).not.toHaveBeenCalled(); }); it("should fetch only from cache and apply UTC default timezone when all calendars are synced", async () => { const selectedCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", syncToken: "token-1", syncSubscribedAt: new Date(), }, ]; const cachedEvents: EventBusyDate[] = [ { start: new Date("2025-01-01T10:00:00Z"), end: new Date("2025-01-01T11:00:00Z"), timeZone: "America/New_York", }, { start: new Date("2025-01-01T14:00:00Z"), end: new Date("2025-01-01T15:00:00Z"), timeZone: null, // Should default to UTC }, ]; vi.mocked(mockRepository.findAllBySelectedCalendarIdsBetween).mockResolvedValue(cachedEvents); const result = await wrapper.getAvailabilityWithTimeZones({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", }); expect(result).toEqual([ { start: cachedEvents[0].start, end: cachedEvents[0].end, timeZone: "America/New_York", }, { start: cachedEvents[1].start, end: cachedEvents[1].end, timeZone: "UTC", // Default applied }, ]); expect(mockOriginalCalendar.getAvailabilityWithTimeZones).not.toHaveBeenCalled(); }); it("should fetch only from original calendar when all calendars are unsynced", async () => { const selectedCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", syncToken: null, syncSubscribedAt: null, }, ]; const originalEvents = [ { start: new Date("2025-01-01T09:00:00Z"), end: new Date("2025-01-01T10:00:00Z"), timeZone: "Europe/London", }, ]; vi.mocked(mockOriginalCalendar.getAvailabilityWithTimeZones).mockResolvedValue(originalEvents); const result = await wrapper.getAvailabilityWithTimeZones({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", }); expect(result).toEqual(originalEvents); expect(mockRepository.findAllBySelectedCalendarIdsBetween).not.toHaveBeenCalled(); expect(mockOriginalCalendar.getAvailabilityWithTimeZones).toHaveBeenCalledWith({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", fallbackToPrimary: undefined, }); }); it("should fetch from both cache and original calendar when calendars are mixed", async () => { const selectedCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", syncToken: "token-1", syncSubscribedAt: new Date(), }, { id: "cal-2", externalId: "ext-2", integration: "google_calendar", syncToken: null, syncSubscribedAt: null, }, ]; const cachedEvents: EventBusyDate[] = [ { start: new Date("2025-01-01T10:00:00Z"), end: new Date("2025-01-01T11:00:00Z"), timeZone: "America/New_York", }, ]; const originalEvents = [ { start: new Date("2025-01-01T14:00:00Z"), end: new Date("2025-01-01T15:00:00Z"), timeZone: "Europe/London", }, ]; vi.mocked(mockRepository.findAllBySelectedCalendarIdsBetween).mockResolvedValue(cachedEvents); vi.mocked(mockOriginalCalendar.getAvailabilityWithTimeZones).mockResolvedValue(originalEvents); const result = await wrapper.getAvailabilityWithTimeZones({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", }); expect(result).toEqual([ { start: cachedEvents[0].start, end: cachedEvents[0].end, timeZone: "America/New_York", }, ...originalEvents, ]); }); it("should handle when originalCalendar.getAvailabilityWithTimeZones is undefined", async () => { const selectedCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", syncToken: "token-1", syncSubscribedAt: new Date(), }, { id: "cal-2", externalId: "ext-2", integration: "google_calendar", syncToken: null, syncSubscribedAt: null, }, ]; const cachedEvents: EventBusyDate[] = [ { start: new Date("2025-01-01T10:00:00Z"), end: new Date("2025-01-01T11:00:00Z"), timeZone: "UTC", }, ]; vi.mocked(mockRepository.findAllBySelectedCalendarIdsBetween).mockResolvedValue(cachedEvents); mockOriginalCalendar.getAvailabilityWithTimeZones = undefined; const result = await wrapper.getAvailabilityWithTimeZones({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", }); expect(result).toEqual([ { start: cachedEvents[0].start, end: cachedEvents[0].end, timeZone: "UTC", }, ]); }); it("should handle when originalCalendar.getAvailabilityWithTimeZones returns empty array", async () => { const selectedCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", syncToken: null, syncSubscribedAt: null, }, ]; vi.mocked(mockOriginalCalendar.getAvailabilityWithTimeZones).mockResolvedValue([]); const result = await wrapper.getAvailabilityWithTimeZones({ dateFrom: "2025-01-01", dateTo: "2025-01-02", selectedCalendars, mode: "slots", }); expect(result).toEqual([]); }); }); describe("other Calendar methods", () => { it("should delegate createEvent to original calendar", async () => { const mockEvent: CalendarServiceEvent = { title: "Test Event", startTime: "2025-01-01T10:00:00Z", endTime: "2025-01-01T11:00:00Z", }; const mockResult: NewCalendarEventType = { uid: "test-uid", id: "test-id", type: "test-type", password: "", url: "", additionalInfo: {}, }; vi.mocked(mockOriginalCalendar.createEvent).mockResolvedValue(mockResult); const result = await wrapper.createEvent(mockEvent, 123, "cal-id"); expect(result).toBe(mockResult); expect(mockOriginalCalendar.createEvent).toHaveBeenCalledWith(mockEvent, 123, "cal-id"); }); it("should delegate updateEvent to original calendar", async () => { const mockEvent: CalendarServiceEvent = { title: "Updated Event", startTime: "2025-01-01T10:00:00Z", endTime: "2025-01-01T11:00:00Z", }; const mockResult: NewCalendarEventType = { uid: "test-uid", id: "test-id", type: "test-type", password: "", url: "", additionalInfo: {}, }; vi.mocked(mockOriginalCalendar.updateEvent).mockResolvedValue(mockResult); const result = await wrapper.updateEvent("uid-123", mockEvent, "cal-id"); expect(result).toBe(mockResult); expect(mockOriginalCalendar.updateEvent).toHaveBeenCalledWith("uid-123", mockEvent, "cal-id"); }); it("should delegate deleteEvent to original calendar", async () => { const mockEvent: CalendarEvent = { uid: "test-uid", title: "Test Event", startTime: "2025-01-01T10:00:00Z", endTime: "2025-01-01T11:00:00Z", }; vi.mocked(mockOriginalCalendar.deleteEvent).mockResolvedValue(undefined); await wrapper.deleteEvent("uid-123", mockEvent, "cal-id"); expect(mockOriginalCalendar.deleteEvent).toHaveBeenCalledWith("uid-123", mockEvent, "cal-id"); }); it("should delegate listCalendars to original calendar", async () => { const mockCalendars: IntegrationCalendar[] = [ { id: "cal-1", externalId: "ext-1", integration: "google_calendar", }, ]; vi.mocked(mockOriginalCalendar.listCalendars).mockResolvedValue(mockCalendars); const result = await wrapper.listCalendars(); expect(result).toBe(mockCalendars); expect(mockOriginalCalendar.listCalendars).toHaveBeenCalled(); }); }); });