summaryrefslogtreecommitdiff
path: root/internal/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers')
-rw-r--r--internal/handlers/widget.go14
-rw-r--r--internal/handlers/widget_test.go20
2 files changed, 34 insertions, 0 deletions
diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go
index 876aaad..6c3b455 100644
--- a/internal/handlers/widget.go
+++ b/internal/handlers/widget.go
@@ -2,12 +2,14 @@ package handlers
import (
"encoding/json"
+ "errors"
"net/http"
"strings"
"time"
"task-dashboard/internal/config"
"task-dashboard/internal/models"
+ "task-dashboard/internal/store"
)
// WidgetAuthMiddleware validates the static bearer token for widget API endpoints.
@@ -126,6 +128,10 @@ func (h *Handler) HandleWidgetReschedule(w http.ResponseWriter, r *http.Request)
tz := config.GetDisplayTimezone()
dueDate := time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 0, 0, 0, 0, tz)
if err := h.store.RescheduleNativeTask(req.ID, dueDate); err != nil {
+ if errors.Is(err, store.ErrNativeTaskNotFound) {
+ http.Error(w, "task not found", http.StatusNotFound)
+ return
+ }
http.Error(w, "failed to reschedule", http.StatusInternalServerError)
return
}
@@ -143,6 +149,14 @@ func (h *Handler) HandleWidgetComplete(w http.ResponseWriter, r *http.Request) {
switch req.Source {
case "doot":
if err := h.store.CompleteNativeTask(req.ID); err != nil {
+ if errors.Is(err, store.ErrNativeTaskNotFound) {
+ // Surfaced as 404 (not a silent 200) so the caller -- the
+ // Android widget -- can tell "nothing changed" apart from
+ // "it worked", instead of the previous behavior where a
+ // stale/wrong id looked identical to a real completion.
+ http.Error(w, "task not found", http.StatusNotFound)
+ return
+ }
http.Error(w, "failed to complete task", http.StatusInternalServerError)
return
}
diff --git a/internal/handlers/widget_test.go b/internal/handlers/widget_test.go
index f56fe07..850dc07 100644
--- a/internal/handlers/widget_test.go
+++ b/internal/handlers/widget_test.go
@@ -121,6 +121,26 @@ func TestHandleWidgetComplete_NonCompletable(t *testing.T) {
}
}
+// TestHandleWidgetComplete_UnknownID_Returns404 proves the 2026-07-12 fix at
+// the handler layer: a "doot" completion for an id that doesn't exist must
+// surface as 404, not the previous silent 200 (see
+// store.ErrNativeTaskNotFound's doc comment for the underlying bug this
+// closes -- a real production incident where the widget's completeTask tap
+// intermittently looked like it worked but changed nothing).
+func TestHandleWidgetComplete_UnknownID_Returns404(t *testing.T) {
+ s, cleanup := setupTestDB(t)
+ defer cleanup()
+ h := &Handler{store: s}
+ body := `{"id":"does-not-exist","source":"doot"}`
+ req := httptest.NewRequest("POST", "/api/widget/complete", strings.NewReader(body))
+ w := httptest.NewRecorder()
+ http.HandlerFunc(h.HandleWidgetComplete).ServeHTTP(w, req)
+
+ if w.Code != http.StatusNotFound {
+ t.Fatalf("expected 404, got %d", w.Code)
+ }
+}
+
func TestWidgetAuthMiddleware_EmptyToken(t *testing.T) {
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
h := WidgetAuthMiddleware("", inner)