1314 lines
42 KiB
TypeScript
1314 lines
42 KiB
TypeScript
import oAuthManagerMock, {
|
|
defaultMockOAuthManager,
|
|
setFullMockOAuthManagerRequest,
|
|
} from "../../../tests/__mocks__/OAuthManager";
|
|
import "../__mocks__/features.repository";
|
|
import "../__mocks__/getGoogleAppKeys";
|
|
import {
|
|
adminMock,
|
|
calendarListMock,
|
|
calendarMock,
|
|
freebusyQueryMock,
|
|
setCredentialsMock,
|
|
setLastCreatedJWT,
|
|
setLastCreatedOAuth2Client,
|
|
} from "../__mocks__/googleapis";
|
|
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
import "vitest-fetch-mock";
|
|
|
|
import process from "node:process";
|
|
import { MeetLocationType } from "@calcom/app-store/constants";
|
|
import logger from "@calcom/lib/logger";
|
|
import type { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential";
|
|
import BuildCalendarService, { createGoogleCalendarServiceWithGoogleType } from "../CalendarService";
|
|
import { createCredentialForCalendarService, createMockJWTInstance } from "./utils";
|
|
|
|
const log = logger.getSubLogger({ prefix: ["CalendarService.test"] });
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
setCredentialsMock.mockClear();
|
|
oAuthManagerMock.OAuthManager = defaultMockOAuthManager;
|
|
calendarMock.calendar_v3.Calendar.mockClear();
|
|
adminMock.admin_directory_v1.Admin.mockClear();
|
|
|
|
setLastCreatedJWT(null);
|
|
setLastCreatedOAuth2Client(null);
|
|
createMockJWTInstance({});
|
|
});
|
|
|
|
const mockCredential: CredentialForCalendarServiceWithEmail = {
|
|
id: 1,
|
|
userId: 1,
|
|
appId: "google-calendar",
|
|
type: "google_calendar",
|
|
key: {
|
|
access_token: "<INVALID_TOKEN>",
|
|
},
|
|
user: {
|
|
email: "user@example.com",
|
|
},
|
|
delegationCredentialId: null,
|
|
delegatedTo: null,
|
|
invalid: false,
|
|
teamId: null,
|
|
encryptedKey: null,
|
|
};
|
|
|
|
describe("getAvailability", () => {
|
|
test("returns availability for selected calendars", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
const mockedBusyTimes1 = [
|
|
{
|
|
start: "2024-01-01",
|
|
end: "2024-01-02",
|
|
},
|
|
];
|
|
const mockedBusyTimes2 = [
|
|
{
|
|
start: "2024-01-03",
|
|
end: "2024-01-04",
|
|
},
|
|
];
|
|
|
|
const mockedBusyTimes = [mockedBusyTimes1, mockedBusyTimes2];
|
|
calendarListMock.mockImplementation(() => {
|
|
return {
|
|
data: {
|
|
items: [
|
|
{
|
|
id: "calendar1@test.com",
|
|
},
|
|
{
|
|
id: "calendar2@test.com",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
});
|
|
// Mock Once so that the getAvailability call doesn't accidentally reuse this mock result
|
|
freebusyQueryMock.mockImplementation(({ requestBody }: { requestBody: any }) => {
|
|
const calendarsObject: any = {};
|
|
requestBody.items.forEach((item: any, index: number) => {
|
|
calendarsObject[item.id] = {
|
|
busy: mockedBusyTimes[index],
|
|
};
|
|
});
|
|
return {
|
|
data: {
|
|
calendars: calendarsObject,
|
|
},
|
|
};
|
|
});
|
|
|
|
const availabilityWithPrimaryAsFallback = await calendarService.getAvailability({
|
|
dateFrom: "2024-01-01",
|
|
dateTo: "2024-01-02",
|
|
selectedCalendars: [],
|
|
mode: "slots",
|
|
fallbackToPrimary: true,
|
|
});
|
|
|
|
expect(availabilityWithPrimaryAsFallback).toEqual(mockedBusyTimes1);
|
|
|
|
const availabilityWithAllCalendarsAsFallback = await calendarService.getAvailability({
|
|
dateFrom: "2024-01-01",
|
|
dateTo: "2024-01-02",
|
|
selectedCalendars: [],
|
|
mode: "slots",
|
|
fallbackToPrimary: false,
|
|
});
|
|
|
|
expect(availabilityWithAllCalendarsAsFallback).toEqual([...mockedBusyTimes1, ...mockedBusyTimes2]);
|
|
});
|
|
});
|
|
|
|
describe("getPrimaryCalendar", () => {
|
|
test("should fetch primary calendar using 'primary' keyword", async () => {
|
|
const calendarService = createGoogleCalendarServiceWithGoogleType(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
const mockPrimaryCalendar = {
|
|
id: "user@example.com",
|
|
summary: "Primary Calendar",
|
|
description: "Primary calendar for user@example.com",
|
|
timeZone: "America/New_York",
|
|
};
|
|
const calendarsGetMock = vi.fn().mockResolvedValue({
|
|
data: mockPrimaryCalendar,
|
|
});
|
|
calendarMock.calendar_v3.Calendar().calendars.get = calendarsGetMock;
|
|
const result = await calendarService.getPrimaryCalendar();
|
|
expect(calendarsGetMock).toHaveBeenCalledTimes(1);
|
|
expect(calendarsGetMock).toHaveBeenCalledWith({
|
|
calendarId: "primary",
|
|
});
|
|
expect(result).toEqual(mockPrimaryCalendar);
|
|
});
|
|
});
|
|
|
|
describe("Date Optimization Benchmarks", () => {
|
|
test("native Date calculations should be significantly faster than dayjs while producing identical results", async () => {
|
|
const dayjs = (await import("@calcom/dayjs")).default;
|
|
|
|
const testCases = [
|
|
{
|
|
dateFrom: "2024-01-01T00:00:00Z",
|
|
dateTo: "2024-01-31T00:00:00Z",
|
|
name: "30 days",
|
|
expectedDiff: 30,
|
|
},
|
|
{
|
|
dateFrom: "2024-01-01T00:00:00Z",
|
|
dateTo: "2024-03-31T00:00:00Z",
|
|
name: "90 days (API limit)",
|
|
expectedDiff: 90,
|
|
},
|
|
{
|
|
dateFrom: "2024-01-01T00:00:00Z",
|
|
dateTo: "2024-07-01T00:00:00Z",
|
|
name: "182 days (chunking required)",
|
|
expectedDiff: 182,
|
|
},
|
|
];
|
|
|
|
const iterations = 1000; // Reduced for test performance
|
|
|
|
for (const testCase of testCases) {
|
|
log.info(`Testing ${testCase.name}...`);
|
|
|
|
// Test correctness first
|
|
const dayjsDiff = dayjs(testCase.dateTo).diff(dayjs(testCase.dateFrom), "days");
|
|
const nativeDiff = Math.floor(
|
|
(new Date(testCase.dateTo).getTime() - new Date(testCase.dateFrom).getTime()) / (1000 * 60 * 60 * 24)
|
|
);
|
|
|
|
// Verify identical results
|
|
expect(nativeDiff).toBe(dayjsDiff);
|
|
expect(nativeDiff).toBe(testCase.expectedDiff);
|
|
|
|
// Performance test - dayjs approach
|
|
const dayjsStart = performance.now();
|
|
for (let i = 0; i < iterations; i++) {
|
|
const start = dayjs(testCase.dateFrom);
|
|
const end = dayjs(testCase.dateTo);
|
|
const diff = end.diff(start, "days");
|
|
}
|
|
const dayjsTime = performance.now() - dayjsStart;
|
|
|
|
// Performance test - native Date approach
|
|
const nativeStart = performance.now();
|
|
for (let i = 0; i < iterations; i++) {
|
|
const start = new Date(testCase.dateFrom);
|
|
const end = new Date(testCase.dateTo);
|
|
const diff = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
|
|
}
|
|
const nativeTime = performance.now() - nativeStart;
|
|
|
|
const speedupRatio = dayjsTime / nativeTime;
|
|
|
|
log.info(
|
|
`${testCase.name} - Dayjs: ${dayjsTime.toFixed(2)}ms, Native: ${nativeTime.toFixed(
|
|
2
|
|
)}ms, Speedup: ${speedupRatio.toFixed(1)}x`
|
|
);
|
|
|
|
if (!process.env.CI) {
|
|
const minSpeedup = 5; // Assert significant performance improvement (at least 5x faster)
|
|
expect(speedupRatio).toBeGreaterThan(minSpeedup);
|
|
}
|
|
}
|
|
});
|
|
|
|
test("chunking logic should produce identical results between dayjs and native Date implementations", async () => {
|
|
const dayjs = (await import("@calcom/dayjs")).default;
|
|
|
|
const testCases = [
|
|
{
|
|
dateFrom: "2024-01-01T00:00:00Z",
|
|
dateTo: "2024-04-01T00:00:00Z", // 91 days - requires chunking
|
|
name: "91 days (minimal chunking)",
|
|
},
|
|
{
|
|
dateFrom: "2024-01-01T00:00:00Z",
|
|
dateTo: "2024-07-01T00:00:00Z", // 182 days - multiple chunks
|
|
name: "182 days (multiple chunks)",
|
|
},
|
|
];
|
|
|
|
for (const testCase of testCases) {
|
|
const fromDate = new Date(testCase.dateFrom);
|
|
const toDate = new Date(testCase.dateTo);
|
|
const diff = Math.floor((toDate.getTime() - fromDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
|
|
// Only test cases that require chunking (> 90 days)
|
|
if (diff <= 90) continue;
|
|
|
|
// OLD WAY (dayjs-based chunking)
|
|
const originalStartDate = dayjs(testCase.dateFrom);
|
|
const originalEndDate = dayjs(testCase.dateTo);
|
|
const loopsNumber = Math.ceil(diff / 90);
|
|
let startDate = originalStartDate;
|
|
let endDate = originalStartDate.add(90, "days");
|
|
|
|
const oldChunks = [];
|
|
for (let i = 0; i < loopsNumber; i++) {
|
|
if (endDate.isAfter(originalEndDate)) endDate = originalEndDate;
|
|
|
|
oldChunks.push({
|
|
start: startDate.toISOString(),
|
|
end: endDate.toISOString(),
|
|
});
|
|
|
|
startDate = endDate.add(1, "minutes");
|
|
endDate = startDate.add(90, "days");
|
|
}
|
|
|
|
// NEW WAY (native Date-based chunking)
|
|
let currentStartTime = fromDate.getTime();
|
|
const originalEndTime = toDate.getTime();
|
|
const ninetyDaysMs = 90 * 24 * 60 * 60 * 1000;
|
|
const oneMinuteMs = 60 * 1000;
|
|
|
|
const newChunks = [];
|
|
for (let i = 0; i < loopsNumber; i++) {
|
|
let currentEndTime = currentStartTime + ninetyDaysMs;
|
|
|
|
if (currentEndTime > originalEndTime) {
|
|
currentEndTime = originalEndTime;
|
|
}
|
|
|
|
newChunks.push({
|
|
start: new Date(currentStartTime).toISOString(),
|
|
end: new Date(currentEndTime).toISOString(),
|
|
});
|
|
|
|
currentStartTime = currentEndTime + oneMinuteMs;
|
|
}
|
|
|
|
// Verify identical chunking results
|
|
expect(newChunks).toHaveLength(oldChunks.length);
|
|
|
|
for (let i = 0; i < oldChunks.length; i++) {
|
|
expect(newChunks[i].start).toBe(oldChunks[i].start);
|
|
expect(newChunks[i].end).toBe(oldChunks[i].end);
|
|
}
|
|
|
|
log.info(`${testCase.name} - Generated ${newChunks.length} identical chunks`);
|
|
}
|
|
});
|
|
|
|
test("date parsing should be consistent between dayjs and native Date for all expected input formats", async () => {
|
|
const dayjs = (await import("@calcom/dayjs")).default;
|
|
|
|
// Test various date formats that Google Calendar API might return
|
|
const testDates = [
|
|
"2024-01-01T00:00:00Z", // UTC
|
|
"2024-01-01T12:30:45.123Z", // UTC with milliseconds
|
|
"2024-01-01T00:00:00-08:00", // Timezone offset
|
|
"2024-01-01T00:00:00+05:30", // Positive timezone offset
|
|
"2024-12-31T23:59:59Z", // End of year
|
|
"2024-02-29T12:00:00Z", // Leap year date
|
|
];
|
|
|
|
for (const dateString of testDates) {
|
|
const dayjsTime = dayjs(dateString).valueOf();
|
|
const nativeTime = new Date(dateString).getTime();
|
|
|
|
expect(nativeTime).toBe(dayjsTime);
|
|
|
|
// Also verify ISO string output consistency
|
|
const dayjsISO = dayjs(dateString).toISOString();
|
|
const nativeISO = new Date(dateString).toISOString();
|
|
|
|
expect(nativeISO).toBe(dayjsISO);
|
|
|
|
log.debug(`Date parsing verified: ${dateString} -> ${nativeTime}`);
|
|
}
|
|
});
|
|
|
|
test("fetchAvailabilityData should handle both single API call and chunked scenarios correctly", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
const mockBusyData = [
|
|
{ start: "2024-01-01T10:00:00Z", end: "2024-01-01T11:00:00Z" },
|
|
{ start: "2024-01-01T14:00:00Z", end: "2024-01-01T15:00:00Z" },
|
|
];
|
|
|
|
// Mock the getCacheOrFetchAvailability method to return consistent data
|
|
const getFreeBusyDataSpy = vi
|
|
.spyOn(calendarService as any, "getFreeBusyData")
|
|
.mockResolvedValue(mockBusyData.map((item) => ({ ...item, id: "test@calendar.com" })));
|
|
|
|
// Test single API call scenario (≤ 90 days)
|
|
const shortRangeResult = await (calendarService as any).fetchAvailabilityData(
|
|
["test@calendar.com"],
|
|
"2024-01-01T00:00:00Z",
|
|
"2024-01-31T00:00:00Z", // 30 days
|
|
false
|
|
);
|
|
|
|
expect(shortRangeResult).toEqual(mockBusyData);
|
|
expect(getFreeBusyDataSpy).toHaveBeenCalledTimes(1);
|
|
|
|
getFreeBusyDataSpy.mockClear();
|
|
|
|
// Test chunked scenario (> 90 days)
|
|
const longRangeResult = await (calendarService as any).fetchAvailabilityData(
|
|
["test@calendar.com"],
|
|
"2024-01-01T00:00:00Z",
|
|
"2024-07-01T00:00:00Z", // 182 days - should require chunking
|
|
false
|
|
);
|
|
|
|
// Should return concatenated results from multiple chunks
|
|
expect(longRangeResult.length).toBeGreaterThan(0);
|
|
expect(getFreeBusyDataSpy).toHaveBeenCalledTimes(3); // 182 days / 90 = ~2.02 -> 3 chunks
|
|
|
|
getFreeBusyDataSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe("createEvent", () => {
|
|
test("should create event with correct input/output format and handle all expected properties", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
// Mock Google Calendar API response
|
|
const mockGoogleEvent = {
|
|
id: "mock-event-id-123",
|
|
summary: "Test Meeting",
|
|
description: "Test meeting description",
|
|
start: {
|
|
dateTime: "2024-06-15T10:00:00Z",
|
|
timeZone: "UTC",
|
|
},
|
|
end: {
|
|
dateTime: "2024-06-15T11:00:00Z",
|
|
timeZone: "UTC",
|
|
},
|
|
attendees: [
|
|
{
|
|
email: "organizer@example.com",
|
|
displayName: "Test Organizer",
|
|
responseStatus: "accepted",
|
|
organizer: true,
|
|
},
|
|
{
|
|
email: "attendee@example.com",
|
|
displayName: "Test Attendee",
|
|
responseStatus: "accepted",
|
|
},
|
|
],
|
|
location: "Test Location",
|
|
iCalUID: "test-ical-uid@google.com",
|
|
recurrence: null,
|
|
};
|
|
|
|
// Mock calendar.events.insert
|
|
const eventsInsertMock = vi.fn().mockResolvedValue({
|
|
data: mockGoogleEvent,
|
|
});
|
|
|
|
calendarMock.calendar_v3.Calendar().events.insert = eventsInsertMock;
|
|
|
|
// Test input - simplified CalendarServiceEvent
|
|
const testCalEvent = {
|
|
type: "test-event-type",
|
|
uid: "cal-event-uid-123",
|
|
title: "Test Meeting",
|
|
startTime: "2024-06-15T10:00:00Z",
|
|
endTime: "2024-06-15T11:00:00Z",
|
|
organizer: {
|
|
id: 1,
|
|
name: "Test Organizer",
|
|
email: "organizer@example.com",
|
|
timeZone: "UTC",
|
|
language: {
|
|
translate: (...args: any[]) => args[0], // Mock translate function
|
|
locale: "en",
|
|
},
|
|
},
|
|
attendees: [
|
|
{
|
|
id: 2,
|
|
name: "Test Attendee",
|
|
email: "attendee@example.com",
|
|
timeZone: "UTC",
|
|
language: {
|
|
translate: (...args: any[]) => args[0], // Mock translate function
|
|
locale: "en",
|
|
},
|
|
},
|
|
],
|
|
location: "Test Location",
|
|
calendarDescription: "Test meeting description",
|
|
destinationCalendar: [
|
|
{
|
|
id: 1,
|
|
integration: "google_calendar",
|
|
externalId: "primary",
|
|
primaryEmail: null,
|
|
userId: mockCredential.userId,
|
|
eventTypeId: null,
|
|
credentialId: mockCredential.id,
|
|
delegationCredentialId: null,
|
|
domainWideDelegationCredentialId: null,
|
|
createdAt: new Date("2024-06-15T11:00:00Z"),
|
|
updatedAt: new Date("2024-06-15T11:00:00Z"),
|
|
customCalendarReminder: 10,
|
|
},
|
|
],
|
|
iCalUID: "test-ical-uid@google.com",
|
|
conferenceData: undefined,
|
|
hideCalendarEventDetails: false,
|
|
seatsPerTimeSlot: null,
|
|
seatsShowAttendees: true,
|
|
};
|
|
|
|
// Call createEvent and verify result using inline snapshot
|
|
const result = await calendarService.createEvent(testCalEvent, mockCredential.id);
|
|
|
|
// Verify input processing - check that Google API was called with correct payload
|
|
expect(eventsInsertMock).toHaveBeenCalledTimes(1);
|
|
const insertCall = eventsInsertMock.mock.calls[0][0];
|
|
|
|
// Use inline snapshot for input validation
|
|
expect(insertCall).toMatchInlineSnapshot(`
|
|
{
|
|
"calendarId": "primary",
|
|
"conferenceDataVersion": 1,
|
|
"requestBody": {
|
|
"attendees": [
|
|
{
|
|
"displayName": "Test Organizer",
|
|
"email": "primary",
|
|
"id": "1",
|
|
"language": {
|
|
"locale": "en",
|
|
"translate": [Function],
|
|
},
|
|
"name": "Test Organizer",
|
|
"organizer": true,
|
|
"responseStatus": "accepted",
|
|
"timeZone": "UTC",
|
|
},
|
|
{
|
|
"email": "attendee@example.com",
|
|
"language": {
|
|
"locale": "en",
|
|
"translate": [Function],
|
|
},
|
|
"name": "Test Attendee",
|
|
"responseStatus": "accepted",
|
|
"timeZone": "UTC",
|
|
},
|
|
],
|
|
"description": "Test meeting description",
|
|
"end": {
|
|
"dateTime": "2024-06-15T11:00:00Z",
|
|
"timeZone": "UTC",
|
|
},
|
|
"guestsCanSeeOtherGuests": true,
|
|
"iCalUID": "test-ical-uid@google.com",
|
|
"location": "Test Location",
|
|
"reminders": {
|
|
"useDefault": true,
|
|
},
|
|
"start": {
|
|
"dateTime": "2024-06-15T10:00:00Z",
|
|
"timeZone": "UTC",
|
|
},
|
|
"summary": "Test Meeting",
|
|
},
|
|
"sendUpdates": "none",
|
|
}
|
|
`);
|
|
|
|
// Use inline snapshot for output validation
|
|
expect(result).toMatchInlineSnapshot(`
|
|
{
|
|
"additionalInfo": {
|
|
"hangoutLink": "",
|
|
},
|
|
"attendees": [
|
|
{
|
|
"displayName": "Test Organizer",
|
|
"email": "organizer@example.com",
|
|
"organizer": true,
|
|
"responseStatus": "accepted",
|
|
},
|
|
{
|
|
"displayName": "Test Attendee",
|
|
"email": "attendee@example.com",
|
|
"responseStatus": "accepted",
|
|
},
|
|
],
|
|
"description": "Test meeting description",
|
|
"end": {
|
|
"dateTime": "2024-06-15T11:00:00Z",
|
|
"timeZone": "UTC",
|
|
},
|
|
"iCalUID": "test-ical-uid@google.com",
|
|
"id": "mock-event-id-123",
|
|
"location": "Test Location",
|
|
"password": "",
|
|
"recurrence": null,
|
|
"start": {
|
|
"dateTime": "2024-06-15T10:00:00Z",
|
|
"timeZone": "UTC",
|
|
},
|
|
"summary": "Test Meeting",
|
|
"thirdPartyRecurringEventId": null,
|
|
"type": "google_calendar",
|
|
"uid": "",
|
|
"url": "",
|
|
}
|
|
`);
|
|
|
|
log.info("createEvent test passed - input/output formats verified");
|
|
});
|
|
|
|
test("should handle recurring events correctly", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
// Mock recurring event response
|
|
const mockRecurringEvent = {
|
|
id: "recurring-event-id",
|
|
summary: "Weekly Meeting",
|
|
recurrence: ["RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10"],
|
|
start: { dateTime: "2024-06-15T10:00:00Z", timeZone: "UTC" },
|
|
end: { dateTime: "2024-06-15T11:00:00Z", timeZone: "UTC" },
|
|
};
|
|
|
|
const mockFirstInstance = {
|
|
id: "recurring-event-id_20240615T100000Z",
|
|
summary: "Weekly Meeting",
|
|
start: { dateTime: "2024-06-15T10:00:00Z", timeZone: "UTC" },
|
|
end: { dateTime: "2024-06-15T11:00:00Z", timeZone: "UTC" },
|
|
};
|
|
|
|
calendarMock.calendar_v3.Calendar().events.insert = vi.fn().mockResolvedValue({
|
|
data: mockRecurringEvent,
|
|
});
|
|
|
|
calendarMock.calendar_v3.Calendar().events.instances = vi.fn().mockResolvedValue({
|
|
data: { items: [mockFirstInstance] },
|
|
});
|
|
|
|
const recurringCalEvent = {
|
|
type: "recurring-meeting",
|
|
title: "Weekly Meeting",
|
|
startTime: "2024-06-15T10:00:00Z",
|
|
endTime: "2024-06-15T11:00:00Z",
|
|
organizer: {
|
|
id: 1,
|
|
name: "Organizer",
|
|
email: "organizer@example.com",
|
|
timeZone: "UTC",
|
|
language: {
|
|
translate: (...args: any[]) => args[0], // Mock translate function
|
|
locale: "en",
|
|
},
|
|
},
|
|
attendees: [],
|
|
recurringEvent: {
|
|
freq: 2, // Weekly
|
|
interval: 1,
|
|
count: 10,
|
|
},
|
|
destinationCalendar: [
|
|
{
|
|
id: 1,
|
|
integration: "google_calendar",
|
|
externalId: "primary",
|
|
primaryEmail: null,
|
|
userId: mockCredential.userId,
|
|
eventTypeId: null,
|
|
credentialId: mockCredential.id,
|
|
delegationCredentialId: null,
|
|
domainWideDelegationCredentialId: null,
|
|
createdAt: new Date("2024-06-15T11:00:00Z"),
|
|
updatedAt: new Date("2024-06-15T11:00:00Z"),
|
|
customCalendarReminder: 10,
|
|
},
|
|
],
|
|
calendarDescription: "Weekly team meeting",
|
|
};
|
|
|
|
const result = await calendarService.createEvent(recurringCalEvent, mockCredential.id);
|
|
|
|
// Use inline snapshot for recurring event result
|
|
expect(result).toMatchInlineSnapshot(`
|
|
{
|
|
"additionalInfo": {
|
|
"hangoutLink": "",
|
|
},
|
|
"end": {
|
|
"dateTime": "2024-06-15T11:00:00Z",
|
|
"timeZone": "UTC",
|
|
},
|
|
"iCalUID": undefined,
|
|
"id": "recurring-event-id_20240615T100000Z",
|
|
"password": "",
|
|
"start": {
|
|
"dateTime": "2024-06-15T10:00:00Z",
|
|
"timeZone": "UTC",
|
|
},
|
|
"summary": "Weekly Meeting",
|
|
"thirdPartyRecurringEventId": "recurring-event-id",
|
|
"type": "google_calendar",
|
|
"uid": "",
|
|
"url": "",
|
|
}
|
|
`);
|
|
|
|
// Verify recurrence rule was included in the request
|
|
const insertCall = calendarMock.calendar_v3.Calendar().events.insert.mock.calls[0][0];
|
|
expect(insertCall.requestBody.recurrence).toEqual(["RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10"]);
|
|
|
|
log.info("createEvent recurring event test passed");
|
|
});
|
|
|
|
test("should use default reminders when no custom reminder is configured", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
const mockGoogleEvent = {
|
|
id: "mock-event-default-reminder",
|
|
summary: "Test Meeting Default Reminder",
|
|
start: { dateTime: "2024-06-15T10:00:00Z", timeZone: "UTC" },
|
|
end: { dateTime: "2024-06-15T11:00:00Z", timeZone: "UTC" },
|
|
};
|
|
|
|
const eventsInsertMock = vi.fn().mockResolvedValue({
|
|
data: mockGoogleEvent,
|
|
});
|
|
calendarMock.calendar_v3.Calendar().events.insert = eventsInsertMock;
|
|
|
|
const testCalEvent = {
|
|
type: "test-event-type",
|
|
uid: "cal-event-uid-456",
|
|
title: "Test Meeting Default Reminder",
|
|
startTime: "2024-06-15T10:00:00Z",
|
|
endTime: "2024-06-15T11:00:00Z",
|
|
organizer: {
|
|
id: 1,
|
|
name: "Test Organizer",
|
|
email: "organizer@example.com",
|
|
timeZone: "UTC",
|
|
language: { translate: (...args: any[]) => args[0], locale: "en" },
|
|
},
|
|
attendees: [],
|
|
calendarDescription: "Test meeting description",
|
|
destinationCalendar: [
|
|
{
|
|
id: 1,
|
|
integration: "google_calendar",
|
|
externalId: "primary",
|
|
primaryEmail: null,
|
|
userId: mockCredential.userId,
|
|
eventTypeId: null,
|
|
credentialId: mockCredential.id,
|
|
delegationCredentialId: null,
|
|
domainWideDelegationCredentialId: null,
|
|
createdAt: new Date("2024-06-15T11:00:00Z"),
|
|
updatedAt: new Date("2024-06-15T11:00:00Z"),
|
|
customCalendarReminder: null,
|
|
},
|
|
],
|
|
};
|
|
|
|
// Mock the getReminderDuration method to return null (no custom reminder configured)
|
|
vi.spyOn(calendarService as any, "getReminderDuration").mockResolvedValue(null);
|
|
|
|
await calendarService.createEvent(testCalEvent, mockCredential.id);
|
|
|
|
const insertCall = eventsInsertMock.mock.calls[0][0];
|
|
|
|
// When no custom reminder is configured, should use Google Calendar's default
|
|
expect(insertCall.requestBody.reminders).toEqual({
|
|
useDefault: true,
|
|
});
|
|
|
|
log.info("createEvent with default reminders test passed");
|
|
});
|
|
|
|
test("should handle 'just in time' reminder (0 minutes) correctly", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
// Mock the getReminderDuration method to return 0 (just in time)
|
|
vi.spyOn(calendarService as any, "getReminderDuration").mockResolvedValue(0);
|
|
|
|
const mockGoogleEvent = {
|
|
id: "mock-event-just-in-time",
|
|
summary: "Test Meeting Just In Time",
|
|
start: { dateTime: "2024-06-15T10:00:00Z", timeZone: "UTC" },
|
|
end: { dateTime: "2024-06-15T11:00:00Z", timeZone: "UTC" },
|
|
};
|
|
|
|
const eventsInsertMock = vi.fn().mockResolvedValue({
|
|
data: mockGoogleEvent,
|
|
});
|
|
calendarMock.calendar_v3.Calendar().events.insert = eventsInsertMock;
|
|
|
|
const testCalEvent = {
|
|
type: "test-event-type",
|
|
uid: "cal-event-uid-789",
|
|
title: "Test Meeting Just In Time",
|
|
startTime: "2024-06-15T10:00:00Z",
|
|
endTime: "2024-06-15T11:00:00Z",
|
|
organizer: {
|
|
id: 1,
|
|
name: "Test Organizer",
|
|
email: "organizer@example.com",
|
|
timeZone: "UTC",
|
|
language: { translate: (...args: any[]) => args[0], locale: "en" },
|
|
},
|
|
attendees: [],
|
|
calendarDescription: "Test meeting description",
|
|
destinationCalendar: [
|
|
{
|
|
id: 1,
|
|
integration: "google_calendar",
|
|
externalId: "primary",
|
|
primaryEmail: null,
|
|
userId: mockCredential.userId,
|
|
eventTypeId: null,
|
|
credentialId: mockCredential.id,
|
|
delegationCredentialId: null,
|
|
domainWideDelegationCredentialId: null,
|
|
createdAt: new Date("2024-06-15T11:00:00Z"),
|
|
updatedAt: new Date("2024-06-15T11:00:00Z"),
|
|
customCalendarReminder: 0,
|
|
},
|
|
],
|
|
};
|
|
|
|
await calendarService.createEvent(testCalEvent, mockCredential.id);
|
|
|
|
const insertCall = eventsInsertMock.mock.calls[0][0];
|
|
|
|
// When "just in time" (0 minutes) is configured, should override with 0-minute reminders
|
|
expect(insertCall.requestBody.reminders).toEqual({
|
|
useDefault: false,
|
|
overrides: [
|
|
{ method: "popup", minutes: 0 },
|
|
{ method: "email", minutes: 0 },
|
|
],
|
|
});
|
|
|
|
log.info("createEvent with just in time reminders test passed");
|
|
});
|
|
|
|
test("should patch event location with hangoutLink when event is created with hangoutLink", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
const mockHangoutLink = "https://meet.google.com/abc-defg-hij";
|
|
const mockGoogleEvent = {
|
|
id: "mock-event-with-hangout",
|
|
summary: "Test Meeting with Hangout",
|
|
start: { dateTime: "2024-06-15T10:00:00Z", timeZone: "UTC" },
|
|
end: { dateTime: "2024-06-15T11:00:00Z", timeZone: "UTC" },
|
|
hangoutLink: mockHangoutLink,
|
|
};
|
|
|
|
const eventsInsertMock = vi.fn().mockResolvedValue({
|
|
data: mockGoogleEvent,
|
|
});
|
|
const eventsPatchMock = vi.fn().mockResolvedValue({
|
|
data: { ...mockGoogleEvent, location: mockHangoutLink },
|
|
});
|
|
|
|
calendarMock.calendar_v3.Calendar().events.insert = eventsInsertMock;
|
|
calendarMock.calendar_v3.Calendar().events.patch = eventsPatchMock;
|
|
|
|
const testCalEvent = {
|
|
type: "test-event-type",
|
|
uid: "cal-event-uid-hangout",
|
|
title: "Test Meeting with Hangout",
|
|
startTime: "2024-06-15T10:00:00Z",
|
|
endTime: "2024-06-15T11:00:00Z",
|
|
organizer: {
|
|
id: 1,
|
|
name: "Test Organizer",
|
|
email: "organizer@example.com",
|
|
timeZone: "UTC",
|
|
language: { translate: (...args: any[]) => args[0], locale: "en" },
|
|
},
|
|
attendees: [],
|
|
location: "Test Location",
|
|
calendarDescription: "Test meeting description",
|
|
destinationCalendar: [
|
|
{
|
|
id: 1,
|
|
integration: "google_calendar",
|
|
externalId: "primary",
|
|
primaryEmail: null,
|
|
userId: mockCredential.userId,
|
|
eventTypeId: null,
|
|
credentialId: mockCredential.id,
|
|
delegationCredentialId: null,
|
|
domainWideDelegationCredentialId: null,
|
|
createdAt: new Date("2024-06-15T11:00:00Z"),
|
|
updatedAt: new Date("2024-06-15T11:00:00Z"),
|
|
customCalendarReminder: null,
|
|
},
|
|
],
|
|
};
|
|
|
|
await calendarService.createEvent(testCalEvent, mockCredential.id);
|
|
|
|
// Verify that patch was called with hangoutLink in additionalInformation
|
|
expect(eventsPatchMock).toHaveBeenCalledTimes(1);
|
|
const patchCall = eventsPatchMock.mock.calls[0][0];
|
|
expect(patchCall.calendarId).toBe("primary");
|
|
expect(patchCall.eventId).toBe("mock-event-with-hangout");
|
|
expect(patchCall.requestBody.location).toBe(mockHangoutLink);
|
|
expect(patchCall.requestBody.description).toBeDefined();
|
|
|
|
log.info("createEvent with hangoutLink patch test passed");
|
|
});
|
|
});
|
|
|
|
describe("updateEvent", () => {
|
|
test("should patch event location with hangoutLink when event is updated with hangoutLink and MeetLocationType", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
const mockHangoutLink = "https://meet.google.com/xyz-uvwx-rst";
|
|
const mockUpdatedEvent = {
|
|
id: "existing-event-id",
|
|
summary: "Updated Meeting with Hangout",
|
|
start: { dateTime: "2024-06-15T10:00:00Z", timeZone: "UTC" },
|
|
end: { dateTime: "2024-06-15T11:00:00Z", timeZone: "UTC" },
|
|
hangoutLink: mockHangoutLink,
|
|
};
|
|
|
|
const eventsUpdateMock = vi.fn().mockResolvedValue({
|
|
data: mockUpdatedEvent,
|
|
});
|
|
const eventsPatchMock = vi.fn().mockResolvedValue({
|
|
data: { ...mockUpdatedEvent, location: mockHangoutLink },
|
|
});
|
|
|
|
calendarMock.calendar_v3.Calendar().events.update = eventsUpdateMock;
|
|
calendarMock.calendar_v3.Calendar().events.patch = eventsPatchMock;
|
|
|
|
const testCalEvent = {
|
|
type: "test-event-type",
|
|
uid: "existing-event-id",
|
|
title: "Updated Meeting with Hangout",
|
|
startTime: "2024-06-15T10:00:00Z",
|
|
endTime: "2024-06-15T11:00:00Z",
|
|
organizer: {
|
|
id: 1,
|
|
name: "Test Organizer",
|
|
email: "organizer@example.com",
|
|
timeZone: "UTC",
|
|
language: { translate: (...args: any[]) => args[0], locale: "en" },
|
|
},
|
|
attendees: [],
|
|
location: MeetLocationType,
|
|
calendarDescription: "Updated meeting description",
|
|
destinationCalendar: [
|
|
{
|
|
id: 1,
|
|
integration: "google_calendar",
|
|
externalId: "primary",
|
|
primaryEmail: null,
|
|
userId: mockCredential.userId,
|
|
eventTypeId: null,
|
|
credentialId: mockCredential.id,
|
|
delegationCredentialId: null,
|
|
domainWideDelegationCredentialId: null,
|
|
createdAt: new Date("2024-06-15T11:00:00Z"),
|
|
updatedAt: new Date("2024-06-15T11:00:00Z"),
|
|
customCalendarReminder: null,
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = await calendarService.updateEvent("existing-event-id", testCalEvent, "primary");
|
|
|
|
// Verify that patch was called with hangoutLink in additionalInformation
|
|
expect(eventsPatchMock).toHaveBeenCalledTimes(1);
|
|
const patchCall = eventsPatchMock.mock.calls[0][0];
|
|
expect(patchCall.calendarId).toBe("primary");
|
|
expect(patchCall.eventId).toBe("existing-event-id");
|
|
expect(patchCall.requestBody.location).toBe(mockHangoutLink);
|
|
expect(patchCall.requestBody.description).toBeDefined();
|
|
|
|
// Verify result includes hangoutLink in additionalInfo
|
|
expect(result.additionalInfo?.hangoutLink).toBe(mockHangoutLink);
|
|
|
|
log.info("updateEvent with hangoutLink patch test passed");
|
|
});
|
|
|
|
test("should not patch event when hangoutLink is present but location is not MeetLocationType", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
const mockHangoutLink = "https://meet.google.com/xyz-uvwx-rst";
|
|
const mockUpdatedEvent = {
|
|
id: "existing-event-id",
|
|
summary: "Updated Meeting with Hangout",
|
|
start: { dateTime: "2024-06-15T10:00:00Z", timeZone: "UTC" },
|
|
end: { dateTime: "2024-06-15T11:00:00Z", timeZone: "UTC" },
|
|
hangoutLink: mockHangoutLink,
|
|
};
|
|
|
|
const eventsUpdateMock = vi.fn().mockResolvedValue({
|
|
data: mockUpdatedEvent,
|
|
});
|
|
const eventsPatchMock = vi.fn();
|
|
|
|
calendarMock.calendar_v3.Calendar().events.update = eventsUpdateMock;
|
|
calendarMock.calendar_v3.Calendar().events.patch = eventsPatchMock;
|
|
|
|
const testCalEvent = {
|
|
type: "test-event-type",
|
|
uid: "existing-event-id",
|
|
title: "Updated Meeting with Hangout",
|
|
startTime: "2024-06-15T10:00:00Z",
|
|
endTime: "2024-06-15T11:00:00Z",
|
|
organizer: {
|
|
id: 1,
|
|
name: "Test Organizer",
|
|
email: "organizer@example.com",
|
|
timeZone: "UTC",
|
|
language: { translate: (...args: any[]) => args[0], locale: "en" },
|
|
},
|
|
attendees: [],
|
|
location: "Some other location",
|
|
calendarDescription: "Updated meeting description",
|
|
destinationCalendar: [
|
|
{
|
|
id: 1,
|
|
integration: "google_calendar",
|
|
externalId: "primary",
|
|
primaryEmail: null,
|
|
userId: mockCredential.userId,
|
|
eventTypeId: null,
|
|
credentialId: mockCredential.id,
|
|
delegationCredentialId: null,
|
|
domainWideDelegationCredentialId: null,
|
|
createdAt: new Date("2024-06-15T11:00:00Z"),
|
|
updatedAt: new Date("2024-06-15T11:00:00Z"),
|
|
customCalendarReminder: null,
|
|
},
|
|
],
|
|
};
|
|
|
|
await calendarService.updateEvent("existing-event-id", testCalEvent, "primary");
|
|
|
|
// Verify that patch was NOT called when location is not MeetLocationType
|
|
expect(eventsPatchMock).not.toHaveBeenCalled();
|
|
|
|
log.info("updateEvent without MeetLocationType should not patch test passed");
|
|
});
|
|
|
|
test("should not patch event when hangoutLink is not present", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
const mockUpdatedEvent = {
|
|
id: "existing-event-id",
|
|
summary: "Updated Meeting without Hangout",
|
|
start: { dateTime: "2024-06-15T10:00:00Z", timeZone: "UTC" },
|
|
end: { dateTime: "2024-06-15T11:00:00Z", timeZone: "UTC" },
|
|
// No hangoutLink
|
|
};
|
|
|
|
const eventsUpdateMock = vi.fn().mockResolvedValue({
|
|
data: mockUpdatedEvent,
|
|
});
|
|
const eventsPatchMock = vi.fn();
|
|
|
|
calendarMock.calendar_v3.Calendar().events.update = eventsUpdateMock;
|
|
calendarMock.calendar_v3.Calendar().events.patch = eventsPatchMock;
|
|
|
|
const testCalEvent = {
|
|
type: "test-event-type",
|
|
uid: "existing-event-id",
|
|
title: "Updated Meeting without Hangout",
|
|
startTime: "2024-06-15T10:00:00Z",
|
|
endTime: "2024-06-15T11:00:00Z",
|
|
organizer: {
|
|
id: 1,
|
|
name: "Test Organizer",
|
|
email: "organizer@example.com",
|
|
timeZone: "UTC",
|
|
language: { translate: (...args: any[]) => args[0], locale: "en" },
|
|
},
|
|
attendees: [],
|
|
location: MeetLocationType,
|
|
calendarDescription: "Updated meeting description",
|
|
destinationCalendar: [
|
|
{
|
|
id: 1,
|
|
integration: "google_calendar",
|
|
externalId: "primary",
|
|
primaryEmail: null,
|
|
userId: mockCredential.userId,
|
|
eventTypeId: null,
|
|
credentialId: mockCredential.id,
|
|
delegationCredentialId: null,
|
|
domainWideDelegationCredentialId: null,
|
|
createdAt: new Date("2024-06-15T11:00:00Z"),
|
|
updatedAt: new Date("2024-06-15T11:00:00Z"),
|
|
customCalendarReminder: null,
|
|
},
|
|
],
|
|
};
|
|
|
|
await calendarService.updateEvent("existing-event-id", testCalEvent, "primary");
|
|
|
|
// Verify that patch was NOT called when hangoutLink is not present
|
|
expect(eventsPatchMock).not.toHaveBeenCalled();
|
|
|
|
log.info("updateEvent without hangoutLink should not patch test passed");
|
|
});
|
|
});
|
|
|
|
describe("listCalendars", () => {
|
|
test("should filter out Google holiday calendars", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
calendarListMock.mockImplementation(() => {
|
|
return {
|
|
data: {
|
|
items: [
|
|
{
|
|
id: "user@example.com",
|
|
summary: "Primary Calendar",
|
|
primary: true,
|
|
accessRole: "owner",
|
|
},
|
|
{
|
|
id: "en.usa#holiday@group.v.calendar.google.com",
|
|
summary: "Holidays in United States",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
{
|
|
id: "en.christian#holiday@group.v.calendar.google.com",
|
|
summary: "Christian Holidays",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
});
|
|
|
|
const calendars = await calendarService.listCalendars();
|
|
|
|
expect(calendars).toHaveLength(1);
|
|
expect(calendars[0].externalId).toBe("user@example.com");
|
|
expect(calendars[0].name).toBe("Primary Calendar");
|
|
});
|
|
|
|
test("should filter out Google birthdays/contacts calendar", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
calendarListMock.mockImplementation(() => {
|
|
return {
|
|
data: {
|
|
items: [
|
|
{
|
|
id: "user@example.com",
|
|
summary: "Primary Calendar",
|
|
primary: true,
|
|
accessRole: "owner",
|
|
},
|
|
{
|
|
id: "addressbook#contacts@group.v.calendar.google.com",
|
|
summary: "Birthdays",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
});
|
|
|
|
const calendars = await calendarService.listCalendars();
|
|
|
|
expect(calendars).toHaveLength(1);
|
|
expect(calendars[0].externalId).toBe("user@example.com");
|
|
});
|
|
|
|
test("should filter out all Google system calendars while keeping regular calendars", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
calendarListMock.mockImplementation(() => {
|
|
return {
|
|
data: {
|
|
items: [
|
|
{
|
|
id: "user@example.com",
|
|
summary: "Primary Calendar",
|
|
primary: true,
|
|
accessRole: "owner",
|
|
},
|
|
{
|
|
id: "work@example.com",
|
|
summary: "Work Calendar",
|
|
primary: false,
|
|
accessRole: "owner",
|
|
},
|
|
{
|
|
id: "en.usa#holiday@group.v.calendar.google.com",
|
|
summary: "Holidays in United States",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
{
|
|
id: "addressbook#contacts@group.v.calendar.google.com",
|
|
summary: "Birthdays",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
{
|
|
id: "shared@example.com",
|
|
summary: "Shared Calendar",
|
|
primary: false,
|
|
accessRole: "writer",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
});
|
|
|
|
const calendars = await calendarService.listCalendars();
|
|
|
|
expect(calendars).toHaveLength(3);
|
|
const calendarIds = calendars.map((cal) => cal.externalId);
|
|
expect(calendarIds).toContain("user@example.com");
|
|
expect(calendarIds).toContain("work@example.com");
|
|
expect(calendarIds).toContain("shared@example.com");
|
|
expect(calendarIds).not.toContain("en.usa#holiday@group.v.calendar.google.com");
|
|
expect(calendarIds).not.toContain("addressbook#contacts@group.v.calendar.google.com");
|
|
});
|
|
|
|
test("should return empty array when all calendars are system calendars", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
calendarListMock.mockImplementation(() => {
|
|
return {
|
|
data: {
|
|
items: [
|
|
{
|
|
id: "en.usa#holiday@group.v.calendar.google.com",
|
|
summary: "Holidays in United States",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
{
|
|
id: "addressbook#contacts@group.v.calendar.google.com",
|
|
summary: "Birthdays",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
});
|
|
|
|
const calendars = await calendarService.listCalendars();
|
|
|
|
expect(calendars).toHaveLength(0);
|
|
});
|
|
|
|
test("should handle calendars with null or undefined ids", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
calendarListMock.mockImplementation(() => {
|
|
return {
|
|
data: {
|
|
items: [
|
|
{
|
|
id: "user@example.com",
|
|
summary: "Primary Calendar",
|
|
primary: true,
|
|
accessRole: "owner",
|
|
},
|
|
{
|
|
id: null,
|
|
summary: "Calendar with null id",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
{
|
|
id: undefined,
|
|
summary: "Calendar with undefined id",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
});
|
|
|
|
const calendars = await calendarService.listCalendars();
|
|
|
|
expect(calendars).toHaveLength(3);
|
|
expect(calendars[0].externalId).toBe("user@example.com");
|
|
expect(calendars[1].externalId).toBe("No id");
|
|
expect(calendars[2].externalId).toBe("No id");
|
|
});
|
|
|
|
test("should correctly identify various holiday calendar formats", async () => {
|
|
const calendarService = BuildCalendarService(mockCredential);
|
|
setFullMockOAuthManagerRequest();
|
|
|
|
calendarListMock.mockImplementation(() => {
|
|
return {
|
|
data: {
|
|
items: [
|
|
{
|
|
id: "user@example.com",
|
|
summary: "Primary Calendar",
|
|
primary: true,
|
|
accessRole: "owner",
|
|
},
|
|
{
|
|
id: "en.uk#holiday@group.v.calendar.google.com",
|
|
summary: "Holidays in United Kingdom",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
{
|
|
id: "de.german#holiday@group.v.calendar.google.com",
|
|
summary: "Holidays in Germany",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
{
|
|
id: "ja.japanese#holiday@group.v.calendar.google.com",
|
|
summary: "Holidays in Japan",
|
|
primary: false,
|
|
accessRole: "reader",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
});
|
|
|
|
const calendars = await calendarService.listCalendars();
|
|
|
|
expect(calendars).toHaveLength(1);
|
|
expect(calendars[0].externalId).toBe("user@example.com");
|
|
});
|
|
});
|