Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
6.4 kB · 161 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162package statedb
import ( "context" "encoding/json" "testing"
glex "github.com/streamplace/glex/runtime" "github.com/stretchr/testify/require" "stream.place/streamplace/pkg/appbsky" notificationpkg "stream.place/streamplace/pkg/notifications" "stream.place/streamplace/pkg/placestream")
// TestNotificationRepoDIDPreserved guards against the regression where// re-registering a push token without a repoDID wiped out the existing// association. CreateNotification used to DB.Save() the row, which issues a// full-row UPDATE and blanked repo_did whenever the client re-registered// before its OAuth session had restored, silently dropping the device from// follower livestream blasts.func TestNotificationRepoDIDPreserved(t *testing.T) { WithAllDatabases(t, func(state *StatefulDB) { const token = "device-token-1" const didA = "did:plc:aaaa" const didB = "did:plc:bbbb"
// Initial registration while logged in associates the DID. require.NoError(t, state.CreateNotification(token, didA, NotificationTypeFirebase)) tokens, err := state.GetManyNotificationTokens([]string{didA}) require.NoError(t, err) require.Equal(t, []string{token}, tokens)
// Re-registration without a DID (e.g. before the OAuth session has // restored) must NOT clobber the existing association. require.NoError(t, state.CreateNotification(token, "", NotificationTypeFirebase)) tokens, err = state.GetManyNotificationTokens([]string{didA}) require.NoError(t, err) require.Equal(t, []string{token}, tokens, "repo_did was wiped by a DID-less re-registration")
// ...and it must not have duplicated the row (token is the primary key). nots, err := state.ListNotifications() require.NoError(t, err) require.Len(t, nots, 1)
// Re-registering with a different DID replaces the association. require.NoError(t, state.CreateNotification(token, didB, NotificationTypeFirebase)) tokens, err = state.GetManyNotificationTokens([]string{didB}) require.NoError(t, err) require.Equal(t, []string{token}, tokens) tokens, err = state.GetManyNotificationTokens([]string{didA}) require.NoError(t, err) require.Empty(t, tokens, "old DID association should be replaced") })}
// TestNotificationAnonymousThenAssociated covers a token that first registers// with no DID (anonymous) and later gets associated once the user logs in,// without creating a duplicate row.func TestNotificationAnonymousThenAssociated(t *testing.T) { WithAllDatabases(t, func(state *StatefulDB) { const token = "device-token-2" const did = "did:plc:cccc"
// Anonymous registration: the row exists but has no association yet. require.NoError(t, state.CreateNotification(token, "", NotificationTypeFirebase)) tokens, err := state.GetManyNotificationTokens([]string{did}) require.NoError(t, err) require.Empty(t, tokens) nots, err := state.ListNotifications() require.NoError(t, err) require.Len(t, nots, 1)
// Once logged in, the association is set without adding a new row. require.NoError(t, state.CreateNotification(token, did, NotificationTypeFirebase)) tokens, err = state.GetManyNotificationTokens([]string{did}) require.NoError(t, err) require.Equal(t, []string{token}, tokens) nots, err = state.ListNotifications() require.NoError(t, err) require.Len(t, nots, 1) })}
// TestNotificationTypeAndDelete covers the Type column (firebase vs web) and// the DeleteNotification path used when a web client unsubscribes.func TestNotificationTypeAndDelete(t *testing.T) { WithAllDatabases(t, func(state *StatefulDB) { const did = "did:plc:dddd" const fbToken = "firebase-token-1" const webToken = `{"endpoint":"https://push.example/abc","keys":{"p256dh":"x","auth":"y"}}`
// Register one firebase and one web subscription for the same user. require.NoError(t, state.CreateNotification(fbToken, did, NotificationTypeFirebase)) require.NoError(t, state.CreateNotification(webToken, did, NotificationTypeWeb))
// GetManyNotifications returns both rows with their types intact. nots, err := state.GetManyNotifications([]string{did}) require.NoError(t, err) require.Len(t, nots, 2)
byType := map[NotificationType]Notification{} for _, n := range nots { byType[n.Type] = n } require.Contains(t, byType, NotificationTypeFirebase) require.Contains(t, byType, NotificationTypeWeb) require.Equal(t, fbToken, byType[NotificationTypeFirebase].Token) require.Equal(t, webToken, byType[NotificationTypeWeb].Token)
// An empty type defaults to firebase. require.NoError(t, state.CreateNotification("defaulted-token", did, "")) nots, err = state.GetManyNotifications([]string{did}) require.NoError(t, err) var defaulted Notification for _, n := range nots { if n.Token == "defaulted-token" { defaulted = n } } require.Equal(t, NotificationTypeFirebase, defaulted.Type, "empty type should default to firebase")
// DeleteNotification removes the row; deleting a missing row is not an error. require.NoError(t, state.DeleteNotification(webToken)) nots, err = state.GetManyNotifications([]string{did}) require.NoError(t, err) require.Len(t, nots, 2, "web token should be gone, leaving firebase + defaulted") for _, n := range nots { require.NotEqual(t, webToken, n.Token) } require.NoError(t, state.DeleteNotification("never-existed")) })}
type identityNotifier struct { blast *notificationpkg.NotificationBlast}
func (n *identityNotifier) Blast(_ context.Context, _ []notificationpkg.NotificationTarget, blast *notificationpkg.NotificationBlast) error { n.blast = blast return nil}
func TestInvalidHandleNotification(t *testing.T) { WithAllDatabases(t, func(state *StatefulDB) { const did = "did:web:streamer.example" noter := &identityNotifier{} state.SetNotifier(noter) payload, err := json.Marshal(NotificationTask{ Livestream: placestream.Livestream_LivestreamView{ Author: appbsky.ActorDefs_ProfileViewBasic{Did: did, Handle: "handle.invalid"}, Record: &glex.LexiconTypeDecoder{Val: &placestream.Livestream{Title: "Live"}}, }, }) require.NoError(t, err) require.NoError(t, state.processNotificationTask(t.Context(), &AppTask{Payload: payload})) require.NotNil(t, noter.blast) require.Contains(t, noter.blast.Title, did) require.NotContains(t, noter.blast.Title, "@did:") require.Equal(t, "/"+did, noter.blast.Data["path"], "notification clicks preserve the account's DID") })}