diff --git a/go.mod b/go.mod index c92f76c3e..a2d3a9c52 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( require github.com/prometheus/common v0.37.0 require ( + github.com/go-playground/validator/v10 v10.4.1 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/mattermost/mattermost/server/public v0.1.1 github.com/mitchellh/mapstructure v1.5.0 @@ -183,12 +184,15 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-playground/locales v0.13.0 // indirect + github.com/go-playground/universal-translator v0.17.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.11 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/leodido/go-urn v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect diff --git a/go.sum b/go.sum index 089f98d54..0eacd324a 100644 --- a/go.sum +++ b/go.sum @@ -637,9 +637,13 @@ github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= @@ -898,6 +902,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/helpers.go b/helpers.go index c0432ca21..eab4e915e 100644 --- a/helpers.go +++ b/helpers.go @@ -5,6 +5,8 @@ import ( "math" "strings" "time" + + "github.com/go-playground/validator/v10" ) // BytesScanner allows to scan for subslices separated by separator. @@ -250,3 +252,9 @@ func MergeToSorted[T Comparable](arr1, arr2 []T) ([]T, error) { return merged, nil } + +// ValidateStruct is a default generic function that uses a validator to validate structure fields. +func ValidateStruct(s any) error { + validator := validator.New() + return validator.Struct(s) +} diff --git a/helpers_test.go b/helpers_test.go index d9c3ca4ee..4b764d6a6 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -290,25 +290,25 @@ func TestMergeToSorted(t *testing.T) { }) Convey("Test with one nil array", func() { - merged, err := MergeToSorted[myInt](nil, []myInt{1, 2, 3}) + merged, err := MergeToSorted(nil, []myInt{1, 2, 3}) So(err, ShouldBeNil) So(merged, ShouldResemble, []myInt{1, 2, 3}) }) Convey("Test with two arrays", func() { - merged, err := MergeToSorted[myInt]([]myInt{4, 5}, []myInt{1, 2, 3}) + merged, err := MergeToSorted([]myInt{4, 5}, []myInt{1, 2, 3}) So(err, ShouldBeNil) So(merged, ShouldResemble, []myInt{1, 2, 3, 4, 5}) }) Convey("Test with empty array", func() { - merged, err := MergeToSorted[myInt]([]myInt{-4, 5}, []myInt{}) + merged, err := MergeToSorted([]myInt{-4, 5}, []myInt{}) So(err, ShouldBeNil) So(merged, ShouldResemble, []myInt{-4, 5}) }) Convey("Test with sorted values but mixed up", func() { - merged, err := MergeToSorted[myInt]([]myInt{1, 9, 10}, []myInt{4, 8, 12}) + merged, err := MergeToSorted([]myInt{1, 9, 10}, []myInt{4, 8, 12}) So(err, ShouldBeNil) So(merged, ShouldResemble, []myInt{1, 4, 8, 9, 10, 12}) }) @@ -333,9 +333,55 @@ func TestMergeToSorted(t *testing.T) { } expected := append(arr2, arr1...) - merged, err := MergeToSorted[myTest](arr1, arr2) + merged, err := MergeToSorted(arr1, arr2) So(err, ShouldBeNil) So(merged, ShouldResemble, expected) }) }) } + +func TestValidateStruct(t *testing.T) { + type ValidationStruct struct { + TestInt int `validate:"required,gt=0"` + TestURL string `validate:"required,url"` + TestBool bool + } + + const ( + validURL = "https://github.com/moira-alert/moira" + validInt = 1 + ) + + Convey("Test ValidateStruct", t, func() { + Convey("With TestInt less than zero", func() { + testStruct := ValidationStruct{ + TestInt: -1, + TestURL: validURL, + } + + err := ValidateStruct(testStruct) + So(err, ShouldNotBeNil) + }) + + Convey("With invalid TestURL format", func() { + testStruct := ValidationStruct{ + TestInt: validInt, + TestURL: "test", + TestBool: true, + } + + err := ValidateStruct(testStruct) + So(err, ShouldNotBeNil) + }) + + Convey("With valid structure", func() { + testStruct := ValidationStruct{ + TestInt: validInt, + TestURL: validURL, + } + + err := ValidateStruct(testStruct) + So(err, ShouldBeNil) + }) + }) +} diff --git a/senders/discord/init.go b/senders/discord/init.go index e991d089f..926746244 100644 --- a/senders/discord/init.go +++ b/senders/discord/init.go @@ -20,7 +20,7 @@ const ( // Structure that represents the Discord configuration in the YAML file. type config struct { ContactType string `mapstructure:"contact_type"` - Token string `mapstructure:"token"` + Token string `mapstructure:"token" validate:"required"` FrontURI string `mapstructure:"front_uri"` } @@ -42,9 +42,10 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to discord config: %w", err) } - if cfg.Token == "" { - return fmt.Errorf("cannot read the discord token from the config") + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("discord config validation error: %w", err) } + sender.session, err = discordgo.New("Bot " + cfg.Token) if err != nil { return fmt.Errorf("error creating discord session: %w", err) diff --git a/senders/discord/init_test.go b/senders/discord/init_test.go index 73c2609af..11043df99 100644 --- a/senders/discord/init_test.go +++ b/senders/discord/init_test.go @@ -1,10 +1,11 @@ package discord import ( - "fmt" + "errors" "testing" "time" + "github.com/go-playground/validator/v10" "github.com/moira-alert/moira" logging "github.com/moira-alert/moira/logging/zerolog_adapter" @@ -31,9 +32,14 @@ func TestInit(t *testing.T) { location, _ := time.LoadLocation("UTC") Convey("Init tests", t, func() { sender := Sender{DataBase: &MockDB{}} - Convey("Empty map", func() { - err := sender.Init(map[string]interface{}{}, logger, nil, "") - So(err, ShouldResemble, fmt.Errorf("cannot read the discord token from the config")) + + validatorErr := validator.ValidationErrors{} + + Convey("With empty token", func() { + senderSettings := map[string]interface{}{} + + err := sender.Init(senderSettings, logger, nil, "") + So(errors.As(err, &validatorErr), ShouldBeTrue) So(sender, ShouldResemble, Sender{DataBase: &MockDB{}}) }) @@ -42,7 +48,9 @@ func TestInit(t *testing.T) { "token": "123", "front_uri": "http://moira.uri", } - sender.Init(senderSettings, logger, location, "15:04") //nolint + + err := sender.Init(senderSettings, logger, location, "15:04") //nolint + So(err, ShouldBeNil) So(sender.frontURI, ShouldResemble, "http://moira.uri") So(sender.session.Token, ShouldResemble, "Bot 123") So(sender.logger, ShouldResemble, logger) diff --git a/senders/mail/mail.go b/senders/mail/mail.go index 39ce73344..10406e043 100644 --- a/senders/mail/mail.go +++ b/senders/mail/mail.go @@ -14,10 +14,10 @@ import ( // Structure that represents the Mail configuration in the YAML file. type config struct { - MailFrom string `mapstructure:"mail_from"` + MailFrom string `mapstructure:"mail_from" validate:"required"` SMTPHello string `mapstructure:"smtp_hello"` - SMTPHost string `mapstructure:"smtp_host"` - SMTPPort int64 `mapstructure:"smtp_port"` + SMTPHost string `mapstructure:"smtp_host" validate:"required"` + SMTPPort int64 `mapstructure:"smtp_port" validate:"required"` InsecureTLS bool `mapstructure:"insecure_tls"` FrontURI string `mapstructure:"front_uri"` SMTPPass string `mapstructure:"smtp_pass"` @@ -64,6 +64,10 @@ func (sender *Sender) fillSettings(senderSettings interface{}, logger moira.Logg return fmt.Errorf("failed to decode senderSettings to mail config: %w", err) } + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("mail config validation error: %w", err) + } + sender.logger = logger sender.From = cfg.MailFrom sender.SMTPHello = cfg.SMTPHello @@ -76,12 +80,11 @@ func (sender *Sender) fillSettings(senderSettings interface{}, logger moira.Logg sender.TemplateFile = cfg.TemplateFile sender.location = location sender.dateTimeFormat = dateTimeFormat + if sender.Username == "" { sender.Username = sender.From } - if sender.From == "" { - return fmt.Errorf("mail_from can't be empty") - } + return nil } @@ -106,11 +109,13 @@ func (sender *Sender) tryDial() error { return err } defer t.Close() + if sender.SMTPHello != "" { if err := t.Hello(sender.SMTPHello); err != nil { return err } } + if sender.Password != "" { tlsConfig := &tls.Config{ InsecureSkipVerify: sender.InsecureTLS, @@ -123,5 +128,6 @@ func (sender *Sender) tryDial() error { return err } } + return nil } diff --git a/senders/mail/mail_test.go b/senders/mail/mail_test.go index 3c3036130..57cc27fb3 100644 --- a/senders/mail/mail_test.go +++ b/senders/mail/mail_test.go @@ -1,33 +1,90 @@ package mail import ( - "fmt" + "errors" "testing" + "github.com/go-playground/validator/v10" . "github.com/smartystreets/goconvey/convey" ) +const ( + defaultMailFrom = "test-mail-from" + defaultSMTPHost = "test-smtp-host" + defaultSMTPPort = 80 + defaultSMTPHello = "test-smtp-hello" + defaultInsecureTLS = true + defaultFrontURI = "test-front-uri" + defaultSMTPPass = "test-smtp-pass" + defaultSMTPUser = "test-smtp-user" + defaultTemplateFile = "test-template-file" +) + func TestFillSettings(t *testing.T) { - Convey("Empty map", t, func() { + Convey("Test fillSettings", t, func() { sender := Sender{} - err := sender.fillSettings(map[string]interface{}{}, nil, nil, "") - So(err, ShouldResemble, fmt.Errorf("mail_from can't be empty")) - So(sender, ShouldResemble, Sender{}) - }) - Convey("Has From", t, func() { - sender := Sender{} - settings := map[string]interface{}{"mail_from": "123"} - Convey("No username", func() { - err := sender.fillSettings(settings, nil, nil, "") - So(err, ShouldBeNil) - So(sender, ShouldResemble, Sender{From: "123", Username: "123"}) + validatorErr := validator.ValidationErrors{} + + Convey("With empty mail_from", func() { + senderSettings := map[string]interface{}{ + "smtp_host": defaultSMTPHost, + "smtp_port": defaultSMTPPort, + } + + err := sender.fillSettings(senderSettings, nil, nil, "") + So(errors.As(err, &validatorErr), ShouldBeTrue) + So(sender, ShouldResemble, Sender{}) }) - Convey("Has username", func() { - settings["smtp_user"] = "user" - err := sender.fillSettings(settings, nil, nil, "") + + Convey("With empty smpt_host", func() { + senderSettings := map[string]interface{}{ + "mail_from": defaultMailFrom, + "smtp_port": defaultSMTPPort, + } + + err := sender.fillSettings(senderSettings, nil, nil, "") + So(errors.As(err, &validatorErr), ShouldBeTrue) + So(sender, ShouldResemble, Sender{}) + }) + + Convey("With empty smpt_port", func() { + senderSettings := map[string]interface{}{ + "mail_from": defaultMailFrom, + "smtp_host": defaultSMTPHost, + } + + err := sender.fillSettings(senderSettings, nil, nil, "") + So(errors.As(err, &validatorErr), ShouldBeTrue) + So(sender, ShouldResemble, Sender{}) + }) + + Convey("With full settings", func() { + senderSettings := map[string]interface{}{ + "mail_from": defaultMailFrom, + "smtp_host": defaultSMTPHost, + "smtp_port": defaultSMTPPort, + "smtp_hello": defaultSMTPHello, + "insecure_tls": defaultInsecureTLS, + "front_uri": defaultFrontURI, + "smtp_user": defaultSMTPUser, + "smtp_pass": defaultSMTPPass, + "template_file": defaultTemplateFile, + } + + err := sender.fillSettings(senderSettings, nil, nil, "") So(err, ShouldBeNil) - So(sender, ShouldResemble, Sender{From: "123", Username: "user"}) + So(sender, ShouldResemble, Sender{ + From: defaultMailFrom, + SMTPHello: defaultSMTPHello, + SMTPHost: defaultSMTPHost, + SMTPPort: 80, + FrontURI: defaultFrontURI, + InsecureTLS: defaultInsecureTLS, + Username: defaultSMTPUser, + Password: defaultSMTPPass, + TemplateFile: defaultTemplateFile, + }) }) }) } diff --git a/senders/mattermost/sender.go b/senders/mattermost/sender.go index 36cbabb37..1b0f85c4a 100644 --- a/senders/mattermost/sender.go +++ b/senders/mattermost/sender.go @@ -18,10 +18,10 @@ import ( // Structure that represents the Mattermost configuration in the YAML file. type config struct { - Url string `mapstructure:"url"` + Url string `mapstructure:"url" validate:"required,url"` InsecureTLS bool `mapstructure:"insecure_tls"` - APIToken string `mapstructure:"api_token"` - FrontURI string `mapstructure:"front_uri"` + APIToken string `mapstructure:"api_token" validate:"required"` + FrontURI string `mapstructure:"front_uri" validate:"required"` UseEmoji bool `mapstructure:"use_emoji"` DefaultEmoji string `mapstructure:"default_emoji"` EmojiMap map[string]string `mapstructure:"emoji_map"` @@ -53,8 +53,8 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to mattermost config: %w", err) } - if cfg.Url == "" { - return fmt.Errorf("can not read Mattermost url from config") + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("mattermost config validation error: %w", err) } client := model.NewAPIv4Client(cfg.Url) @@ -68,20 +68,13 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca } sender.client = client - - if cfg.APIToken == "" { - return fmt.Errorf("can not read Mattermost api_token from config") - } sender.client.SetToken(cfg.APIToken) - if cfg.FrontURI == "" { - return fmt.Errorf("can not read Mattermost front_uri from config") - } - emojiProvider, err := emoji_provider.NewEmojiProvider(cfg.DefaultEmoji, cfg.EmojiMap) if err != nil { return fmt.Errorf("cannot initialize mattermost sender, err: %w", err) } + sender.logger = logger sender.formatter = msgformat.NewHighlightSyntaxFormatter( emojiProvider, diff --git a/senders/mattermost/sender_internal_test.go b/senders/mattermost/sender_internal_test.go index 1ae3da0a2..2b6bb7a64 100644 --- a/senders/mattermost/sender_internal_test.go +++ b/senders/mattermost/sender_internal_test.go @@ -21,7 +21,7 @@ func TestSendEvents(t *testing.T) { Convey("Given configured sender", t, func() { senderSettings := map[string]interface{}{ // redundant, but necessary config - "url": "qwerty", + "url": "https://mattermost.com/", "api_token": "qwerty", "front_uri": "qwerty", "insecure_tls": true, diff --git a/senders/mattermost/sender_manual_test.go b/senders/mattermost/sender_manual_test.go index a93a627a9..35e71665f 100644 --- a/senders/mattermost/sender_manual_test.go +++ b/senders/mattermost/sender_manual_test.go @@ -18,7 +18,7 @@ func TestSender(t *testing.T) { logger, _ := logging.ConfigureLog("stdout", "debug", "test", true) const ( - url = "http://localhost:8065" + url = "https://mattermost.com/" apiToken = "8pdo6yoiutgidgxs9qxhbo7w4h" channelID = "3y6ab8rptfdr9m1hzskghpxwsc" ) diff --git a/senders/mattermost/sender_test.go b/senders/mattermost/sender_test.go index c47baa562..333182219 100644 --- a/senders/mattermost/sender_test.go +++ b/senders/mattermost/sender_test.go @@ -1,69 +1,79 @@ package mattermost import ( + "errors" "testing" + "github.com/go-playground/validator/v10" logging "github.com/moira-alert/moira/logging/zerolog_adapter" . "github.com/smartystreets/goconvey/convey" ) +const ( + defaultURL = "https://mattermost.com/" + defaultAPIToken = "test-api-token" + defaultFrontURI = "test-front-uri" + defaultInsecureTLS = true + defaultUseEmoji = true + defaultEmoji = "test-emoji" +) + +var defaultEmojiMap = map[string]string{ + "OK": ":dance_mops:", +} + func TestInit(t *testing.T) { logger, _ := logging.ConfigureLog("stdout", "debug", "test", true) + Convey("Init tests", t, func() { - sender := &Sender{} + sender := Sender{} - Convey("No url", func() { + validatorErr := validator.ValidationErrors{} + + Convey("With empty url", func() { senderSettings := map[string]interface{}{ - "api_token": "qwerty", - "front_uri": "qwerty", - "insecure_tls": true, + "api_token": defaultAPIToken, + "front_uri": defaultFrontURI, } + err := sender.Init(senderSettings, logger, nil, "") - So(err, ShouldNotBeNil) + So(errors.As(err, &validatorErr), ShouldBeTrue) + So(sender, ShouldResemble, Sender{}) }) - Convey("Empty url", func() { + Convey("With empty api_token", func() { senderSettings := map[string]interface{}{ - "url": "", - "api_token": "qwerty", - "front_uri": "qwerty", - "insecure_tls": true, + "url": defaultURL, + "front_uri": defaultFrontURI, } - err := sender.Init(senderSettings, logger, nil, "") - So(err, ShouldNotBeNil) - }) - Convey("No api_token", func() { - senderSettings := map[string]interface{}{"url": "qwerty", "front_uri": "qwerty"} err := sender.Init(senderSettings, logger, nil, "") - So(err, ShouldNotBeNil) + So(errors.As(err, &validatorErr), ShouldBeTrue) + So(sender, ShouldResemble, Sender{}) }) - Convey("Empty api_token", func() { - senderSettings := map[string]interface{}{"url": "qwerty", "front_uri": "qwerty", "api_token": ""} - err := sender.Init(senderSettings, logger, nil, "") - So(err, ShouldNotBeNil) - }) - - Convey("No front_uri", func() { - senderSettings := map[string]interface{}{"url": "qwerty", "api_token": "qwerty"} - err := sender.Init(senderSettings, logger, nil, "") - So(err, ShouldNotBeNil) - }) + Convey("With empty front_uri", func() { + senderSettings := map[string]interface{}{ + "url": defaultURL, + "api_token": defaultAPIToken, + } - Convey("Empty front_uri", func() { - senderSettings := map[string]interface{}{"url": "qwerty", "api_token": "qwerty", "front_uri": ""} err := sender.Init(senderSettings, logger, nil, "") - So(err, ShouldNotBeNil) + So(errors.As(err, &validatorErr), ShouldBeTrue) + So(sender, ShouldResemble, Sender{}) }) - Convey("Full config", func() { + Convey("With full config", func() { senderSettings := map[string]interface{}{ - "url": "qwerty", - "api_token": "qwerty", - "front_uri": "qwerty", - "insecure_tls": true, + "url": defaultURL, + "api_token": defaultAPIToken, + "front_uri": defaultFrontURI, + "insecure_tls": defaultInsecureTLS, + "use_emoji": defaultUseEmoji, + "default_emoji": defaultEmoji, + "emoji_map": defaultEmojiMap, } + err := sender.Init(senderSettings, logger, nil, "") So(err, ShouldBeNil) }) diff --git a/senders/msteams/msteams.go b/senders/msteams/msteams.go index 4bfd282f8..8e7e03bcc 100644 --- a/senders/msteams/msteams.go +++ b/senders/msteams/msteams.go @@ -29,15 +29,19 @@ const ( quotes = "```" ) -var throttleWarningFact = Fact{ - Name: "Warning", - Value: "Please, *fix your system or tune this trigger* to generate less events.", -} +var ( + throttleWarningFact = Fact{ + Name: "Warning", + Value: "Please, *fix your system or tune this trigger* to generate less events.", + } -var headers = map[string]string{ - "User-Agent": "Moira", - "Content-Type": "application/json", -} + headers = map[string]string{ + "User-Agent": "Moira", + "Content-Type": "application/json", + } + + defaultClientTimeout = 30 * time.Second +) // Structure that represents the MSTeams configuration in the YAML file. type config struct { @@ -62,13 +66,18 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to msteams config: %w", err) } + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("msteams config validation error: %w", err) + } + sender.logger = logger sender.location = location sender.frontURI = cfg.FrontURI sender.maxEvents = cfg.MaxEvents sender.client = &http.Client{ - Timeout: time.Duration(30) * time.Second, //nolint + Timeout: defaultClientTimeout, } + return nil } diff --git a/senders/msteams/msteams_test.go b/senders/msteams/msteams_test.go index faf612731..3fa71726a 100644 --- a/senders/msteams/msteams_test.go +++ b/senders/msteams/msteams_test.go @@ -11,18 +11,28 @@ import ( "gopkg.in/h2non/gock.v1" ) +const ( + defaultFrontURI = "test-front-uri" + defaultMaxEvents = -1 +) + func TestInit(t *testing.T) { logger, _ := logging.ConfigureLog("stdout", "debug", "test", true) + Convey("Init tests", t, func() { sender := Sender{} + senderSettings := map[string]interface{}{ - "max_events": -1, + "max_events": defaultMaxEvents, + "front_uri": defaultFrontURI, } + Convey("Minimal settings", func() { err := sender.Init(senderSettings, logger, nil, "") So(err, ShouldResemble, nil) So(sender, ShouldNotResemble, Sender{}) - So(sender.maxEvents, ShouldResemble, -1) + So(sender.maxEvents, ShouldResemble, defaultMaxEvents) + So(sender.frontURI, ShouldResemble, defaultFrontURI) }) }) } diff --git a/senders/opsgenie/init.go b/senders/opsgenie/init.go index 83c205165..72913a760 100644 --- a/senders/opsgenie/init.go +++ b/senders/opsgenie/init.go @@ -13,8 +13,7 @@ import ( // Structure that represents the OpsGenie configuration in the YAML file. type config struct { - APIKey string `mapstructure:"api_key"` - FrontURI string `mapstructure:"front_uri"` + APIKey string `mapstructure:"api_key" validate:"required"` } // Sender implements the Sender interface for opsgenie. @@ -27,7 +26,6 @@ type Sender struct { imageStoreID string imageStore moira.ImageStore imageStoreConfigured bool - frontURI string } // Init initializes the opsgenie sender. @@ -39,11 +37,11 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to opsgenie config: %w", err) } - sender.apiKey = cfg.APIKey - if sender.apiKey == "" { - return fmt.Errorf("cannot read the api_key from the sender settings") + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("opsgenie config validation error: %w", err) } + sender.apiKey = cfg.APIKey sender.imageStoreID, sender.imageStore, sender.imageStoreConfigured = senders.ReadImageStoreConfig(senderSettings, sender.ImageStores, logger) sender.client, err = alert.NewClient(&client.Config{ @@ -53,7 +51,6 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("error while creating opsgenie client: %w", err) } - sender.frontURI = cfg.FrontURI sender.logger = logger sender.location = location return nil diff --git a/senders/opsgenie/init_test.go b/senders/opsgenie/init_test.go index e39702834..5c8c952a6 100644 --- a/senders/opsgenie/init_test.go +++ b/senders/opsgenie/init_test.go @@ -1,10 +1,11 @@ package opsgenie import ( - "fmt" + "errors" "testing" "time" + "github.com/go-playground/validator/v10" "github.com/moira-alert/moira" "go.uber.org/mock/gomock" @@ -25,9 +26,13 @@ func TestInit(t *testing.T) { "s3": imageStore, }} - Convey("Empty map", func() { - err := sender.Init(map[string]interface{}{}, logger, nil, "") - So(err, ShouldResemble, fmt.Errorf("cannot read the api_key from the sender settings")) + validatorErr := validator.ValidationErrors{} + + Convey("With empty api_key", func() { + senderSettings := map[string]interface{}{} + + err := sender.Init(senderSettings, logger, nil, "") + So(errors.As(err, &validatorErr), ShouldBeTrue) So(sender, ShouldResemble, Sender{ ImageStores: map[string]moira.ImageStore{ "s3": imageStore, @@ -35,7 +40,7 @@ func TestInit(t *testing.T) { }) }) - Convey("Has settings", func() { + Convey("With full settings", func() { imageStore.EXPECT().IsEnabled().Return(true) senderSettings := map[string]interface{}{ "api_key": "testkey", @@ -44,7 +49,6 @@ func TestInit(t *testing.T) { } sender.Init(senderSettings, logger, location, "15:04") //nolint So(sender.apiKey, ShouldResemble, "testkey") - So(sender.frontURI, ShouldResemble, "http://moira.uri") So(sender.logger, ShouldResemble, logger) So(sender.location, ShouldResemble, location) }) diff --git a/senders/opsgenie/send_test.go b/senders/opsgenie/send_test.go index db81c15a2..048184e44 100644 --- a/senders/opsgenie/send_test.go +++ b/senders/opsgenie/send_test.go @@ -146,7 +146,6 @@ func TestMakeCreateAlertRequest(t *testing.T) { imageStore := mock_moira_alert.NewMockImageStore(mockCtrl) sender := Sender{ - frontURI: "https://my-moira.com", location: location, logger: logger, imageStoreConfigured: true, diff --git a/senders/pagerduty/init.go b/senders/pagerduty/init.go index 0e58afc2c..cbb36c5b6 100644 --- a/senders/pagerduty/init.go +++ b/senders/pagerduty/init.go @@ -39,5 +39,6 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca sender.logger = logger sender.location = location + return nil } diff --git a/senders/pagerduty/init_test.go b/senders/pagerduty/init_test.go index df0ecbbe7..7edd32231 100644 --- a/senders/pagerduty/init_test.go +++ b/senders/pagerduty/init_test.go @@ -37,6 +37,7 @@ func TestInit(t *testing.T) { So(sender.imageStoreConfigured, ShouldResemble, true) So(sender.imageStore, ShouldResemble, imageStore) }) + Convey("Wrong image_store name", func() { senderSettings := map[string]interface{}{ "front_uri": "http://moira.uri", @@ -46,6 +47,7 @@ func TestInit(t *testing.T) { So(sender.imageStoreConfigured, ShouldResemble, false) So(sender.imageStore, ShouldResemble, nil) }) + Convey("image store not configured", func() { imageStore.EXPECT().IsEnabled().Return(false) senderSettings := map[string]interface{}{ diff --git a/senders/pushover/pushover.go b/senders/pushover/pushover.go index d8f0c4b69..ecd88d1c4 100644 --- a/senders/pushover/pushover.go +++ b/senders/pushover/pushover.go @@ -19,7 +19,7 @@ const ( // Structure that represents the Pushover configuration in the YAML file. type config struct { - APIToken string `mapstructure:"api_token"` + APIToken string `mapstructure:"api_token" validate:"required"` FrontURI string `mapstructure:"front_uri"` } @@ -41,14 +41,16 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to pushover config: %w", err) } - sender.apiToken = cfg.APIToken - if sender.apiToken == "" { - return fmt.Errorf("can not read pushover api_token from config") + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("pushover config validation error: %w", err) } + + sender.apiToken = cfg.APIToken sender.client = pushover_client.New(sender.apiToken) sender.logger = logger sender.frontURI = cfg.FrontURI sender.location = location + return nil } diff --git a/senders/pushover/pushover_test.go b/senders/pushover/pushover_test.go index a25b6ccdd..1416501d0 100644 --- a/senders/pushover/pushover_test.go +++ b/senders/pushover/pushover_test.go @@ -2,36 +2,51 @@ package pushover import ( "bytes" - "fmt" + "errors" "testing" "time" + "github.com/go-playground/validator/v10" pushover_client "github.com/gregdel/pushover" "github.com/moira-alert/moira" logging "github.com/moira-alert/moira/logging/zerolog_adapter" . "github.com/smartystreets/goconvey/convey" ) -func TestSender_Init(t *testing.T) { +func TestInit(t *testing.T) { logger, _ := logging.ConfigureLog("stdout", "debug", "test", true) + + validatorErr := validator.ValidationErrors{} + Convey("Empty map", t, func() { sender := Sender{} - err := sender.Init(map[string]interface{}{}, logger, nil, "") - So(err, ShouldResemble, fmt.Errorf("can not read pushover api_token from config")) + senderSettings := map[string]interface{}{} + + err := sender.Init(senderSettings, logger, nil, "") + So(errors.As(err, &validatorErr), ShouldBeTrue) So(sender, ShouldResemble, Sender{}) }) Convey("Settings has api_token", t, func() { sender := Sender{} - err := sender.Init(map[string]interface{}{"api_token": "123"}, logger, nil, "") + senderSettings := map[string]interface{}{ + "api_token": "123", + } + + err := sender.Init(senderSettings, logger, nil, "") So(err, ShouldBeNil) So(sender, ShouldResemble, Sender{apiToken: "123", client: pushover_client.New("123"), logger: logger}) }) Convey("Settings has all data", t, func() { sender := Sender{} + senderSettings := map[string]interface{}{ + "api_token": "123", + "front_uri": "321", + } location, _ := time.LoadLocation("UTC") - err := sender.Init(map[string]interface{}{"api_token": "123", "front_uri": "321"}, logger, location, "") + + err := sender.Init(senderSettings, logger, location, "") So(err, ShouldBeNil) So(sender, ShouldResemble, Sender{apiToken: "123", client: pushover_client.New("123"), frontURI: "321", logger: logger, location: location}) }) diff --git a/senders/script/script.go b/senders/script/script.go index c78e37dde..d7b7351d1 100644 --- a/senders/script/script.go +++ b/senders/script/script.go @@ -15,7 +15,7 @@ import ( // Structure that represents the Script configuration in the YAML file. type config struct { - Exec string `mapstructure:"exec"` + Exec string `mapstructure:"exec" validate:"required"` } // Sender implements moira sender interface via script execution. @@ -40,6 +40,10 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to script config: %w", err) } + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("script config validation error: %w", err) + } + _, _, err = parseExec(cfg.Exec) if err != nil { return err diff --git a/senders/script/script_test.go b/senders/script/script_test.go index 0c2d5cb67..83d500636 100644 --- a/senders/script/script_test.go +++ b/senders/script/script_test.go @@ -1,9 +1,11 @@ package script import ( + "errors" "fmt" "testing" + "github.com/go-playground/validator/v10" "github.com/moira-alert/moira" logging "github.com/moira-alert/moira/logging/zerolog_adapter" . "github.com/smartystreets/goconvey/convey" @@ -50,9 +52,11 @@ func TestInit(t *testing.T) { sender := Sender{} settings := map[string]interface{}{} + validatorErr := validator.ValidationErrors{} + Convey("Empty exec", func() { err := sender.Init(settings, logger, nil, "") - So(err, ShouldResemble, fmt.Errorf("file not found")) + So(errors.As(err, &validatorErr), ShouldBeTrue) So(sender, ShouldResemble, Sender{}) }) diff --git a/senders/slack/slack.go b/senders/slack/slack.go index 28976c6c6..d1a5f4d2d 100644 --- a/senders/slack/slack.go +++ b/senders/slack/slack.go @@ -31,7 +31,7 @@ var ( // Structure that represents the Slack configuration in the YAML file. type config struct { - APIToken string `mapstructure:"api_token"` + APIToken string `mapstructure:"api_token" validate:"required"` UseEmoji bool `mapstructure:"use_emoji"` FrontURI string `mapstructure:"front_uri"` DefaultEmoji string `mapstructure:"default_emoji"` @@ -54,13 +54,15 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to slack config: %w", err) } - if cfg.APIToken == "" { - return fmt.Errorf("can not read slack api_token from config") + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("slack config validation error: %w", err) } + emojiProvider, err := emoji_provider.NewEmojiProvider(cfg.DefaultEmoji, cfg.EmojiMap) if err != nil { return fmt.Errorf("cannot initialize slack sender, err: %w", err) } + sender.logger = logger sender.emojiProvider = emojiProvider sender.formatter = msgformat.NewHighlightSyntaxFormatter( @@ -75,7 +77,9 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca eventStringFormatter, codeBlockStart, codeBlockEnd) + sender.client = slack_client.New(cfg.APIToken) + return nil } diff --git a/senders/slack/slack_test.go b/senders/slack/slack_test.go index 43471896f..37cd85eda 100644 --- a/senders/slack/slack_test.go +++ b/senders/slack/slack_test.go @@ -1,10 +1,12 @@ package slack import ( + "errors" "strings" "testing" "time" + "github.com/go-playground/validator/v10" "github.com/moira-alert/moira" logging "github.com/moira-alert/moira/logging/zerolog_adapter" . "github.com/smartystreets/goconvey/convey" @@ -15,15 +17,12 @@ func TestInit(t *testing.T) { Convey("Init tests", t, func() { sender := Sender{} senderSettings := map[string]interface{}{} - Convey("Empty map", func() { - err := sender.Init(senderSettings, logger, nil, "") - So(err, ShouldNotBeNil) - }) - Convey("has empty api_token", func() { - senderSettings["api_token"] = "" + validatorErr := validator.ValidationErrors{} + + Convey("With empty api_token", func() { err := sender.Init(senderSettings, logger, nil, "") - So(err, ShouldNotBeNil) + So(errors.As(err, &validatorErr), ShouldBeTrue) }) Convey("has api_token", func() { diff --git a/senders/telegram/init.go b/senders/telegram/init.go index aabd2a4fd..f49332c94 100644 --- a/senders/telegram/init.go +++ b/senders/telegram/init.go @@ -27,7 +27,7 @@ var pollerTimeout = 10 * time.Second // Structure that represents the Telegram configuration in the YAML file. type config struct { ContactType string `mapstructure:"contact_type"` - APIToken string `mapstructure:"api_token"` + APIToken string `mapstructure:"api_token" validate:"required"` FrontURI string `mapstructure:"front_uri"` } @@ -66,9 +66,10 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to telegram config: %w", err) } - if cfg.APIToken == "" { - return fmt.Errorf("can not read telegram api_token from config") + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("telegram config validation error: %w", err) } + sender.apiToken = cfg.APIToken emojiProvider := telegramEmojiProvider{} diff --git a/senders/telegram/init_test.go b/senders/telegram/init_test.go index 9179cb920..b5a0bbc4e 100644 --- a/senders/telegram/init_test.go +++ b/senders/telegram/init_test.go @@ -7,11 +7,11 @@ import ( "testing" "time" - mock_moira_alert "github.com/moira-alert/moira/mock/moira-alert" - "go.uber.org/mock/gomock" - + "github.com/go-playground/validator/v10" logging "github.com/moira-alert/moira/logging/zerolog_adapter" + mock_moira_alert "github.com/moira-alert/moira/mock/moira-alert" . "github.com/smartystreets/goconvey/convey" + "go.uber.org/mock/gomock" ) func TestInit(t *testing.T) { @@ -19,9 +19,12 @@ func TestInit(t *testing.T) { location, _ := time.LoadLocation("UTC") Convey("Init tests", t, func() { sender := Sender{} - Convey("Empty map", func() { + + validatorErr := validator.ValidationErrors{} + + Convey("With empty api_token", func() { err := sender.Init(map[string]interface{}{}, logger, nil, "") - So(err, ShouldResemble, fmt.Errorf("can not read telegram api_token from config")) + So(errors.As(err, &validatorErr), ShouldBeTrue) So(sender, ShouldResemble, Sender{}) }) diff --git a/senders/twilio/twilio.go b/senders/twilio/twilio.go index d8fc8a77a..7fff15cf4 100644 --- a/senders/twilio/twilio.go +++ b/senders/twilio/twilio.go @@ -11,10 +11,10 @@ import ( // Structure that represents the Twilio configuration in the YAML file. type config struct { - Type string `mapstructure:"sender_type"` - APIAsid string `mapstructure:"api_asid"` - APIAuthToken string `mapstructure:"api_authtoken"` - APIFromPhone string `mapstructure:"api_fromphone"` + Type string `mapstructure:"sender_type" validate:"required"` + APIAsid string `mapstructure:"api_asid" validate:"required"` + APIAuthToken string `mapstructure:"api_authtoken" validate:"required"` + APIFromPhone string `mapstructure:"api_fromphone" validate:"required"` VoiceURL string `mapstructure:"voiceurl"` TwimletsEcho bool `mapstructure:"twimlets_echo"` AppendMessage bool `mapstructure:"append_message"` @@ -43,19 +43,12 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca if err != nil { return fmt.Errorf("failed to decode senderSettings to twilio config: %w", err) } - apiType := cfg.Type - - if cfg.APIAsid == "" { - return fmt.Errorf("can not read [%s] api_sid param from config", apiType) - } - if cfg.APIAuthToken == "" { - return fmt.Errorf("can not read [%s] api_authtoken param from config", apiType) + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("twilio config validation error: %w", err) } - if cfg.APIFromPhone == "" { - return fmt.Errorf("can not read [%s] api_fromphone param from config", apiType) - } + apiType := cfg.Type twilioClient := twilio_client.NewClient(cfg.APIAsid, cfg.APIAuthToken) @@ -65,6 +58,7 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca logger: logger, location: location, } + switch apiType { case "twilio sms": sender.sender = &twilioSenderSms{tSender} diff --git a/senders/twilio/twilio_test.go b/senders/twilio/twilio_test.go index 11b8715e3..1cd1b7753 100644 --- a/senders/twilio/twilio_test.go +++ b/senders/twilio/twilio_test.go @@ -1,11 +1,13 @@ package twilio import ( + "errors" "fmt" "testing" "time" twilio_client "github.com/carlosdp/twiliogo" + "github.com/go-playground/validator/v10" logging "github.com/moira-alert/moira/logging/zerolog_adapter" . "github.com/smartystreets/goconvey/convey" ) @@ -16,9 +18,12 @@ func TestInit(t *testing.T) { logger, _ := logging.ConfigureLog("stdout", "debug", "test", true) location, _ := time.LoadLocation("UTC") settings := map[string]interface{}{} + + validatorErr := validator.ValidationErrors{} + Convey("no api asid", func() { err := sender.Init(settings, logger, nil, "15:04") - So(err, ShouldResemble, fmt.Errorf("can not read [%s] api_sid param from config", "")) + So(errors.As(err, &validatorErr), ShouldBeTrue) So(sender, ShouldResemble, Sender{}) }) @@ -26,7 +31,7 @@ func TestInit(t *testing.T) { Convey("no api authtoken", func() { err := sender.Init(settings, logger, nil, "15:04") - So(err, ShouldResemble, fmt.Errorf("can not read [%s] api_authtoken param from config", "")) + So(errors.As(err, &validatorErr), ShouldBeTrue) So(sender, ShouldResemble, Sender{}) }) @@ -34,7 +39,7 @@ func TestInit(t *testing.T) { Convey("no api fromphone", func() { err := sender.Init(settings, logger, nil, "15:04") - So(err, ShouldResemble, fmt.Errorf("can not read [%s] api_fromphone param from config", "")) + So(errors.As(err, &validatorErr), ShouldBeTrue) So(sender, ShouldResemble, Sender{}) }) @@ -42,7 +47,15 @@ func TestInit(t *testing.T) { Convey("no api type", func() { err := sender.Init(settings, logger, nil, "15:04") - So(err, ShouldResemble, fmt.Errorf("wrong twilio type: %s", "")) + So(errors.As(err, &validatorErr), ShouldBeTrue) + So(sender, ShouldResemble, Sender{}) + }) + + settings["sender_type"] = "test" + + Convey("with unknown api type", func() { + err := sender.Init(settings, logger, nil, "15:04") + So(err, ShouldResemble, fmt.Errorf("wrong twilio type: %s", "test")) So(sender, ShouldResemble, Sender{}) }) diff --git a/senders/victorops/init.go b/senders/victorops/init.go index e47e53485..c623f8208 100644 --- a/senders/victorops/init.go +++ b/senders/victorops/init.go @@ -12,7 +12,7 @@ import ( // Structure that represents the VictorOps configuration in the YAML file. type config struct { - RoutingURL string `mapstructure:"routing_url"` + RoutingURL string `mapstructure:"routing_url" validate:"required"` ImageStore string `mapstructure:"image_store"` FrontURI string `mapstructure:"front_uri"` } @@ -40,11 +40,12 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to victorops config: %w", err) } - sender.routingURL = cfg.RoutingURL - if sender.routingURL == "" { - return fmt.Errorf("cannot read the routing url from the yaml config") + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("victorops config validation error: %w", err) } + sender.routingURL = cfg.RoutingURL + sender.imageStoreID = cfg.ImageStore if sender.imageStoreID == "" { logger.Warning().Msg("Cannot read image_store from the config, will not be able to attach plot images to events") diff --git a/senders/victorops/init_test.go b/senders/victorops/init_test.go index 05c2111a0..e34b6abfe 100644 --- a/senders/victorops/init_test.go +++ b/senders/victorops/init_test.go @@ -1,10 +1,11 @@ package victorops import ( - "fmt" + "errors" "testing" "time" + "github.com/go-playground/validator/v10" "github.com/moira-alert/moira" "github.com/moira-alert/moira/senders/victorops/api" "go.uber.org/mock/gomock" @@ -25,9 +26,12 @@ func TestInit(t *testing.T) { sender := Sender{ImageStores: map[string]moira.ImageStore{ "s3": imageStore, }} - Convey("Empty map", func() { + + validatorErr := validator.ValidationErrors{} + + Convey("With empty routing url", func() { err := sender.Init(map[string]interface{}{}, logger, nil, "") - So(err, ShouldResemble, fmt.Errorf("cannot read the routing url from the yaml config")) + So(errors.As(err, &validatorErr), ShouldBeTrue) So(sender, ShouldResemble, Sender{ ImageStores: map[string]moira.ImageStore{ "s3": imageStore, diff --git a/senders/webhook/webhook.go b/senders/webhook/webhook.go index 3812a8799..036c877cb 100644 --- a/senders/webhook/webhook.go +++ b/senders/webhook/webhook.go @@ -1,7 +1,6 @@ package webhook import ( - "errors" "fmt" "io" "net/http" @@ -11,11 +10,9 @@ import ( "github.com/moira-alert/moira" ) -var ErrMissingURL = errors.New("can not read url from config") - // Structure that represents the Webhook configuration in the YAML file. type config struct { - URL string `mapstructure:"url"` + URL string `mapstructure:"url" validate:"required"` Body string `mapstructure:"body"` Headers map[string]string `mapstructure:"headers"` User string `mapstructure:"user"` @@ -42,13 +39,12 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca return fmt.Errorf("failed to decode senderSettings to webhook config: %w", err) } - sender.url = cfg.URL - if sender.url == "" { - return ErrMissingURL + if err = moira.ValidateStruct(cfg); err != nil { + return fmt.Errorf("webhook config validation error: %w", err) } + sender.url = cfg.URL sender.body = cfg.Body - sender.user, sender.password = cfg.User, cfg.Password sender.headers = map[string]string{ diff --git a/senders/webhook/webhook_test.go b/senders/webhook/webhook_test.go index 07369f11c..845054881 100644 --- a/senders/webhook/webhook_test.go +++ b/senders/webhook/webhook_test.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "encoding/base64" + "errors" "fmt" "net/http" "net/http/httptest" @@ -12,6 +13,7 @@ import ( "testing" "time" + "github.com/go-playground/validator/v10" "github.com/moira-alert/moira" logging "github.com/moira-alert/moira/logging/zerolog_adapter" @@ -38,12 +40,14 @@ var ( func TestSender_Init(t *testing.T) { Convey("Test Init function", t, func() { - Convey("With empty settings", func() { + validatorErr := validator.ValidationErrors{} + + Convey("With empty url", func() { settings := map[string]interface{}{} sender := Sender{} err := sender.Init(settings, logger, location, dateTimeFormat) - So(err, ShouldResemble, ErrMissingURL) + So(errors.As(err, &validatorErr), ShouldBeTrue) }) Convey("With only url", func() {