From 492912845f0c3c3e8c1e8ee758b2b7fd5923cd84 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 14 Jan 2020 17:27:16 +0100 Subject: [PATCH] Chore: Adds basic alerting notification service tests (#21467) Adds basic alerting notification tests. First steps towards getting #18011 merged. Ref #18011 --- go.sum | 4 - pkg/services/alerting/notifier.go | 7 +- pkg/services/alerting/notifier_test.go | 238 +++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 pkg/services/alerting/notifier_test.go diff --git a/go.sum b/go.sum index 9d534c6073e..4efa0137927 100644 --- a/go.sum +++ b/go.sum @@ -123,10 +123,6 @@ github.com/gosimple/slug v1.4.2 h1:jDmprx3q/9Lfk4FkGZtvzDQ9Cj9eAmsjzeQGp24PeiQ= github.com/gosimple/slug v1.4.2/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0= github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 h1:SPdxCL9BChFTlyi0Khv64vdCW4TMna8+sxL7+Chx+Ag= github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4/go.mod h1:nc0XxBzjeGcrMltCDw269LoWF9S8ibhgxolCdA1R8To= -github.com/grafana/grafana-plugin-sdk-go v0.4.1-0.20200106141801-810a19341b9e h1:wRxFEkkFzqVAes3zBTVrNDrVfnknj89dtsr18wcfqH0= -github.com/grafana/grafana-plugin-sdk-go v0.4.1-0.20200106141801-810a19341b9e/go.mod h1:ve2cjkGc2fafTxW3KvhFcE6h0la7La6Z3FCfPqxx+Jk= -github.com/grafana/grafana-plugin-sdk-go v0.4.1-0.20200107153407-ccbf1374e434 h1:j9tPXQ/Wri4oZqySnyL/TrFhXshOHn9Wg1rz9mfNzO8= -github.com/grafana/grafana-plugin-sdk-go v0.4.1-0.20200107153407-ccbf1374e434/go.mod h1:ve2cjkGc2fafTxW3KvhFcE6h0la7La6Z3FCfPqxx+Jk= github.com/grafana/grafana-plugin-sdk-go v0.5.0 h1:qe60CcvhWn0H+CNoXgD6527GXcIJ78dU+pv1PmJypM8= github.com/grafana/grafana-plugin-sdk-go v0.5.0/go.mod h1:78zFGQy3wwAtNE5imCeXklnD1xJ1VtIhF4JmxUIrwrw= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 4f2946a3f71..c2d85db1ed2 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -13,6 +13,11 @@ import ( "github.com/grafana/grafana/pkg/setting" ) +// for stubbing in tests +var newImageUploaderProvider = func() (imguploader.ImageUploader, error) { + return imguploader.NewImageUploader() +} + // NotifierPlugin holds meta information about a notifier. type NotifierPlugin struct { Type string `json:"type"` @@ -119,7 +124,7 @@ func (n *notificationService) sendNotifications(evalContext *EvalContext, notifi } func (n *notificationService) uploadImage(context *EvalContext) (err error) { - uploader, err := imguploader.NewImageUploader() + uploader, err := newImageUploaderProvider() if err != nil { return err } diff --git a/pkg/services/alerting/notifier_test.go b/pkg/services/alerting/notifier_test.go new file mode 100644 index 00000000000..5d48e2890a8 --- /dev/null +++ b/pkg/services/alerting/notifier_test.go @@ -0,0 +1,238 @@ +package alerting + +import ( + "context" + "testing" + "time" + + "github.com/grafana/grafana/pkg/components/simplejson" + + "github.com/grafana/grafana/pkg/services/rendering" + + "github.com/grafana/grafana/pkg/bus" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/components/imguploader" + "github.com/grafana/grafana/pkg/models" +) + +func TestNotificationService(t *testing.T) { + testRule := &Rule{ + ID: 1, + DashboardID: 1, + PanelID: 1, + OrgID: 1, + Name: "Test", + Message: "Something is bad", + State: models.AlertStateAlerting, + Notifications: []string{"1"}, + } + evalCtx := NewEvalContext(context.Background(), testRule) + + notificationServiceScenario(t, "SendIfNeeded should render and upload image and send notification", evalCtx, true, func(scenarioCtx *scenarioContext) { + err := scenarioCtx.notificationService.SendIfNeeded(evalCtx) + require.NoError(t, err) + + require.Equalf(t, 1, scenarioCtx.renderCount, "expected render to be called, but wasn't") + require.Equalf(t, 1, scenarioCtx.imageUploadCount, "expected image to be uploaded, but wasn't") + require.Truef(t, evalCtx.Ctx.Value("notificationSent").(bool), "expected notification to be sent, but wasn't") + }) + + notificationServiceScenario(t, "SendIfNeeded should not render and upload image, but send notification", evalCtx, false, func(scenarioCtx *scenarioContext) { + err := scenarioCtx.notificationService.SendIfNeeded(evalCtx) + require.NoError(t, err) + + require.Equalf(t, 0, scenarioCtx.renderCount, "expected render to be called, but wasn't") + require.Equalf(t, 0, scenarioCtx.imageUploadCount, "expected image to be uploaded, but wasn't") + require.Truef(t, evalCtx.Ctx.Value("notificationSent").(bool), "expected notification to be sent, but wasn't") + }) +} + +type scenarioContext struct { + evalCtx *EvalContext + notificationService *notificationService + imageUploadCount int + renderCount int +} + +type scenarioFunc func(c *scenarioContext) + +func notificationServiceScenario(t *testing.T, name string, evalCtx *EvalContext, uploadImage bool, fn scenarioFunc) { + t.Run(name, func(t *testing.T) { + RegisterNotifier(&NotifierPlugin{ + Type: "test", + Name: "Test", + Description: "Test notifier", + Factory: newTestNotifier, + }) + + evalCtx.dashboardRef = &models.DashboardRef{Uid: "db-uid"} + + bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetAlertNotificationsWithUidToSendQuery) error { + query.Result = []*models.AlertNotification{ + { + Id: 1, + Type: "test", + Settings: simplejson.NewFromAny(map[string]interface{}{ + "uploadImage": uploadImage, + }), + }, + } + return nil + }) + + bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetOrCreateNotificationStateQuery) error { + query.Result = &models.AlertNotificationState{ + AlertId: evalCtx.Rule.ID, + AlertRuleStateUpdatedVersion: 1, + Id: 1, + OrgId: evalCtx.Rule.OrgID, + State: models.AlertNotificationStateUnknown, + } + return nil + }) + + bus.AddHandlerCtx("test", func(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error { + return nil + }) + + bus.AddHandlerCtx("test", func(ctx context.Context, cmd *models.SetAlertNotificationStateToCompleteCommand) error { + return nil + }) + + scenarioCtx := &scenarioContext{ + evalCtx: evalCtx, + } + + imageUploader := &testImageUploader{ + uploadProvider: func(ctx context.Context, path string) (string, error) { + scenarioCtx.imageUploadCount++ + return "", nil + }, + } + + origNewImageUploaderProvider := newImageUploaderProvider + newImageUploaderProvider = func() (imguploader.ImageUploader, error) { + return imageUploader, nil + } + defer func() { + newImageUploaderProvider = origNewImageUploaderProvider + }() + + renderService := &testRenderService{ + renderProvider: func(ctx context.Context, opts rendering.Opts) (*rendering.RenderResult, error) { + scenarioCtx.renderCount++ + return &rendering.RenderResult{FilePath: "image.png"}, nil + }, + } + + scenarioCtx.notificationService = newNotificationService(renderService) + fn(scenarioCtx) + }) +} + +type testNotifier struct { + Name string + Type string + UID string + IsDeault bool + UploadImage bool + SendReminder bool + DisableResolveMessage bool + Frequency time.Duration +} + +func newTestNotifier(model *models.AlertNotification) (Notifier, error) { + uploadImage := true + value, exist := model.Settings.CheckGet("uploadImage") + if exist { + uploadImage = value.MustBool() + } + + return &testNotifier{ + UID: model.Uid, + Name: model.Name, + IsDeault: model.IsDefault, + Type: model.Type, + UploadImage: uploadImage, + SendReminder: model.SendReminder, + DisableResolveMessage: model.DisableResolveMessage, + Frequency: model.Frequency, + }, nil +} + +func (n *testNotifier) Notify(evalCtx *EvalContext) error { + evalCtx.Ctx = context.WithValue(evalCtx.Ctx, "notificationSent", true) + return nil +} + +func (n *testNotifier) ShouldNotify(ctx context.Context, evalCtx *EvalContext, notiferState *models.AlertNotificationState) bool { + return true +} + +func (n *testNotifier) GetType() string { + return n.Type +} + +func (n *testNotifier) NeedsImage() bool { + return n.UploadImage +} + +func (n *testNotifier) GetNotifierUID() string { + return n.UID +} + +func (n *testNotifier) GetIsDefault() bool { + return n.IsDeault +} + +func (n *testNotifier) GetSendReminder() bool { + return n.SendReminder +} + +func (n *testNotifier) GetDisableResolveMessage() bool { + return n.DisableResolveMessage +} + +func (n *testNotifier) GetFrequency() time.Duration { + return n.Frequency +} + +var _ Notifier = &testNotifier{} + +type testRenderService struct { + renderProvider func(ctx context.Context, opts rendering.Opts) (*rendering.RenderResult, error) + renderErrorImageProvider func(error error) (*rendering.RenderResult, error) +} + +func (s *testRenderService) Render(ctx context.Context, opts rendering.Opts) (*rendering.RenderResult, error) { + if s.renderProvider != nil { + return s.renderProvider(ctx, opts) + } + + return &rendering.RenderResult{FilePath: "image.png"}, nil +} + +func (s *testRenderService) RenderErrorImage(err error) (*rendering.RenderResult, error) { + if s.renderErrorImageProvider != nil { + return s.renderErrorImageProvider(err) + } + + return &rendering.RenderResult{FilePath: "image.png"}, nil +} + +var _ rendering.Service = &testRenderService{} + +type testImageUploader struct { + uploadProvider func(ctx context.Context, path string) (string, error) +} + +func (u *testImageUploader) Upload(ctx context.Context, path string) (string, error) { + if u.uploadProvider != nil { + return u.uploadProvider(ctx, path) + } + + return "", nil +} + +var _ imguploader.ImageUploader = &testImageUploader{}