diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-12 11:12:55 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-12 11:12:55 +0000 |
| commit | 6fdd09bfe030ce91ee188f18c198fd6a57c7b42f (patch) | |
| tree | b358dcd34aee62de8bcefafdffa2049d57f900df /internal/handlers | |
| parent | f31b4722243b8b69564c94e47e07678153c31815 (diff) | |
feat(widget): add recurrence lookup endpoint (GET /api/widget/recurrence)
Diffstat (limited to 'internal/handlers')
| -rw-r--r-- | internal/handlers/timeline_logic_test.go | 7 | ||||
| -rw-r--r-- | internal/handlers/widget.go | 22 | ||||
| -rw-r--r-- | internal/handlers/widget_test.go | 49 |
3 files changed, 78 insertions, 0 deletions
diff --git a/internal/handlers/timeline_logic_test.go b/internal/handlers/timeline_logic_test.go index 9ddaeda..5e7cb27 100644 --- a/internal/handlers/timeline_logic_test.go +++ b/internal/handlers/timeline_logic_test.go @@ -23,6 +23,9 @@ type MockCalendarClient struct { // with, for tests that need to assert on how the caller resolved its // calendar ID list (e.g. fetchCalendarEvents' comma-split fallback). SetCalendarIDsCalls [][]string + // RecurrenceRule is returned by GetRecurrenceRule for any id when RecurrenceErr is nil. + RecurrenceRule string + RecurrenceErr error } func (m *MockCalendarClient) GetUpcomingEvents(ctx context.Context, maxResults int) ([]models.CalendarEvent, error) { @@ -41,6 +44,10 @@ func (m *MockCalendarClient) SetCalendarIDs(ids []string) { m.SetCalendarIDsCalls = append(m.SetCalendarIDsCalls, ids) } +func (m *MockCalendarClient) GetRecurrenceRule(ctx context.Context, recurringEventID string) (string, error) { + return m.RecurrenceRule, m.RecurrenceErr +} + func setupTestStore(t *testing.T) *store.Store { t.Helper() tempDir := t.TempDir() diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go index 29124fb..6b1e774 100644 --- a/internal/handlers/widget.go +++ b/internal/handlers/widget.go @@ -195,6 +195,28 @@ func (h *Handler) HandleWidgetComplete(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } +// HandleWidgetRecurrence looks up and formats a recurring calendar event's schedule. +func (h *Handler) HandleWidgetRecurrence(w http.ResponseWriter, r *http.Request) { + recurringEventID := r.URL.Query().Get("recurring_event_id") + if recurringEventID == "" { + http.Error(w, "recurring_event_id is required", http.StatusBadRequest) + return + } + + recurrence, err := h.googleCalendarClient.GetRecurrenceRule(r.Context(), recurringEventID) + if err != nil { + http.Error(w, "recurring event not found", http.StatusNotFound) + return + } + + resp := struct { + Recurrence string `json:"recurrence"` + }{Recurrence: recurrence} + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(resp) +} + // HandleWidgetAdd creates a new undated native task from the widget's quick-add sheet. func (h *Handler) HandleWidgetAdd(w http.ResponseWriter, r *http.Request) { var req widgetAddRequest diff --git a/internal/handlers/widget_test.go b/internal/handlers/widget_test.go index efe6858..610a55c 100644 --- a/internal/handlers/widget_test.go +++ b/internal/handlers/widget_test.go @@ -1,6 +1,8 @@ package handlers import ( + "encoding/json" + "fmt" "net/http" "net/http/httptest" "strings" @@ -424,3 +426,50 @@ func TestTimelineItemToWidgetItem_NonRecurring_EmptyRecurringEventID(t *testing. t.Errorf("expected empty RecurringEventID for a non-recurring event, got %q", wi.RecurringEventID) } } + +// TestHandleWidgetRecurrence_ReturnsFormattedSchedule proves the recurrence +// lookup endpoint: given a recurring_event_id query param, it calls the +// calendar client's GetRecurrenceRule and returns the formatted text. +func TestHandleWidgetRecurrence_ReturnsFormattedSchedule(t *testing.T) { + mock := &MockCalendarClient{RecurrenceRule: "Repeats weekly on Monday"} + h := &Handler{googleCalendarClient: mock} + req := httptest.NewRequest("GET", "/api/widget/recurrence?recurring_event_id=master-1", nil) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetRecurrence).ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", w.Code) + } + var resp struct { + Recurrence string `json:"recurrence"` + } + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if resp.Recurrence != "Repeats weekly on Monday" { + t.Errorf("recurrence = %q, want %q", resp.Recurrence, "Repeats weekly on Monday") + } +} + +func TestHandleWidgetRecurrence_NotFound_Returns404(t *testing.T) { + mock := &MockCalendarClient{RecurrenceErr: fmt.Errorf("not found")} + h := &Handler{googleCalendarClient: mock} + req := httptest.NewRequest("GET", "/api/widget/recurrence?recurring_event_id=missing", nil) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetRecurrence).ServeHTTP(w, req) + + if w.Code != http.StatusNotFound { + t.Fatalf("expected 404, got %d", w.Code) + } +} + +func TestHandleWidgetRecurrence_MissingParam_Returns400(t *testing.T) { + h := &Handler{} + req := httptest.NewRequest("GET", "/api/widget/recurrence", nil) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetRecurrence).ServeHTTP(w, req) + + if w.Code != http.StatusBadRequest { + t.Fatalf("expected 400, got %d", w.Code) + } +} |
