mirror of
https://github.com/imjasonh/webpush
synced 2026-07-06 23:52:25 +00:00
241 lines
6.3 KiB
Go
241 lines
6.3 KiB
Go
package webpush_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/imjasonh/webpush"
|
|
"github.com/imjasonh/webpush/keys"
|
|
"github.com/imjasonh/webpush/storage"
|
|
)
|
|
|
|
// TestIntegration_FullFlow tests the complete flow of generating keys,
|
|
// storing subscriptions, and sending push notifications.
|
|
func TestIntegration_FullFlow(t *testing.T) {
|
|
// 1. Generate VAPID keys
|
|
privateKeyB64, publicKeyB64, err := keys.GenerateKeyPair()
|
|
if err != nil {
|
|
t.Fatalf("GenerateKeyPair() error = %v", err)
|
|
}
|
|
t.Logf("Generated public key: %s", publicKeyB64)
|
|
|
|
// 2. Create signer from generated key
|
|
signer, err := keys.NewFileSignerFromBase64(privateKeyB64)
|
|
if err != nil {
|
|
t.Fatalf("NewFileSignerFromBase64() error = %v", err)
|
|
}
|
|
|
|
// Verify public keys match
|
|
if signer.PublicKeyBase64() != publicKeyB64 {
|
|
t.Errorf("Public keys don't match")
|
|
}
|
|
|
|
// 3. Create storage
|
|
store := storage.NewMemory()
|
|
|
|
// 4. Create a mock push service
|
|
pushReceived := make(chan struct{}, 1)
|
|
pushServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Verify headers
|
|
if r.Header.Get("Authorization") == "" {
|
|
t.Error("Missing Authorization header")
|
|
}
|
|
if r.Header.Get("Content-Encoding") != "aes128gcm" {
|
|
t.Errorf("Content-Encoding = %q, want %q", r.Header.Get("Content-Encoding"), "aes128gcm")
|
|
}
|
|
if r.Header.Get("TTL") == "" {
|
|
t.Error("Missing TTL header")
|
|
}
|
|
|
|
// Read body to verify it's encrypted
|
|
body, _ := io.ReadAll(r.Body)
|
|
if len(body) < 86 { // Minimum header size
|
|
t.Errorf("Body too short: %d bytes", len(body))
|
|
}
|
|
|
|
pushReceived <- struct{}{}
|
|
w.WriteHeader(http.StatusCreated)
|
|
}))
|
|
defer pushServer.Close()
|
|
|
|
// 5. Simulate client subscription
|
|
// In real world, this would come from the browser's PushManager.subscribe()
|
|
clientSubJSON := []byte(`{
|
|
"endpoint": "` + pushServer.URL + `/push/abc123",
|
|
"keys": {
|
|
"p256dh": "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM",
|
|
"auth": "tBHItJI5svbpez7KI4CCXg"
|
|
}
|
|
}`)
|
|
|
|
sub, err := webpush.ParseSubscription(clientSubJSON)
|
|
if err != nil {
|
|
t.Fatalf("ParseSubscription() error = %v", err)
|
|
}
|
|
|
|
// 6. Store the subscription
|
|
record := &storage.Record{
|
|
ID: "sub-1",
|
|
UserID: "user-123",
|
|
Subscription: sub,
|
|
}
|
|
if err := store.Save(context.Background(), record); err != nil {
|
|
t.Fatalf("Save() error = %v", err)
|
|
}
|
|
|
|
// 7. Retrieve subscriptions for user
|
|
records, err := store.GetByUserID(context.Background(), "user-123")
|
|
if err != nil {
|
|
t.Fatalf("GetByUserID() error = %v", err)
|
|
}
|
|
if len(records) != 1 {
|
|
t.Fatalf("GetByUserID() count = %d, want 1", len(records))
|
|
}
|
|
|
|
// 8. Create web push client and send notification
|
|
client := webpush.NewClient(signer, "mailto:test@example.com")
|
|
client.WithHTTPClient(pushServer.Client())
|
|
|
|
payload := map[string]string{
|
|
"title": "Hello",
|
|
"body": "World",
|
|
}
|
|
payloadJSON, _ := json.Marshal(payload)
|
|
|
|
err = client.Send(context.Background(), records[0].Subscription, payloadJSON, &webpush.Options{
|
|
TTL: 3600,
|
|
Urgency: "high",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Send() error = %v", err)
|
|
}
|
|
|
|
// 9. Verify push was received
|
|
select {
|
|
case <-pushReceived:
|
|
t.Log("Push notification received by mock service")
|
|
default:
|
|
t.Error("Push notification not received")
|
|
}
|
|
}
|
|
|
|
// TestIntegration_MultipleSubscriptions tests sending to multiple subscriptions.
|
|
func TestIntegration_MultipleSubscriptions(t *testing.T) {
|
|
// Generate keys
|
|
privateKeyB64, _, err := keys.GenerateKeyPair()
|
|
if err != nil {
|
|
t.Fatalf("GenerateKeyPair() error = %v", err)
|
|
}
|
|
|
|
signer, err := keys.NewFileSignerFromBase64(privateKeyB64)
|
|
if err != nil {
|
|
t.Fatalf("NewFileSignerFromBase64() error = %v", err)
|
|
}
|
|
|
|
// Track received pushes
|
|
receivedCount := 0
|
|
pushServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
receivedCount++
|
|
w.WriteHeader(http.StatusCreated)
|
|
}))
|
|
defer pushServer.Close()
|
|
|
|
store := storage.NewMemory()
|
|
ctx := context.Background()
|
|
|
|
// Create multiple subscriptions for same user
|
|
for i := 0; i < 3; i++ {
|
|
record := &storage.Record{
|
|
ID: "sub-" + string(rune('a'+i)),
|
|
UserID: "user-1",
|
|
Subscription: &webpush.Subscription{
|
|
Endpoint: pushServer.URL + "/push/" + string(rune('a'+i)),
|
|
Keys: webpush.Keys{
|
|
P256dh: "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM",
|
|
Auth: "tBHItJI5svbpez7KI4CCXg",
|
|
},
|
|
},
|
|
}
|
|
if err := store.Save(ctx, record); err != nil {
|
|
t.Fatalf("Save() error = %v", err)
|
|
}
|
|
}
|
|
|
|
// Send to all user's subscriptions
|
|
client := webpush.NewClient(signer, "mailto:test@example.com")
|
|
client.WithHTTPClient(pushServer.Client())
|
|
|
|
records, err := store.GetByUserID(ctx, "user-1")
|
|
if err != nil {
|
|
t.Fatalf("GetByUserID() error = %v", err)
|
|
}
|
|
|
|
for _, record := range records {
|
|
err := client.Send(ctx, record.Subscription, []byte(`{"test":true}`), nil)
|
|
if err != nil {
|
|
t.Errorf("Send() error = %v", err)
|
|
}
|
|
}
|
|
|
|
if receivedCount != 3 {
|
|
t.Errorf("Received %d pushes, want 3", receivedCount)
|
|
}
|
|
}
|
|
|
|
// TestIntegration_SQLiteStorage tests the SQLite storage backend.
|
|
func TestIntegration_SQLiteStorage(t *testing.T) {
|
|
store, err := storage.NewSQLite(":memory:")
|
|
if err != nil {
|
|
t.Fatalf("NewSQLite() error = %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Save multiple subscriptions
|
|
for i := 0; i < 5; i++ {
|
|
record := &storage.Record{
|
|
ID: "sub-" + string(rune('a'+i)),
|
|
UserID: "user-1",
|
|
Subscription: &webpush.Subscription{
|
|
Endpoint: "https://push.example.com/" + string(rune('a'+i)),
|
|
Keys: webpush.Keys{
|
|
P256dh: "key-" + string(rune('a'+i)),
|
|
Auth: "auth-" + string(rune('a'+i)),
|
|
},
|
|
},
|
|
}
|
|
if err := store.Save(ctx, record); err != nil {
|
|
t.Fatalf("Save() error = %v", err)
|
|
}
|
|
}
|
|
|
|
// List with pagination
|
|
page1, err := store.List(ctx, 2, 0)
|
|
if err != nil {
|
|
t.Fatalf("List() error = %v", err)
|
|
}
|
|
if len(page1) != 2 {
|
|
t.Errorf("Page 1 len = %d, want 2", len(page1))
|
|
}
|
|
|
|
page2, err := store.List(ctx, 2, 2)
|
|
if err != nil {
|
|
t.Fatalf("List() error = %v", err)
|
|
}
|
|
if len(page2) != 2 {
|
|
t.Errorf("Page 2 len = %d, want 2", len(page2))
|
|
}
|
|
|
|
page3, err := store.List(ctx, 2, 4)
|
|
if err != nil {
|
|
t.Fatalf("List() error = %v", err)
|
|
}
|
|
if len(page3) != 1 {
|
|
t.Errorf("Page 3 len = %d, want 1", len(page3))
|
|
}
|
|
}
|