1package backend_test
2
3import (
4	"encoding/json"
5	"fmt"
6	"sync"
7	"testing"
8	"time"
9
10	"github.com/google/go-cmp/cmp"
11	"github.com/grafana/grafana-plugin-sdk-go/backend"
12	"github.com/grafana/grafana-plugin-sdk-go/data"
13	"github.com/stretchr/testify/require"
14)
15
16func testDataResponse() backend.DataResponse {
17	frames := data.Frames{
18		data.NewFrame("simple",
19			data.NewField("time", nil, []time.Time{
20				time.Date(2020, 1, 2, 3, 4, 0, 0, time.UTC),
21				time.Date(2020, 1, 2, 3, 5, 0, 0, time.UTC),
22			}),
23			data.NewField("valid", nil, []bool{true, false}),
24		),
25		data.NewFrame("other",
26			data.NewField("value", nil, []float64{1.0}),
27		),
28	}
29	return backend.DataResponse{
30		Frames: frames,
31	}
32}
33
34// TestResponseEncoder makes sure that the JSON produced from arrow and dataframes match
35func TestResponseEncoder(t *testing.T) {
36	dr := testDataResponse()
37
38	b, err := json.Marshal(dr)
39	require.NoError(t, err)
40
41	str := string(b)
42	require.Equal(t, `{"frames":[{"schema":{"name":"simple","fields":[{"name":"time","type":"time","typeInfo":{"frame":"time.Time"}},{"name":"valid","type":"boolean","typeInfo":{"frame":"bool"}}]},"data":{"values":[[1577934240000,1577934300000],[true,false]]}},{"schema":{"name":"other","fields":[{"name":"value","type":"number","typeInfo":{"frame":"float64"}}]},"data":{"values":[[1]]}}]}`, str)
43
44	b2, err := json.Marshal(&dr)
45	require.NoError(t, err)
46	require.Equal(t, str, string(b2), "same result from pointer or object")
47
48	// Now the same thing in query data
49	qdr := backend.NewQueryDataResponse()
50	qdr.Responses["A"] = dr
51
52	b, err = json.Marshal(qdr)
53	require.NoError(t, err)
54
55	str = string(b)
56	require.Equal(t, `{"results":{"A":{"frames":[{"schema":{"name":"simple","fields":[{"name":"time","type":"time","typeInfo":{"frame":"time.Time"}},{"name":"valid","type":"boolean","typeInfo":{"frame":"bool"}}]},"data":{"values":[[1577934240000,1577934300000],[true,false]]}},{"schema":{"name":"other","fields":[{"name":"value","type":"number","typeInfo":{"frame":"float64"}}]},"data":{"values":[[1]]}}]}}}`, str)
57
58	// Read the parsed result and make sure it is the same
59	respCopy := &backend.QueryDataResponse{}
60	err = json.Unmarshal(b, respCopy)
61	require.NoError(t, err)
62	require.Equal(t, len(qdr.Responses), len(respCopy.Responses))
63
64	// Check the final result
65	for k, val := range qdr.Responses {
66		other := respCopy.Responses[k]
67		require.Equal(t, len(val.Frames), len(other.Frames))
68
69		for idx := range val.Frames {
70			a := val.Frames[idx]
71			b := other.Frames[idx]
72
73			if diff := cmp.Diff(a, b, data.FrameTestCompareOptions()...); diff != "" {
74				t.Errorf("Result mismatch (-want +got):\n%s", diff)
75			}
76		}
77	}
78}
79
80func TestDataResponseMarshalJSONConcurrent(t *testing.T) {
81	dr := testDataResponse()
82	initialJSON, err := json.Marshal(dr)
83	require.NoError(t, err)
84	var wg sync.WaitGroup
85	for i := 0; i < 2; i++ {
86		wg.Add(1)
87		go func(dr backend.DataResponse) {
88			defer wg.Done()
89			for j := 0; j < 100; j++ {
90				jsonData, err := json.Marshal(dr)
91				require.NoError(t, err)
92				require.JSONEq(t, string(initialJSON), string(jsonData))
93			}
94		}(dr)
95	}
96	wg.Wait()
97}
98
99func TestQueryDataResponseMarshalJSONConcurrent(t *testing.T) {
100	qdr := backend.NewQueryDataResponse()
101	qdr.Responses["A"] = testDataResponse()
102	initialJSON, err := json.Marshal(qdr)
103	require.NoError(t, err)
104	var wg sync.WaitGroup
105	for i := 0; i < 2; i++ {
106		wg.Add(1)
107		go func(qdr *backend.QueryDataResponse) {
108			defer wg.Done()
109			for j := 0; j < 100; j++ {
110				jsonData, err := json.Marshal(qdr)
111				require.NoError(t, err)
112				require.JSONEq(t, string(initialJSON), string(jsonData))
113			}
114		}(qdr)
115	}
116	wg.Wait()
117}
118
119func TestQueryDataResponseOrdering(t *testing.T) {
120	qdr := backend.NewQueryDataResponse()
121	qdr.Responses["C"] = testDataResponse()
122	qdr.Responses["A"] = testDataResponse()
123	qdr.Responses["B"] = testDataResponse()
124	b, err := json.Marshal(qdr)
125	require.NoError(t, err)
126
127	expectedDataResponse := `{"frames":[{"schema":{"name":"simple","fields":[{"name":"time","type":"time","typeInfo":{"frame":"time.Time"}},{"name":"valid","type":"boolean","typeInfo":{"frame":"bool"}}]},"data":{"values":[[1577934240000,1577934300000],[true,false]]}},{"schema":{"name":"other","fields":[{"name":"value","type":"number","typeInfo":{"frame":"float64"}}]},"data":{"values":[[1]]}}]}`
128	expected := fmt.Sprintf(`{"results":{"A":%s,"B":%s,"C":%s}}`, expectedDataResponse, expectedDataResponse, expectedDataResponse)
129	require.Equal(t, expected, string(b))
130}
131