- Enhanced Notifications component tests to include support for Discord, Gotify, and Webhook provider types. - Updated test cases to validate the correct handling of provider type options and ensure proper payload structure during creation, preview, and testing. - Introduced new tests for Gotify token handling and ensured sensitive information is not exposed in the UI. - Refactored existing tests for clarity and maintainability, including improved assertions and error handling. - Added comprehensive coverage for payload validation scenarios, including malformed requests and security checks against SSRF and oversized payloads.
125 lines
3.4 KiB
Go
125 lines
3.4 KiB
Go
//go:build integration
|
|
// +build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/notifications"
|
|
)
|
|
|
|
func TestNotificationHTTPWrapperIntegration_RetriesOn429AndSucceeds(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var calls int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
current := atomic.AddInt32(&calls, 1)
|
|
if current == 1 {
|
|
w.WriteHeader(http.StatusTooManyRequests)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`{"ok":true}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
wrapper := notifications.NewNotifyHTTPWrapper()
|
|
result, err := wrapper.Send(context.Background(), notifications.HTTPWrapperRequest{
|
|
URL: server.URL,
|
|
Body: []byte(`{"message":"hello"}`),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("expected retry success, got error: %v", err)
|
|
}
|
|
if result.Attempts != 2 {
|
|
t.Fatalf("expected 2 attempts, got %d", result.Attempts)
|
|
}
|
|
}
|
|
|
|
func TestNotificationHTTPWrapperIntegration_DoesNotRetryOn400(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var calls int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
atomic.AddInt32(&calls, 1)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}))
|
|
defer server.Close()
|
|
|
|
wrapper := notifications.NewNotifyHTTPWrapper()
|
|
_, err := wrapper.Send(context.Background(), notifications.HTTPWrapperRequest{
|
|
URL: server.URL,
|
|
Body: []byte(`{"message":"hello"}`),
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("expected non-retryable 400 error")
|
|
}
|
|
if atomic.LoadInt32(&calls) != 1 {
|
|
t.Fatalf("expected one request attempt, got %d", calls)
|
|
}
|
|
}
|
|
|
|
func TestNotificationHTTPWrapperIntegration_RejectsTokenizedQueryWithoutEcho(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
wrapper := notifications.NewNotifyHTTPWrapper()
|
|
secret := "pr1-secret-token-value"
|
|
_, err := wrapper.Send(context.Background(), notifications.HTTPWrapperRequest{
|
|
URL: "http://example.com/hook?token=" + secret,
|
|
Body: []byte(`{"message":"hello"}`),
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("expected tokenized query rejection")
|
|
}
|
|
if !strings.Contains(err.Error(), "query authentication is not allowed") {
|
|
t.Fatalf("expected sanitized query-auth rejection, got: %v", err)
|
|
}
|
|
if strings.Contains(err.Error(), secret) {
|
|
t.Fatalf("error must not echo secret token")
|
|
}
|
|
}
|
|
|
|
func TestNotificationHTTPWrapperIntegration_HeaderAllowlistSafety(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var seenAuthHeader string
|
|
var seenCookieHeader string
|
|
var seenGotifyKey string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
seenAuthHeader = r.Header.Get("Authorization")
|
|
seenCookieHeader = r.Header.Get("Cookie")
|
|
seenGotifyKey = r.Header.Get("X-Gotify-Key")
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
wrapper := notifications.NewNotifyHTTPWrapper()
|
|
_, err := wrapper.Send(context.Background(), notifications.HTTPWrapperRequest{
|
|
URL: server.URL,
|
|
Headers: map[string]string{
|
|
"Authorization": "Bearer should-not-leak",
|
|
"Cookie": "session=should-not-leak",
|
|
"X-Gotify-Key": "allowed-token",
|
|
},
|
|
Body: []byte(`{"message":"hello"}`),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("expected success, got error: %v", err)
|
|
}
|
|
if seenAuthHeader != "" {
|
|
t.Fatalf("authorization header must be stripped")
|
|
}
|
|
if seenCookieHeader != "" {
|
|
t.Fatalf("cookie header must be stripped")
|
|
}
|
|
if seenGotifyKey != "allowed-token" {
|
|
t.Fatalf("expected X-Gotify-Key to pass through")
|
|
}
|
|
}
|