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())
}
})