summaryrefslogtreecommitdiff
path: root/internal/handlers/timeline_logic_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-12 07:18:01 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-12 07:18:01 +0000
commitaf0d59d78524127f6cc76e43a7e397da01788d97 (patch)
treeb534567450d8b43bf6f687cc4fca645f26b1491e /internal/handlers/timeline_logic_test.go
parent84252756c687044d73a0ea5cebf8088c7c9ed3e8 (diff)
fix(calendar): split comma-joined GOOGLE_CALENDAR_ID before use
fetchCalendarEvents falls back to config.GoogleCalendarID whenever no source_configs rows exist yet for the gcal source -- which is the current live state (0 rows). GoogleCalendarID is a single env var that itself holds a comma-separated list of calendar IDs, but the whole joined string was being wrapped in a single-element []string{...} and passed straight to SetCalendarIDs. Google's API takes one calendarId per call, so every fetch failed with '404 Not Found' on the literal comma-joined string -- confirmed live in production logs. This broke all calendar events, in both the web dashboard and the widget, silently. Now splits on commas and trims whitespace before use.
Diffstat (limited to 'internal/handlers/timeline_logic_test.go')
-rw-r--r--internal/handlers/timeline_logic_test.go52
1 files changed, 51 insertions, 1 deletions
diff --git a/internal/handlers/timeline_logic_test.go b/internal/handlers/timeline_logic_test.go
index 3678b62..0b7bc37 100644
--- a/internal/handlers/timeline_logic_test.go
+++ b/internal/handlers/timeline_logic_test.go
@@ -19,6 +19,10 @@ import (
type MockCalendarClient struct {
Events []models.CalendarEvent
Err error
+ // SetCalendarIDsCalls records every ids slice SetCalendarIDs was called
+ // with, for tests that need to assert on how the caller resolved its
+ // calendar ID list (e.g. fetchCalendarEvents' comma-split fallback).
+ SetCalendarIDsCalls [][]string
}
func (m *MockCalendarClient) GetUpcomingEvents(ctx context.Context, maxResults int) ([]models.CalendarEvent, error) {
@@ -33,7 +37,9 @@ func (m *MockCalendarClient) GetCalendarList(ctx context.Context) ([]models.Cale
return nil, m.Err
}
-func (m *MockCalendarClient) SetCalendarIDs(ids []string) {}
+func (m *MockCalendarClient) SetCalendarIDs(ids []string) {
+ m.SetCalendarIDsCalls = append(m.SetCalendarIDsCalls, ids)
+}
func setupTestStore(t *testing.T) *store.Store {
t.Helper()
@@ -335,6 +341,50 @@ func TestFetchCalendarEvents_CacheFallbackOnAPIError(t *testing.T) {
}
}
+// TestFetchCalendarEvents_ConfigFallback_SplitsCommaJoinedIDs proves the
+// 2026-07-12 fix: when no source_configs rows exist yet for "gcal" (the
+// normal state before any calendar-discovery sync has run), fetchCalendarEvents
+// falls back to config.GoogleCalendarID -- but that's a single env var that
+// itself holds a comma-separated list of calendar IDs (see GOOGLE_CALENDAR_ID
+// in .env). Previously the whole joined string was passed to SetCalendarIDs
+// as a single one-element slice, which Google's API rejected with a 404
+// ("Not Found") on every single fetch -- a real production incident that
+// broke all calendar events, in both the web dashboard and the widget, until
+// this fix. It must now be split into separate IDs.
+func TestFetchCalendarEvents_ConfigFallback_SplitsCommaJoinedIDs(t *testing.T) {
+ db, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ mock := &MockCalendarClient{Events: []models.CalendarEvent{{ID: "e1"}}}
+ h := &Handler{
+ store: db,
+ googleCalendarClient: mock,
+ config: &config.Config{
+ CacheTTLMinutes: 5,
+ GoogleCalendarID: "cal-a@group.calendar.google.com, cal-b@gmail.com,cal-c@group.calendar.google.com",
+ },
+ renderer: newTestRenderer(),
+ }
+
+ if _, err := h.fetchCalendarEvents(context.Background(), true); err != nil {
+ t.Fatalf("fetchCalendarEvents: %v", err)
+ }
+
+ if len(mock.SetCalendarIDsCalls) == 0 {
+ t.Fatal("expected SetCalendarIDs to be called")
+ }
+ got := mock.SetCalendarIDsCalls[len(mock.SetCalendarIDsCalls)-1]
+ want := []string{"cal-a@group.calendar.google.com", "cal-b@gmail.com", "cal-c@group.calendar.google.com"}
+ if len(got) != len(want) {
+ t.Fatalf("SetCalendarIDs called with %d ids, want %d: got %v", len(got), len(want), got)
+ }
+ for i := range want {
+ if got[i] != want[i] {
+ t.Errorf("id[%d] = %q, want %q", i, got[i], want[i])
+ }
+ }
+}
+
func TestSaveAndGetCalendarEvents(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()