Reset
This commit is contained in:
243
alerting/provider/ntfy/ntfy.go
Normal file
243
alerting/provider/ntfy/ntfy.go
Normal file
@ -0,0 +1,243 @@
|
||||
package ntfy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/TwiN/gatus/v5/alerting/alert"
|
||||
"github.com/TwiN/gatus/v5/client"
|
||||
"github.com/TwiN/gatus/v5/config/endpoint"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultURL = "https://ntfy.sh"
|
||||
DefaultPriority = 3
|
||||
TokenPrefix = "tk_"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidToken = errors.New("invalid token")
|
||||
ErrTopicNotSet = errors.New("topic not set")
|
||||
ErrInvalidPriority = errors.New("priority must between 1 and 5 inclusively")
|
||||
ErrDuplicateGroupOverride = errors.New("duplicate group override")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Topic string `yaml:"topic"`
|
||||
URL string `yaml:"url,omitempty"` // Defaults to DefaultURL
|
||||
Priority int `yaml:"priority,omitempty"` // Defaults to DefaultPriority
|
||||
Token string `yaml:"token,omitempty"` // Defaults to ""
|
||||
Email string `yaml:"email,omitempty"` // Defaults to ""
|
||||
Click string `yaml:"click,omitempty"` // Defaults to ""
|
||||
DisableFirebase bool `yaml:"disable-firebase,omitempty"` // Defaults to false
|
||||
DisableCache bool `yaml:"disable-cache,omitempty"` // Defaults to false
|
||||
}
|
||||
|
||||
func (cfg *Config) Validate() error {
|
||||
if len(cfg.URL) == 0 {
|
||||
cfg.URL = DefaultURL
|
||||
}
|
||||
if cfg.Priority == 0 {
|
||||
cfg.Priority = DefaultPriority
|
||||
}
|
||||
if len(cfg.Token) > 0 && !strings.HasPrefix(cfg.Token, TokenPrefix) {
|
||||
return ErrInvalidToken
|
||||
}
|
||||
if len(cfg.Topic) == 0 {
|
||||
return ErrTopicNotSet
|
||||
}
|
||||
if cfg.Priority < 1 || cfg.Priority > 5 {
|
||||
return ErrInvalidPriority
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) Merge(override *Config) {
|
||||
if len(override.Topic) > 0 {
|
||||
cfg.Topic = override.Topic
|
||||
}
|
||||
if len(override.URL) > 0 {
|
||||
cfg.URL = override.URL
|
||||
}
|
||||
if override.Priority > 0 {
|
||||
cfg.Priority = override.Priority
|
||||
}
|
||||
if len(override.Token) > 0 {
|
||||
cfg.Token = override.Token
|
||||
}
|
||||
if len(override.Email) > 0 {
|
||||
cfg.Email = override.Email
|
||||
}
|
||||
if len(override.Click) > 0 {
|
||||
cfg.Click = override.Click
|
||||
}
|
||||
if override.DisableFirebase {
|
||||
cfg.DisableFirebase = true
|
||||
}
|
||||
if override.DisableCache {
|
||||
cfg.DisableCache = true
|
||||
}
|
||||
}
|
||||
|
||||
// AlertProvider is the configuration necessary for sending an alert using Slack
|
||||
type AlertProvider struct {
|
||||
DefaultConfig Config `yaml:",inline"`
|
||||
|
||||
// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
|
||||
DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"`
|
||||
|
||||
// Overrides is a list of Override that may be prioritized over the default configuration
|
||||
Overrides []Override `yaml:"overrides,omitempty"`
|
||||
}
|
||||
|
||||
// Override is a case under which the default integration is overridden
|
||||
type Override struct {
|
||||
Group string `yaml:"group"`
|
||||
Config `yaml:",inline"`
|
||||
}
|
||||
|
||||
// Validate the provider's configuration
|
||||
func (provider *AlertProvider) Validate() error {
|
||||
registeredGroups := make(map[string]bool)
|
||||
if provider.Overrides != nil {
|
||||
for _, override := range provider.Overrides {
|
||||
if len(override.Group) == 0 {
|
||||
return ErrDuplicateGroupOverride
|
||||
}
|
||||
if _, ok := registeredGroups[override.Group]; ok {
|
||||
return ErrDuplicateGroupOverride
|
||||
}
|
||||
if len(override.Token) > 0 && !strings.HasPrefix(override.Token, TokenPrefix) {
|
||||
return ErrDuplicateGroupOverride
|
||||
}
|
||||
if override.Priority < 0 || override.Priority >= 6 {
|
||||
return ErrDuplicateGroupOverride
|
||||
}
|
||||
registeredGroups[override.Group] = true
|
||||
}
|
||||
}
|
||||
|
||||
return provider.DefaultConfig.Validate()
|
||||
}
|
||||
|
||||
// Send an alert using the provider
|
||||
func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error {
|
||||
cfg, err := provider.GetConfig(ep.Group, alert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(cfg, ep, alert, result, resolved))
|
||||
url := cfg.URL
|
||||
request, err := http.NewRequest(http.MethodPost, url, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
if token := cfg.Token; len(token) > 0 {
|
||||
request.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
if cfg.DisableFirebase {
|
||||
request.Header.Set("Firebase", "no")
|
||||
}
|
||||
if cfg.DisableCache {
|
||||
request.Header.Set("Cache", "no")
|
||||
}
|
||||
response, err := client.GetHTTPClient(nil).Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Topic string `json:"topic"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Tags []string `json:"tags"`
|
||||
Priority int `json:"priority"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Click string `json:"click,omitempty"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte {
|
||||
var message, formattedConditionResults, tag string
|
||||
if resolved {
|
||||
tag = "white_check_mark"
|
||||
message = "An alert has been resolved after passing successfully " + strconv.Itoa(alert.SuccessThreshold) + " time(s) in a row"
|
||||
} else {
|
||||
tag = "rotating_light"
|
||||
message = "An alert has been triggered due to having failed " + strconv.Itoa(alert.FailureThreshold) + " time(s) in a row"
|
||||
}
|
||||
for _, conditionResult := range result.ConditionResults {
|
||||
var prefix string
|
||||
if conditionResult.Success {
|
||||
prefix = "🟢"
|
||||
} else {
|
||||
prefix = "🔴"
|
||||
}
|
||||
formattedConditionResults += fmt.Sprintf("\n%s %s", prefix, conditionResult.Condition)
|
||||
}
|
||||
if len(alert.GetDescription()) > 0 {
|
||||
message += " with the following description: " + alert.GetDescription()
|
||||
}
|
||||
message += formattedConditionResults
|
||||
body, _ := json.Marshal(Body{
|
||||
Topic: cfg.Topic,
|
||||
Title: "Gatus: " + ep.DisplayName(),
|
||||
Message: message,
|
||||
Tags: []string{tag},
|
||||
Priority: cfg.Priority,
|
||||
Email: cfg.Email,
|
||||
Click: cfg.Click,
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// GetDefaultAlert returns the provider's default alert configuration
|
||||
func (provider *AlertProvider) GetDefaultAlert() *alert.Alert {
|
||||
return provider.DefaultAlert
|
||||
}
|
||||
|
||||
// GetConfig returns the configuration for the provider with the overrides applied
|
||||
func (provider *AlertProvider) GetConfig(group string, alert *alert.Alert) (*Config, error) {
|
||||
cfg := provider.DefaultConfig
|
||||
// Handle group overrides
|
||||
if provider.Overrides != nil {
|
||||
for _, override := range provider.Overrides {
|
||||
if group == override.Group {
|
||||
cfg.Merge(&override.Config)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle alert overrides
|
||||
if len(alert.ProviderOverride) != 0 {
|
||||
overrideConfig := Config{}
|
||||
if err := yaml.Unmarshal(alert.ProviderOverrideAsBytes(), &overrideConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Merge(&overrideConfig)
|
||||
}
|
||||
// Validate the configuration
|
||||
err := cfg.Validate()
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
// ValidateOverrides validates the alert's provider override and, if present, the group override
|
||||
func (provider *AlertProvider) ValidateOverrides(group string, alert *alert.Alert) error {
|
||||
_, err := provider.GetConfig(group, alert)
|
||||
return err
|
||||
}
|
414
alerting/provider/ntfy/ntfy_test.go
Normal file
414
alerting/provider/ntfy/ntfy_test.go
Normal file
@ -0,0 +1,414 @@
|
||||
package ntfy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/TwiN/gatus/v5/alerting/alert"
|
||||
"github.com/TwiN/gatus/v5/config/endpoint"
|
||||
)
|
||||
|
||||
func TestAlertProvider_Validate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
provider AlertProvider
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "no-url-should-use-default-value",
|
||||
provider: AlertProvider{DefaultConfig: Config{Topic: "example", Priority: 1}},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "valid-with-token",
|
||||
provider: AlertProvider{DefaultConfig: Config{Topic: "example", Priority: 1, Token: "tk_faketoken"}},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-token",
|
||||
provider: AlertProvider{DefaultConfig: Config{Topic: "example", Priority: 1, Token: "xx_faketoken"}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "invalid-topic",
|
||||
provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "", Priority: 1}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "invalid-priority-too-high",
|
||||
provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 6}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "invalid-priority-too-low",
|
||||
provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: -1}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no-priority-should-use-default-value",
|
||||
provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example"}},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-override-token",
|
||||
provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{Group: "g", Config: Config{Token: "xx_faketoken"}}}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "invalid-override-priority",
|
||||
provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{Group: "g", Config: Config{Priority: 8}}}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no-override-group-name",
|
||||
provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{}}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "duplicate-override-group-names",
|
||||
provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{Group: "g"}, {Group: "g"}}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "valid-override",
|
||||
provider: AlertProvider{DefaultConfig: Config{Topic: "example"}, Overrides: []Override{{Group: "g1", Config: Config{Priority: 4, Click: "https://example.com"}}, {Group: "g2", Config: Config{Topic: "Example", Token: "tk_faketoken"}}}},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
err := scenario.provider.Validate()
|
||||
if scenario.expected && err != nil {
|
||||
t.Error("expected no error, got", err.Error())
|
||||
}
|
||||
if !scenario.expected && err == nil {
|
||||
t.Error("expected error, got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
firstDescription := "description-1"
|
||||
secondDescription := "description-2"
|
||||
scenarios := []struct {
|
||||
Name string
|
||||
Provider AlertProvider
|
||||
Alert alert.Alert
|
||||
Resolved bool
|
||||
ExpectedBody string
|
||||
}{
|
||||
{
|
||||
Name: "triggered",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1}},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1}`,
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 2}},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been resolved after passing successfully 5 time(s) in a row with the following description: description-2\n🟢 [CONNECTED] == true\n🟢 [STATUS] == 200","tags":["white_check_mark"],"priority":2}`,
|
||||
},
|
||||
{
|
||||
Name: "triggered-email",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com"}},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
},
|
||||
{
|
||||
Name: "resolved-email",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 2, Email: "test@example.com", Click: "example.com"}},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been resolved after passing successfully 5 time(s) in a row with the following description: description-2\n🟢 [CONNECTED] == true\n🟢 [STATUS] == 200","tags":["white_check_mark"],"priority":2,"email":"test@example.com","click":"example.com"}`,
|
||||
},
|
||||
{
|
||||
Name: "group-override",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 5, Email: "test@example.com", Click: "example.com"}, Overrides: []Override{{Group: "g", Config: Config{Topic: "group-topic", Priority: 4, Email: "override@test.com", Click: "test.com"}}}},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: `{"topic":"group-topic","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":4,"email":"override@test.com","click":"test.com"}`,
|
||||
},
|
||||
{
|
||||
Name: "alert-override",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 5, Email: "test@example.com", Click: "example.com"}, Overrides: []Override{{Group: "g", Config: Config{Topic: "group-topic", Priority: 4, Email: "override@test.com", Click: "test.com"}}}},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3, ProviderOverride: map[string]any{"topic": "alert-topic"}},
|
||||
Resolved: false,
|
||||
ExpectedBody: `{"topic":"alert-topic","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":4,"email":"override@test.com","click":"test.com"}`,
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
cfg, err := scenario.Provider.GetConfig("g", &scenario.Alert)
|
||||
if err != nil {
|
||||
t.Error("expected no error, got", err.Error())
|
||||
}
|
||||
body := scenario.Provider.buildRequestBody(
|
||||
cfg,
|
||||
&endpoint.Endpoint{Name: "endpoint-name"},
|
||||
&scenario.Alert,
|
||||
&endpoint.Result{
|
||||
ConditionResults: []*endpoint.ConditionResult{
|
||||
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
||||
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
||||
},
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertProvider_Send(t *testing.T) {
|
||||
description := "description-1"
|
||||
scenarios := []struct {
|
||||
Name string
|
||||
Provider AlertProvider
|
||||
Alert alert.Alert
|
||||
Resolved bool
|
||||
Group string
|
||||
ExpectedBody string
|
||||
ExpectedHeaders map[string]string
|
||||
}{
|
||||
{
|
||||
Name: "triggered",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com"}},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "token",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", Token: "tk_mytoken"}},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer tk_mytoken",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no firebase",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", DisableFirebase: true}},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Firebase": "no",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no cache",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", DisableCache: true}},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Cache": "no",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "neither firebase & cache",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", DisableFirebase: true, DisableCache: true}},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Firebase": "no",
|
||||
"Cache": "no",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "overrides",
|
||||
Provider: AlertProvider{DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", Token: "tk_mytoken"}, Overrides: []Override{Override{Group: "other-group", Config: Config{URL: "https://example.com", Token: "tk_othertoken"}}, Override{Group: "test-group", Config: Config{Token: "tk_test_token"}}}},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "test-group",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: test-group/endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer tk_test_token",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
// Start a local HTTP server
|
||||
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
// Test request parameters
|
||||
for header, value := range scenario.ExpectedHeaders {
|
||||
if value != req.Header.Get(header) {
|
||||
t.Errorf("expected: %s, got: %s", value, req.Header.Get(header))
|
||||
}
|
||||
}
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
// Send response to be tested
|
||||
rw.Write([]byte(`OK`))
|
||||
}))
|
||||
// Close the server when test finishes
|
||||
defer server.Close()
|
||||
|
||||
scenario.Provider.DefaultConfig.URL = server.URL
|
||||
err := scenario.Provider.Send(
|
||||
&endpoint.Endpoint{Name: "endpoint-name", Group: scenario.Group},
|
||||
&scenario.Alert,
|
||||
&endpoint.Result{
|
||||
ConditionResults: []*endpoint.ConditionResult{
|
||||
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
||||
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
||||
},
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if err != nil {
|
||||
t.Error("Encountered an error on Send: ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertProvider_GetConfig(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
Name string
|
||||
Provider AlertProvider
|
||||
InputGroup string
|
||||
InputAlert alert.Alert
|
||||
ExpectedOutput Config
|
||||
}{
|
||||
{
|
||||
Name: "provider-no-override-specify-no-group-should-default",
|
||||
Provider: AlertProvider{
|
||||
DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
Overrides: nil,
|
||||
},
|
||||
InputGroup: "",
|
||||
InputAlert: alert.Alert{},
|
||||
ExpectedOutput: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
},
|
||||
{
|
||||
Name: "provider-no-override-specify-group-should-default",
|
||||
Provider: AlertProvider{
|
||||
DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
Overrides: nil,
|
||||
},
|
||||
InputGroup: "group",
|
||||
InputAlert: alert.Alert{},
|
||||
ExpectedOutput: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
},
|
||||
{
|
||||
Name: "provider-with-override-specify-no-group-should-default",
|
||||
Provider: AlertProvider{
|
||||
DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
Overrides: []Override{
|
||||
{
|
||||
Group: "group",
|
||||
Config: Config{URL: "https://group-example.com", Topic: "group-topic", Priority: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
InputGroup: "",
|
||||
InputAlert: alert.Alert{},
|
||||
ExpectedOutput: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
},
|
||||
{
|
||||
Name: "provider-with-override-specify-group-should-override",
|
||||
Provider: AlertProvider{
|
||||
DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
Overrides: []Override{
|
||||
{
|
||||
Group: "group",
|
||||
Config: Config{URL: "https://group-example.com", Topic: "group-topic", Priority: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
InputGroup: "group",
|
||||
InputAlert: alert.Alert{},
|
||||
ExpectedOutput: Config{URL: "https://group-example.com", Topic: "group-topic", Priority: 2},
|
||||
},
|
||||
{
|
||||
Name: "provider-with-group-override-and-alert-override--alert-override-should-take-precedence",
|
||||
Provider: AlertProvider{
|
||||
DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
Overrides: []Override{
|
||||
{
|
||||
Group: "group",
|
||||
Config: Config{URL: "https://group-example.com", Topic: "group-topic", Priority: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
InputGroup: "group",
|
||||
InputAlert: alert.Alert{ProviderOverride: map[string]any{"url": "http://alert-example.com", "topic": "alert-topic", "priority": 3}},
|
||||
ExpectedOutput: Config{URL: "http://alert-example.com", Topic: "alert-topic", Priority: 3},
|
||||
},
|
||||
{
|
||||
Name: "provider-with-partial-overrides",
|
||||
Provider: AlertProvider{
|
||||
DefaultConfig: Config{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
Overrides: []Override{
|
||||
{
|
||||
Group: "group",
|
||||
Config: Config{Topic: "group-topic"},
|
||||
},
|
||||
},
|
||||
},
|
||||
InputGroup: "group",
|
||||
InputAlert: alert.Alert{ProviderOverride: map[string]any{"priority": 3}},
|
||||
ExpectedOutput: Config{URL: "https://ntfy.sh", Topic: "group-topic", Priority: 3},
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
got, err := scenario.Provider.GetConfig(scenario.InputGroup, &scenario.InputAlert)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if got.URL != scenario.ExpectedOutput.URL {
|
||||
t.Errorf("expected url %s, got %s", scenario.ExpectedOutput.URL, got.URL)
|
||||
}
|
||||
if got.Topic != scenario.ExpectedOutput.Topic {
|
||||
t.Errorf("expected topic %s, got %s", scenario.ExpectedOutput.Topic, got.Topic)
|
||||
}
|
||||
if got.Priority != scenario.ExpectedOutput.Priority {
|
||||
t.Errorf("expected priority %d, got %d", scenario.ExpectedOutput.Priority, got.Priority)
|
||||
}
|
||||
// Test ValidateOverrides as well, since it really just calls GetConfig
|
||||
if err = scenario.Provider.ValidateOverrides(scenario.InputGroup, &scenario.InputAlert); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user