1package flux 2 3import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "net/http/httptest" 9 "path/filepath" 10 "testing" 11 "time" 12 13 "github.com/google/go-cmp/cmp" 14 "github.com/grafana/grafana-plugin-sdk-go/backend" 15 "github.com/grafana/grafana-plugin-sdk-go/data" 16 "github.com/grafana/grafana-plugin-sdk-go/experimental" 17 "github.com/grafana/grafana/pkg/components/simplejson" 18 "github.com/grafana/grafana/pkg/tsdb/influxdb/models" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 "github.com/xorcare/pointer" 22 23 influxdb2 "github.com/influxdata/influxdb-client-go/v2" 24 "github.com/influxdata/influxdb-client-go/v2/api" 25) 26 27//-------------------------------------------------------------- 28// TestData -- reads result from saved files 29//-------------------------------------------------------------- 30 31// MockRunner reads local file path for testdata. 32type MockRunner struct { 33 testDataPath string 34} 35 36func (r *MockRunner) runQuery(ctx context.Context, q string) (*api.QueryTableResult, error) { 37 bytes, err := ioutil.ReadFile(filepath.Join("testdata", r.testDataPath)) 38 if err != nil { 39 return nil, err 40 } 41 42 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 43 time.Sleep(100 * time.Millisecond) 44 if r.Method == http.MethodPost { 45 w.WriteHeader(http.StatusOK) 46 _, err := w.Write(bytes) 47 if err != nil { 48 panic(fmt.Sprintf("Failed to write response: %s", err)) 49 } 50 } else { 51 w.WriteHeader(http.StatusNotFound) 52 } 53 })) 54 defer server.Close() 55 56 client := influxdb2.NewClient(server.URL, "a") 57 return client.QueryAPI("x").Query(ctx, q) 58} 59 60func executeMockedQuery(t *testing.T, name string, query queryModel) *backend.DataResponse { 61 runner := &MockRunner{ 62 testDataPath: name + ".csv", 63 } 64 65 dr := executeQuery(context.Background(), query, runner, 50) 66 return &dr 67} 68 69func verifyGoldenResponse(t *testing.T, name string) *backend.DataResponse { 70 dr := executeMockedQuery(t, name, queryModel{MaxDataPoints: 100}) 71 72 err := experimental.CheckGoldenDataResponse(filepath.Join("testdata", fmt.Sprintf("%s.golden.txt", name)), 73 dr, true) 74 require.NoError(t, err) 75 require.NoError(t, dr.Error) 76 77 return dr 78} 79 80func TestExecuteSimple(t *testing.T) { 81 dr := verifyGoldenResponse(t, "simple") 82 require.Len(t, dr.Frames, 1) 83 require.Contains(t, dr.Frames[0].Name, "test") 84 require.Len(t, dr.Frames[0].Fields[1].Labels, 2) 85 require.Equal(t, "Time", dr.Frames[0].Fields[0].Name) 86 87 st, err := dr.Frames[0].StringTable(-1, -1) 88 require.NoError(t, err) 89 fmt.Println(st) 90 fmt.Println("----------------------") 91} 92 93func TestExecuteSingle(t *testing.T) { 94 dr := verifyGoldenResponse(t, "single") 95 require.Len(t, dr.Frames, 1) 96} 97 98func TestExecuteMultiple(t *testing.T) { 99 dr := verifyGoldenResponse(t, "multiple") 100 require.Len(t, dr.Frames, 3) 101 require.Contains(t, dr.Frames[0].Name, "test") 102 require.Len(t, dr.Frames[0].Fields[1].Labels, 2) 103 require.Equal(t, "Time", dr.Frames[0].Fields[0].Name) 104 105 st, err := dr.Frames[0].StringTable(-1, -1) 106 require.NoError(t, err) 107 fmt.Println(st) 108 fmt.Println("----------------------") 109} 110 111func TestExecuteColumnNamedTable(t *testing.T) { 112 dr := verifyGoldenResponse(t, "table") 113 require.Len(t, dr.Frames, 1) 114} 115 116func TestExecuteGrouping(t *testing.T) { 117 dr := verifyGoldenResponse(t, "grouping") 118 require.Len(t, dr.Frames, 3) 119 require.Contains(t, dr.Frames[0].Name, "system") 120 require.Len(t, dr.Frames[0].Fields[1].Labels, 1) 121 require.Equal(t, "Time", dr.Frames[0].Fields[0].Name) 122 123 st, err := dr.Frames[0].StringTable(-1, -1) 124 require.NoError(t, err) 125 fmt.Println(st) 126 fmt.Println("----------------------") 127} 128 129func TestAggregateGrouping(t *testing.T) { 130 dr := verifyGoldenResponse(t, "aggregate") 131 require.Len(t, dr.Frames, 1) 132 133 str, err := dr.Frames[0].StringTable(-1, -1) 134 require.NoError(t, err) 135 fmt.Println(str) 136 137 // `Name: 138 // Dimensions: 2 Fields by 3 Rows 139 // +-------------------------------+--------------------------+ 140 // | Name: Time | Name: Value | 141 // | Labels: | Labels: host=hostname.ru | 142 // | Type: []*time.Time | Type: []*float64 | 143 // +-------------------------------+--------------------------+ 144 // | 2020-06-05 12:06:00 +0000 UTC | 8.291 | 145 // | 2020-06-05 12:07:00 +0000 UTC | 0.534 | 146 // | 2020-06-05 12:08:00 +0000 UTC | 0.667 | 147 // +-------------------------------+--------------------------+ 148 // ` 149 150 t1 := time.Date(2020, 6, 5, 12, 6, 0, 0, time.UTC) 151 t2 := time.Date(2020, 6, 5, 12, 7, 0, 0, time.UTC) 152 t3 := time.Date(2020, 6, 5, 12, 8, 0, 0, time.UTC) 153 154 expectedFrame := data.NewFrame("", 155 data.NewField("Time", nil, []*time.Time{&t1, &t2, &t3}), 156 data.NewField("Value", map[string]string{"host": "hostname.ru"}, []*float64{ 157 pointer.Float64(8.291), 158 pointer.Float64(0.534), 159 pointer.Float64(0.667), 160 }), 161 ) 162 expectedFrame.Meta = &data.FrameMeta{} 163 164 diff := cmp.Diff(expectedFrame, dr.Frames[0], data.FrameTestCompareOptions()...) 165 assert.Empty(t, diff) 166} 167 168func TestNonStandardTimeColumn(t *testing.T) { 169 dr := verifyGoldenResponse(t, "non_standard_time_column") 170 require.Len(t, dr.Frames, 1) 171 172 str, err := dr.Frames[0].StringTable(-1, -1) 173 require.NoError(t, err) 174 fmt.Println(str) 175 176 // Dimensions: 3 Fields by 1 Rows 177 // +-----------------------------------------+-----------------------------------------+------------------+ 178 // | Name: _start_water | Name: _stop_water | Name: _value | 179 // | Labels: st=1 | Labels: st=1 | Labels: st=1 | 180 // | Type: []*time.Time | Type: []*time.Time | Type: []*float64 | 181 // +-----------------------------------------+-----------------------------------------+------------------+ 182 // | 2020-06-28 17:50:13.012584046 +0000 UTC | 2020-06-29 17:50:13.012584046 +0000 UTC | 156.304 | 183 // +-----------------------------------------+-----------------------------------------+------------------+ 184 185 t1 := time.Date(2020, 6, 28, 17, 50, 13, 12584046, time.UTC) 186 t2 := time.Date(2020, 6, 29, 17, 50, 13, 12584046, time.UTC) 187 188 expectedFrame := data.NewFrame("", 189 data.NewField("_start_water", map[string]string{"st": "1"}, []*time.Time{&t1}), 190 data.NewField("_stop_water", map[string]string{"st": "1"}, []*time.Time{&t2}), 191 data.NewField("_value", map[string]string{"st": "1"}, []*float64{ 192 pointer.Float64(156.304), 193 }), 194 ) 195 expectedFrame.Meta = &data.FrameMeta{} 196 197 diff := cmp.Diff(expectedFrame, dr.Frames[0], data.FrameTestCompareOptions()...) 198 assert.Empty(t, diff) 199} 200 201func TestBuckets(t *testing.T) { 202 verifyGoldenResponse(t, "buckets") 203} 204 205func TestBooleanTagGrouping(t *testing.T) { 206 verifyGoldenResponse(t, "boolean_tag") 207} 208 209func TestBooleanData(t *testing.T) { 210 verifyGoldenResponse(t, "boolean_data") 211} 212 213func TestGoldenFiles(t *testing.T) { 214 verifyGoldenResponse(t, "renamed") 215} 216 217func TestRealQuery(t *testing.T) { 218 t.Skip() // this is used for local testing 219 220 t.Run("Check buckets() query on localhost", func(t *testing.T) { 221 json := simplejson.New() 222 json.Set("organization", "test-org") 223 224 dsInfo := &models.DatasourceInfo{ 225 URL: "http://localhost:9999", // NOTE! no api/v2 226 } 227 228 runner, err := runnerFromDataSource(dsInfo) 229 require.NoError(t, err) 230 231 dr := executeQuery(context.Background(), queryModel{ 232 MaxDataPoints: 100, 233 RawQuery: "buckets()", 234 }, runner, 50) 235 err = experimental.CheckGoldenDataResponse(filepath.Join("testdata", "buckets-real.golden.txt"), &dr, true) 236 require.NoError(t, err) 237 }) 238} 239 240func assertDataResponseDimensions(t *testing.T, dr *backend.DataResponse, rows int, columns int) { 241 require.Len(t, dr.Frames, 1) 242 fields := dr.Frames[0].Fields 243 require.Len(t, fields, rows) 244 require.Equal(t, fields[0].Len(), columns) 245 require.Equal(t, fields[1].Len(), columns) 246} 247 248func TestMaxDataPointsExceededNoAggregate(t *testing.T) { 249 // unfortunately the golden-response style tests do not support 250 // responses that contain errors, so we can only do manual checks 251 // on the DataResponse 252 dr := executeMockedQuery(t, "max_data_points_exceeded", queryModel{MaxDataPoints: 2}) 253 254 // it should contain the error-message 255 require.EqualError(t, dr.Error, "A query returned too many datapoints and the results have been truncated at 21 points to prevent memory issues. At the current graph size, Grafana can only draw 2. Try using the aggregateWindow() function in your query to reduce the number of points returned.") 256 assertDataResponseDimensions(t, dr, 2, 21) 257} 258 259func TestMaxDataPointsExceededWithAggregate(t *testing.T) { 260 // unfortunately the golden-response style tests do not support 261 // responses that contain errors, so we can only do manual checks 262 // on the DataResponse 263 dr := executeMockedQuery(t, "max_data_points_exceeded", queryModel{RawQuery: "aggregateWindow()", MaxDataPoints: 2}) 264 265 // it should contain the error-message 266 require.EqualError(t, dr.Error, "A query returned too many datapoints and the results have been truncated at 21 points to prevent memory issues. At the current graph size, Grafana can only draw 2.") 267 assertDataResponseDimensions(t, dr, 2, 21) 268} 269 270func TestMultivalue(t *testing.T) { 271 // we await a non-labeled _time column 272 // and two value-columns named _value and _value2 273 dr := verifyGoldenResponse(t, "multivalue") 274 require.Len(t, dr.Frames, 4) 275 frame := dr.Frames[0] 276 require.Len(t, frame.Fields, 3) 277 require.Equal(t, frame.Fields[0].Name, "_time") 278 require.Equal(t, frame.Fields[0].Len(), 2) 279 require.Len(t, frame.Fields[0].Labels, 0) 280 require.Equal(t, frame.Fields[1].Name, "_value") 281 require.Len(t, frame.Fields[1].Labels, 5) 282 require.Equal(t, frame.Fields[2].Name, "_value2") 283 require.Len(t, frame.Fields[2].Labels, 5) 284} 285 286func TestMultiTime(t *testing.T) { 287 // we await three columns, _time, _time2, _value 288 // all have all labels 289 dr := verifyGoldenResponse(t, "multitime") 290 require.Len(t, dr.Frames, 4) 291 frame := dr.Frames[0] 292 require.Len(t, frame.Fields, 3) 293 require.Equal(t, frame.Fields[0].Name, "_time") 294 require.Equal(t, frame.Fields[0].Len(), 1) 295 require.Len(t, frame.Fields[0].Labels, 5) 296 require.Equal(t, frame.Fields[1].Name, "_time2") 297 require.Len(t, frame.Fields[1].Labels, 5) 298 require.Equal(t, frame.Fields[2].Name, "_value") 299 require.Len(t, frame.Fields[2].Labels, 5) 300} 301 302func TestTimestampFirst(t *testing.T) { 303 dr := verifyGoldenResponse(t, "time_first") 304 require.Len(t, dr.Frames, 1) 305 // we make sure the timestamp-column is the first column 306 // in the dataframe, even if it was not the first column 307 // in the csv. 308 require.Equal(t, "Time", dr.Frames[0].Fields[0].Name) 309 require.Equal(t, "Value", dr.Frames[0].Fields[1].Name) 310} 311