Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160package db
import ( "context" "path/filepath" "testing" "time"
"tangled.org/core/deliberi/models" "tangled.org/core/orm")
func testDB(t *testing.T) *DB { t.Helper() d, err := Make(context.Background(), filepath.Join(t.TempDir(), "x.db")) if err != nil { t.Fatalf("Make: %v", err) } t.Cleanup(func() { d.Close() }) return d}
// one source record fans out to a row per recipient; read state is per row// (recipient_did, at_uri), so marking one recipient's row read leaves another// recipient's untouched.func TestNotificationsIsolatedPerRecipient(t *testing.T) { d := testDB(t) const uri = "at://did:plc:author/sh.tangled.feed.comment/abc"
for _, did := range []string{"did:plc:alice", "did:plc:bob"} { if err := CreateNotification(d, &models.Notification{ RecipientDid: did, AtUri: uri, Type: models.NotificationTypeIssueCommented, ActorDid: "did:plc:author", }); err != nil { t.Fatalf("CreateNotification %s: %v", did, err) } }
if err := MarkRead(d, "did:plc:alice", uri, true); err != nil { t.Fatalf("MarkRead: %v", err) }
aliceUnread, _ := CountNotifications(d, "did:plc:alice", orm.FilterEq("read", 0)) bobUnread, _ := CountNotifications(d, "did:plc:bob", orm.FilterEq("read", 0)) if aliceUnread != 0 { t.Fatalf("alice unread = %d, want 0", aliceUnread) } if bobUnread != 1 { t.Fatalf("bob unread = %d, want 1 (must not inherit alice's read)", bobUnread) }}
// CreateNotification dedupes on (recipient_did, at_uri): a firehose replay or a// user both subscribed and mentioned yields a single row.func TestCreateNotificationDedupe(t *testing.T) { d := testDB(t) const uri = "at://did:plc:repo/sh.tangled.repo.issue/abc"
for range 2 { if err := CreateNotification(d, &models.Notification{ RecipientDid: "did:plc:alice", AtUri: uri, Type: models.NotificationTypeIssueCreated, ActorDid: "did:plc:author", }); err != nil { t.Fatalf("CreateNotification: %v", err) } }
count, _ := CountNotifications(d, "did:plc:alice") if count != 1 { t.Fatalf("row count = %d, want 1 (deduped)", count) }}
const inviteUri = "at://did:plc:scallop/sh.tangled.repo.collaboratorInvite/did:plc:alice"
func inviteOffer(recipientDid string) *models.Notification { return &models.Notification{ RecipientDid: recipientDid, AtUri: inviteUri, Type: models.NotificationTypeCollaboratorInvited, ActorDid: "did:plc:admin", RepoDid: "did:plc:scallop", KnotDid: "did:web:knot.example", }}
func TestDeleteNotificationIsolatedPerRecipient(t *testing.T) { d := testDB(t)
for _, did := range []string{"did:plc:alice", "did:plc:bob"} { if err := CreateNotification(d, inviteOffer(did)); err != nil { t.Fatalf("CreateNotification %s: %v", did, err) } }
if err := DeleteNotification(d, "did:plc:alice", inviteUri); err != nil { t.Fatalf("DeleteNotification: %v", err) }
aliceRows, _ := CountNotifications(d, "did:plc:alice") bobRows, _ := CountNotifications(d, "did:plc:bob") if aliceRows != 0 { t.Fatalf("alice rows = %d, want 0", aliceRows) } if bobRows != 1 { t.Fatalf("bob rows = %d, want 1 (alice's delete mustn't reach bob)", bobRows) }}
func TestCreateAfterDeleteInserts(t *testing.T) { d := testDB(t)
if err := CreateNotification(d, inviteOffer("did:plc:alice")); err != nil { t.Fatalf("first CreateNotification: %v", err) } if err := DeleteNotification(d, "did:plc:alice", inviteUri); err != nil { t.Fatalf("DeleteNotification: %v", err) } if err := CreateNotification(d, inviteOffer("did:plc:alice")); err != nil { t.Fatalf("second CreateNotification: %v", err) }
rows, err := GetNotifications(d, "did:plc:alice", 0) if err != nil { t.Fatalf("GetNotifications: %v", err) } if len(rows) != 1 { t.Fatalf("row count = %d, want 1 (re-offer must insert)", len(rows)) } if rows[0].KnotDid != "did:web:knot.example" { t.Fatalf("knotDid = %q, want did:web:knot.example", rows[0].KnotDid) }}
func TestCreateNotificationKeepsSuppliedCreated(t *testing.T) { d := testDB(t) made := time.Date(2026, 8, 24, 9, 30, 15, 0, time.UTC)
offer := inviteOffer("did:plc:alice") offer.Created = made if err := CreateNotification(d, offer); err != nil { t.Fatalf("CreateNotification: %v", err) }
rows, err := GetNotifications(d, "did:plc:alice", 0) if err != nil { t.Fatalf("GetNotifications: %v", err) } if len(rows) != 1 { t.Fatalf("row count = %d, want 1", len(rows)) } if !rows[0].Created.Equal(made) { t.Fatalf("created = %s, want %s (offer's own timestamp)", rows[0].Created, made) }}