diff --git a/outputs/webhook.go b/outputs/webhook.go index d70e1f8c..d3b30162 100644 --- a/outputs/webhook.go +++ b/outputs/webhook.go @@ -17,8 +17,9 @@ const ( ) type WebhookOutput struct { - Name string - Url string + Name string + Url string + Headers map[string][]string } func (webhook *WebhookOutput) GetType() string { @@ -47,7 +48,22 @@ func (webhook *WebhookOutput) Init() error { func (webhook *WebhookOutput) Send(content map[string]string) (data.OutputResponse, error) { log.Logger.Infof("Sending webhook to %q", webhook.Url) dataStr := content["description"] //it's not supposed to work with legacy renderer - resp, err := http.Post(webhook.Url, "application/json", strings.NewReader(dataStr)) + + req, err := http.NewRequest("POST", webhook.Url, strings.NewReader(dataStr)) + if err != nil { + log.Logger.Errorf("Sending webhook Error: %v", err) + return data.OutputResponse{}, err + } + req.Header.Set("Content-Type", "application/json") + + for key, values := range webhook.Headers { + for _, v := range values { + req.Header.Add(key, v) + } + } + + client := &http.Client{} + resp, err := client.Do(req) if err != nil { log.Logger.Errorf("Sending webhook Error: %v", err) return data.OutputResponse{}, err diff --git a/outputs/webhook_test.go b/outputs/webhook_test.go new file mode 100644 index 00000000..88fefcee --- /dev/null +++ b/outputs/webhook_test.go @@ -0,0 +1,111 @@ +package outputs + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWebhookOutput_Send_WithHeaders(t *testing.T) { + tests := []struct { + name string + headers map[string][]string + expectedHeaders map[string]string + }{ + { + name: "Bearer token auth", + headers: map[string][]string{ + "Authorization": {"Bearer test-token-123"}, + }, + expectedHeaders: map[string]string{ + "Authorization": "Bearer test-token-123", + "Content-Type": "application/json", + }, + }, + { + name: "Basic auth", + headers: map[string][]string{ + "Authorization": {"Basic dXNlcjpwYXNz"}, + }, + expectedHeaders: map[string]string{ + "Authorization": "Basic dXNlcjpwYXNz", + "Content-Type": "application/json", + }, + }, + { + name: "No headers", + headers: nil, + expectedHeaders: map[string]string{ + "Content-Type": "application/json", + }, + }, + { + name: "Multiple custom headers", + headers: map[string][]string{ + "Authorization": {"Bearer token"}, + "X-Custom": {"custom-value"}, + }, + expectedHeaders: map[string]string{ + "Authorization": "Bearer token", + "X-Custom": "custom-value", + "Content-Type": "application/json", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var capturedHeaders http.Header + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + capturedHeaders = r.Header + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + webhook := &WebhookOutput{ + Name: "test-webhook", + Url: server.URL, + Headers: tt.headers, + } + + _, err := webhook.Send(map[string]string{"description": `{"test": "data"}`}) + assert.NoError(t, err) + + for key, expectedValue := range tt.expectedHeaders { + assert.Equal(t, expectedValue, capturedHeaders.Get(key), "Header %s mismatch", key) + } + }) + } +} + +func TestWebhookOutput_Send_ErrorCases(t *testing.T) { + t.Run("Bad URL", func(t *testing.T) { + webhook := &WebhookOutput{ + Name: "test-webhook", + Url: "not-a-valid-url://invalid", + } + + _, err := webhook.Send(map[string]string{"description": `{}`}) + assert.Error(t, err) + }) + + t.Run("Server returns error status", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte("Unauthorized")) + })) + defer server.Close() + + webhook := &WebhookOutput{ + Name: "test-webhook", + Url: server.URL, + } + + _, err := webhook.Send(map[string]string{"description": `{}`}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Unauthorized") + }) +} diff --git a/router/builders.go b/router/builders.go index dfa7df56..2d23949a 100644 --- a/router/builders.go +++ b/router/builders.go @@ -27,8 +27,9 @@ func buildSplunkOutput(sourceSettings *data.OutputSettings) *outputs.SplunkOutpu func buildWebhookOutput(sourceSettings *data.OutputSettings) *outputs.WebhookOutput { return &outputs.WebhookOutput{ - Name: sourceSettings.Name, - Url: sourceSettings.Url, + Name: sourceSettings.Name, + Url: sourceSettings.Url, + Headers: sourceSettings.Headers, } }