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.

185 lines
4.4 KiB

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