package pushserver import ( "context" "encoding/json" "errors" "io" "log/slog" "net/http" "net/http/httptest" "sync/atomic" "testing" "time" "github.com/dariuszjaniak/warp/server/internal/storage" ) func TestValidateGatewayURL(t *testing.T) { for raw, ok := range map[string]bool{ "https://push.example/_matrix/push/v1/notify": true, "http://10.0.0.5:5000/_matrix/push/v1/notify": true, "https://push.example/notify": false, "ftp://push.example/_matrix/push/v1/notify": false, "/_matrix/push/v1/notify": false, "https://user:pw@push.example/_matrix/push/v1/notify": false, } { err := validateGatewayURL(raw) if (err == nil) != ok { t.Errorf("validateGatewayURL(%q) = %v", raw, err) } if err != nil && !errors.Is(err, ErrInvalidPusher) { t.Errorf("validateGatewayURL(%q) = %v, not ErrInvalidPusher", raw, err) } } } func TestGatewayRetriesServerErrorsAndNotRefusals(t *testing.T) { saved := gatewayRetryDelay gatewayRetryDelay = func(int) time.Duration { return time.Millisecond } t.Cleanup(func() { gatewayRetryDelay = saved }) var calls atomic.Int32 status := atomic.Int32{} status.Store(http.StatusInternalServerError) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls.Add(1) _, _ = io.Copy(io.Discard, r.Body) w.WriteHeader(int(status.Load())) _, _ = w.Write([]byte(`{"rejected":[]}`)) })) defer ts.Close() ctx, cancel := context.WithCancel(context.Background()) defer cancel() g := newGateway(gatewayOptions{ client: ts.Client(), rejected: func(string, string, string) {}, log: slog.New(slog.DiscardHandler), }) g.ctx = ctx job := pushJob{ localpart: "alice", pusher: storage.Pusher{AppID: "a", PushKey: "k", Data: json.RawMessage(`{"url":"` + ts.URL + `/_matrix/push/v1/notify"}`)}, note: notificationPush{EventID: "$e", RoomID: "!r:x"}, } g.deliver(job) if got := calls.Load(); got != gatewayAttempts { t.Errorf("a failing gateway was tried %d times, want %d", got, gatewayAttempts) } calls.Store(0) status.Store(http.StatusBadRequest) g.deliver(job) if got := calls.Load(); got != 1 { t.Errorf("a refusing gateway was tried %d times, want 1", got) } } func TestGatewayPayloadLeavesTheURLOut(t *testing.T) { g := newGateway(gatewayOptions{includeContent: false, log: slog.New(slog.DiscardHandler)}) job := pushJob{ pusher: storage.Pusher{AppID: "a", PushKey: "k", PushKeyTSMS: 5000, Data: json.RawMessage(`{"url":"https://p.example/_matrix/push/v1/notify","extra":1}`)}, note: notificationPush{EventID: "$e", Content: json.RawMessage(`{"body":"secret"}`)}, } raw, err := g.payload(job, pusherData{}) if err != nil { t.Fatal(err) } var body struct { Notification struct { Content json.RawMessage `json:"content"` Devices []struct { PushKeyTS int64 `json:"pushkey_ts"` Data map[string]any `json:"data"` } `json:"devices"` } `json:"notification"` } if err := json.Unmarshal(raw, &body); err != nil { t.Fatal(err) } d := body.Notification.Devices[0] if body.Notification.Content != nil || d.Data["url"] != nil || d.Data["extra"] != float64(1) || d.PushKeyTS != 5 { t.Errorf("payload = %s", raw) } }