query_test.go 4.6 KB

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