fix: filter out Google system calendars from calendar list (#27074)
* fix: filter out Google system calendars from calendar list Google system calendars (holidays, birthdays) don't return proper free/busy information, making them useless for availability checking. This change filters them out from the listCalendars() method so users won't see these calendars as selectable options. Filtered calendar types: - Holiday calendars (#holiday@group.v.calendar.google.com) - Birthdays/contacts calendar (#contacts@group.v.calendar.google.com) Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com> * test: add tests for Google system calendar filtering Tests verify that listCalendars() correctly filters out: - Holiday calendars (#holiday@group.v.calendar.google.com) - Birthdays/contacts calendar (#contacts@group.v.calendar.google.com) Test cases cover: - Filtering holiday calendars - Filtering birthdays calendar - Mixed calendars (keeping regular, filtering system) - All system calendars (returns empty array) - Null/undefined calendar IDs - Various international holiday calendar formats Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2 files changed
+258
-1
No files matched your search
@@ -29,6 +29,22 @@ type FreeBusyArgs = { timeMin: string; timeMax: string; items: { id: string }[]
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["app-store/googlecalendar/lib/CalendarService"] });
|
||||
|
||||
/**
|
||||
* Google system calendars that don't return proper free/busy information,
|
||||
* making them useless for availability checking. These include:
|
||||
* - Holiday calendars (e.g., "en.usa#holiday@group.v.calendar.google.com")
|
||||
* - Birthdays/contacts calendar ("addressbook#contacts@group.v.calendar.google.com")
|
||||
*/
|
||||
const GOOGLE_SYSTEM_CALENDAR_SUFFIXES = [
|
||||
"#holiday@group.v.calendar.google.com",
|
||||
"#contacts@group.v.calendar.google.com",
|
||||
];
|
||||
|
||||
function isGoogleSystemCalendar(calendarId: string | null | undefined): boolean {
|
||||
if (!calendarId) return false;
|
||||
return GOOGLE_SYSTEM_CALENDAR_SUFFIXES.some((suffix) => calendarId.endsWith(suffix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended interface for Google Calendar service that includes Google-specific methods.
|
||||
* This interface is used by internal Google Calendar modules (callback, tests, etc.)
|
||||
@@ -710,7 +726,11 @@ class GoogleCalendarService implements Calendar {
|
||||
);
|
||||
|
||||
if (!cals.items) return [];
|
||||
return cals.items.map(
|
||||
|
||||
// Filter out Google system calendars (holidays, birthdays) as they don't return proper free/busy information
|
||||
const filteredCalendars = cals.items.filter((cal) => !isGoogleSystemCalendar(cal.id));
|
||||
|
||||
return filteredCalendars.map(
|
||||
(cal) =>
|
||||
({
|
||||
externalId: cal.id ?? "No id",
|
||||
|
||||
@@ -803,3 +803,240 @@ describe("createEvent", () => {
|
||||
log.info("createEvent with just in time reminders 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");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user