List each Google calendar once when calendarList pages overlap
CI / ci (push) Successful in 3m41s
CI / ci (pull_request) Successful in 3m41s

Deduplicate by calendar id as the pages are collected, keeping the
first occurrence in Google's order (MQ-126-01), with a test serving
two overlapping pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 21:36:37 -04:00
co-authored by Claude Opus 5.5
parent 1e3e42e0b1
commit d23d30e695
2 changed files with 36 additions and 3 deletions
+7 -3
View File
@@ -102,14 +102,17 @@ func (s *Service) SelectCalendar(ctx context.Context, id string) (Selection, err
return Selection{}, ErrUnknownCalendar
}
// calendars lists the writable calendars with the stored refresh token. The
// caller holds s.account.
// calendars lists the writable calendars, each once, with the stored refresh
// token. The caller holds s.account.
func (s *Service) calendars(ctx context.Context) ([]Calendar, error) {
access, err := s.accessToken(ctx)
if err != nil {
return nil, err
}
cals := []Calendar{}
// listed holds the ids in cals: pages can overlap, and a calendar is
// listed once, as Google first lists it.
listed := map[string]bool{}
pageToken := ""
for range maxCalendarPages {
page, err := s.calendarPage(ctx, access, pageToken)
@@ -120,7 +123,8 @@ func (s *Service) calendars(ctx context.Context) ([]Calendar, error) {
if item.ID == "" {
return nil, &CalendarError{msg: "gcal: Google listed a calendar without an id"}
}
if item.AccessRole == "writer" || item.AccessRole == "owner" {
if (item.AccessRole == "writer" || item.AccessRole == "owner") && !listed[item.ID] {
listed[item.ID] = true
cals = append(cals, Calendar{ID: item.ID, Summary: item.Summary, Primary: item.Primary, AccessRole: item.AccessRole})
}
}
+29
View File
@@ -66,6 +66,35 @@ func TestCalendarsListsWritableCalendars(t *testing.T) {
}
}
func TestCalendarsOverlappingPages(t *testing.T) {
t.Parallel()
h := connected(t)
// Two to a page, family ends the first page and starts the second
// again, renamed in between.
renamed := family
renamed.Summary = "Family (renamed)"
h.google.SetCalendars(2, ownPrimary, family, renamed, dinners)
cals, err := h.svc.Calendars(context.Background())
if err != nil {
t.Fatalf("Calendars: %v", err)
}
got, err := json.Marshal(cals)
if err != nil {
t.Fatal(err)
}
// family once, as first listed, in Google's order.
want := `[` +
`{"id":"household@gmail.com","summary":"household@gmail.com","primary":true,"accessRole":"owner"},` +
`{"id":"family@group.calendar.google.com","summary":"Family","primary":false,"accessRole":"writer"},` +
`{"id":"dinners@group.calendar.google.com","summary":"Dinners","primary":false,"accessRole":"owner"}]`
if string(got) != want {
t.Errorf("Calendars =\n%s\nwant\n%s", got, want)
}
if n := h.google.CalendarCalls(); n != 2 {
t.Errorf("calendarList called %d times, want 2 (every page)", n)
}
}
func TestCalendarsEmpty(t *testing.T) {
t.Parallel()
h := connected(t)