config_reader_test.go 8.1 KB

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