package notify import "context" import "encoding/json" import "io" import "net/http" import "net/http/httptest" import "testing" import "codit/internal/models" import "codit/internal/monitor" func testEvent() models.Event { return models.Event{ Kind: "monitor.down", Severity: "critical", Title: "api.example.com is down", Body: "connection refused", SourceType: models.EventSourceMonitor, SourceID: "mon-123", Payload: `{"state":"down"}`, CreatedAt: 1700000000, } } func TestDeliverWebhookPostsJSONWithAuthHeader(t *testing.T) { var gotMethod string var gotAuth string var gotType string var payload webhookPayload var srv *httptest.Server var d *Dispatcher var target models.DeliveryTarget var err error srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body []byte gotMethod = r.Method gotAuth = r.Header.Get("X-Token") gotType = r.Header.Get("Content-Type") body, _ = io.ReadAll(r.Body) _ = json.Unmarshal(body, &payload) w.WriteHeader(http.StatusNoContent) })) defer srv.Close() d = NewDispatcher(nil, "", "", monitor.DefaultEgressPolicy(), "", nil) target = models.DeliveryTarget{ ChannelName: "ops-webhook", TransportType: models.TransportTypeWebhook, Config: `{"header_name":"X-Token"}`, Address: srv.URL, } err = d.deliverWebhook(context.Background(), target, map[string]string{"token": "s3cr3t"}, testEvent()) if err != nil { t.Fatalf("deliverWebhook returned error: %v", err) } if gotMethod != http.MethodPost { t.Fatalf("expected POST, got %s", gotMethod) } if gotType != "application/json" { t.Fatalf("expected application/json content-type, got %q", gotType) } if gotAuth != "s3cr3t" { t.Fatalf("expected auth header X-Token=s3cr3t, got %q", gotAuth) } if payload.Kind != "monitor.down" || payload.SourceID != "mon-123" || payload.Channel != "ops-webhook" { t.Fatalf("unexpected payload: %+v", payload) } } func TestDeliverWebhookErrorsOnBadStatus(t *testing.T) { var srv *httptest.Server var d *Dispatcher var err error srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() d = NewDispatcher(nil, "", "", monitor.DefaultEgressPolicy(), "", nil) err = d.deliverWebhook(context.Background(), models.DeliveryTarget{ ChannelName: "ops-webhook", TransportType: models.TransportTypeWebhook, Address: srv.URL, }, map[string]string{}, testEvent()) if err == nil { t.Fatalf("expected error on 500 response, got nil") } } func TestClientForDefaultsToSharedClient(t *testing.T) { var d *Dispatcher var c *http.Client var err error d = NewDispatcher(nil, "", "", monitor.DefaultEgressPolicy(), "", nil) c, err = d.clientFor(webhookConfig{}) if err != nil { t.Fatalf("unexpected error: %v", err) } if c != d.client { t.Fatalf("no TLS options should reuse the shared client") } } func TestClientForInsecureSkipVerifyBuildsClient(t *testing.T) { var d *Dispatcher var c *http.Client var err error d = NewDispatcher(nil, "", "", monitor.DefaultEgressPolicy(), "", nil) c, err = d.clientFor(webhookConfig{InsecureSkipVerify: true}) if err != nil { t.Fatalf("unexpected error: %v", err) } if c == d.client { t.Fatalf("insecure_skip_verify should build a dedicated client") } } func TestClientForFailsClosedWhenCAUnresolvable(t *testing.T) { var d *Dispatcher var err error // nil store => the pinned CA cannot be resolved; must fail closed rather // than fall back to system roots. d = NewDispatcher(nil, "", "", monitor.DefaultEgressPolicy(), "", nil) _, err = d.clientFor(webhookConfig{CARef: "ca-123"}) if err == nil { t.Fatalf("expected fail-closed error when CA cannot be resolved") } } func TestDeliverWebhookErrorsOnEmptyAddress(t *testing.T) { var d *Dispatcher var err error d = NewDispatcher(nil, "", "", monitor.DefaultEgressPolicy(), "", nil) err = d.deliverWebhook(context.Background(), models.DeliveryTarget{ChannelName: "x", TransportType: models.TransportTypeWebhook}, map[string]string{}, testEvent()) if err == nil { t.Fatalf("expected error on empty address, got nil") } }