query_test.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package conditions
  2. import (
  3. "context"
  4. "testing"
  5. null "gopkg.in/guregu/null.v3"
  6. "github.com/grafana/grafana/pkg/bus"
  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("Empty series", func() {
  58. Convey("Should set NoDataFound both series are empty", func() {
  59. ctx.series = tsdb.TimeSeriesSlice{
  60. tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
  61. tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs()),
  62. }
  63. cr, err := ctx.exec()
  64. So(err, ShouldBeNil)
  65. So(cr.NoDataFound, ShouldBeTrue)
  66. })
  67. Convey("Should set NoDataFound both series contains null", func() {
  68. ctx.series = tsdb.TimeSeriesSlice{
  69. tsdb.NewTimeSeries("test1", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
  70. tsdb.NewTimeSeries("test2", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
  71. }
  72. cr, err := ctx.exec()
  73. So(err, ShouldBeNil)
  74. So(cr.NoDataFound, ShouldBeTrue)
  75. })
  76. Convey("Should not set NoDataFound if one serie is empty", func() {
  77. ctx.series = tsdb.TimeSeriesSlice{
  78. tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
  79. tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
  80. }
  81. cr, err := ctx.exec()
  82. So(err, ShouldBeNil)
  83. So(cr.NoDataFound, ShouldBeFalse)
  84. })
  85. })
  86. })
  87. })
  88. }
  89. type queryConditionTestContext struct {
  90. reducer string
  91. evaluator string
  92. series tsdb.TimeSeriesSlice
  93. result *alerting.EvalContext
  94. condition *QueryCondition
  95. }
  96. type queryConditionScenarioFunc func(c *queryConditionTestContext)
  97. func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error) {
  98. jsonModel, err := simplejson.NewJson([]byte(`{
  99. "type": "query",
  100. "query": {
  101. "params": ["A", "5m", "now"],
  102. "datasourceId": 1,
  103. "model": {"target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
  104. },
  105. "reducer":` + ctx.reducer + `,
  106. "evaluator":` + ctx.evaluator + `
  107. }`))
  108. So(err, ShouldBeNil)
  109. condition, err := NewQueryCondition(jsonModel, 0)
  110. So(err, ShouldBeNil)
  111. ctx.condition = condition
  112. condition.HandleRequest = func(context context.Context, req *tsdb.Request) (*tsdb.Response, error) {
  113. return &tsdb.Response{
  114. Results: map[string]*tsdb.QueryResult{
  115. "A": {Series: ctx.series},
  116. },
  117. }, nil
  118. }
  119. return condition.Eval(ctx.result)
  120. }
  121. func queryConditionScenario(desc string, fn queryConditionScenarioFunc) {
  122. Convey(desc, func() {
  123. bus.AddHandler("test", func(query *m.GetDataSourceByIdQuery) error {
  124. query.Result = &m.DataSource{Id: 1, Type: "graphite"}
  125. return nil
  126. })
  127. ctx := &queryConditionTestContext{}
  128. ctx.result = &alerting.EvalContext{
  129. Rule: &alerting.Rule{},
  130. }
  131. fn(ctx)
  132. })
  133. }