diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-14 20:35:55 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-16 02:44:52 +0000 |
| commit | 146034b08446c4bf0099bb41ab5e689d418e6dce (patch) | |
| tree | 46db3cca04eb3d57b58a5195bbe42d0c193a9043 /internal/handlers/widget_test.go | |
| parent | ba5205213f6f1995ca4e64b08925bcc6c48b8211 (diff) | |
feat(tasks): add HTTP endpoints for task detail, update, and recurrence
GET /api/widget/task, POST /api/widget/task/update,
POST /api/widget/task/recurrence, POST /api/widget/task/next-date.
Doot-only; the recurrence/next-date fields are null in the detail
response for a non-recurring task.
Diffstat (limited to 'internal/handlers/widget_test.go')
| -rw-r--r-- | internal/handlers/widget_test.go | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/internal/handlers/widget_test.go b/internal/handlers/widget_test.go index 2a6967e..35c21e2 100644 --- a/internal/handlers/widget_test.go +++ b/internal/handlers/widget_test.go @@ -816,3 +816,200 @@ func TestTimelineItemToWidgetItem_NonRecurring_EmptyRecurringEventID(t *testing. } } +func TestHandleWidgetTaskDetail_ReturnsFullDetail(t *testing.T) { + s, cleanup := setupTestDB(t) + defer cleanup() + h := &Handler{store: s} + + if _, err := s.DB().Exec(` + INSERT INTO native_tasks (id, content, description, due_date, recurrence_freq, recurrence_interval, recurrence_weekdays, recurrence_series_id) + VALUES ('rec-1', 'Water plants', 'Use the blue can', '2026-07-13', 'weekly', 1, '1,3,5', 'series-1') + `); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/api/widget/task?id=rec-1&source=doot", nil) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetTaskDetail).ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) + } + var resp taskDetailResponse + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if resp.Title != "Water plants" { + t.Errorf("Title = %q, want %q", resp.Title, "Water plants") + } + if resp.Description != "Use the blue can" { + t.Errorf("Description = %q, want %q", resp.Description, "Use the blue can") + } + if resp.Recurrence == nil { + t.Fatal("expected non-nil Recurrence") + } + if resp.Recurrence.Freq != "weekly" || len(resp.Recurrence.Weekdays) != 3 { + t.Errorf("Recurrence = %+v, want freq=weekly with 3 weekdays", resp.Recurrence) + } + if resp.NextDate == nil { + t.Fatal("expected computed NextDate for a recurring task") + } +} + +func TestHandleWidgetTaskDetail_NonRecurring_NilRecurrenceAndNextDate(t *testing.T) { + s, cleanup := setupTestDB(t) + defer cleanup() + h := &Handler{store: s} + + if _, err := s.DB().Exec(`INSERT INTO native_tasks (id, content) VALUES ('plain-1', 'Buy milk')`); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/api/widget/task?id=plain-1&source=doot", nil) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetTaskDetail).ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", w.Code) + } + var resp taskDetailResponse + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if resp.Recurrence != nil { + t.Errorf("expected nil Recurrence for a non-recurring task, got %+v", resp.Recurrence) + } + if resp.NextDate != nil { + t.Errorf("expected nil NextDate for a non-recurring task, got %v", resp.NextDate) + } +} + +func TestHandleWidgetTaskDetail_UnknownID_Returns404(t *testing.T) { + s, cleanup := setupTestDB(t) + defer cleanup() + h := &Handler{store: s} + + req := httptest.NewRequest("GET", "/api/widget/task?id=does-not-exist&source=doot", nil) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetTaskDetail).ServeHTTP(w, req) + + if w.Code != http.StatusNotFound { + t.Errorf("expected 404, got %d", w.Code) + } +} + +func TestHandleWidgetTaskUpdate_UpdatesTitleAndDescription(t *testing.T) { + s, cleanup := setupTestDB(t) + defer cleanup() + h := &Handler{store: s} + + if _, err := s.DB().Exec(`INSERT INTO native_tasks (id, content) VALUES ('plain-1', 'Old title')`); err != nil { + t.Fatal(err) + } + + body := `{"id":"plain-1","title":"New title","description":"New description"}` + req := httptest.NewRequest("POST", "/api/widget/task/update", strings.NewReader(body)) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetTaskUpdate).ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) + } + task, err := s.GetNativeTaskByID("plain-1") + if err != nil { + t.Fatal(err) + } + if task.Content != "New title" || task.Description != "New description" { + t.Errorf("task = %+v, want title/description updated", task) + } +} + +func TestHandleWidgetTaskRecurrence_SetsPattern(t *testing.T) { + s, cleanup := setupTestDB(t) + defer cleanup() + h := &Handler{store: s} + + if _, err := s.DB().Exec(`INSERT INTO native_tasks (id, content) VALUES ('plain-1', 'Water plants')`); err != nil { + t.Fatal(err) + } + + body := `{"id":"plain-1","freq":"weekly","interval":2,"weekdays":[1,3]}` + req := httptest.NewRequest("POST", "/api/widget/task/recurrence", strings.NewReader(body)) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetTaskRecurrence).ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) + } + task, err := s.GetNativeTaskByID("plain-1") + if err != nil { + t.Fatal(err) + } + if task.RecurrenceFreq != "weekly" || task.RecurrenceInterval != 2 || task.RecurrenceSeriesID == "" { + t.Errorf("task = %+v, want recurrence set with a generated series id", task) + } +} + +func TestHandleWidgetTaskRecurrence_InvalidFreq_Returns400(t *testing.T) { + s, cleanup := setupTestDB(t) + defer cleanup() + h := &Handler{store: s} + + body := `{"id":"plain-1","freq":"bogus","interval":1}` + req := httptest.NewRequest("POST", "/api/widget/task/recurrence", strings.NewReader(body)) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetTaskRecurrence).ServeHTTP(w, req) + + if w.Code != http.StatusBadRequest { + t.Errorf("expected 400 for an invalid freq, got %d", w.Code) + } +} + +func TestHandleWidgetTaskNextDate_SetsOverride(t *testing.T) { + s, cleanup := setupTestDB(t) + defer cleanup() + h := &Handler{store: s} + + if _, err := s.DB().Exec(` + INSERT INTO native_tasks (id, content, due_date, recurrence_freq, recurrence_series_id) + VALUES ('rec-1', 'Water plants', '2026-07-13', 'weekly', 'series-1') + `); err != nil { + t.Fatal(err) + } + + body := `{"id":"rec-1","date":"2026-08-01"}` + req := httptest.NewRequest("POST", "/api/widget/task/next-date", strings.NewReader(body)) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetTaskNextDate).ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) + } + task, err := s.GetNativeTaskByID("rec-1") + if err != nil { + t.Fatal(err) + } + if task.NextOccurrenceOverride == nil || task.NextOccurrenceOverride.Format("2006-01-02") != "2026-08-01" { + t.Errorf("NextOccurrenceOverride = %v, want 2026-08-01", task.NextOccurrenceOverride) + } +} + +func TestHandleWidgetTaskNextDate_NonRecurringTask_Returns400(t *testing.T) { + s, cleanup := setupTestDB(t) + defer cleanup() + h := &Handler{store: s} + + if _, err := s.DB().Exec(`INSERT INTO native_tasks (id, content) VALUES ('plain-1', 'Buy milk')`); err != nil { + t.Fatal(err) + } + + body := `{"id":"plain-1","date":"2026-08-01"}` + req := httptest.NewRequest("POST", "/api/widget/task/next-date", strings.NewReader(body)) + w := httptest.NewRecorder() + http.HandlerFunc(h.HandleWidgetTaskNextDate).ServeHTTP(w, req) + + if w.Code != http.StatusBadRequest { + t.Errorf("expected 400 for a non-recurring task, got %d", w.Code) + } +} + |
