package outbound import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/example/notify-gateway/internal/config" ) func TestWebhookDeliveryAndDryRun(t *testing.T) { calls := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls++ if r.Method != "POST" || r.Header.Get("Authorization") != "Bearer private-token" || r.Header.Get("Content-Type") != "application/json" { t.Error("incorrect outbound request") } var payload map[string]any if err := json.NewDecoder(r.Body).Decode(&payload); err != nil || payload["message"] != "Grüße" { t.Error("incorrect payload") } w.WriteHeader(http.StatusNoContent) })) defer srv.Close() o := config.OutboundConfig{ID: "test", Provider: "webhook", URL: srv.URL, BearerToken: "private-token"} r, err := Send(context.Background(), o, map[string]any{"message": "Grüße"}) if err != nil || calls != 0 || !strings.Contains(r.Body, `"dry_run":true`) || strings.Contains(r.Body, "private-token") { t.Fatalf("dry-run: %+v %v calls=%d", r, err, calls) } o.Live = true r, err = Send(context.Background(), o, map[string]any{"message": "Grüße"}) if err != nil || r.StatusCode != 204 || calls != 1 { t.Fatalf("live: %+v %v calls=%d", r, err, calls) } } func TestHTTPFailuresAndRedirectsDoNotLeakSecrets(t *testing.T) { for _, status := range []int{302, 400, 429, 500} { t.Run(fmt.Sprint(status), func(t *testing.T) { calls := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls++ w.Header().Set("Location", "/secret") w.WriteHeader(status) fmt.Fprint(w, "private-token") })) defer srv.Close() r, err := Send(context.Background(), config.OutboundConfig{ID: "test", Provider: "webhook", URL: srv.URL + "/private-token", Live: true}, map[string]any{}) if err == nil || r.StatusCode != status || calls != 1 || r.Body != "" || strings.Contains(err.Error(), "private-token") { t.Fatalf("result=%+v err=%v calls=%d", r, err, calls) } }) } } type transportFunc func(*http.Request) (*http.Response, error) func (f transportFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } func TestDiscordWaitAndTransportErrorRedaction(t *testing.T) { previous := http.DefaultTransport t.Cleanup(func() { http.DefaultTransport = previous }) http.DefaultTransport = transportFunc(func(r *http.Request) (*http.Response, error) { if r.URL.Query().Get("wait") != "true" || r.URL.Query().Get("thread_id") != "123" { t.Error("Discord query parameters missing") } return nil, fmt.Errorf("failure at %s", r.URL) }) _, err := Send(context.Background(), config.OutboundConfig{ID: "discord", Provider: "discord", URL: "https://discord.com/api/webhooks/123/private-token?thread_id=123&wait=false", Live: true}, map[string]any{"content": "hello"}) if err == nil || strings.Contains(err.Error(), "private-token") { t.Fatalf("unexpected error: %v", err) } } func TestCanceledContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() _, err := Send(ctx, config.OutboundConfig{ID: "test", Provider: "webhook", URL: "http://127.0.0.1:1/private-token", Live: true}, map[string]any{}) if err != context.Canceled { t.Fatalf("error=%v", err) } } func TestNtfyAndGotifyProtocols(t *testing.T) { for _, provider := range []string{"ntfy", "gotify"} { t.Run(provider, func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var payload map[string]any if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { t.Fatal(err) } if payload["title"] != "Test" || payload["message"] != "Hello" { t.Error(payload) } if provider == "ntfy" { if payload["topic"] != "ops" || payload["priority"] != float64(5) || r.Header.Get("Authorization") != "Bearer secret" { t.Error("ntfy protocol", payload) } } else { if r.Header.Get("X-Gotify-Key") != "secret" || r.Header.Get("Authorization") != "" { t.Error("gotify auth") } } w.WriteHeader(200) })) defer srv.Close() t.Setenv("TEST_PROVIDER_TOKEN", "secret") _, err := Send(context.Background(), config.OutboundConfig{ID: provider, Provider: provider, URL: srv.URL, Topic: "ops", BearerToken: "env:TEST_PROVIDER_TOKEN", Live: true}, map[string]any{"title": "Test", "message": "Hello", "priority": 9}) if err != nil { t.Fatal(err) } }) } }