Opsgenie Alert Provider (#214)
* ✨ opsgenie alert provider * ✅ add unit tests * ✏️ typofix * 📝 update readme * ✨ add details * ✨ use group to previne colisions * ✏️ typofix * ✏️ typofix
This commit is contained in:
		
							
								
								
									
										22
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								README.md
									
									
									
									
									
								
							| @ -311,8 +311,10 @@ ignored. | ||||
| | Parameter              | Description                                                                                                            | Default        | | ||||
| |:-----------------------|:---------------------------------------------------------------------------------------------------------------------- |:-------| | ||||
| | `alerting.discord`     | Configuration for alerts of type `discord`. <br />See [Configuring Discord alerts](#configuring-discord-alerts).             | `{}`   | | ||||
| | `alerting.mattermost`  | Configuration for alerts of type `mattermost`. <br />See [Configuring Mattermost alerts](#configuring-mattermost-alerts).   | `{}`   | | ||||
| | `alerting.email`       | Configuration for alerts of type `email`. <br />See [Configuring Email alerts](#configuring-email-alerts).                   | `{}`   | | ||||
| | `alerting.mattermost`  | Configuration for alerts of type `mattermost`. <br />See [Configuring Mattermost alerts](#configuring-mattermost-alerts).    | `{}`   | | ||||
| | `alerting.messagebird` | Configuration for alerts of type `messagebird`. <br />See [Configuring Messagebird alerts](#configuring-messagebird-alerts). | `{}`   | | ||||
| | `alerting.opsgenie`    | Configuration for alerts of type `opsgenie`. <br />See [Configuring Opsgenie alerts](#configuring-opsgenie-alerts).          | `{}`   | | ||||
| | `alerting.pagerduty`   | Configuration for alerts of type `pagerduty`. <br />See [Configuring PagerDuty alerts](#configuring-pagerduty-alerts).       | `{}`   | | ||||
| | `alerting.slack`       | Configuration for alerts of type `slack`. <br />See [Configuring Slack alerts](#configuring-slack-alerts).                   | `{}`   | | ||||
| | `alerting.teams`       | Configuration for alerts of type `teams`. <br />See [Configuring Teams alerts](#configuring-teams-alerts).                   | `{}`   | | ||||
| @ -455,6 +457,24 @@ endpoints: | ||||
|         description: "healthcheck failed" | ||||
| ``` | ||||
|  | ||||
| #### Configuring Opsgenie alerts | ||||
| | Parameter                                              | Description                                                                   | Default              | | ||||
| |:------------------------------------------------------ |:----------------------------------------------------------------------------- |:-------------------- | | ||||
| | `alerting.opsgenie`                                    | Configuration for alerts of type `opsgenie`                                   | `{}`                 | | ||||
| | `alerting.opsgenie.api-key`                            | Opsgenie API Key                                                              |  Required `""`       | | ||||
| | `alerting.opsgenie.priority`                           | Priority level of the alert.                                                  | `P1`                 | | ||||
| | `alerting.opsgenie.source`                             | Source field of the alert.                                                    | `gatus`              | | ||||
| | `alerting.opsgenie.entity-prefix`                      | Entity field prefix.                                                          | `gatus-`             | | ||||
| | `alerting.opsgenie.alias-prefix`                       | Alias field prefix.                                                           | `gatus-healthcheck-` | | ||||
| | `alerting.opsgenie.tags`                               | Tags of alert.                                                                | `[]`                 | | ||||
|  | ||||
| Opsgenie provider will automatically open and close alerts. | ||||
|  | ||||
| ```yaml | ||||
| alerting: | ||||
|   opsgenie: | ||||
|     api-key: "00000000-0000-0000-0000-000000000000" | ||||
| ``` | ||||
|  | ||||
| #### Configuring PagerDuty alerts | ||||
| | Parameter                                              | Description                                                                   | Default        | | ||||
|  | ||||
| @ -34,4 +34,7 @@ const ( | ||||
|  | ||||
| 	// TypeTwilio is the Type for the twilio alerting provider | ||||
| 	TypeTwilio Type = "twilio" | ||||
|  | ||||
| 	// TypeOpsgenie is the Type for the opsgenie alerting provider | ||||
| 	TypeOpsgenie Type = "opsgenie" | ||||
| ) | ||||
|  | ||||
| @ -8,6 +8,7 @@ import ( | ||||
| 	"github.com/TwiN/gatus/v3/alerting/provider/email" | ||||
| 	"github.com/TwiN/gatus/v3/alerting/provider/mattermost" | ||||
| 	"github.com/TwiN/gatus/v3/alerting/provider/messagebird" | ||||
| 	"github.com/TwiN/gatus/v3/alerting/provider/opsgenie" | ||||
| 	"github.com/TwiN/gatus/v3/alerting/provider/pagerduty" | ||||
| 	"github.com/TwiN/gatus/v3/alerting/provider/slack" | ||||
| 	"github.com/TwiN/gatus/v3/alerting/provider/teams" | ||||
| @ -46,6 +47,9 @@ type Config struct { | ||||
|  | ||||
| 	// Twilio is the configuration for the twilio alerting provider | ||||
| 	Twilio *twilio.AlertProvider `yaml:"twilio,omitempty"` | ||||
|  | ||||
| 	// Opsgenie is the configuration for the opsgenie alerting provider | ||||
| 	Opsgenie *opsgenie.AlertProvider `yaml:"opsgenie,omitempty"` | ||||
| } | ||||
|  | ||||
| // GetAlertingProviderByAlertType returns an provider.AlertProvider by its corresponding alert.Type | ||||
| @ -81,6 +85,12 @@ func (config Config) GetAlertingProviderByAlertType(alertType alert.Type) provid | ||||
| 			return nil | ||||
| 		} | ||||
| 		return config.Messagebird | ||||
| 	case alert.TypeOpsgenie: | ||||
| 		if config.Opsgenie == nil { | ||||
| 			// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil | ||||
| 			return nil | ||||
| 		} | ||||
| 		return config.Opsgenie | ||||
| 	case alert.TypePagerDuty: | ||||
| 		if config.PagerDuty == nil { | ||||
| 			// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil | ||||
|  | ||||
							
								
								
									
										267
									
								
								alerting/provider/opsgenie/opsgenie.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								alerting/provider/opsgenie/opsgenie.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,267 @@ | ||||
| package opsgenie | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/TwiN/gatus/v3/alerting/alert" | ||||
| 	"github.com/TwiN/gatus/v3/client" | ||||
| 	"github.com/TwiN/gatus/v3/core" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	restAPI = "https://api.opsgenie.com/v2/alerts" | ||||
| ) | ||||
|  | ||||
| type opsgenieAlertCreateRequest struct { | ||||
| 	Message     string            `json:"message"` | ||||
| 	Priority    string            `json:"priority"` | ||||
| 	Source      string            `json:"source"` | ||||
| 	Entity      string            `json:"entity"` | ||||
| 	Alias       string            `json:"alias"` | ||||
| 	Description string            `json:"description"` | ||||
| 	Tags        []string          `json:"tags,omitempty"` | ||||
| 	Details     map[string]string `json:"details"` | ||||
| } | ||||
|  | ||||
| type opsgenieAlertCloseRequest struct { | ||||
| 	Source string `json:"source"` | ||||
| 	Note   string `json:"note"` | ||||
| } | ||||
|  | ||||
| type AlertProvider struct { | ||||
| 	APIKey string `yaml:"api-key"` | ||||
|  | ||||
| 	//Priority define priority to be used in opsgenie alert payload | ||||
| 	// defaults: P1 | ||||
| 	Priority string `yaml:"priority"` | ||||
|  | ||||
| 	//Source define source to be used in opsgenie alert payload | ||||
| 	// defaults: gatus | ||||
| 	Source string `yaml:"source"` | ||||
|  | ||||
| 	//EntityPrefix is a prefix to be used in entity argument in opsgenie alert payload | ||||
| 	// defaults: gatus- | ||||
| 	EntityPrefix string `yaml:"entity-prefix"` | ||||
|  | ||||
| 	//AliasPrefix is a prefix to be used in alias argument in opsgenie alert payload | ||||
| 	// defaults: gatus-healthcheck- | ||||
| 	AliasPrefix string `yaml:"alias-prefix"` | ||||
|  | ||||
| 	//tags define tags to be used in opsgenie alert payload | ||||
| 	// defaults: [] | ||||
| 	Tags []string `yaml:"tags"` | ||||
|  | ||||
| 	// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type | ||||
| 	DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) IsValid() bool { | ||||
| 	return len(provider.APIKey) > 0 | ||||
| } | ||||
|  | ||||
| // Send an alert using the provider | ||||
| // | ||||
| // Relevant: https://docs.opsgenie.com/docs/alert-api | ||||
| func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { | ||||
| 	err := provider.createAlert(endpoint, alert, result, resolved) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if resolved { | ||||
| 		err = provider.closeAlert(endpoint, alert) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if alert.IsSendingOnResolved() { | ||||
| 		if resolved { | ||||
| 			// The alert has been resolved and there's no error, so we can clear the alert's ResolveKey | ||||
| 			alert.ResolveKey = "" | ||||
| 		} else { | ||||
| 			alert.ResolveKey = provider.alias(buildKey(endpoint)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) createAlert(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { | ||||
| 	payload := provider.buildCreateRequestBody(endpoint, alert, result, resolved) | ||||
|  | ||||
| 	_, err := provider.sendRequest(restAPI, http.MethodPost, payload) | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) closeAlert(endpoint *core.Endpoint, alert *alert.Alert) error { | ||||
| 	payload := provider.buildCloseRequestBody(endpoint, alert) | ||||
| 	url := restAPI + "/" + provider.alias(buildKey(endpoint)) + "/close?identifierType=alias" | ||||
|  | ||||
| 	_, err := provider.sendRequest(url, http.MethodPost, payload) | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) sendRequest(url, method string, payload interface{}) (*http.Response, error) { | ||||
|  | ||||
| 	body, err := json.Marshal(payload) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("fail to build alert payload: %v", payload) | ||||
| 	} | ||||
|  | ||||
| 	request, err := http.NewRequest(method, url, bytes.NewBuffer(body)) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	request.Header.Set("Content-Type", "application/json") | ||||
| 	request.Header.Set("Authorization", "GenieKey "+provider.APIKey) | ||||
|  | ||||
| 	res, err := client.GetHTTPClient(nil).Do(request) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if res.StatusCode > 399 { | ||||
| 		rBody, _ := io.ReadAll(res.Body) | ||||
| 		return nil, fmt.Errorf("call to provider alert returned status code %d: %s", res.StatusCode, string(rBody)) | ||||
| 	} | ||||
|  | ||||
| 	return res, nil | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) opsgenieAlertCreateRequest { | ||||
| 	var message, description, results string | ||||
|  | ||||
| 	if resolved { | ||||
| 		message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.Name, alert.GetDescription()) | ||||
| 		description = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.Name, alert.SuccessThreshold) | ||||
| 	} else { | ||||
| 		message = fmt.Sprintf("%s - %s", endpoint.Name, alert.GetDescription()) | ||||
| 		description = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.Name, alert.FailureThreshold) | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	if endpoint.Group != "" { | ||||
| 		message = fmt.Sprintf("[%s] %s", endpoint.Group, message) | ||||
| 	} | ||||
|  | ||||
| 	for _, conditionResult := range result.ConditionResults { | ||||
| 		var prefix string | ||||
| 		if conditionResult.Success { | ||||
| 			prefix = "▣" | ||||
| 		} else { | ||||
| 			prefix = "▢" | ||||
| 		} | ||||
| 		results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition) | ||||
| 	} | ||||
|  | ||||
| 	description = description + "\n" + results | ||||
|  | ||||
| 	key := buildKey(endpoint) | ||||
| 	details := map[string]string{ | ||||
| 		"endpoint:url":    endpoint.URL, | ||||
| 		"endpoint:group":  endpoint.Group, | ||||
| 		"result:hostname": result.Hostname, | ||||
| 		"result:ip":       result.IP, | ||||
| 		"result:dns_code": result.DNSRCode, | ||||
| 		"result:errors":   strings.Join(result.Errors, ","), | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range details { | ||||
| 		if v == "" { | ||||
| 			delete(details, k) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if result.HTTPStatus > 0 { | ||||
| 		details["result:http_status"] = strconv.Itoa(result.HTTPStatus) | ||||
| 	} | ||||
|  | ||||
| 	return opsgenieAlertCreateRequest{ | ||||
| 		Message:     message, | ||||
| 		Description: description, | ||||
| 		Source:      provider.source(), | ||||
| 		Priority:    provider.priority(), | ||||
| 		Alias:       provider.alias(key), | ||||
| 		Entity:      provider.entity(key), | ||||
| 		Tags:        provider.Tags, | ||||
| 		Details:     details, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) buildCloseRequestBody(endpoint *core.Endpoint, alert *alert.Alert) opsgenieAlertCloseRequest { | ||||
| 	return opsgenieAlertCloseRequest{ | ||||
| 		Source: buildKey(endpoint), | ||||
| 		Note:   fmt.Sprintf("RESOLVED: %s - %s", endpoint.Name, alert.GetDescription()), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) source() string { | ||||
| 	source := provider.Source | ||||
|  | ||||
| 	if source == "" { | ||||
| 		return "gatus" | ||||
| 	} | ||||
|  | ||||
| 	return source | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) alias(key string) string { | ||||
| 	alias := provider.AliasPrefix | ||||
|  | ||||
| 	if alias == "" { | ||||
| 		alias = "gatus-healthcheck-" | ||||
| 	} | ||||
|  | ||||
| 	return alias + key | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) entity(key string) string { | ||||
| 	alias := provider.EntityPrefix | ||||
| 	if alias == "" { | ||||
| 		alias = "gatus-" | ||||
| 	} | ||||
|  | ||||
| 	return alias + key | ||||
| } | ||||
|  | ||||
| func (provider *AlertProvider) priority() string { | ||||
| 	priority := provider.Priority | ||||
|  | ||||
| 	if priority == "" { | ||||
| 		return "P1" | ||||
| 	} | ||||
|  | ||||
| 	return priority | ||||
| } | ||||
|  | ||||
| func (provider AlertProvider) GetDefaultAlert() *alert.Alert { | ||||
| 	return provider.DefaultAlert | ||||
| } | ||||
|  | ||||
| func buildKey(endpoint *core.Endpoint) string { | ||||
| 	name := toKebabCase(endpoint.Name) | ||||
|  | ||||
| 	if endpoint.Group == "" { | ||||
| 		return name | ||||
| 	} | ||||
|  | ||||
| 	return toKebabCase(endpoint.Group) + "-" + name | ||||
| } | ||||
|  | ||||
| func toKebabCase(val string) string { | ||||
| 	return strings.ToLower(strings.ReplaceAll(val, " ", "-")) | ||||
| } | ||||
							
								
								
									
										331
									
								
								alerting/provider/opsgenie/opsgenie_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								alerting/provider/opsgenie/opsgenie_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,331 @@ | ||||
| package opsgenie | ||||
|  | ||||
| import ( | ||||
| 	"github.com/TwiN/gatus/v3/alerting/alert" | ||||
| 	"github.com/TwiN/gatus/v3/client" | ||||
| 	"github.com/TwiN/gatus/v3/core" | ||||
| 	"github.com/TwiN/gatus/v3/test" | ||||
| 	"net/http" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestAlertProvider_IsValid(t *testing.T) { | ||||
| 	invalidProvider := AlertProvider{APIKey: ""} | ||||
| 	if invalidProvider.IsValid() { | ||||
| 		t.Error("provider shouldn't have been valid") | ||||
| 	} | ||||
| 	validProvider := AlertProvider{APIKey: "00000000-0000-0000-0000-000000000000"} | ||||
| 	if !validProvider.IsValid() { | ||||
| 		t.Error("provider should've been valid") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestAlertProvider_Send(t *testing.T) { | ||||
| 	defer client.InjectHTTPClient(nil) | ||||
|  | ||||
| 	description := "my bad alert description" | ||||
|  | ||||
| 	scenarios := []struct { | ||||
| 		Name             string | ||||
| 		Provider         AlertProvider | ||||
| 		Alert            alert.Alert | ||||
| 		Resolved         bool | ||||
| 		MockRoundTripper test.MockRoundTripper | ||||
| 		ExpectedError    bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			Name:          "triggered", | ||||
| 			Provider:      AlertProvider{}, | ||||
| 			Alert:         alert.Alert{Description: &description, SuccessThreshold: 1, FailureThreshold: 1}, | ||||
| 			Resolved:      false, | ||||
| 			ExpectedError: false, | ||||
| 			MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { | ||||
| 				return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} | ||||
| 			}), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:          "triggered-error", | ||||
| 			Provider:      AlertProvider{}, | ||||
| 			Alert:         alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, | ||||
| 			Resolved:      false, | ||||
| 			ExpectedError: true, | ||||
| 			MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { | ||||
| 				return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} | ||||
| 			}), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:          "resolved", | ||||
| 			Provider:      AlertProvider{}, | ||||
| 			Alert:         alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, | ||||
| 			Resolved:      true, | ||||
| 			ExpectedError: false, | ||||
| 			MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { | ||||
| 				return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} | ||||
| 			}), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:          "resolved-error", | ||||
| 			Provider:      AlertProvider{}, | ||||
| 			Alert:         alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, | ||||
| 			Resolved:      true, | ||||
| 			ExpectedError: true, | ||||
| 			MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response { | ||||
| 				return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody} | ||||
| 			}), | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, scenario := range scenarios { | ||||
| 		t.Run(scenario.Name, func(t *testing.T) { | ||||
| 			client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper}) | ||||
|  | ||||
| 			err := scenario.Provider.Send( | ||||
| 				&core.Endpoint{Name: "endpoint-name"}, | ||||
| 				&scenario.Alert, | ||||
| 				&core.Result{ | ||||
| 					ConditionResults: []*core.ConditionResult{ | ||||
| 						{Condition: "[CONNECTED] == true", Success: scenario.Resolved}, | ||||
| 						{Condition: "[STATUS] == 200", Success: scenario.Resolved}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				scenario.Resolved, | ||||
| 			) | ||||
| 			if scenario.ExpectedError && err == nil { | ||||
| 				t.Error("expected error, got none") | ||||
| 			} | ||||
| 			if !scenario.ExpectedError && err != nil { | ||||
| 				t.Error("expected no error, got", err.Error()) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestAlertProvider_buildCreateRequestBody(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	description := "alert description" | ||||
|  | ||||
| 	scenarios := []struct { | ||||
| 		Name     string | ||||
| 		Provider *AlertProvider | ||||
| 		Alert    *alert.Alert | ||||
| 		Endpoint *core.Endpoint | ||||
| 		Result   *core.Result | ||||
| 		Resolved bool | ||||
| 		want     opsgenieAlertCreateRequest | ||||
| 	}{ | ||||
| 		{ | ||||
| 			Name:     "missing all params (unresolved)", | ||||
| 			Provider: &AlertProvider{}, | ||||
| 			Alert:    &alert.Alert{}, | ||||
| 			Endpoint: &core.Endpoint{}, | ||||
| 			Result:   &core.Result{}, | ||||
| 			Resolved: false, | ||||
| 			want: opsgenieAlertCreateRequest{ | ||||
| 				Message:     " - ", | ||||
| 				Priority:    "P1", | ||||
| 				Source:      "gatus", | ||||
| 				Entity:      "gatus-", | ||||
| 				Alias:       "gatus-healthcheck-", | ||||
| 				Description: "An alert for ** has been triggered due to having failed 0 time(s) in a row\n", | ||||
| 				Tags:        nil, | ||||
| 				Details:     map[string]string{}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:     "missing all params (resolved)", | ||||
| 			Provider: &AlertProvider{}, | ||||
| 			Alert:    &alert.Alert{}, | ||||
| 			Endpoint: &core.Endpoint{}, | ||||
| 			Result:   &core.Result{}, | ||||
| 			Resolved: true, | ||||
| 			want: opsgenieAlertCreateRequest{ | ||||
| 				Message:     "RESOLVED:  - ", | ||||
| 				Priority:    "P1", | ||||
| 				Source:      "gatus", | ||||
| 				Entity:      "gatus-", | ||||
| 				Alias:       "gatus-healthcheck-", | ||||
| 				Description: "An alert for ** has been resolved after passing successfully 0 time(s) in a row\n", | ||||
| 				Tags:        nil, | ||||
| 				Details:     map[string]string{}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:     "with default options (unresolved)", | ||||
| 			Provider: &AlertProvider{}, | ||||
| 			Alert: &alert.Alert{ | ||||
| 				Description:      &description, | ||||
| 				FailureThreshold: 3, | ||||
| 			}, | ||||
| 			Endpoint: &core.Endpoint{ | ||||
| 				Name: "my supper app", | ||||
| 			}, | ||||
| 			Result: &core.Result{ | ||||
| 				ConditionResults: []*core.ConditionResult{ | ||||
| 					{ | ||||
| 						Condition: "[STATUS] == 200", | ||||
| 						Success:   true, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Condition: "[BODY] == OK", | ||||
| 						Success:   false, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			Resolved: false, | ||||
| 			want: opsgenieAlertCreateRequest{ | ||||
| 				Message:  "my supper app - " + description, | ||||
| 				Priority: "P1", | ||||
| 				Source:   "gatus", | ||||
| 				Entity:   "gatus-my-supper-app", | ||||
| 				Alias:    "gatus-healthcheck-my-supper-app", | ||||
| 				Description: "An alert for *my supper app* has been triggered due to having failed 3 time(s) in a row\n" + | ||||
| 					"▣ - `[STATUS] == 200`\n" + | ||||
| 					"▢ - `[BODY] == OK`\n", | ||||
| 				Tags:    nil, | ||||
| 				Details: map[string]string{}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "with custom options (resolved)", | ||||
| 			Provider: &AlertProvider{ | ||||
| 				Priority:     "P5", | ||||
| 				EntityPrefix: "oompa-", | ||||
| 				AliasPrefix:  "loompa-", | ||||
| 				Source:       "gatus-hc", | ||||
| 				Tags:         []string{"do-ba-dee-doo"}, | ||||
| 			}, | ||||
| 			Alert: &alert.Alert{ | ||||
| 				Description:      &description, | ||||
| 				SuccessThreshold: 4, | ||||
| 			}, | ||||
| 			Endpoint: &core.Endpoint{ | ||||
| 				Name: "my mega app", | ||||
| 			}, | ||||
| 			Result: &core.Result{ | ||||
| 				ConditionResults: []*core.ConditionResult{ | ||||
| 					{ | ||||
| 						Condition: "[STATUS] == 200", | ||||
| 						Success:   true, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			Resolved: true, | ||||
| 			want: opsgenieAlertCreateRequest{ | ||||
| 				Message:  "RESOLVED: my mega app - " + description, | ||||
| 				Priority: "P5", | ||||
| 				Source:   "gatus-hc", | ||||
| 				Entity:   "oompa-my-mega-app", | ||||
| 				Alias:    "loompa-my-mega-app", | ||||
| 				Description: "An alert for *my mega app* has been resolved after passing successfully 4 time(s) in a row\n" + | ||||
| 					"▣ - `[STATUS] == 200`\n", | ||||
| 				Tags:    []string{"do-ba-dee-doo"}, | ||||
| 				Details: map[string]string{}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "with default options and details (unresolved)", | ||||
| 			Provider: &AlertProvider{ | ||||
| 				Tags: []string{"foo"}, | ||||
| 			}, | ||||
| 			Alert: &alert.Alert{ | ||||
| 				Description:      &description, | ||||
| 				FailureThreshold: 6, | ||||
| 			}, | ||||
| 			Endpoint: &core.Endpoint{ | ||||
| 				Name:  "my app", | ||||
| 				Group: "end game", | ||||
| 				URL:   "https://my.go/app", | ||||
| 			}, | ||||
| 			Result: &core.Result{ | ||||
| 				HTTPStatus: 400, | ||||
| 				Hostname:   "my.go", | ||||
| 				Errors:     []string{"error 01", "error 02"}, | ||||
| 				Success:    false, | ||||
| 				ConditionResults: []*core.ConditionResult{ | ||||
| 					{ | ||||
| 						Condition: "[STATUS] == 200", | ||||
| 						Success:   false, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			Resolved: false, | ||||
| 			want: opsgenieAlertCreateRequest{ | ||||
| 				Message:  "[end game] my app - " + description, | ||||
| 				Priority: "P1", | ||||
| 				Source:   "gatus", | ||||
| 				Entity:   "gatus-end-game-my-app", | ||||
| 				Alias:    "gatus-healthcheck-end-game-my-app", | ||||
| 				Description: "An alert for *my app* has been triggered due to having failed 6 time(s) in a row\n" + | ||||
| 					"▢ - `[STATUS] == 200`\n", | ||||
| 				Tags: []string{"foo"}, | ||||
| 				Details: map[string]string{ | ||||
| 					"endpoint:url":       "https://my.go/app", | ||||
| 					"endpoint:group":     "end game", | ||||
| 					"result:hostname":    "my.go", | ||||
| 					"result:errors":      "error 01,error 02", | ||||
| 					"result:http_status": "400", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, scenario := range scenarios { | ||||
| 		actual := scenario | ||||
| 		t.Run(actual.Name, func(t *testing.T) { | ||||
| 			if got := actual.Provider.buildCreateRequestBody(actual.Endpoint, actual.Alert, actual.Result, actual.Resolved); !reflect.DeepEqual(got, actual.want) { | ||||
| 				t.Errorf("buildCreateRequestBody() = %v, want %v", got, actual.want) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestAlertProvider_buildCloseRequestBody(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	description := "alert description" | ||||
|  | ||||
| 	scenarios := []struct { | ||||
| 		Name     string | ||||
| 		Provider *AlertProvider | ||||
| 		Alert    *alert.Alert | ||||
| 		Endpoint *core.Endpoint | ||||
| 		want     opsgenieAlertCloseRequest | ||||
| 	}{ | ||||
| 		{ | ||||
| 			Name:     "Missing all values", | ||||
| 			Provider: &AlertProvider{}, | ||||
| 			Alert:    &alert.Alert{}, | ||||
| 			Endpoint: &core.Endpoint{}, | ||||
| 			want: opsgenieAlertCloseRequest{ | ||||
| 				Source: "", | ||||
| 				Note:   "RESOLVED:  - ", | ||||
| 			}, | ||||
| 		}, | ||||
|  | ||||
| 		{ | ||||
| 			Name:     "Basic values", | ||||
| 			Provider: &AlertProvider{}, | ||||
| 			Alert: &alert.Alert{ | ||||
| 				Description: &description, | ||||
| 			}, | ||||
| 			Endpoint: &core.Endpoint{ | ||||
| 				Name: "endpoint name", | ||||
| 			}, | ||||
| 			want: opsgenieAlertCloseRequest{ | ||||
| 				Source: "endpoint-name", | ||||
| 				Note:   "RESOLVED: endpoint name - alert description", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, scenario := range scenarios { | ||||
| 		actual := scenario | ||||
| 		t.Run(actual.Name, func(t *testing.T) { | ||||
| 			if got := actual.Provider.buildCloseRequestBody(actual.Endpoint, actual.Alert); !reflect.DeepEqual(got, actual.want) { | ||||
| 				t.Errorf("buildCloseRequestBody() = %v, want %v", got, actual.want) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user