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:
authored and GitHub committed 2026-01-21 10:00:08 -03:00
1 parent 1c3c6292df
commit f44ee33c6c
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");
});
});