feat(api): read-all takes an optional up_to cutoff (M489)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m40s
release / integration (push) Successful in 4m40s
release / android (push) Successful in 5m1s
release / Build signed APK (releases and dev) (push) Successful in 5m12s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m40s
release / integration (push) Successful in 4m40s
release / android (push) Successful in 5m1s
release / Build signed APK (releases and dev) (push) Successful in 5m12s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
POST /api/me/notifications/read-all accepts {"up_to": RFC3339}. Android
queues "mark all read" for replay when offline, and a replay landing later
must not mark notices that arrived in between, which the user never saw.
A coalesced notice updated since then has a newer created_at, so it stays
unread. An empty body still marks everything.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -127,6 +128,33 @@ func TestMyNotifications_MarkReadIsOwnerScopedAndCountsDown(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A "mark all read" replayed from an offline queue carries the moment the
|
||||
// user asked; what arrived after it stays unread.
|
||||
func TestMyNotifications_ReadAllUpToLeavesLaterOnesUnread(t *testing.T) {
|
||||
h, pool := testHandlers(t)
|
||||
alice := seedUser(t, pool, "notif-upto", "pw", false)
|
||||
r := notificationsRouter(h)
|
||||
|
||||
notifyN(t, pool, alice, 1)
|
||||
var page notificationsPageResp
|
||||
callAs(t, r, alice, http.MethodGet, "/api/me/notifications", "", &page)
|
||||
seen := page.Items[0].CreatedAt
|
||||
notifyN(t, pool, alice, 1)
|
||||
|
||||
body := `{"up_to":"` + seen.Format(time.RFC3339Nano) + `"}`
|
||||
if code := callAs(t, r, alice, http.MethodPost, "/api/me/notifications/read-all", body, nil); code != http.StatusNoContent {
|
||||
t.Fatalf("read-all up_to = %d", code)
|
||||
}
|
||||
var count unreadCountResp
|
||||
callAs(t, r, alice, http.MethodGet, "/api/me/notifications/unread-count", "", &count)
|
||||
if count.UnreadCount != 1 {
|
||||
t.Errorf("unread = %d, want 1 (the one that arrived later)", count.UnreadCount)
|
||||
}
|
||||
if code := callAs(t, r, alice, http.MethodPost, "/api/me/notifications/read-all", `{"up_to":"yesterday"}`, nil); code != http.StatusBadRequest {
|
||||
t.Errorf("malformed up_to = %d, want 400", code)
|
||||
}
|
||||
}
|
||||
|
||||
func strPtr(s string) *string { return &s }
|
||||
|
||||
func setSMTP(t *testing.T, pool *pgxpool.Pool, enabled bool) {
|
||||
|
||||
Reference in New Issue
Block a user