Alerting: Add tests for Slack Notifier Image Upload (#49669)

Tests that file bytes are correctly read from storage when an image is
available without a URL. Tests that not found and success cases are both
handled.
This commit is contained in:
Joe Blubaugh
2022-05-27 10:30:44 +08:00
committed by GitHub
parent be06d37a20
commit d5a327f43d
2 changed files with 204 additions and 8 deletions

View File

@@ -7,10 +7,12 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/setting"
@@ -24,17 +26,45 @@ import (
func TestSlackNotifier(t *testing.T) {
tmpl := templateForTests(t)
// Create our temporary image file.
f, err := os.CreateTemp("", "ngalert-images-example*.png")
if err != nil {
panic("Temp file error!")
}
defer func() { _ = os.Remove(f.Name()) }()
fakeImageStore := &fakeImageStore{
Images: []*models.Image{
{
Token: "test-with-url",
URL: "https://www.example.com/image.jpg",
},
{
Token: "test-with-path-not-found",
Path: "usr/home/nouser/noway.jpg",
},
{
Token: "test-with-path-found",
Path: f.Name(), // Has the full path because of how CreateTemp works.
},
},
}
_, _ = f.Write([]byte("test image"))
_ = f.Close()
externalURL, err := url.Parse("http://localhost")
require.NoError(t, err)
tmpl.ExternalURL = externalURL
cases := []struct {
name string
settings string
alerts []*types.Alert
expMsg *slackMessage
expInitError string
expMsgError error
name string
settings string
alerts []*types.Alert
expMsg *slackMessage
expInitError string
expMsgError error
expWebhookURL string
}{
{
name: "Correct config with one alert",
@@ -106,6 +136,114 @@ func TestSlackNotifier(t *testing.T) {
},
expMsgError: nil,
},
{
name: "Image URL in alert appears in slack message",
settings: `{
"token": "1234",
"recipient": "#testchannel",
"icon_emoji": ":emoji:"
}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__alertScreenshotToken__": "test-with-url"},
},
},
},
expMsg: &slackMessage{
Channel: "#testchannel",
Username: "Grafana",
IconEmoji: ":emoji:",
Attachments: []attachment{
{
Title: "[FIRING:1] (val1)",
TitleLink: "http://localhost/alerting/list",
Text: "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
Fallback: "[FIRING:1] (val1)",
Fields: nil,
Footer: "Grafana v" + setting.BuildVersion,
FooterIcon: "https://grafana.com/assets/img/fav32.png",
Color: "#D63232",
Ts: 0,
ImageURL: "https://www.example.com/image.jpg",
},
},
},
expMsgError: nil,
},
{
name: "Image URL with path but no file creates message with no error",
settings: `{
"token": "1234",
"image_upload_url": "https://www.webhook.com",
"recipient": "#testchannel",
"icon_emoji": ":emoji:"
}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__alertScreenshotToken__": "test-with-path-not-found"},
},
},
},
expMsg: &slackMessage{
Channel: "#testchannel",
Username: "Grafana",
IconEmoji: ":emoji:",
Attachments: []attachment{
{
Title: "[FIRING:1] (val1)",
TitleLink: "http://localhost/alerting/list",
Text: "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
Fallback: "[FIRING:1] (val1)",
Fields: nil,
Footer: "Grafana v" + setting.BuildVersion,
FooterIcon: "https://grafana.com/assets/img/fav32.png",
Color: "#D63232",
Ts: 0,
},
},
},
expMsgError: nil,
},
{
name: "Image URL with path and file creates message and uploads image",
settings: `{
"token": "1234",
"recipient": "#testchannel",
"icon_emoji": ":emoji:"
}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__alertScreenshotToken__": "test-with-path-found"},
},
},
},
expMsg: &slackMessage{
Channel: "#testchannel",
Username: "Grafana",
IconEmoji: ":emoji:",
Attachments: []attachment{
{
Title: "[FIRING:1] (val1)",
TitleLink: "http://localhost/alerting/list",
Text: "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
Fallback: "[FIRING:1] (val1)",
Fields: nil,
Footer: "Grafana v" + setting.BuildVersion,
FooterIcon: "https://grafana.com/assets/img/fav32.png",
Color: "#D63232",
Ts: 0,
},
},
},
expMsgError: nil,
expWebhookURL: SlackImageAPIEndpoint,
},
{
name: "Correct config with multiple alerts and template",
settings: `{
@@ -196,6 +334,43 @@ func TestSlackNotifier(t *testing.T) {
},
expMsgError: nil,
},
{
name: "Custom image upload URL",
settings: `{
"token": "1234",
"recipient": "#testchannel",
"icon_emoji": ":emoji:",
"imageUploadUrl": "https://custom-domain.upload"
}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__alertScreenshotToken__": "test-with-path-found"},
},
},
},
expMsg: &slackMessage{
Channel: "#testchannel",
Username: "Grafana",
IconEmoji: ":emoji:",
Attachments: []attachment{
{
Title: "[FIRING:1] (val1)",
TitleLink: "http://localhost/alerting/list",
Text: "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
Fallback: "[FIRING:1] (val1)",
Fields: nil,
Footer: "Grafana v" + setting.BuildVersion,
FooterIcon: "https://grafana.com/assets/img/fav32.png",
Color: "#D63232",
Ts: 0,
},
},
},
expMsgError: nil,
expWebhookURL: "https://custom-domain.upload",
},
}
for _, c := range cases {
@@ -206,6 +381,8 @@ func TestSlackNotifier(t *testing.T) {
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
decryptFn := secretsService.GetDecryptedValue
notificationService := mockNotificationService()
fc := FactoryConfig{
Config: &NotificationChannelConfig{
Name: "slack_testing",
@@ -213,8 +390,9 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON,
SecureSettings: secureSettings,
},
ImageStore: &UnavailableImageStore{},
NotificationService: mockNotificationService(),
ImageStore: fakeImageStore,
// TODO: allow changing the associated values for different tests.
NotificationService: notificationService,
DecryptFunc: decryptFn,
}
@@ -271,6 +449,10 @@ func TestSlackNotifier(t *testing.T) {
require.NoError(t, err)
require.JSONEq(t, string(expBody), body)
// If we should have sent to the webhook, the mock notification service
// will have a record of it.
require.Equal(t, c.expWebhookURL, notificationService.Webhook.Url)
})
}
}

View File

@@ -5,6 +5,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
)
// mockTimeNow replaces function timeNow to return constant time.
@@ -48,3 +49,16 @@ func (ns *notificationServiceMock) SendEmailCommandHandler(ctx context.Context,
}
func mockNotificationService() *notificationServiceMock { return &notificationServiceMock{} }
type fakeImageStore struct {
Images []*ngmodels.Image
}
func (f *fakeImageStore) GetImage(ctx context.Context, token string) (*ngmodels.Image, error) {
for _, img := range f.Images {
if img.Token == token {
return img, nil
}
}
return nil, ngmodels.ErrImageNotFound
}