Files
Bookra/apps/backend/internal/activity/service_test.go
T
Tomas DvorakandDevin 2464aa45fa feat(notifications): persistent read cursor + delivery history
notification_cursors table stores one read-cursor per user per tenant;
GET /v1/activity returns unreadCount + readAt, POST /v1/activity/read
persists the cursor. Reminder dispatches now append reminder.sent /
reminder.failed system events so delivery history reaches the feed.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 16:55:21 +02:00

83 lines
2.2 KiB
Go

package activity
import (
"context"
"testing"
"bookra/apps/backend/internal/db"
"bookra/apps/backend/internal/domain"
)
var demoPrincipal = domain.Principal{Subject: "demo-owner", Role: "owner"}
func TestListCountsUnreadAgainstCursor(t *testing.T) {
repo := db.NewMemoryRepository()
svc := NewService(repo)
ctx := context.Background()
feed, err := svc.List(ctx, demoPrincipal, 10)
if err != nil {
t.Fatalf("list: %v", err)
}
if len(feed.Events) == 0 {
t.Fatal("expected seeded demo events")
}
if feed.UnreadCount != len(feed.Events) {
t.Fatalf("expected all %d events unread without cursor, got %d", len(feed.Events), feed.UnreadCount)
}
if feed.ReadAt != nil {
t.Fatalf("expected nil readAt before first mark, got %v", feed.ReadAt)
}
}
func TestMarkAllReadPersistsCursor(t *testing.T) {
repo := db.NewMemoryRepository()
svc := NewService(repo)
ctx := context.Background()
readAt, err := svc.MarkAllRead(ctx, demoPrincipal)
if err != nil {
t.Fatalf("mark all read: %v", err)
}
if readAt.IsZero() {
t.Fatal("expected non-zero readAt")
}
feed, err := svc.List(ctx, demoPrincipal, 10)
if err != nil {
t.Fatalf("list: %v", err)
}
if feed.UnreadCount != 0 {
t.Fatalf("expected 0 unread after mark, got %d", feed.UnreadCount)
}
if feed.ReadAt == nil || !feed.ReadAt.Equal(readAt) {
t.Fatalf("expected readAt %v, got %v", readAt, feed.ReadAt)
}
// A new event after the cursor is unread again.
if err := repo.AppendActivityEvent(ctx, db.ActivityEventParams{
TenantID: "5d6b3551-0a3e-4b86-bdf0-e9df20a47148",
ActorKind: "staff",
Action: "booking.created",
EntityType: "booking",
Summary: "New — Booking",
}); err != nil {
t.Fatalf("append event: %v", err)
}
feed, err = svc.List(ctx, demoPrincipal, 10)
if err != nil {
t.Fatalf("list: %v", err)
}
if feed.UnreadCount != 1 {
t.Fatalf("expected 1 unread after new event, got %d", feed.UnreadCount)
}
}
func TestMarkAllReadRequiresMembership(t *testing.T) {
repo := db.NewMemoryRepository()
svc := NewService(repo)
if _, err := svc.MarkAllRead(context.Background(), domain.Principal{Subject: "nobody"}); err == nil {
t.Fatal("expected membership error for unknown subject")
}
}