eval_handler_test.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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 false with not passing asdf", func() {
  32. context := NewEvalContext(context.TODO(), &Rule{
  33. Conditions: []Condition{
  34. &conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}},
  35. &conditionStub{firing: false, operator: "and"},
  36. },
  37. })
  38. handler.Eval(context)
  39. So(context.Firing, ShouldEqual, false)
  40. So(context.ConditionEvals, ShouldEqual, "[true AND false] = false")
  41. })
  42. Convey("Show return true if any of the condition is passing with OR operator", func() {
  43. context := NewEvalContext(context.TODO(), &Rule{
  44. Conditions: []Condition{
  45. &conditionStub{firing: true, operator: "and"},
  46. &conditionStub{firing: false, operator: "or"},
  47. },
  48. })
  49. handler.Eval(context)
  50. So(context.Firing, ShouldEqual, true)
  51. So(context.ConditionEvals, ShouldEqual, "[true OR false] = true")
  52. })
  53. Convey("Show return false if any of the condition is failing with AND operator", func() {
  54. context := NewEvalContext(context.TODO(), &Rule{
  55. Conditions: []Condition{
  56. &conditionStub{firing: true, operator: "and"},
  57. &conditionStub{firing: false, operator: "and"},
  58. },
  59. })
  60. handler.Eval(context)
  61. So(context.Firing, ShouldEqual, false)
  62. So(context.ConditionEvals, ShouldEqual, "[true AND false] = false")
  63. })
  64. Convey("Show return true if one condition is failing with nested OR operator", func() {
  65. context := NewEvalContext(context.TODO(), &Rule{
  66. Conditions: []Condition{
  67. &conditionStub{firing: true, operator: "and"},
  68. &conditionStub{firing: true, operator: "and"},
  69. &conditionStub{firing: false, operator: "or"},
  70. },
  71. })
  72. handler.Eval(context)
  73. So(context.Firing, ShouldEqual, true)
  74. So(context.ConditionEvals, ShouldEqual, "[[true AND true] OR false] = true")
  75. })
  76. Convey("Show return false if one condition is passing with nested OR operator", func() {
  77. context := NewEvalContext(context.TODO(), &Rule{
  78. Conditions: []Condition{
  79. &conditionStub{firing: true, operator: "and"},
  80. &conditionStub{firing: false, operator: "and"},
  81. &conditionStub{firing: false, operator: "or"},
  82. },
  83. })
  84. handler.Eval(context)
  85. So(context.Firing, ShouldEqual, false)
  86. So(context.ConditionEvals, ShouldEqual, "[[true AND false] OR false] = false")
  87. })
  88. Convey("Show return false if a condition is failing with nested AND operator", func() {
  89. context := NewEvalContext(context.TODO(), &Rule{
  90. Conditions: []Condition{
  91. &conditionStub{firing: true, operator: "and"},
  92. &conditionStub{firing: false, operator: "and"},
  93. &conditionStub{firing: true, operator: "and"},
  94. },
  95. })
  96. handler.Eval(context)
  97. So(context.Firing, ShouldEqual, false)
  98. So(context.ConditionEvals, ShouldEqual, "[[true AND false] AND true] = false")
  99. })
  100. Convey("Show return true if a condition is passing with nested OR operator", func() {
  101. context := NewEvalContext(context.TODO(), &Rule{
  102. Conditions: []Condition{
  103. &conditionStub{firing: true, operator: "and"},
  104. &conditionStub{firing: false, operator: "or"},
  105. &conditionStub{firing: true, operator: "or"},
  106. },
  107. })
  108. handler.Eval(context)
  109. So(context.Firing, ShouldEqual, true)
  110. So(context.ConditionEvals, ShouldEqual, "[[true OR false] OR true] = true")
  111. })
  112. Convey("Should return no data if one condition has nodata", func() {
  113. context := NewEvalContext(context.TODO(), &Rule{
  114. Conditions: []Condition{
  115. &conditionStub{operator: "and", noData: true},
  116. },
  117. })
  118. handler.Eval(context)
  119. So(context.Firing, ShouldEqual, false)
  120. So(context.NoDataFound, ShouldBeTrue)
  121. })
  122. Convey("Should return no data if both conditions have no data and using AND", func() {
  123. context := NewEvalContext(context.TODO(), &Rule{
  124. Conditions: []Condition{
  125. &conditionStub{operator: "and", noData: true},
  126. &conditionStub{operator: "and", noData: false},
  127. },
  128. })
  129. handler.Eval(context)
  130. So(context.NoDataFound, ShouldBeFalse)
  131. })
  132. Convey("Should not return no data if both conditions have no data and using OR", func() {
  133. context := NewEvalContext(context.TODO(), &Rule{
  134. Conditions: []Condition{
  135. &conditionStub{operator: "or", noData: true},
  136. &conditionStub{operator: "or", noData: false},
  137. },
  138. })
  139. handler.Eval(context)
  140. So(context.NoDataFound, ShouldBeTrue)
  141. })
  142. Convey("EvalHandler can replace alert state based for errors and no_data", func() {
  143. ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
  144. dummieError := fmt.Errorf("dummie error")
  145. Convey("Should update alert state", func() {
  146. Convey("ok -> alerting", func() {
  147. ctx.PrevAlertState = models.AlertStateOK
  148. ctx.Firing = true
  149. So(handler.getNewState(ctx), ShouldEqual, models.AlertStateAlerting)
  150. })
  151. Convey("ok -> error(alerting)", func() {
  152. ctx.PrevAlertState = models.AlertStateOK
  153. ctx.Error = dummieError
  154. ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
  155. ctx.Rule.State = handler.getNewState(ctx)
  156. So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
  157. })
  158. Convey("ok -> error(keep_last)", func() {
  159. ctx.PrevAlertState = models.AlertStateOK
  160. ctx.Error = dummieError
  161. ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
  162. ctx.Rule.State = handler.getNewState(ctx)
  163. So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
  164. })
  165. Convey("pending -> error(keep_last)", func() {
  166. ctx.PrevAlertState = models.AlertStatePending
  167. ctx.Error = dummieError
  168. ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
  169. ctx.Rule.State = handler.getNewState(ctx)
  170. So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
  171. })
  172. Convey("ok -> no_data(alerting)", func() {
  173. ctx.PrevAlertState = models.AlertStateOK
  174. ctx.Rule.NoDataState = models.NoDataSetAlerting
  175. ctx.NoDataFound = true
  176. ctx.Rule.State = handler.getNewState(ctx)
  177. So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
  178. })
  179. Convey("ok -> no_data(keep_last)", func() {
  180. ctx.PrevAlertState = models.AlertStateOK
  181. ctx.Rule.NoDataState = models.NoDataKeepState
  182. ctx.NoDataFound = true
  183. ctx.Rule.State = handler.getNewState(ctx)
  184. So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
  185. })
  186. Convey("pending -> no_data(keep_last)", func() {
  187. ctx.PrevAlertState = models.AlertStatePending
  188. ctx.Rule.NoDataState = models.NoDataKeepState
  189. ctx.NoDataFound = true
  190. ctx.Rule.State = handler.getNewState(ctx)
  191. So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
  192. })
  193. })
  194. })
  195. })
  196. }