diff --git a/notify/slack/slack.go b/notify/slack/slack.go index 22bb30932e..1a04a00f1a 100644 --- a/notify/slack/slack.go +++ b/notify/slack/slack.go @@ -18,6 +18,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "os" "strings" @@ -43,6 +44,8 @@ type Notifier struct { logger log.Logger client *http.Client retrier *notify.Retrier + + postJSONFunc func(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error) } // New returns a new Slack notification handler. @@ -53,11 +56,12 @@ func New(c *config.SlackConfig, t *template.Template, l log.Logger, httpOpts ... } return &Notifier{ - conf: c, - tmpl: t, - logger: l, - client: client, - retrier: ¬ify.Retrier{}, + conf: c, + tmpl: t, + logger: l, + client: client, + retrier: ¬ify.Retrier{}, + postJSONFunc: notify.PostJSON, }, nil } @@ -202,7 +206,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) u = strings.TrimSpace(string(content)) } - resp, err := notify.PostJSON(ctx, n.client, u, &buf) + resp, err := n.postJSONFunc(ctx, n.client, u, &buf) if err != nil { return true, notify.RedactURL(err) } @@ -213,5 +217,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) // https://api.slack.com/changelog/2016-05-17-changes-to-errors-for-incoming-webhooks retry, err := n.retrier.Check(resp.StatusCode, resp.Body) err = errors.Wrap(err, fmt.Sprintf("channel %q", req.Channel)) - return retry, err + reasonErr := notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err) + + return retry, reasonErr } diff --git a/notify/slack/slack_test.go b/notify/slack/slack_test.go index 9a4f16cf0a..2329043da5 100644 --- a/notify/slack/slack_test.go +++ b/notify/slack/slack_test.go @@ -14,16 +14,25 @@ package slack import ( + "context" "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" "os" "testing" + "time" "github.com/go-kit/log" commoncfg "github.com/prometheus/common/config" + "github.com/prometheus/common/model" "github.com/stretchr/testify/require" "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify/test" + "github.com/prometheus/alertmanager/types" ) func TestSlackRetry(t *testing.T) { @@ -102,3 +111,61 @@ func TestTrimmingSlackURLFromFile(t *testing.T) { test.AssertNotifyLeaksNoSecret(ctx, t, notifier, u.String()) } + +func TestNotifier_Notify_WithReason(t *testing.T) { + tests := []struct { + name string + statusCode int + expectedReason notify.Reason + }{ + { + name: "with a 4xx status code", + statusCode: http.StatusUnauthorized, + expectedReason: notify.ClientErrorReason, + }, + { + name: "with a 5xx status code", + statusCode: http.StatusInternalServerError, + expectedReason: notify.ServerErrorReason, + }, + { + name: "with any other status code", + statusCode: http.StatusTemporaryRedirect, + expectedReason: notify.DefaultReason, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + apiurl, _ := url.Parse("https://slack.com/post.Message") + notifier, err := New( + &config.SlackConfig{ + NotifierConfig: config.NotifierConfig{}, + HTTPConfig: &commoncfg.HTTPClientConfig{}, + APIURL: &config.SecretURL{URL: apiurl}, + }, + test.CreateTmpl(t), + log.NewNopLogger(), + ) + require.NoError(t, err) + + notifier.postJSONFunc = func(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error) { + resp := httptest.NewRecorder() + resp.WriteHeader(tt.statusCode) + return resp.Result(), nil + } + ctx := context.Background() + ctx = notify.WithGroupKey(ctx, "1") + + alert1 := &types.Alert{ + Alert: model.Alert{ + StartsAt: time.Now(), + EndsAt: time.Now().Add(time.Hour), + }, + } + _, err = notifier.Notify(ctx, alert1) + reasonError, ok := err.(*notify.ErrorWithReason) + require.True(t, ok) + require.Equal(t, tt.expectedReason, reasonError.Reason) + }) + } +}