diff --git a/alerting/provider/teams/teams.go b/alerting/provider/teams/teams.go index 01c98c5a..0ada941b 100644 --- a/alerting/provider/teams/teams.go +++ b/alerting/provider/teams/teams.go @@ -2,6 +2,7 @@ package teams import ( "bytes" + "encoding/json" "fmt" "io" "net/http" @@ -44,7 +45,7 @@ func (provider *AlertProvider) IsValid() bool { // Send an alert using the provider func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { - buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved))) + buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer) if err != nil { return err @@ -62,8 +63,22 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, return err } +type Body struct { + Type string `json:"@type"` + Context string `json:"@context"` + ThemeColor string `json:"themeColor"` + Title string `json:"title"` + Text string `json:"text"` + Sections []Section `json:"sections"` +} + +type Section struct { + ActivityTitle string `json:"activityTitle"` + Text string `json:"text"` +} + // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string { +func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { var message, color string if resolved { message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) @@ -84,25 +99,22 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * } var description string if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { - description = ":\\n> " + alertDescription + description = ": " + alertDescription } - return fmt.Sprintf(`{ - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "themeColor": "%s", - "title": "🚨 Gatus", - "text": "%s%s", - "sections": [ - { - "activityTitle": "URL", - "text": "%s" - }, - { - "activityTitle": "Condition results", - "text": "%s" - } - ] -}`, color, message, description, endpoint.URL, results) + body, _ := json.Marshal(Body{ + Type: "MessageCard", + Context: "http://schema.org/extensions", + ThemeColor: color, + Title: "🚨 Gatus", + Text: message + description, + Sections: []Section{ + { + ActivityTitle: "Condition results", + Text: results, + }, + }, + }) + return body } // getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group diff --git a/alerting/provider/teams/teams_test.go b/alerting/provider/teams/teams_test.go index 2b31a1dd..c3ab998c 100644 --- a/alerting/provider/teams/teams_test.go +++ b/alerting/provider/teams/teams_test.go @@ -151,14 +151,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { Provider: AlertProvider{}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, - ExpectedBody: "{\n \"@type\": \"MessageCard\",\n \"@context\": \"http://schema.org/extensions\",\n \"themeColor\": \"#DD0000\",\n \"title\": \"🚨 Gatus\",\n \"text\": \"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row:\\n> description-1\",\n \"sections\": [\n {\n \"activityTitle\": \"URL\",\n \"text\": \"\"\n },\n {\n \"activityTitle\": \"Condition results\",\n \"text\": \"❌ - `[CONNECTED] == true`
❌ - `[STATUS] == 200`
\"\n }\n ]\n}", + ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#DD0000\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row: description-1\",\"sections\":[{\"activityTitle\":\"Condition results\",\"text\":\"\\u0026#x274C; - `[CONNECTED] == true`\\u003cbr/\\u003e\\u0026#x274C; - `[STATUS] == 200`\\u003cbr/\\u003e\"}]}", }, { Name: "resolved", Provider: AlertProvider{}, Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: true, - ExpectedBody: "{\n \"@type\": \"MessageCard\",\n \"@context\": \"http://schema.org/extensions\",\n \"themeColor\": \"#36A64F\",\n \"title\": \"🚨 Gatus\",\n \"text\": \"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row:\\n> description-2\",\n \"sections\": [\n {\n \"activityTitle\": \"URL\",\n \"text\": \"\"\n },\n {\n \"activityTitle\": \"Condition results\",\n \"text\": \"✅ - `[CONNECTED] == true`
✅ - `[STATUS] == 200`
\"\n }\n ]\n}", + ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#36A64F\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row: description-2\",\"sections\":[{\"activityTitle\":\"Condition results\",\"text\":\"\\u0026#x2705; - `[CONNECTED] == true`\\u003cbr/\\u003e\\u0026#x2705; - `[STATUS] == 200`\\u003cbr/\\u003e\"}]}", }, } for _, scenario := range scenarios { @@ -174,11 +174,11 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { }, scenario.Resolved, ) - if body != scenario.ExpectedBody { - t.Errorf("expected %s, got %s", scenario.ExpectedBody, body) + 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([]byte(body), &out); err != nil { + if err := json.Unmarshal(body, &out); err != nil { t.Error("expected body to be valid JSON, got error:", err.Error()) } })