1package cloudwatch
2
3import (
4	"testing"
5	"time"
6
7	"github.com/grafana/grafana-plugin-sdk-go/backend"
8	"github.com/grafana/grafana/pkg/components/simplejson"
9	"github.com/grafana/grafana/pkg/tsdb/legacydata"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12)
13
14func TestRequestParser(t *testing.T) {
15	t.Run("Query migration ", func(t *testing.T) {
16		t.Run("legacy statistics field is migrated", func(t *testing.T) {
17			startTime := time.Now()
18			endTime := startTime.Add(2 * time.Hour)
19			oldQuery := &backend.DataQuery{
20				MaxDataPoints: 0,
21				QueryType:     "timeSeriesQuery",
22				Interval:      0,
23			}
24			oldQuery.RefID = "A"
25			oldQuery.JSON = []byte(`{
26				"region": "us-east-1",
27				"namespace": "ec2",
28				"metricName": "CPUUtilization",
29				"dimensions": {
30				  "InstanceId": ["test"]
31				},
32				"statistics": ["Average", "Sum"],
33				"period": "600",
34				"hide": false
35			  }`)
36			migratedQueries, err := migrateLegacyQuery([]backend.DataQuery{*oldQuery}, startTime, endTime)
37			require.NoError(t, err)
38			assert.Equal(t, 1, len(migratedQueries))
39
40			migratedQuery := migratedQueries[0]
41			assert.Equal(t, "A", migratedQuery.RefID)
42			model, err := simplejson.NewJson(migratedQuery.JSON)
43			require.NoError(t, err)
44			assert.Equal(t, "Average", model.Get("statistic").MustString())
45			res, err := model.Get("statistic").Array()
46			assert.Error(t, err)
47			assert.Nil(t, res)
48		})
49	})
50
51	timeRange := legacydata.NewDataTimeRange("now-1h", "now-2h")
52	from, err := timeRange.ParseFrom()
53	require.NoError(t, err)
54	to, err := timeRange.ParseTo()
55	require.NoError(t, err)
56
57	t.Run("New dimensions structure", func(t *testing.T) {
58		query := simplejson.NewFromAny(map[string]interface{}{
59			"refId":      "ref1",
60			"region":     "us-east-1",
61			"namespace":  "ec2",
62			"metricName": "CPUUtilization",
63			"id":         "",
64			"expression": "",
65			"dimensions": map[string]interface{}{
66				"InstanceId":   []interface{}{"test"},
67				"InstanceType": []interface{}{"test2", "test3"},
68			},
69			"statistic": "Average",
70			"period":    "600",
71			"hide":      false,
72		})
73
74		res, err := parseRequestQuery(query, "ref1", from, to)
75		require.NoError(t, err)
76		assert.Equal(t, "us-east-1", res.Region)
77		assert.Equal(t, "ref1", res.RefId)
78		assert.Equal(t, "ec2", res.Namespace)
79		assert.Equal(t, "CPUUtilization", res.MetricName)
80		assert.Equal(t, "queryref1", res.Id)
81		assert.Empty(t, res.Expression)
82		assert.Equal(t, 600, res.Period)
83		assert.True(t, res.ReturnData)
84		assert.Len(t, res.Dimensions, 2)
85		assert.Len(t, res.Dimensions["InstanceId"], 1)
86		assert.Len(t, res.Dimensions["InstanceType"], 2)
87		assert.Equal(t, "test3", res.Dimensions["InstanceType"][1])
88		assert.Equal(t, "Average", res.Statistic)
89	})
90
91	t.Run("Old dimensions structure (backwards compatibility)", func(t *testing.T) {
92		query := simplejson.NewFromAny(map[string]interface{}{
93			"refId":      "ref1",
94			"region":     "us-east-1",
95			"namespace":  "ec2",
96			"metricName": "CPUUtilization",
97			"id":         "",
98			"expression": "",
99			"dimensions": map[string]interface{}{
100				"InstanceId":   "test",
101				"InstanceType": "test2",
102			},
103			"statistic": "Average",
104			"period":    "600",
105			"hide":      false,
106		})
107
108		res, err := parseRequestQuery(query, "ref1", from, to)
109		require.NoError(t, err)
110		assert.Equal(t, "us-east-1", res.Region)
111		assert.Equal(t, "ref1", res.RefId)
112		assert.Equal(t, "ec2", res.Namespace)
113		assert.Equal(t, "CPUUtilization", res.MetricName)
114		assert.Equal(t, "queryref1", res.Id)
115		assert.Empty(t, res.Expression)
116		assert.Equal(t, 600, res.Period)
117		assert.True(t, res.ReturnData)
118		assert.Len(t, res.Dimensions, 2)
119		assert.Len(t, res.Dimensions["InstanceId"], 1)
120		assert.Len(t, res.Dimensions["InstanceType"], 1)
121		assert.Equal(t, "test2", res.Dimensions["InstanceType"][0])
122		assert.Equal(t, "Average", res.Statistic)
123	})
124
125	t.Run("Period defined in the editor by the user is being used when time range is short", func(t *testing.T) {
126		query := simplejson.NewFromAny(map[string]interface{}{
127			"refId":      "ref1",
128			"region":     "us-east-1",
129			"namespace":  "ec2",
130			"metricName": "CPUUtilization",
131			"id":         "",
132			"expression": "",
133			"dimensions": map[string]interface{}{
134				"InstanceId":   "test",
135				"InstanceType": "test2",
136			},
137			"statistic": "Average",
138			"hide":      false,
139		})
140		query.Set("period", "900")
141		timeRange := legacydata.NewDataTimeRange("now-1h", "now-2h")
142		from, err := timeRange.ParseFrom()
143		require.NoError(t, err)
144		to, err := timeRange.ParseTo()
145		require.NoError(t, err)
146
147		res, err := parseRequestQuery(query, "ref1", from, to)
148		require.NoError(t, err)
149		assert.Equal(t, 900, res.Period)
150	})
151
152	t.Run("Period is parsed correctly if not defined by user", func(t *testing.T) {
153		query := simplejson.NewFromAny(map[string]interface{}{
154			"refId":      "ref1",
155			"region":     "us-east-1",
156			"namespace":  "ec2",
157			"metricName": "CPUUtilization",
158			"id":         "",
159			"expression": "",
160			"dimensions": map[string]interface{}{
161				"InstanceId":   "test",
162				"InstanceType": "test2",
163			},
164			"statistic": "Average",
165			"hide":      false,
166			"period":    "auto",
167		})
168
169		t.Run("Time range is 5 minutes", func(t *testing.T) {
170			query.Set("period", "auto")
171			to := time.Now()
172			from := to.Local().Add(time.Minute * time.Duration(5))
173
174			res, err := parseRequestQuery(query, "ref1", from, to)
175			require.NoError(t, err)
176			assert.Equal(t, 60, res.Period)
177		})
178
179		t.Run("Time range is 1 day", func(t *testing.T) {
180			query.Set("period", "auto")
181			to := time.Now()
182			from := to.AddDate(0, 0, -1)
183
184			res, err := parseRequestQuery(query, "ref1", from, to)
185			require.NoError(t, err)
186			assert.Equal(t, 60, res.Period)
187		})
188
189		t.Run("Time range is 2 days", func(t *testing.T) {
190			query.Set("period", "auto")
191			to := time.Now()
192			from := to.AddDate(0, 0, -2)
193			res, err := parseRequestQuery(query, "ref1", from, to)
194			require.NoError(t, err)
195			assert.Equal(t, 300, res.Period)
196		})
197
198		t.Run("Time range is 7 days", func(t *testing.T) {
199			query.Set("period", "auto")
200			to := time.Now()
201			from := to.AddDate(0, 0, -7)
202
203			res, err := parseRequestQuery(query, "ref1", from, to)
204			require.NoError(t, err)
205			assert.Equal(t, 900, res.Period)
206		})
207
208		t.Run("Time range is 30 days", func(t *testing.T) {
209			query.Set("period", "auto")
210			to := time.Now()
211			from := to.AddDate(0, 0, -30)
212
213			res, err := parseRequestQuery(query, "ref1", from, to)
214			require.NoError(t, err)
215			assert.Equal(t, 3600, res.Period)
216		})
217
218		t.Run("Time range is 90 days", func(t *testing.T) {
219			query.Set("period", "auto")
220			to := time.Now()
221			from := to.AddDate(0, 0, -90)
222
223			res, err := parseRequestQuery(query, "ref1", from, to)
224			require.NoError(t, err)
225			assert.Equal(t, 21600, res.Period)
226		})
227
228		t.Run("Time range is 1 year", func(t *testing.T) {
229			query.Set("period", "auto")
230			to := time.Now()
231			from := to.AddDate(-1, 0, 0)
232
233			res, err := parseRequestQuery(query, "ref1", from, to)
234			require.Nil(t, err)
235			assert.Equal(t, 21600, res.Period)
236		})
237
238		t.Run("Time range is 2 years", func(t *testing.T) {
239			query.Set("period", "auto")
240			to := time.Now()
241			from := to.AddDate(-2, 0, 0)
242
243			res, err := parseRequestQuery(query, "ref1", from, to)
244			require.NoError(t, err)
245			assert.Equal(t, 86400, res.Period)
246		})
247
248		t.Run("Time range is 2 days, but 16 days ago", func(t *testing.T) {
249			query.Set("period", "auto")
250			to := time.Now().AddDate(0, 0, -14)
251			from := to.AddDate(0, 0, -2)
252			res, err := parseRequestQuery(query, "ref1", from, to)
253			require.NoError(t, err)
254			assert.Equal(t, 300, res.Period)
255		})
256
257		t.Run("Time range is 2 days, but 90 days ago", func(t *testing.T) {
258			query.Set("period", "auto")
259			to := time.Now().AddDate(0, 0, -88)
260			from := to.AddDate(0, 0, -2)
261			res, err := parseRequestQuery(query, "ref1", from, to)
262			require.NoError(t, err)
263			assert.Equal(t, 3600, res.Period)
264		})
265
266		t.Run("Time range is 2 days, but 456 days ago", func(t *testing.T) {
267			query.Set("period", "auto")
268			to := time.Now().AddDate(0, 0, -454)
269			from := to.AddDate(0, 0, -2)
270			res, err := parseRequestQuery(query, "ref1", from, to)
271			require.NoError(t, err)
272			assert.Equal(t, 21600, res.Period)
273		})
274	})
275
276	t.Run("Metric query type, metric editor mode and query api mode", func(t *testing.T) {
277		timeRange := legacydata.NewDataTimeRange("now-1h", "now-2h")
278		from, err := timeRange.ParseFrom()
279		require.NoError(t, err)
280		to, err := timeRange.ParseTo()
281		require.NoError(t, err)
282
283		t.Run("when metric query type and metric editor mode is not specified", func(t *testing.T) {
284			t.Run("it should be metric search builder", func(t *testing.T) {
285				query := getBaseJsonQuery()
286				res, err := parseRequestQuery(query, "ref1", from, to)
287				require.NoError(t, err)
288				assert.Equal(t, MetricQueryTypeSearch, res.MetricQueryType)
289				assert.Equal(t, MetricEditorModeBuilder, res.MetricEditorMode)
290				assert.Equal(t, GMDApiModeMetricStat, res.getGMDAPIMode())
291			})
292
293			t.Run("and an expression is specified it should be metric search builder", func(t *testing.T) {
294				query := getBaseJsonQuery()
295				query.Set("expression", "SUM(a)")
296				res, err := parseRequestQuery(query, "ref1", from, to)
297				require.NoError(t, err)
298				assert.Equal(t, MetricQueryTypeSearch, res.MetricQueryType)
299				assert.Equal(t, MetricEditorModeRaw, res.MetricEditorMode)
300				assert.Equal(t, GMDApiModeMathExpression, res.getGMDAPIMode())
301			})
302		})
303
304		t.Run("and an expression is specified it should be metric search builder", func(t *testing.T) {
305			query := getBaseJsonQuery()
306			query.Set("expression", "SUM(a)")
307			res, err := parseRequestQuery(query, "ref1", from, to)
308			require.NoError(t, err)
309			assert.Equal(t, MetricQueryTypeSearch, res.MetricQueryType)
310			assert.Equal(t, MetricEditorModeRaw, res.MetricEditorMode)
311			assert.Equal(t, GMDApiModeMathExpression, res.getGMDAPIMode())
312		})
313	})
314}
315
316func getBaseJsonQuery() *simplejson.Json {
317	return simplejson.NewFromAny(map[string]interface{}{
318		"refId":      "ref1",
319		"region":     "us-east-1",
320		"namespace":  "ec2",
321		"metricName": "CPUUtilization",
322		"statistic":  "Average",
323		"period":     "900",
324	})
325}
326