mirror of
https://github.com/imjasonh/webpush
synced 2026-07-15 11:35:53 +00:00
Implement Web Push API Go library with VAPID support and pluggable storage
Co-authored-by: imjasonh <210737+imjasonh@users.noreply.github.com>
This commit is contained in:
parent
7d0082c045
commit
78bd40dc88
15 changed files with 2708 additions and 1 deletions
250
storage/storage_test.go
Normal file
250
storage/storage_test.go
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/imjasonh/webpush"
|
||||
)
|
||||
|
||||
func TestMemory(t *testing.T) {
|
||||
testStorage(t, NewMemory())
|
||||
}
|
||||
|
||||
func TestSQLite(t *testing.T) {
|
||||
// Use in-memory SQLite for testing
|
||||
storage, err := NewSQLite(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("NewSQLite() error = %v", err)
|
||||
}
|
||||
defer storage.Close()
|
||||
|
||||
testStorage(t, storage)
|
||||
}
|
||||
|
||||
func testStorage(t *testing.T, s Storage) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Test Save and Get
|
||||
record := &Record{
|
||||
ID: "test-id-1",
|
||||
UserID: "user-1",
|
||||
Subscription: &webpush.Subscription{
|
||||
Endpoint: "https://push.example.com/abc123",
|
||||
Keys: webpush.Keys{
|
||||
P256dh: "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM",
|
||||
Auth: "tBHItJI5svbpez7KI4CCXg",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.Save(ctx, record); err != nil {
|
||||
t.Fatalf("Save() error = %v", err)
|
||||
}
|
||||
|
||||
// Get by ID
|
||||
got, err := s.Get(ctx, record.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Get() error = %v", err)
|
||||
}
|
||||
if got.ID != record.ID {
|
||||
t.Errorf("Get() ID = %q, want %q", got.ID, record.ID)
|
||||
}
|
||||
if got.UserID != record.UserID {
|
||||
t.Errorf("Get() UserID = %q, want %q", got.UserID, record.UserID)
|
||||
}
|
||||
if got.Subscription.Endpoint != record.Subscription.Endpoint {
|
||||
t.Errorf("Get() Endpoint = %q, want %q", got.Subscription.Endpoint, record.Subscription.Endpoint)
|
||||
}
|
||||
if got.CreatedAt.IsZero() {
|
||||
t.Error("Get() CreatedAt is zero")
|
||||
}
|
||||
|
||||
// Get by endpoint
|
||||
got, err = s.GetByEndpoint(ctx, record.Subscription.Endpoint)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByEndpoint() error = %v", err)
|
||||
}
|
||||
if got.ID != record.ID {
|
||||
t.Errorf("GetByEndpoint() ID = %q, want %q", got.ID, record.ID)
|
||||
}
|
||||
|
||||
// Get by user ID
|
||||
records, err := s.GetByUserID(ctx, record.UserID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByUserID() error = %v", err)
|
||||
}
|
||||
if len(records) != 1 {
|
||||
t.Errorf("GetByUserID() count = %d, want 1", len(records))
|
||||
}
|
||||
|
||||
// Add another record for same user
|
||||
record2 := &Record{
|
||||
ID: "test-id-2",
|
||||
UserID: "user-1",
|
||||
Subscription: &webpush.Subscription{
|
||||
Endpoint: "https://push.example.com/def456",
|
||||
Keys: webpush.Keys{
|
||||
P256dh: "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM",
|
||||
Auth: "tBHItJI5svbpez7KI4CCXg",
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := s.Save(ctx, record2); err != nil {
|
||||
t.Fatalf("Save() error = %v", err)
|
||||
}
|
||||
|
||||
// Get by user ID should return 2
|
||||
records, err = s.GetByUserID(ctx, "user-1")
|
||||
if err != nil {
|
||||
t.Fatalf("GetByUserID() error = %v", err)
|
||||
}
|
||||
if len(records) != 2 {
|
||||
t.Errorf("GetByUserID() count = %d, want 2", len(records))
|
||||
}
|
||||
|
||||
// List
|
||||
records, err = s.List(ctx, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("List() error = %v", err)
|
||||
}
|
||||
if len(records) != 2 {
|
||||
t.Errorf("List() count = %d, want 2", len(records))
|
||||
}
|
||||
|
||||
// List with pagination
|
||||
records, err = s.List(ctx, 1, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("List() error = %v", err)
|
||||
}
|
||||
if len(records) != 1 {
|
||||
t.Errorf("List(limit=1) count = %d, want 1", len(records))
|
||||
}
|
||||
|
||||
// Delete by ID
|
||||
if err := s.Delete(ctx, record.ID); err != nil {
|
||||
t.Fatalf("Delete() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify deleted
|
||||
_, err = s.Get(ctx, record.ID)
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Get() after delete error = %v, want ErrNotFound", err)
|
||||
}
|
||||
|
||||
// Delete by endpoint
|
||||
if err := s.DeleteByEndpoint(ctx, record2.Subscription.Endpoint); err != nil {
|
||||
t.Fatalf("DeleteByEndpoint() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify deleted
|
||||
_, err = s.GetByEndpoint(ctx, record2.Subscription.Endpoint)
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("GetByEndpoint() after delete error = %v, want ErrNotFound", err)
|
||||
}
|
||||
|
||||
// List should be empty now
|
||||
records, err = s.List(ctx, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("List() error = %v", err)
|
||||
}
|
||||
if len(records) != 0 {
|
||||
t.Errorf("List() count = %d, want 0", len(records))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemory_NotFound(t *testing.T) {
|
||||
s := NewMemory()
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := s.Get(ctx, "nonexistent")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Get() error = %v, want ErrNotFound", err)
|
||||
}
|
||||
|
||||
_, err = s.GetByEndpoint(ctx, "https://nonexistent")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("GetByEndpoint() error = %v, want ErrNotFound", err)
|
||||
}
|
||||
|
||||
err = s.Delete(ctx, "nonexistent")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Delete() error = %v, want ErrNotFound", err)
|
||||
}
|
||||
|
||||
err = s.DeleteByEndpoint(ctx, "https://nonexistent")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("DeleteByEndpoint() error = %v, want ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemory_Update(t *testing.T) {
|
||||
s := NewMemory()
|
||||
ctx := context.Background()
|
||||
|
||||
record := &Record{
|
||||
ID: "test-id",
|
||||
UserID: "user-1",
|
||||
Subscription: &webpush.Subscription{
|
||||
Endpoint: "https://push.example.com/abc123",
|
||||
Keys: webpush.Keys{
|
||||
P256dh: "key1",
|
||||
Auth: "auth1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.Save(ctx, record); err != nil {
|
||||
t.Fatalf("Save() error = %v", err)
|
||||
}
|
||||
|
||||
// Update
|
||||
record.UserID = "user-2"
|
||||
record.Subscription.Endpoint = "https://push.example.com/new"
|
||||
|
||||
if err := s.Save(ctx, record); err != nil {
|
||||
t.Fatalf("Save() error = %v", err)
|
||||
}
|
||||
|
||||
got, err := s.Get(ctx, record.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Get() error = %v", err)
|
||||
}
|
||||
|
||||
if got.UserID != "user-2" {
|
||||
t.Errorf("Get() UserID = %q, want %q", got.UserID, "user-2")
|
||||
}
|
||||
if got.Subscription.Endpoint != "https://push.example.com/new" {
|
||||
t.Errorf("Get() Endpoint = %q, want %q", got.Subscription.Endpoint, "https://push.example.com/new")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLite_NotFound(t *testing.T) {
|
||||
s, err := NewSQLite(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("NewSQLite() error = %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
_, err = s.Get(ctx, "nonexistent")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Get() error = %v, want ErrNotFound", err)
|
||||
}
|
||||
|
||||
_, err = s.GetByEndpoint(ctx, "https://nonexistent")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("GetByEndpoint() error = %v, want ErrNotFound", err)
|
||||
}
|
||||
|
||||
err = s.Delete(ctx, "nonexistent")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Delete() error = %v, want ErrNotFound", err)
|
||||
}
|
||||
|
||||
err = s.DeleteByEndpoint(ctx, "https://nonexistent")
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("DeleteByEndpoint() error = %v, want ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue