mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
This PR aims to close #14253 We keep the default behavior using the Coder logo if there's no logo set. Otherwise we want to use the logo based on the URL set in appearance. --------- Co-authored-by: defelmnq <yvincent@coder.com>
159 lines
4.1 KiB
Go
159 lines
4.1 KiB
Go
package dispatch_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"cdr.dev/slog"
|
|
"cdr.dev/slog/sloggers/slogtest"
|
|
"github.com/coder/serpent"
|
|
|
|
"github.com/coder/coder/v2/coderd/notifications/dispatch"
|
|
"github.com/coder/coder/v2/coderd/notifications/types"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
func TestWebhook(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
titlePlaintext = "this is the title"
|
|
titleMarkdown = "this *is* _the_ title"
|
|
bodyPlaintext = "this is the body"
|
|
bodyMarkdown = "~this~ is the `body`"
|
|
)
|
|
|
|
msgPayload := types.MessagePayload{
|
|
Version: "1.0",
|
|
NotificationName: "test",
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
serverURL string
|
|
serverDeadline time.Time
|
|
serverFn func(uuid.UUID, http.ResponseWriter, *http.Request)
|
|
|
|
expectSuccess bool
|
|
expectRetryable bool
|
|
expectErr string
|
|
}{
|
|
{
|
|
name: "successful",
|
|
serverFn: func(msgID uuid.UUID, w http.ResponseWriter, r *http.Request) {
|
|
var payload dispatch.WebhookPayload
|
|
err := json.NewDecoder(r.Body).Decode(&payload)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
assert.Equal(t, msgID, payload.MsgID)
|
|
assert.Equal(t, msgID.String(), r.Header.Get("X-Message-Id"))
|
|
|
|
assert.Equal(t, titlePlaintext, payload.Title)
|
|
assert.Equal(t, titleMarkdown, payload.TitleMarkdown)
|
|
assert.Equal(t, bodyPlaintext, payload.Body)
|
|
assert.Equal(t, bodyMarkdown, payload.BodyMarkdown)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err = w.Write([]byte(fmt.Sprintf("received %s", payload.MsgID)))
|
|
assert.NoError(t, err)
|
|
},
|
|
expectSuccess: true,
|
|
},
|
|
{
|
|
name: "invalid endpoint",
|
|
// Build a deliberately invalid URL to fail validation.
|
|
serverURL: "invalid .com",
|
|
expectSuccess: false,
|
|
expectErr: "invalid URL escape",
|
|
expectRetryable: false,
|
|
},
|
|
{
|
|
name: "timeout",
|
|
serverDeadline: time.Now().Add(-time.Hour),
|
|
expectSuccess: false,
|
|
expectRetryable: true,
|
|
serverFn: func(u uuid.UUID, writer http.ResponseWriter, request *http.Request) {
|
|
t.Fatalf("should not get here")
|
|
},
|
|
expectErr: "request timeout",
|
|
},
|
|
{
|
|
name: "non-200 response",
|
|
serverFn: func(_ uuid.UUID, w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
},
|
|
expectSuccess: false,
|
|
expectRetryable: true,
|
|
expectErr: "non-2xx response (500)",
|
|
},
|
|
}
|
|
|
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
|
|
|
// nolint:paralleltest // Irrelevant as of Go v1.22
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
)
|
|
|
|
if !tc.serverDeadline.IsZero() {
|
|
ctx, cancel = context.WithDeadline(context.Background(), tc.serverDeadline)
|
|
} else {
|
|
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
}
|
|
t.Cleanup(cancel)
|
|
|
|
var (
|
|
err error
|
|
msgID = uuid.New()
|
|
)
|
|
|
|
var endpoint *url.URL
|
|
if tc.serverURL != "" {
|
|
endpoint = &url.URL{Host: tc.serverURL}
|
|
} else {
|
|
// Mock server to simulate webhook endpoint.
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
tc.serverFn(msgID, w, r)
|
|
}))
|
|
defer server.Close()
|
|
|
|
endpoint, err = url.Parse(server.URL)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
cfg := codersdk.NotificationsWebhookConfig{
|
|
Endpoint: *serpent.URLOf(endpoint),
|
|
}
|
|
handler := dispatch.NewWebhookHandler(cfg, logger.With(slog.F("test", tc.name)))
|
|
deliveryFn, err := handler.Dispatcher(msgPayload, titleMarkdown, bodyMarkdown, helpers())
|
|
require.NoError(t, err)
|
|
|
|
retryable, err := deliveryFn(ctx, msgID)
|
|
if tc.expectSuccess {
|
|
require.NoError(t, err)
|
|
require.False(t, retryable)
|
|
return
|
|
}
|
|
|
|
require.ErrorContains(t, err, tc.expectErr)
|
|
require.Equal(t, tc.expectRetryable, retryable)
|
|
})
|
|
}
|
|
}
|