package server import ( "fmt" "github.com/stretchr/testify/require" "net/netip" "path/filepath" "testing" "time" ) func TestWebPushStore_UpsertSubscription_SubscriptionsForTopic(t *testing.T) { webPush := newTestWebPushStore(t) defer webPush.Close() require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint, "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"test-topic", "mytopic"})) subs, err := webPush.SubscriptionsForTopic("test-topic") require.Nil(t, err) require.Len(t, subs, 1) require.Equal(t, subs[0].Endpoint, testWebPushEndpoint) require.Equal(t, subs[0].P256dh, "p256dh-key") require.Equal(t, subs[0].Auth, "auth-key") require.Equal(t, subs[0].UserID, "u_1234") subs2, err := webPush.SubscriptionsForTopic("mytopic") require.Nil(t, err) require.Len(t, subs2, 1) require.Equal(t, subs[0].Endpoint, subs2[0].Endpoint) } func TestWebPushStore_UpsertSubscription_SubscriberIPLimitReached(t *testing.T) { webPush := newTestWebPushStore(t) defer webPush.Close() // Insert 10 subscriptions with the same IP address for i := 0; i < 10; i++ { endpoint := fmt.Sprintf(testWebPushEndpoint+"%d", i) require.Nil(t, webPush.UpsertSubscription(endpoint, "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"test-topic", "mytopic"})) } // Another one for the same endpoint should be fine require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint+"0", "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"test-topic", "mytopic"})) // But with a different endpoint it should fail require.Equal(t, errWebPushTooManySubscriptions, webPush.UpsertSubscription(testWebPushEndpoint+"11", "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"test-topic", "mytopic"})) // But with a different IP address it should be fine again require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint+"99", "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("9.9.9.9"), []string{"test-topic", "mytopic"})) } func TestWebPushStore_UpsertSubscription_UpdateTopics(t *testing.T) { webPush := newTestWebPushStore(t) defer webPush.Close() // Insert subscription with two topics, and another with one topic require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint+"0", "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"topic1", "topic2"})) require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint+"1", "auth-key", "p256dh-key", "", netip.MustParseAddr("9.9.9.9"), []string{"topic1"})) subs, err := webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 2) require.Equal(t, testWebPushEndpoint+"0", subs[0].Endpoint) require.Equal(t, testWebPushEndpoint+"1", subs[1].Endpoint) subs, err = webPush.SubscriptionsForTopic("topic2") require.Nil(t, err) require.Len(t, subs, 1) require.Equal(t, testWebPushEndpoint+"0", subs[0].Endpoint) // Update the first subscription to have only one topic require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint+"0", "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"topic1"})) subs, err = webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 2) require.Equal(t, testWebPushEndpoint+"0", subs[0].Endpoint) subs, err = webPush.SubscriptionsForTopic("topic2") require.Nil(t, err) require.Len(t, subs, 0) } func TestWebPushStore_RemoveSubscriptionsByEndpoint(t *testing.T) { webPush := newTestWebPushStore(t) defer webPush.Close() // Insert subscription with two topics require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint, "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"topic1", "topic2"})) subs, err := webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 1) // And remove it again require.Nil(t, webPush.RemoveSubscriptionsByEndpoint(testWebPushEndpoint)) subs, err = webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 0) } func TestWebPushStore_RemoveSubscriptionsByUserID(t *testing.T) { webPush := newTestWebPushStore(t) defer webPush.Close() // Insert subscription with two topics require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint, "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"topic1", "topic2"})) subs, err := webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 1) // And remove it again require.Nil(t, webPush.RemoveSubscriptionsByUserID("u_1234")) subs, err = webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 0) } func TestWebPushStore_RemoveSubscriptionsByUserID_Empty(t *testing.T) { webPush := newTestWebPushStore(t) defer webPush.Close() require.Equal(t, errWebPushUserIDCannotBeEmpty, webPush.RemoveSubscriptionsByUserID("")) } func TestWebPushStore_MarkExpiryWarningSent(t *testing.T) { webPush := newTestWebPushStore(t) defer webPush.Close() // Insert subscription with two topics require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint, "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"topic1", "topic2"})) subs, err := webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 1) // Mark them as warning sent require.Nil(t, webPush.MarkExpiryWarningSent(subs)) rows, err := webPush.db.Query("SELECT endpoint FROM subscription WHERE warned_at > 0") require.Nil(t, err) defer rows.Close() var endpoint string require.True(t, rows.Next()) require.Nil(t, rows.Scan(&endpoint)) require.Nil(t, err) require.Equal(t, testWebPushEndpoint, endpoint) require.False(t, rows.Next()) } func TestWebPushStore_SubscriptionsExpiring(t *testing.T) { webPush := newTestWebPushStore(t) defer webPush.Close() // Insert subscription with two topics require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint, "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"topic1", "topic2"})) subs, err := webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 1) // Fake-mark them as soon-to-expire _, err = webPush.db.Exec("UPDATE subscription SET updated_at = ? WHERE endpoint = ?", time.Now().Add(-8*24*time.Hour).Unix(), testWebPushEndpoint) require.Nil(t, err) // Should not be cleaned up yet require.Nil(t, webPush.RemoveExpiredSubscriptions(9*24*time.Hour)) // Run expiration subs, err = webPush.SubscriptionsExpiring(7 * 24 * time.Hour) require.Nil(t, err) require.Len(t, subs, 1) require.Equal(t, testWebPushEndpoint, subs[0].Endpoint) } func TestWebPushStore_RemoveExpiredSubscriptions(t *testing.T) { webPush := newTestWebPushStore(t) defer webPush.Close() // Insert subscription with two topics require.Nil(t, webPush.UpsertSubscription(testWebPushEndpoint, "auth-key", "p256dh-key", "u_1234", netip.MustParseAddr("1.2.3.4"), []string{"topic1", "topic2"})) subs, err := webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 1) // Fake-mark them as expired _, err = webPush.db.Exec("UPDATE subscription SET updated_at = ? WHERE endpoint = ?", time.Now().Add(-10*24*time.Hour).Unix(), testWebPushEndpoint) require.Nil(t, err) // Run expiration require.Nil(t, webPush.RemoveExpiredSubscriptions(9*24*time.Hour)) // List again, should be 0 subs, err = webPush.SubscriptionsForTopic("topic1") require.Nil(t, err) require.Len(t, subs, 0) } func newTestWebPushStore(t *testing.T) *webPushStore { webPush, err := newWebPushStore(filepath.Join(t.TempDir(), "webpush.db")) require.Nil(t, err) return webPush }