You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

183 lines
4.4 KiB

  1. package alertmanager
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/matrix-org/go-neb/database"
  7. "github.com/matrix-org/go-neb/testutils"
  8. "github.com/matrix-org/go-neb/types"
  9. "github.com/matrix-org/gomatrix"
  10. "io/ioutil"
  11. "net/http"
  12. "net/http/httptest"
  13. "net/url"
  14. "regexp"
  15. "strings"
  16. "testing"
  17. )
  18. func TestNotify(t *testing.T) {
  19. database.SetServiceDB(&database.NopStorage{})
  20. // Intercept message sending to Matrix and mock responses
  21. msgs := []gomatrix.HTMLMessage{}
  22. matrixCli := buildTestClient(&msgs)
  23. // create the service
  24. srv := buildTestService(t)
  25. // send a notification
  26. req, err := http.NewRequest(
  27. "POST", "", bytes.NewBufferString(`
  28. {
  29. "externalURL": "http://alertmanager",
  30. "alerts": [
  31. {
  32. "labels": {
  33. "alertname": "alert 1",
  34. "severity": "huge"
  35. },
  36. "generatorURL": "http://x"
  37. },
  38. {
  39. "labels": {
  40. "alertname": "alert 2",
  41. "severity": "tiny"
  42. },
  43. "generatorURL": "http://y"
  44. }
  45. ]
  46. }
  47. `),
  48. )
  49. if err != nil {
  50. t.Fatalf("Failed to create webhook request: %s", err)
  51. }
  52. mockWriter := httptest.NewRecorder()
  53. srv.OnReceiveWebhook(mockWriter, req, matrixCli)
  54. // check response
  55. if mockWriter.Code != 200 {
  56. t.Fatalf("Expected response 200 OK, got %d", mockWriter.Code)
  57. }
  58. if len(msgs) != 1 {
  59. t.Fatalf("Expected sent 1 msgs, sent %d", len(msgs))
  60. }
  61. msg := msgs[0]
  62. if msg.MsgType != "m.text" {
  63. t.Errorf("Wrong msgtype: got %s want m.text", msg.MsgType)
  64. }
  65. lines := strings.Split(msg.FormattedBody, "\n")
  66. // <a href="http://alertmanager#silences/new?filter=%7balertname%3D%22alert%202%22,severity%3D%22tiny%22%7d">silence</a>
  67. matchedSilence := 0
  68. for _, line := range lines {
  69. if !strings.Contains(line, "silence") {
  70. continue
  71. }
  72. matchedSilence++
  73. checkSilenceLine(t, line, map[string]string{
  74. "alertname": "\"alert 1\"",
  75. "severity": "\"huge\"",
  76. })
  77. break
  78. }
  79. if matchedSilence == 0 {
  80. t.Errorf("Did not find any silence lines")
  81. }
  82. }
  83. func buildTestClient(msgs *[]gomatrix.HTMLMessage) *gomatrix.Client {
  84. matrixTrans := struct{ testutils.MockTransport }{}
  85. matrixTrans.RT = func(req *http.Request) (*http.Response, error) {
  86. if !strings.Contains(req.URL.String(), "/send/m.room.message") {
  87. return nil, fmt.Errorf("Unhandled URL: %s", req.URL.String())
  88. }
  89. var msg gomatrix.HTMLMessage
  90. if err := json.NewDecoder(req.Body).Decode(&msg); err != nil {
  91. return nil, fmt.Errorf("Failed to decode request JSON: %s", err)
  92. }
  93. *msgs = append(*msgs, msg)
  94. return &http.Response{
  95. StatusCode: 200,
  96. Body: ioutil.NopCloser(bytes.NewBufferString(`{"event_id":"$yup:event"}`)),
  97. }, nil
  98. }
  99. matrixCli, _ := gomatrix.NewClient("https://hs", "@neb:hs", "its_a_secret")
  100. matrixCli.Client = &http.Client{Transport: matrixTrans}
  101. return matrixCli
  102. }
  103. func buildTestService(t *testing.T) types.Service {
  104. htmlTemplate, err := json.Marshal(
  105. `{{range .Alerts}}
  106. {{index .Labels "severity" }} : {{- index .Labels "alertname" -}}
  107. <a href="{{ .GeneratorURL }}">source</a>
  108. <a href="{{ .SilenceURL }}">silence</a>
  109. {{- end }}
  110. `,
  111. )
  112. if err != nil {
  113. t.Fatal(err)
  114. }
  115. textTemplate, err := json.Marshal(`{{range .Alerts}}{{index .Labels "alertname"}} {{end}}`)
  116. if err != nil {
  117. t.Fatal(err)
  118. }
  119. config := fmt.Sprintf(`{
  120. "rooms":{ "!testroom:id" : {
  121. "text_template":%s,
  122. "html_template":%s,
  123. "msg_type":"m.text"
  124. }}
  125. }`, textTemplate, htmlTemplate,
  126. )
  127. srv, err := types.CreateService("id", "alertmanager", "@neb:hs", []byte(config))
  128. if err != nil {
  129. t.Fatal(err)
  130. }
  131. return srv
  132. }
  133. func checkSilenceLine(t *testing.T, line string, expectedKeys map[string]string) {
  134. silenceRegexp := regexp.MustCompile(`<a href="http://alertmanager#silences/new\?filter=%7b([^"]*)%7d">silence</a>`)
  135. m := silenceRegexp.FindStringSubmatch(line)
  136. if m == nil {
  137. t.Errorf("silence line %s had bad format", line)
  138. return
  139. }
  140. unesc, err := url.QueryUnescape(m[1])
  141. if err != nil {
  142. t.Errorf("Unable to decode filter, %v", err)
  143. return
  144. }
  145. matched := 0
  146. for _, f := range strings.Split(unesc, ",") {
  147. splits := strings.SplitN(f, "=", 2)
  148. key := splits[0]
  149. exp, ok := expectedKeys[key]
  150. if !ok {
  151. t.Errorf("unexpected key in filter: %v", key)
  152. } else if exp != splits[1] {
  153. t.Errorf("bad value for filter key %v: got %q, want %q", key, splits[1], exp)
  154. } else {
  155. matched++
  156. }
  157. }
  158. if matched != len(expectedKeys) {
  159. t.Errorf("number of filter fields got %d, want %d", matched, len(expectedKeys))
  160. }
  161. }