query_test.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package conditions
  2. import (
  3. "context"
  4. "testing"
  5. "github.com/grafana/grafana/pkg/bus"
  6. "github.com/grafana/grafana/pkg/components/null"
  7. "github.com/grafana/grafana/pkg/components/simplejson"
  8. m "github.com/grafana/grafana/pkg/models"
  9. "github.com/grafana/grafana/pkg/services/alerting"
  10. "github.com/grafana/grafana/pkg/tsdb"
  11. . "github.com/smartystreets/goconvey/convey"
  12. )
  13. func TestQueryCondition(t *testing.T) {
  14. Convey("when evaluating query condition", t, func() {
  15. queryConditionScenario("Given avg() and > 100", func(ctx *queryConditionTestContext) {
  16. ctx.reducer = `{"type": "avg"}`
  17. ctx.evaluator = `{"type": "gt", "params": [100]}`
  18. Convey("Can read query condition from json model", func() {
  19. ctx.exec()
  20. So(ctx.condition.Query.From, ShouldEqual, "5m")
  21. So(ctx.condition.Query.To, ShouldEqual, "now")
  22. So(ctx.condition.Query.DatasourceId, ShouldEqual, 1)
  23. Convey("Can read query reducer", func() {
  24. reducer, ok := ctx.condition.Reducer.(*SimpleReducer)
  25. So(ok, ShouldBeTrue)
  26. So(reducer.Type, ShouldEqual, "avg")
  27. })
  28. Convey("Can read evaluator", func() {
  29. evaluator, ok := ctx.condition.Evaluator.(*ThresholdEvaluator)
  30. So(ok, ShouldBeTrue)
  31. So(evaluator.Type, ShouldEqual, "gt")
  32. })
  33. })
  34. Convey("should fire when avg is above 100", func() {
  35. points := tsdb.NewTimeSeriesPointsFromArgs(120, 0)
  36. ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", points)}
  37. cr, err := ctx.exec()
  38. So(err, ShouldBeNil)
  39. So(cr.Firing, ShouldBeTrue)
  40. })
  41. Convey("Should not fire when avg is below 100", func() {
  42. points := tsdb.NewTimeSeriesPointsFromArgs(90, 0)
  43. ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", points)}
  44. cr, err := ctx.exec()
  45. So(err, ShouldBeNil)
  46. So(cr.Firing, ShouldBeFalse)
  47. })
  48. Convey("Should fire if only first serie matches", func() {
  49. ctx.series = tsdb.TimeSeriesSlice{
  50. tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
  51. tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs(0, 0)),
  52. }
  53. cr, err := ctx.exec()
  54. So(err, ShouldBeNil)
  55. So(cr.Firing, ShouldBeTrue)
  56. })
  57. Convey("No series", func() {
  58. Convey("Should set NoDataFound when condition is gt", func() {
  59. ctx.series = tsdb.TimeSeriesSlice{}
  60. cr, err := ctx.exec()
  61. So(err, ShouldBeNil)
  62. So(cr.Firing, ShouldBeFalse)
  63. So(cr.NoDataFound, ShouldBeTrue)
  64. })
  65. Convey("Should be firing when condition is no_value", func() {
  66. ctx.evaluator = `{"type": "no_value", "params": []}`
  67. ctx.series = tsdb.TimeSeriesSlice{}
  68. cr, err := ctx.exec()
  69. So(err, ShouldBeNil)
  70. So(cr.Firing, ShouldBeTrue)
  71. })
  72. })
  73. Convey("Empty series", func() {
  74. Convey("Should set Firing if eval match", func() {
  75. ctx.evaluator = `{"type": "no_value", "params": []}`
  76. ctx.series = tsdb.TimeSeriesSlice{
  77. tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
  78. }
  79. cr, err := ctx.exec()
  80. So(err, ShouldBeNil)
  81. So(cr.Firing, ShouldBeTrue)
  82. })
  83. Convey("Should set NoDataFound both series are empty", func() {
  84. ctx.series = tsdb.TimeSeriesSlice{
  85. tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
  86. tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs()),
  87. }
  88. cr, err := ctx.exec()
  89. So(err, ShouldBeNil)
  90. So(cr.NoDataFound, ShouldBeTrue)
  91. })
  92. Convey("Should set NoDataFound both series contains null", func() {
  93. ctx.series = tsdb.TimeSeriesSlice{
  94. tsdb.NewTimeSeries("test1", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
  95. tsdb.NewTimeSeries("test2", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
  96. }
  97. cr, err := ctx.exec()
  98. So(err, ShouldBeNil)
  99. So(cr.NoDataFound, ShouldBeTrue)
  100. })
  101. Convey("Should not set NoDataFound if one serie is empty", func() {
  102. ctx.series = tsdb.TimeSeriesSlice{
  103. tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
  104. tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
  105. }
  106. cr, err := ctx.exec()
  107. So(err, ShouldBeNil)
  108. So(cr.NoDataFound, ShouldBeFalse)
  109. })
  110. })
  111. })
  112. })
  113. }
  114. type queryConditionTestContext struct {
  115. reducer string
  116. evaluator string
  117. series tsdb.TimeSeriesSlice
  118. result *alerting.EvalContext
  119. condition *QueryCondition
  120. }
  121. type queryConditionScenarioFunc func(c *queryConditionTestContext)
  122. func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error) {
  123. jsonModel, err := simplejson.NewJson([]byte(`{
  124. "type": "query",
  125. "query": {
  126. "params": ["A", "5m", "now"],
  127. "datasourceId": 1,
  128. "model": {"target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
  129. },
  130. "reducer":` + ctx.reducer + `,
  131. "evaluator":` + ctx.evaluator + `
  132. }`))
  133. So(err, ShouldBeNil)
  134. condition, err := NewQueryCondition(jsonModel, 0)
  135. So(err, ShouldBeNil)
  136. ctx.condition = condition
  137. condition.HandleRequest = func(context context.Context, req *tsdb.Request) (*tsdb.Response, error) {
  138. return &tsdb.Response{
  139. Results: map[string]*tsdb.QueryResult{
  140. "A": {Series: ctx.series},
  141. },
  142. }, nil
  143. }
  144. return condition.Eval(ctx.result)
  145. }
  146. func queryConditionScenario(desc string, fn queryConditionScenarioFunc) {
  147. Convey(desc, func() {
  148. bus.AddHandler("test", func(query *m.GetDataSourceByIdQuery) error {
  149. query.Result = &m.DataSource{Id: 1, Type: "graphite"}
  150. return nil
  151. })
  152. ctx := &queryConditionTestContext{}
  153. ctx.result = &alerting.EvalContext{
  154. Rule: &alerting.Rule{},
  155. }
  156. fn(ctx)
  157. })
  158. }