eval_handler_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. package alerting
  2. import (
  3. "context"
  4. "fmt"
  5. "testing"
  6. "github.com/grafana/grafana/pkg/models"
  7. . "github.com/smartystreets/goconvey/convey"
  8. )
  9. type conditionStub struct {
  10. firing bool
  11. operator string
  12. matches []*EvalMatch
  13. noData bool
  14. }
  15. func (c *conditionStub) Eval(context *EvalContext) (*ConditionResult, error) {
  16. return &ConditionResult{Firing: c.firing, EvalMatches: c.matches, Operator: c.operator, NoDataFound: c.noData}, nil
  17. }
  18. func TestAlertingEvaluationHandler(t *testing.T) {
  19. Convey("Test alert evaluation handler", t, func() {
  20. handler := NewEvalHandler()
  21. Convey("Show return triggered with single passing condition", func() {
  22. context := NewEvalContext(context.TODO(), &Rule{
  23. Conditions: []Condition{&conditionStub{
  24. firing: true,
  25. }},
  26. })
  27. handler.Eval(context)
  28. So(context.Firing, ShouldEqual, true)
  29. So(context.ConditionEvals, ShouldEqual, "true = true")
  30. })
  31. Convey("Show return triggered with single passing condition2", func() {
  32. context := NewEvalContext(context.TODO(), &Rule{
  33. Conditions: []Condition{&conditionStub{firing: true, operator: "and"}},
  34. })
  35. handler.Eval(context)
  36. So(context.Firing, ShouldEqual, true)
  37. So(context.ConditionEvals, ShouldEqual, "true = true")
  38. })
  39. Convey("Show return false with not passing asdf", func() {
  40. context := NewEvalContext(context.TODO(), &Rule{
  41. Conditions: []Condition{
  42. &conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}},
  43. &conditionStub{firing: false, operator: "and"},
  44. },
  45. })
  46. handler.Eval(context)
  47. So(context.Firing, ShouldEqual, false)
  48. So(context.ConditionEvals, ShouldEqual, "[true AND false] = false")
  49. })
  50. Convey("Show return true if any of the condition is passing with OR operator", func() {
  51. context := NewEvalContext(context.TODO(), &Rule{
  52. Conditions: []Condition{
  53. &conditionStub{firing: true, operator: "and"},
  54. &conditionStub{firing: false, operator: "or"},
  55. },
  56. })
  57. handler.Eval(context)
  58. So(context.Firing, ShouldEqual, true)
  59. So(context.ConditionEvals, ShouldEqual, "[true OR false] = true")
  60. })
  61. Convey("Show return false if any of the condition is failing with AND operator", func() {
  62. context := NewEvalContext(context.TODO(), &Rule{
  63. Conditions: []Condition{
  64. &conditionStub{firing: true, operator: "and"},
  65. &conditionStub{firing: false, operator: "and"},
  66. },
  67. })
  68. handler.Eval(context)
  69. So(context.Firing, ShouldEqual, false)
  70. So(context.ConditionEvals, ShouldEqual, "[true AND false] = false")
  71. })
  72. Convey("Show return true if one condition is failing with nested OR operator", func() {
  73. context := NewEvalContext(context.TODO(), &Rule{
  74. Conditions: []Condition{
  75. &conditionStub{firing: true, operator: "and"},
  76. &conditionStub{firing: true, operator: "and"},
  77. &conditionStub{firing: false, operator: "or"},
  78. },
  79. })
  80. handler.Eval(context)
  81. So(context.Firing, ShouldEqual, true)
  82. So(context.ConditionEvals, ShouldEqual, "[[true AND true] OR false] = true")
  83. })
  84. Convey("Show return false if one condition is passing with nested OR operator", func() {
  85. context := NewEvalContext(context.TODO(), &Rule{
  86. Conditions: []Condition{
  87. &conditionStub{firing: true, operator: "and"},
  88. &conditionStub{firing: false, operator: "and"},
  89. &conditionStub{firing: false, operator: "or"},
  90. },
  91. })
  92. handler.Eval(context)
  93. So(context.Firing, ShouldEqual, false)
  94. So(context.ConditionEvals, ShouldEqual, "[[true AND false] OR false] = false")
  95. })
  96. Convey("Show return false if a condition is failing with nested AND operator", func() {
  97. context := NewEvalContext(context.TODO(), &Rule{
  98. Conditions: []Condition{
  99. &conditionStub{firing: true, operator: "and"},
  100. &conditionStub{firing: false, operator: "and"},
  101. &conditionStub{firing: true, operator: "and"},
  102. },
  103. })
  104. handler.Eval(context)
  105. So(context.Firing, ShouldEqual, false)
  106. So(context.ConditionEvals, ShouldEqual, "[[true AND false] AND true] = false")
  107. })
  108. Convey("Show return true if a condition is passing with nested OR operator", func() {
  109. context := NewEvalContext(context.TODO(), &Rule{
  110. Conditions: []Condition{
  111. &conditionStub{firing: true, operator: "and"},
  112. &conditionStub{firing: false, operator: "or"},
  113. &conditionStub{firing: true, operator: "or"},
  114. },
  115. })
  116. handler.Eval(context)
  117. So(context.Firing, ShouldEqual, true)
  118. So(context.ConditionEvals, ShouldEqual, "[[true OR false] OR true] = true")
  119. })
  120. Convey("Should return false if no condition is firing using OR operator", func() {
  121. context := NewEvalContext(context.TODO(), &Rule{
  122. Conditions: []Condition{
  123. &conditionStub{firing: false, operator: "or"},
  124. &conditionStub{firing: false, operator: "or"},
  125. &conditionStub{firing: false, operator: "or"},
  126. },
  127. })
  128. handler.Eval(context)
  129. So(context.Firing, ShouldEqual, false)
  130. So(context.ConditionEvals, ShouldEqual, "[[false OR false] OR false] = false")
  131. })
  132. Convey("Should retuasdfrn no data if one condition has nodata", func() {
  133. context := NewEvalContext(context.TODO(), &Rule{
  134. Conditions: []Condition{
  135. &conditionStub{operator: "or", noData: false},
  136. &conditionStub{operator: "or", noData: false},
  137. &conditionStub{operator: "or", noData: false},
  138. },
  139. })
  140. handler.Eval(context)
  141. So(context.NoDataFound, ShouldBeFalse)
  142. })
  143. Convey("Should return no data if one condition has nodata", func() {
  144. context := NewEvalContext(context.TODO(), &Rule{
  145. Conditions: []Condition{
  146. &conditionStub{operator: "and", noData: true},
  147. },
  148. })
  149. handler.Eval(context)
  150. So(context.Firing, ShouldEqual, false)
  151. So(context.NoDataFound, ShouldBeTrue)
  152. })
  153. Convey("Should return no data if both conditions have no data and using AND", func() {
  154. context := NewEvalContext(context.TODO(), &Rule{
  155. Conditions: []Condition{
  156. &conditionStub{operator: "and", noData: true},
  157. &conditionStub{operator: "and", noData: false},
  158. },
  159. })
  160. handler.Eval(context)
  161. So(context.NoDataFound, ShouldBeFalse)
  162. })
  163. Convey("Should not return no data if both conditions have no data and using OR", func() {
  164. context := NewEvalContext(context.TODO(), &Rule{
  165. Conditions: []Condition{
  166. &conditionStub{operator: "or", noData: true},
  167. &conditionStub{operator: "or", noData: false},
  168. },
  169. })
  170. handler.Eval(context)
  171. So(context.NoDataFound, ShouldBeTrue)
  172. })
  173. Convey("EvalHandler can replace alert state based for errors and no_data", func() {
  174. ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
  175. dummieError := fmt.Errorf("dummie error")
  176. Convey("Should update alert state", func() {
  177. Convey("ok -> alerting", func() {
  178. ctx.PrevAlertState = models.AlertStateOK
  179. ctx.Firing = true
  180. So(handler.getNewState(ctx), ShouldEqual, models.AlertStateAlerting)
  181. })
  182. Convey("ok -> error(alerting)", func() {
  183. ctx.PrevAlertState = models.AlertStateOK
  184. ctx.Error = dummieError
  185. ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
  186. ctx.Rule.State = handler.getNewState(ctx)
  187. So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
  188. })
  189. Convey("ok -> error(keep_last)", func() {
  190. ctx.PrevAlertState = models.AlertStateOK
  191. ctx.Error = dummieError
  192. ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
  193. ctx.Rule.State = handler.getNewState(ctx)
  194. So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
  195. })
  196. Convey("pending -> error(keep_last)", func() {
  197. ctx.PrevAlertState = models.AlertStatePending
  198. ctx.Error = dummieError
  199. ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
  200. ctx.Rule.State = handler.getNewState(ctx)
  201. So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
  202. })
  203. Convey("ok -> no_data(alerting)", func() {
  204. ctx.PrevAlertState = models.AlertStateOK
  205. ctx.Rule.NoDataState = models.NoDataSetAlerting
  206. ctx.NoDataFound = true
  207. ctx.Rule.State = handler.getNewState(ctx)
  208. So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
  209. })
  210. Convey("ok -> no_data(keep_last)", func() {
  211. ctx.PrevAlertState = models.AlertStateOK
  212. ctx.Rule.NoDataState = models.NoDataKeepState
  213. ctx.NoDataFound = true
  214. ctx.Rule.State = handler.getNewState(ctx)
  215. So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
  216. })
  217. Convey("pending -> no_data(keep_last)", func() {
  218. ctx.PrevAlertState = models.AlertStatePending
  219. ctx.Rule.NoDataState = models.NoDataKeepState
  220. ctx.NoDataFound = true
  221. ctx.Rule.State = handler.getNewState(ctx)
  222. So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
  223. })
  224. })
  225. })
  226. })
  227. }