The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/tsdb/elasticsearch/snapshot_test.go

176 lines
5.6 KiB

package elasticsearch
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/experimental"
"github.com/stretchr/testify/require"
)
// these snapshot-tests test the whole request-response flow:
// the inputs:
// - the backend.DataQuery query
// - the elastic-response json
// the snapshot verifies:
// - the elastic-request json
// - the dataframe result
// a regex that matches the request-snapshot-filenames, and extracts the name of the test
var requestRe = regexp.MustCompile(`^(.*)\.request\.line\d+\.json$`)
// the "elastic request" is often in multiple json-snapshot-files,
// so we have to find them on disk, so we have to look at every file in
// the folder.
func findRequestSnapshots(t *testing.T, folder string) map[string][]string {
allTestSnapshotFiles, err := os.ReadDir(folder)
require.NoError(t, err)
snapshots := make(map[string][]string)
for _, file := range allTestSnapshotFiles {
fileName := file.Name()
match := requestRe.FindStringSubmatch(fileName)
if len(match) == 2 {
testName := match[1]
files := append(snapshots[testName], filepath.Join(folder, fileName))
snapshots[testName] = files
}
}
return snapshots
}
// a regex that matches the response-snapshot-filenames, and extracts the name of the test
var responseRe = regexp.MustCompile(`^([^\.]+)\.[^\.]+.golden.jsonc$`)
func findResponseSnapshotCounts(t *testing.T, folder string) map[string]int {
allTestSnapshotFiles, err := os.ReadDir(folder)
require.NoError(t, err)
snapshots := make(map[string]int)
for _, file := range allTestSnapshotFiles {
fileName := file.Name()
match := responseRe.FindStringSubmatch(fileName)
if len(match) == 2 {
testName := match[1]
snapshots[testName] = snapshots[testName] + 1
}
}
return snapshots
}
func TestRequestSnapshots(t *testing.T) {
tt := []struct {
name string
path string
}{
{name: "simple metric test", path: "metric_simple"},
{name: "complex metric test", path: "metric_complex"},
{name: "multi metric test", path: "metric_multi"},
{name: "raw data test", path: "raw_data"},
{name: "raw document test", path: "raw_document"},
{name: "logs test", path: "logs"},
}
queryHeader := []byte(`
{
"ignore_unavailable": true,
"index": "testdb-2022.11.14",
"search_type": "query_then_fetch"
}
`)
requestSnapshots := findRequestSnapshots(t, "testdata_request")
for _, test := range tt {
t.Run(test.name, func(t *testing.T) {
responseBytes := []byte(`{"responses":[]}`)
queriesFileName := filepath.Join("testdata_request", test.path+".queries.json")
queriesBytes, err := os.ReadFile(filepath.Clean(queriesFileName))
require.NoError(t, err)
var requestLines [][]byte
for _, fileName := range requestSnapshots[test.path] {
bytes, err := os.ReadFile(filepath.Clean(fileName))
require.NoError(t, err)
requestLines = append(requestLines, bytes)
}
require.True(t, len(requestLines) > 0, "requestLines must not be empty")
result, err := queryDataTest(queriesBytes, responseBytes)
require.NoError(t, err)
reqLines := strings.Split(strings.TrimSpace(string(result.requestBytes)), "\n")
require.Len(t, reqLines, len(requestLines)*2)
for i, expectedRequestLine := range requestLines {
actualRequestHeaderLine := reqLines[2*i]
actualRequestLine := reqLines[2*i+1]
require.JSONEq(t, string(queryHeader), actualRequestHeaderLine, fmt.Sprintf("invalid request-header at index: %v", i))
require.JSONEq(t, string(expectedRequestLine), actualRequestLine, fmt.Sprintf("invalid request at index: %v", i))
}
})
}
}
func TestResponseSnapshots(t *testing.T) {
tt := []struct {
name string
path string
}{
{name: "simple metric test", path: "metric_simple"},
{name: "complex metric test", path: "metric_complex"},
{name: "multi metric test", path: "metric_multi"},
{name: "metric avg test", path: "metric_avg"},
{name: "metric percentiles test", path: "metric_percentiles"},
{name: "metric top_metrics test", path: "metric_top_metrics"},
{name: "metric extended_stats test", path: "metric_extended_stats"},
{name: "raw data test", path: "raw_data"},
{name: "logs test", path: "logs"},
}
snapshotCount := findResponseSnapshotCounts(t, "testdata_response")
for _, test := range tt {
t.Run(test.name, func(t *testing.T) {
responseFileName := filepath.Join("testdata_response", test.path+".response.json")
responseBytes, err := os.ReadFile(filepath.Clean(responseFileName))
require.NoError(t, err)
queriesFileName := filepath.Join("testdata_response", test.path+".queries.json")
queriesBytes, err := os.ReadFile(filepath.Clean(queriesFileName))
require.NoError(t, err)
result, err := queryDataTest(queriesBytes, responseBytes)
require.NoError(t, err)
// first we need to test that the number of items in `result.response.Responses`,
// is exactly the same as the count of our response snapshot files
// (this is so that we avoid situations where we provide more snapshot-files than
// what is returned)
expectedResponseCount := snapshotCount[test.path]
require.True(t, expectedResponseCount > 0, "response snapshots not found")
require.Len(t, result.response.Responses, expectedResponseCount)
for refId, dataRes := range result.response.Responses {
goldenFileName := fmt.Sprintf("%v.%v.golden", test.path, strings.ToLower(refId))
// we make a copy of the variable to avoid this linter-warning:
// "G601: Implicit memory aliasing in for loop."
dataResCopy := dataRes
experimental.CheckGoldenJSONResponse(t, "testdata_response", goldenFileName, &dataResCopy, false)
}
})
}
}