query_test.go 5.6 KB

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