config_reader_test.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package alert_notifications
  2. import (
  3. "testing"
  4. "github.com/grafana/grafana/pkg/bus"
  5. "github.com/grafana/grafana/pkg/log"
  6. "github.com/grafana/grafana/pkg/models"
  7. "github.com/grafana/grafana/pkg/services/alerting"
  8. "github.com/grafana/grafana/pkg/services/alerting/notifiers"
  9. . "github.com/smartystreets/goconvey/convey"
  10. )
  11. var (
  12. logger = log.New("fake.log")
  13. correct_properties = "./test-configs/correct-properties"
  14. incorrect_properties = "./test-configs/incorrect-properties"
  15. correct_properties_with_orgName = "./test-configs/correct-properties-with-orgName"
  16. brokenYaml = "./test-configs/broken-yaml"
  17. doubleNotificationsConfig = "./test-configs/double-default"
  18. emptyFolder = "./test-configs/empty_folder"
  19. emptyFile = "./test-configs/empty"
  20. twoNotificationsConfig = "./test-configs/two-notifications"
  21. unknownNotifier = "./test-configs/unknown-notifier"
  22. fakeRepo *fakeRepository
  23. )
  24. func TestNotificationAsConfig(t *testing.T) {
  25. Convey("Testing notification as configuration", t, func() {
  26. fakeRepo = &fakeRepository{}
  27. bus.ClearBusHandlers()
  28. bus.AddHandler("test", mockDelete)
  29. bus.AddHandler("test", mockInsert)
  30. bus.AddHandler("test", mockUpdate)
  31. bus.AddHandler("test", mockGet)
  32. bus.AddHandler("test", mockGetOrg)
  33. alerting.RegisterNotifier(&alerting.NotifierPlugin{
  34. Type: "slack",
  35. Name: "slack",
  36. Factory: notifiers.NewSlackNotifier,
  37. })
  38. alerting.RegisterNotifier(&alerting.NotifierPlugin{
  39. Type: "email",
  40. Name: "email",
  41. Factory: notifiers.NewEmailNotifier,
  42. })
  43. Convey("Can read correct properties", func() {
  44. cfgProvifer := &configReader{log: log.New("test logger")}
  45. cfg, err := cfgProvifer.readConfig(correct_properties)
  46. if err != nil {
  47. t.Fatalf("readConfig return an error %v", err)
  48. }
  49. So(len(cfg), ShouldEqual, 1)
  50. ntCfg := cfg[0]
  51. nts := ntCfg.Notifications
  52. So(len(nts), ShouldEqual, 4)
  53. nt := nts[0]
  54. So(nt.Name, ShouldEqual, "default-slack-notification")
  55. So(nt.Type, ShouldEqual, "slack")
  56. So(nt.OrgId, ShouldEqual, 2)
  57. So(nt.IsDefault, ShouldBeTrue)
  58. So(nt.Settings, ShouldResemble, map[string]interface{}{
  59. "recipient": "XXX", "token": "xoxb", "uploadImage": true, "url": "https://slack.com",
  60. })
  61. nt = nts[1]
  62. So(nt.Name, ShouldEqual, "another-not-default-notification")
  63. So(nt.Type, ShouldEqual, "email")
  64. So(nt.OrgId, ShouldEqual, 3)
  65. So(nt.IsDefault, ShouldBeFalse)
  66. nt = nts[2]
  67. So(nt.Name, ShouldEqual, "check-unset-is_default-is-false")
  68. So(nt.Type, ShouldEqual, "slack")
  69. So(nt.OrgId, ShouldEqual, 3)
  70. So(nt.IsDefault, ShouldBeFalse)
  71. nt = nts[3]
  72. So(nt.Name, ShouldEqual, "Added notification with whitespaces in name")
  73. So(nt.Type, ShouldEqual, "email")
  74. So(nt.OrgId, ShouldEqual, 3)
  75. deleteNts := ntCfg.DeleteNotifications
  76. So(len(deleteNts), ShouldEqual, 4)
  77. deleteNt := deleteNts[0]
  78. So(deleteNt.Name, ShouldEqual, "default-slack-notification")
  79. So(deleteNt.OrgId, ShouldEqual, 2)
  80. deleteNt = deleteNts[1]
  81. So(deleteNt.Name, ShouldEqual, "deleted-notification-without-orgId")
  82. So(deleteNt.OrgId, ShouldEqual, 1)
  83. deleteNt = deleteNts[2]
  84. So(deleteNt.Name, ShouldEqual, "deleted-notification-with-0-orgId")
  85. So(deleteNt.OrgId, ShouldEqual, 1)
  86. deleteNt = deleteNts[3]
  87. So(deleteNt.Name, ShouldEqual, "Deleted notification with whitespaces in name")
  88. So(deleteNt.OrgId, ShouldEqual, 1)
  89. })
  90. Convey("One configured notification", func() {
  91. Convey("no notification in database", func() {
  92. dc := newNotificationProvisioner(logger)
  93. err := dc.applyChanges(twoNotificationsConfig)
  94. if err != nil {
  95. t.Fatalf("applyChanges return an error %v", err)
  96. }
  97. So(len(fakeRepo.deleted), ShouldEqual, 0)
  98. So(len(fakeRepo.inserted), ShouldEqual, 2)
  99. So(len(fakeRepo.updated), ShouldEqual, 0)
  100. })
  101. Convey("One notification in database with same name", func() {
  102. fakeRepo.loadAll = []*models.AlertNotification{
  103. {Name: "channel1", OrgId: 1, Id: 1},
  104. }
  105. Convey("should update one notification", func() {
  106. dc := newNotificationProvisioner(logger)
  107. err := dc.applyChanges(twoNotificationsConfig)
  108. if err != nil {
  109. t.Fatalf("applyChanges return an error %v", err)
  110. }
  111. So(len(fakeRepo.deleted), ShouldEqual, 0)
  112. So(len(fakeRepo.inserted), ShouldEqual, 1)
  113. So(len(fakeRepo.updated), ShouldEqual, 1)
  114. })
  115. })
  116. Convey("Two notifications with is_default", func() {
  117. dc := newNotificationProvisioner(logger)
  118. err := dc.applyChanges(doubleNotificationsConfig)
  119. Convey("should both be inserted", func() {
  120. So(err, ShouldBeNil)
  121. So(len(fakeRepo.deleted), ShouldEqual, 0)
  122. So(len(fakeRepo.inserted), ShouldEqual, 2)
  123. So(len(fakeRepo.updated), ShouldEqual, 0)
  124. })
  125. })
  126. })
  127. Convey("Two configured notification", func() {
  128. Convey("two other notifications in database", func() {
  129. fakeRepo.loadAll = []*models.AlertNotification{
  130. {Name: "channel1", OrgId: 1, Id: 1},
  131. {Name: "channel3", OrgId: 1, Id: 2},
  132. }
  133. Convey("should have two new notifications", func() {
  134. dc := newNotificationProvisioner(logger)
  135. err := dc.applyChanges(twoNotificationsConfig)
  136. if err != nil {
  137. t.Fatalf("applyChanges return an error %v", err)
  138. }
  139. So(len(fakeRepo.deleted), ShouldEqual, 0)
  140. So(len(fakeRepo.inserted), ShouldEqual, 1)
  141. So(len(fakeRepo.updated), ShouldEqual, 1)
  142. })
  143. })
  144. })
  145. Convey("Can read correct properties with orgName instead of orgId", func() {
  146. fakeRepo.loadAllOrg = []*models.Org{
  147. {Name: "Main Org. 1", Id: 1},
  148. {Name: "Main Org. 2", Id: 2},
  149. }
  150. fakeRepo.loadAll = []*models.AlertNotification{
  151. {Name: "default-slack-notification", OrgId: 1, Id: 1},
  152. {Name: "another-not-default-notification", OrgId: 2, Id: 2},
  153. }
  154. dc := newNotificationProvisioner(logger)
  155. err := dc.applyChanges(correct_properties_with_orgName)
  156. if err != nil {
  157. t.Fatalf("applyChanges return an error %v", err)
  158. }
  159. So(len(fakeRepo.deleted), ShouldEqual, 2)
  160. So(len(fakeRepo.inserted), ShouldEqual, 0)
  161. So(len(fakeRepo.updated), ShouldEqual, 2)
  162. updated := fakeRepo.updated
  163. nt := updated[0]
  164. So(nt.Name, ShouldEqual, "default-slack-notification")
  165. So(nt.OrgId, ShouldEqual, 1)
  166. nt = updated[1]
  167. So(nt.Name, ShouldEqual, "another-not-default-notification")
  168. So(nt.OrgId, ShouldEqual, 2)
  169. })
  170. Convey("Empty yaml file", func() {
  171. Convey("should have not changed repo", func() {
  172. dc := newNotificationProvisioner(logger)
  173. err := dc.applyChanges(emptyFile)
  174. if err != nil {
  175. t.Fatalf("applyChanges return an error %v", err)
  176. }
  177. So(len(fakeRepo.deleted), ShouldEqual, 0)
  178. So(len(fakeRepo.inserted), ShouldEqual, 0)
  179. So(len(fakeRepo.updated), ShouldEqual, 0)
  180. })
  181. })
  182. Convey("Broken yaml should return error", func() {
  183. reader := &configReader{log: log.New("test logger")}
  184. _, err := reader.readConfig(brokenYaml)
  185. So(err, ShouldNotBeNil)
  186. })
  187. Convey("Skip invalid directory", func() {
  188. cfgProvifer := &configReader{log: log.New("test logger")}
  189. cfg, err := cfgProvifer.readConfig(emptyFolder)
  190. if err != nil {
  191. t.Fatalf("readConfig return an error %v", err)
  192. }
  193. So(len(cfg), ShouldEqual, 0)
  194. })
  195. Convey("Unknown notifier should return error", func() {
  196. cfgProvifer := &configReader{log: log.New("test logger")}
  197. _, err := cfgProvifer.readConfig(unknownNotifier)
  198. So(err, ShouldEqual, ErrInvalidNotifierType)
  199. })
  200. Convey("Read incorrect properties", func() {
  201. cfgProvifer := &configReader{log: log.New("test logger")}
  202. _, err := cfgProvifer.readConfig(incorrect_properties)
  203. So(err, ShouldNotBeNil)
  204. So(err.Error(), ShouldEqual, "Alert validation error: Could not find url property in settings")
  205. })
  206. })
  207. }
  208. type fakeRepository struct {
  209. inserted []*models.CreateAlertNotificationCommand
  210. deleted []*models.DeleteAlertNotificationCommand
  211. updated []*models.UpdateAlertNotificationCommand
  212. loadAll []*models.AlertNotification
  213. loadAllOrg []*models.Org
  214. }
  215. func mockDelete(cmd *models.DeleteAlertNotificationCommand) error {
  216. fakeRepo.deleted = append(fakeRepo.deleted, cmd)
  217. return nil
  218. }
  219. func mockUpdate(cmd *models.UpdateAlertNotificationCommand) error {
  220. fakeRepo.updated = append(fakeRepo.updated, cmd)
  221. return nil
  222. }
  223. func mockInsert(cmd *models.CreateAlertNotificationCommand) error {
  224. fakeRepo.inserted = append(fakeRepo.inserted, cmd)
  225. return nil
  226. }
  227. func mockGet(cmd *models.GetAlertNotificationsQuery) error {
  228. for _, v := range fakeRepo.loadAll {
  229. if cmd.Name == v.Name && cmd.OrgId == v.OrgId {
  230. cmd.Result = v
  231. return nil
  232. }
  233. }
  234. return nil
  235. }
  236. func mockGetOrg(cmd *models.GetOrgByNameQuery) error {
  237. for _, v := range fakeRepo.loadAllOrg {
  238. if cmd.Name == v.Name {
  239. cmd.Result = v
  240. return nil
  241. }
  242. }
  243. return nil
  244. }