diff options
Diffstat (limited to 'internal/handlers/timeline_logic_test.go')
| -rw-r--r-- | internal/handlers/timeline_logic_test.go | 157 |
1 files changed, 148 insertions, 9 deletions
diff --git a/internal/handlers/timeline_logic_test.go b/internal/handlers/timeline_logic_test.go index 11406b9..b42ad4c 100644 --- a/internal/handlers/timeline_logic_test.go +++ b/internal/handlers/timeline_logic_test.go @@ -2,11 +2,13 @@ package handlers import ( "context" + "fmt" "os" "path/filepath" "testing" "time" + "task-dashboard/internal/config" "task-dashboard/internal/models" "task-dashboard/internal/store" @@ -79,6 +81,15 @@ func setupTestStore(t *testing.T) *store.Store { url TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); + CREATE TABLE IF NOT EXISTS calendar_events ( + id TEXT PRIMARY KEY, + summary TEXT NOT NULL, + description TEXT, + start_time DATETIME NOT NULL, + end_time DATETIME NOT NULL, + html_link TEXT, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); ` if err := os.WriteFile(filepath.Join(migrationDir, "001_init.sql"), []byte(schema), 0644); err != nil { t.Fatalf("Failed to write migration file: %v", err) @@ -122,19 +133,17 @@ func TestBuildTimeline(t *testing.T) { }, }) - // Calendar Event: 09:00 + // Calendar Event: 09:00 (saved to store cache) eventDate := baseTime.Add(1 * time.Hour) - mockCal := &MockCalendarClient{ - Events: []models.CalendarEvent{ - {ID: "e1", Summary: "Event 1", Start: eventDate, End: eventDate.Add(1 * time.Hour)}, - }, - } + _ = s.SaveCalendarEvents([]models.CalendarEvent{ + {ID: "e1", Summary: "Event 1", Start: eventDate, End: eventDate.Add(1 * time.Hour)}, + }) // Test Range: Full Day start := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) end := time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC) - items, err := BuildTimeline(context.Background(), s, mockCal, nil, start, end) + items, err := BuildTimeline(context.Background(), s, nil, start, end) if err != nil { t.Fatalf("BuildTimeline failed: %v", err) } @@ -268,7 +277,7 @@ func TestBuildTimeline_IncludesOverdueItems(t *testing.T) { // Query range: today through tomorrow end := today.AddDate(0, 0, 1) - items, err := BuildTimeline(context.Background(), s, nil, nil, today, end) + items, err := BuildTimeline(context.Background(), s, nil,today, end) if err != nil { t.Fatalf("BuildTimeline failed: %v", err) } @@ -311,7 +320,7 @@ func TestBuildTimeline_ExcludesCompletedOverdue(t *testing.T) { {ID: "done1", Content: "Done overdue", DueDate: &yesterday, Completed: true}, }) - items, err := BuildTimeline(context.Background(), s, nil, nil, today, end) + items, err := BuildTimeline(context.Background(), s, nil,today, end) if err != nil { t.Fatalf("BuildTimeline failed: %v", err) } @@ -323,6 +332,136 @@ func TestBuildTimeline_ExcludesCompletedOverdue(t *testing.T) { } } +func TestBuildTimeline_ReadsCalendarEventsFromStore(t *testing.T) { + s := setupTestStore(t) + + start := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + end := time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC) + + // Save events to the store (simulating a prior cache) + eventTime := time.Date(2023, 1, 1, 10, 0, 0, 0, time.UTC) + err := s.SaveCalendarEvents([]models.CalendarEvent{ + {ID: "cached-e1", Summary: "Cached Meeting", Start: eventTime, End: eventTime.Add(time.Hour)}, + }) + if err != nil { + t.Fatalf("Failed to save calendar events: %v", err) + } + + // Call BuildTimeline with NO calendar client (nil) — events should come from store + items, err := BuildTimeline(context.Background(), s, nil,start, end) + if err != nil { + t.Fatalf("BuildTimeline failed: %v", err) + } + + foundEvent := false + for _, item := range items { + if item.ID == "cached-e1" { + foundEvent = true + if item.Title != "Cached Meeting" { + t.Errorf("Expected title 'Cached Meeting', got %q", item.Title) + } + if item.Source != "calendar" { + t.Errorf("Expected source 'calendar', got %q", item.Source) + } + } + } + if !foundEvent { + t.Error("BuildTimeline should read calendar events from store, but cached event was not found") + } +} + +func TestFetchCalendarEvents_CacheFallbackOnAPIError(t *testing.T) { + db, cleanup := setupTestDB(t) + defer cleanup() + + // Pre-cache some calendar events + eventTime := time.Date(2023, 6, 15, 10, 0, 0, 0, time.UTC) + err := db.SaveCalendarEvents([]models.CalendarEvent{ + {ID: "e-cached", Summary: "Cached Event", Start: eventTime, End: eventTime.Add(time.Hour)}, + }) + if err != nil { + t.Fatalf("Failed to seed calendar events: %v", err) + } + // Mark cache as valid + if err := db.UpdateCacheMetadata(store.CacheKeyGoogleCalendar, 60); err != nil { + t.Fatalf("Failed to update cache metadata: %v", err) + } + + // Create handler with a failing calendar client + failingCal := &MockCalendarClient{Err: fmt.Errorf("API unavailable")} + h := &Handler{ + store: db, + googleCalendarClient: failingCal, + config: &config.Config{CacheTTLMinutes: 5}, + renderer: newTestRenderer(), + } + + // Force refresh to hit the API (which fails), should fall back to cache + events, err := h.fetchCalendarEvents(context.Background(), true) + if err != nil { + t.Fatalf("fetchCalendarEvents should not return error on API failure with cached data, got: %v", err) + } + + if len(events) != 1 { + t.Fatalf("Expected 1 cached event on fallback, got %d", len(events)) + } + if events[0].ID != "e-cached" { + t.Errorf("Expected cached event ID 'e-cached', got %q", events[0].ID) + } +} + +func TestSaveAndGetCalendarEvents(t *testing.T) { + db, cleanup := setupTestDB(t) + defer cleanup() + + events := []models.CalendarEvent{ + { + ID: "evt-1", + Summary: "Morning Standup", + Description: "Daily team sync", + Start: time.Date(2023, 6, 1, 9, 0, 0, 0, time.UTC), + End: time.Date(2023, 6, 1, 9, 30, 0, 0, time.UTC), + HTMLLink: "https://calendar.google.com/event/1", + }, + { + ID: "evt-2", + Summary: "Lunch", + Start: time.Date(2023, 6, 1, 12, 0, 0, 0, time.UTC), + End: time.Date(2023, 6, 1, 13, 0, 0, 0, time.UTC), + }, + } + + if err := db.SaveCalendarEvents(events); err != nil { + t.Fatalf("SaveCalendarEvents failed: %v", err) + } + + // Get all events + got, err := db.GetCalendarEvents() + if err != nil { + t.Fatalf("GetCalendarEvents failed: %v", err) + } + if len(got) != 2 { + t.Fatalf("Expected 2 events, got %d", len(got)) + } + if got[0].Summary != "Morning Standup" { + t.Errorf("Expected first event 'Morning Standup', got %q", got[0].Summary) + } + + // Get by date range (only morning) + rangeStart := time.Date(2023, 6, 1, 8, 0, 0, 0, time.UTC) + rangeEnd := time.Date(2023, 6, 1, 10, 0, 0, 0, time.UTC) + ranged, err := db.GetCalendarEventsByDateRange(rangeStart, rangeEnd) + if err != nil { + t.Fatalf("GetCalendarEventsByDateRange failed: %v", err) + } + if len(ranged) != 1 { + t.Fatalf("Expected 1 event in range, got %d", len(ranged)) + } + if ranged[0].ID != "evt-1" { + t.Errorf("Expected event ID 'evt-1', got %q", ranged[0].ID) + } +} + func timePtr(t time.Time) *time.Time { return &t } |
