2022-12-21 01:03:15 -06:00
package notifier
import (
"context"
"net/url"
"os"
"testing"
2023-06-21 18:53:30 -05:00
alertingImages "github.com/grafana/alerting/images"
2023-02-03 10:36:49 -06:00
alertingLogging "github.com/grafana/alerting/logging"
"github.com/grafana/alerting/receivers"
alertingEmail "github.com/grafana/alerting/receivers/email"
alertingTemplates "github.com/grafana/alerting/templates"
2022-12-21 01:03:15 -06:00
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/setting"
)
2023-02-03 10:36:49 -06:00
// TestEmailNotifierIntegration tests channels.EmailNotifier in conjunction with Grafana notifications.EmailSender and two staged expansion of the alertingEmail body
2022-12-21 01:03:15 -06:00
func TestEmailNotifierIntegration ( t * testing . T ) {
ns := createEmailSender ( t )
emailTmpl := templateForTests ( t )
externalURL , err := url . Parse ( "http://localhost/base" )
require . NoError ( t , err )
emailTmpl . ExternalURL = externalURL
cases := [ ] struct {
name string
alerts [ ] * types . Alert
messageTmpl string
subjectTmpl string
expSubject string
expSnippets [ ] string
} {
{
name : "single alert with templated message" ,
alerts : [ ] * types . Alert {
{
Alert : model . Alert {
Labels : model . LabelSet { "alertname" : "AlwaysFiring" , "severity" : "warning" } ,
Annotations : model . LabelSet { "runbook_url" : "http://fix.me" , "__dashboardUid__" : "abc" , "__panelId__" : "5" } ,
} ,
} ,
} ,
messageTmpl : ` Hi , this is a custom template .
{ { if gt ( len . Alerts . Firing ) 0 } }
You have { { len . Alerts . Firing } } alerts firing .
{ { range . Alerts . Firing } } Firing : { { . Labels . alertname } } at { { . Labels . severity } } { { end } }
{ { end } } ` ,
expSubject : "[FIRING:1] (AlwaysFiring warning)" ,
expSnippets : [ ] string {
"Hi, this is a custom template." ,
"You have 1 alerts firing." ,
"Firing: AlwaysFiring at warning" ,
} ,
} ,
{
name : "multiple alerts with templated message" ,
alerts : [ ] * types . Alert {
{
Alert : model . Alert {
Labels : model . LabelSet { "alertname" : "FiringOne" , "severity" : "warning" } ,
Annotations : model . LabelSet { "runbook_url" : "http://fix.me" , "__dashboardUid__" : "abc" , "__panelId__" : "5" } ,
} ,
} ,
{
Alert : model . Alert {
Labels : model . LabelSet { "alertname" : "FiringTwo" , "severity" : "critical" } ,
Annotations : model . LabelSet { "runbook_url" : "http://fix.me" , "__dashboardUid__" : "abc" , "__panelId__" : "5" } ,
} ,
} ,
} ,
messageTmpl : ` Hi , this is a custom template .
{ { if gt ( len . Alerts . Firing ) 0 } }
You have { { len . Alerts . Firing } } alerts firing .
{ { range . Alerts . Firing } } Firing : { { . Labels . alertname } } at { { . Labels . severity } } { { end } }
{ { end } } ` ,
expSubject : "[FIRING:2] " ,
expSnippets : [ ] string {
"Hi, this is a custom template." ,
"You have 2 alerts firing." ,
"Firing: FiringOne at warning" ,
"Firing: FiringTwo at critical" ,
} ,
} ,
{
name : "empty message with alerts uses default template content" ,
alerts : [ ] * types . Alert {
{
Alert : model . Alert {
Labels : model . LabelSet { "alertname" : "FiringOne" , "severity" : "warning" } ,
Annotations : model . LabelSet { "runbook_url" : "http://fix.me" , "__dashboardUid__" : "abc" , "__panelId__" : "5" } ,
} ,
} ,
{
Alert : model . Alert {
Labels : model . LabelSet { "alertname" : "FiringTwo" , "severity" : "critical" } ,
Annotations : model . LabelSet { "runbook_url" : "http://fix.me" , "__dashboardUid__" : "abc" , "__panelId__" : "5" } ,
} ,
} ,
} ,
messageTmpl : "" ,
expSubject : "[FIRING:2] " ,
expSnippets : [ ] string {
"2 firing instances" ,
"<strong>severity</strong>" ,
2023-05-09 14:27:40 -05:00
"warning" ,
"critical" ,
2022-12-21 01:03:15 -06:00
"<strong>alertname</strong>" ,
2023-05-09 14:27:40 -05:00
"FiringTwo" ,
"FiringOne" ,
2022-12-21 01:03:15 -06:00
"<a href=\"http://fix.me\"" ,
"<a href=\"http://localhost/base/d/abc" ,
"<a href=\"http://localhost/base/d/abc?viewPanel=5" ,
} ,
} ,
{
name : "message containing HTML gets HTMLencoded" ,
alerts : [ ] * types . Alert {
{
Alert : model . Alert {
Labels : model . LabelSet { "alertname" : "AlwaysFiring" , "severity" : "warning" } ,
Annotations : model . LabelSet { "runbook_url" : "http://fix.me" , "__dashboardUid__" : "abc" , "__panelId__" : "5" } ,
} ,
} ,
} ,
messageTmpl : ` < marquee > Hi , this is a custom template . < / marquee >
{ { if gt ( len . Alerts . Firing ) 0 } }
< ol >
{ { range . Alerts . Firing } } < li > Firing : { { . Labels . alertname } } at { { . Labels . severity } } < / li > { { end } }
< / ol >
{ { end } } ` ,
expSubject : "[FIRING:1] (AlwaysFiring warning)" ,
expSnippets : [ ] string {
"<marquee>Hi, this is a custom template.</marquee>" ,
"<li>Firing: AlwaysFiring at warning </li>" ,
} ,
} ,
{
name : "single alert with templated subject" ,
alerts : [ ] * types . Alert {
{
Alert : model . Alert {
Labels : model . LabelSet { "alertname" : "AlwaysFiring" , "severity" : "warning" } ,
Annotations : model . LabelSet { "runbook_url" : "http://fix.me" , "__dashboardUid__" : "abc" , "__panelId__" : "5" } ,
} ,
} ,
} ,
subjectTmpl : ` This notification is {{ .Status }} ! ` ,
expSubject : "This notification is firing!" ,
expSnippets : [ ] string { } ,
} ,
}
for _ , c := range cases {
t . Run ( c . name , func ( t * testing . T ) {
emailNotifier := createSut ( t , c . messageTmpl , c . subjectTmpl , emailTmpl , ns )
ok , err := emailNotifier . Notify ( context . Background ( ) , c . alerts ... )
require . NoError ( t , err )
require . True ( t , ok )
sentMsg := getSingleSentMessage ( t , ns )
require . NotNil ( t , sentMsg )
require . Equal ( t , "\"Grafana Admin\" <from@address.com>" , sentMsg . From )
require . Equal ( t , sentMsg . To [ 0 ] , "someops@example.com" )
require . Equal ( t , c . expSubject , sentMsg . Subject )
require . Contains ( t , sentMsg . Body , "text/html" )
html := sentMsg . Body [ "text/html" ]
require . NotNil ( t , html )
for _ , s := range c . expSnippets {
require . Contains ( t , html , s )
}
} )
}
}
2023-04-25 12:39:46 -05:00
func createSut ( t * testing . T , messageTmpl string , subjectTmpl string , emailTmpl * template . Template , ns receivers . EmailSender ) * alertingEmail . Notifier {
2022-12-21 01:03:15 -06:00
t . Helper ( )
2023-04-25 12:39:46 -05:00
if subjectTmpl == "" {
subjectTmpl = alertingTemplates . DefaultMessageTitleEmbed
2022-12-21 01:03:15 -06:00
}
2023-04-25 12:39:46 -05:00
return alertingEmail . New ( alertingEmail . Config {
SingleEmail : true ,
Addresses : [ ] string {
"someops@example.com" ,
"somedev@example.com" ,
2022-12-21 01:03:15 -06:00
} ,
2023-04-25 12:39:46 -05:00
Message : messageTmpl ,
Subject : subjectTmpl ,
2023-06-21 18:53:30 -05:00
} , receivers . Metadata { } , emailTmpl , ns , & alertingImages . UnavailableProvider { } , & alertingLogging . FakeLogger { } )
2022-12-21 01:03:15 -06:00
}
func getSingleSentMessage ( t * testing . T , ns * emailSender ) * notifications . Message {
t . Helper ( )
mailer := ns . ns . GetMailer ( ) . ( * notifications . FakeMailer )
require . Len ( t , mailer . Sent , 1 )
sent := mailer . Sent [ 0 ]
mailer . Sent = [ ] * notifications . Message { }
return sent
}
type emailSender struct {
ns * notifications . NotificationService
}
2023-02-03 10:36:49 -06:00
func ( e emailSender ) SendWebhook ( ctx context . Context , cmd * receivers . SendWebhookSettings ) error {
2022-12-21 01:03:15 -06:00
panic ( "not implemented" )
}
2023-02-03 10:36:49 -06:00
func ( e emailSender ) SendEmail ( ctx context . Context , cmd * receivers . SendEmailSettings ) error {
2023-01-17 13:47:31 -06:00
attached := make ( [ ] * notifications . SendEmailAttachFile , 0 , len ( cmd . AttachedFiles ) )
2022-12-21 01:03:15 -06:00
for _ , file := range cmd . AttachedFiles {
2023-01-17 13:47:31 -06:00
attached = append ( attached , & notifications . SendEmailAttachFile {
2022-12-21 01:03:15 -06:00
Name : file . Name ,
Content : file . Content ,
} )
}
2023-01-17 13:47:31 -06:00
return e . ns . SendEmailCommandHandlerSync ( ctx , & notifications . SendEmailCommandSync {
SendEmailCommand : notifications . SendEmailCommand {
2022-12-21 01:03:15 -06:00
To : cmd . To ,
SingleEmail : cmd . SingleEmail ,
Template : cmd . Template ,
Subject : cmd . Subject ,
Data : cmd . Data ,
Info : cmd . Info ,
ReplyTo : cmd . ReplyTo ,
EmbeddedFiles : cmd . EmbeddedFiles ,
AttachedFiles : attached ,
} ,
} )
}
func createEmailSender ( t * testing . T ) * emailSender {
t . Helper ( )
tracer := tracing . InitializeTracerForTest ( )
bus := bus . ProvideBus ( tracer )
cfg := setting . NewCfg ( )
cfg . StaticRootPath = "../../../../public/"
cfg . BuildVersion = "4.0.0"
cfg . Smtp . Enabled = true
cfg . Smtp . TemplatesPatterns = [ ] string { "emails/*.html" , "emails/*.txt" }
cfg . Smtp . FromAddress = "from@address.com"
cfg . Smtp . FromName = "Grafana Admin"
cfg . Smtp . ContentTypes = [ ] string { "text/html" , "text/plain" }
cfg . Smtp . Host = "localhost:1234"
mailer := notifications . NewFakeMailer ( )
ns , err := notifications . ProvideService ( bus , cfg , mailer , nil )
require . NoError ( t , err )
return & emailSender { ns : ns }
}
func templateForTests ( t * testing . T ) * template . Template {
f , err := os . CreateTemp ( "/tmp" , "template" )
require . NoError ( t , err )
defer func ( f * os . File ) {
_ = f . Close ( )
} ( f )
t . Cleanup ( func ( ) {
require . NoError ( t , os . RemoveAll ( f . Name ( ) ) )
} )
2023-02-03 10:36:49 -06:00
_ , err = f . WriteString ( alertingTemplates . TemplateForTestsString )
2022-12-21 01:03:15 -06:00
require . NoError ( t , err )
2023-01-10 10:17:07 -06:00
tmpl , err := template . FromGlobs ( [ ] string { f . Name ( ) } )
2022-12-21 01:03:15 -06:00
require . NoError ( t , err )
return tmpl
}