Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 07b4a69

Browse files
committed
chore: add webhook tests
Signed-off-by: Danny Kopping <[email protected]>
1 parent 5db00ce commit 07b4a69

File tree

3 files changed

+154
-6
lines changed

3 files changed

+154
-6
lines changed

coderd/notifications/dispatch/webhook.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,16 @@ func (w *WebhookHandler) dispatch(msgPayload types.MessagePayload, title, body,
7878
return false, xerrors.Errorf("create HTTP request: %v", err)
7979
}
8080
req.Header.Set("Content-Type", "application/json")
81+
req.Header.Set("X-Message-Id", msgID.String())
8182

8283
// Send request.
8384
resp, err := w.cl.Do(req)
8485
if err != nil {
85-
return true, xerrors.Errorf("failed to send HTTP request: %v", err)
86+
if errors.Is(err, context.DeadlineExceeded) {
87+
return true, xerrors.Errorf("request timeout: %w", err)
88+
}
89+
90+
return true, xerrors.Errorf("request failed: %w", err)
8691
}
8792
defer resp.Body.Close()
8893

@@ -93,11 +98,11 @@ func (w *WebhookHandler) dispatch(msgPayload types.MessagePayload, title, body,
9398
lr := io.LimitReader(resp.Body, int64(len(respBody)))
9499
n, err := lr.Read(respBody)
95100
if err != nil && !errors.Is(err, io.EOF) {
96-
return true, xerrors.Errorf("non-200 response (%d), read body: %w", resp.StatusCode, err)
101+
return true, xerrors.Errorf("non-2xx response (%d), read body: %w", resp.StatusCode, err)
97102
}
98103
w.log.Warn(ctx, "unsuccessful delivery", slog.F("status_code", resp.StatusCode),
99104
slog.F("response", respBody[:n]), slog.F("msg_id", msgID))
100-
return true, xerrors.Errorf("non-200 response (%d)", resp.StatusCode)
105+
return true, xerrors.Errorf("non-2xx response (%d)", resp.StatusCode)
101106
}
102107

103108
return false, nil
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package dispatch_test
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"net/http/httptest"
8+
"net/url"
9+
"testing"
10+
"time"
11+
12+
"github.com/google/uuid"
13+
"github.com/stretchr/testify/require"
14+
15+
"cdr.dev/slog"
16+
"cdr.dev/slog/sloggers/slogtest"
17+
"github.com/coder/serpent"
18+
19+
"github.com/coder/coder/v2/coderd/notifications/dispatch"
20+
"github.com/coder/coder/v2/coderd/notifications/types"
21+
"github.com/coder/coder/v2/codersdk"
22+
"github.com/coder/coder/v2/testutil"
23+
)
24+
25+
func TestWebhook(t *testing.T) {
26+
t.Parallel()
27+
28+
const (
29+
titleTemplate = "this is the title ({{.Labels.foo}})"
30+
bodyTemplate = "this is the body ({{.Labels.baz}})"
31+
)
32+
33+
msgPayload := types.MessagePayload{
34+
Version: "1.0",
35+
NotificationName: "test",
36+
Labels: map[string]string{
37+
"foo": "bar",
38+
"baz": "quux",
39+
},
40+
}
41+
42+
tests := []struct {
43+
name string
44+
serverURL string
45+
serverTimeout time.Duration
46+
serverFn func(uuid.UUID, http.ResponseWriter, *http.Request)
47+
48+
expectSuccess bool
49+
expectRetryable bool
50+
expectErr string
51+
}{
52+
{
53+
name: "successful",
54+
serverFn: func(msgID uuid.UUID, w http.ResponseWriter, r *http.Request) {
55+
var payload dispatch.WebhookPayload
56+
err := json.NewDecoder(r.Body).Decode(&payload)
57+
require.NoError(t, err)
58+
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
59+
require.Equal(t, msgID, payload.MsgID)
60+
require.Equal(t, msgID.String(), r.Header.Get("X-Message-Id"))
61+
62+
w.WriteHeader(http.StatusOK)
63+
_, err = w.Write([]byte(fmt.Sprintf("received %s", payload.MsgID)))
64+
require.NoError(t, err)
65+
},
66+
expectSuccess: true,
67+
},
68+
{
69+
name: "invalid endpoint",
70+
// Build a deliberately invalid URL to fail validation.
71+
serverURL: "invalid .com",
72+
expectSuccess: false,
73+
expectErr: "invalid URL escape",
74+
expectRetryable: false,
75+
},
76+
{
77+
name: "timeout",
78+
serverTimeout: time.Millisecond,
79+
expectSuccess: false,
80+
expectRetryable: true,
81+
expectErr: "request timeout",
82+
},
83+
{
84+
name: "non-200 response",
85+
serverFn: func(_ uuid.UUID, w http.ResponseWriter, r *http.Request) {
86+
w.WriteHeader(http.StatusInternalServerError)
87+
},
88+
expectSuccess: false,
89+
expectRetryable: true,
90+
expectErr: "non-2xx response (500)",
91+
},
92+
}
93+
94+
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
95+
96+
// nolint:paralleltest // Irrelevant as of Go v1.22
97+
for _, tc := range tests {
98+
t.Run(tc.name, func(t *testing.T) {
99+
t.Parallel()
100+
101+
timeout := testutil.WaitLong
102+
if tc.serverTimeout > 0 {
103+
timeout = tc.serverTimeout
104+
}
105+
106+
var (
107+
err error
108+
ctx = testutil.Context(t, timeout)
109+
msgID = uuid.New()
110+
)
111+
112+
var endpoint *url.URL
113+
if tc.serverURL != "" {
114+
endpoint = &url.URL{Host: tc.serverURL}
115+
} else {
116+
// Mock server to simulate webhook endpoint.
117+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118+
tc.serverFn(msgID, w, r)
119+
}))
120+
defer server.Close()
121+
122+
endpoint, err = url.Parse(server.URL)
123+
require.NoError(t, err)
124+
}
125+
126+
cfg := codersdk.NotificationsWebhookConfig{
127+
Endpoint: *serpent.URLOf(endpoint),
128+
}
129+
handler := dispatch.NewWebhookHandler(cfg, logger.With(slog.F("test", tc.name)))
130+
deliveryFn, err := handler.Dispatcher(msgPayload, titleTemplate, bodyTemplate)
131+
require.NoError(t, err)
132+
133+
retryable, err := deliveryFn(ctx, msgID)
134+
if tc.expectSuccess {
135+
require.NoError(t, err)
136+
require.False(t, retryable)
137+
return
138+
}
139+
140+
require.ErrorContains(t, err, tc.expectErr)
141+
require.Equal(t, tc.expectRetryable, retryable)
142+
})
143+
}
144+
}

coderd/notifications/metrics_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,9 @@ func TestPendingUpdatesMetric(t *testing.T) {
263263
interceptor.proceed.Broadcast()
264264

265265
// Validate that the store synced the expected number of updates.
266-
require.Eventuallyf(t, func() bool {
266+
require.Eventually(t, func() bool {
267267
return syncer.sent.Load() == 1 && syncer.failed.Load() == 1
268-
}, testutil.WaitLong, testutil.IntervalFast,
269-
"sent: %d, failed: %d", syncer.sent.Load(), syncer.failed.Load())
268+
}, testutil.WaitShort, testutil.IntervalFast)
270269

271270
// Wait for the updates to be synced and the metric to reflect that.
272271
require.Eventually(t, func() bool {

0 commit comments

Comments
 (0)