mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
182 lines
7.3 KiB
Go
182 lines
7.3 KiB
Go
package loki
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/grafana/grafana-plugin-sdk-go/experimental"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// NOTE: in these tests there several different json-content-types.
|
|
// different versions of Loki use different json-content-types.
|
|
// this is probably not important when we will parse them,
|
|
// but i wanted to test for all of them, to be sure.
|
|
|
|
func TestSuccessResponse(t *testing.T) {
|
|
matrixQuery := lokiQuery{Expr: "up(ALERTS)", Step: time.Second * 42, QueryType: QueryTypeRange, Direction: DirectionBackward, RefID: "mq"}
|
|
vectorQuery := lokiQuery{Expr: "query1", QueryType: QueryTypeInstant, Direction: DirectionBackward, RefID: "vq"}
|
|
streamsQuery := lokiQuery{Expr: "query1", QueryType: QueryTypeRange, Direction: DirectionBackward, RefID: "sq"}
|
|
|
|
tt := []struct {
|
|
name string
|
|
filepath string
|
|
query lokiQuery
|
|
}{
|
|
{name: "parse a simple matrix response", filepath: "matrix_simple", query: matrixQuery},
|
|
{name: "parse a matrix response with a time-gap in the middle", filepath: "matrix_gap", query: matrixQuery},
|
|
// you can produce NaN by having a metric query and add ` / 0` to the end
|
|
{name: "parse a matrix response with NaN", filepath: "matrix_nan", query: matrixQuery},
|
|
// you can produce Infinity by using `quantile_over_time(42,` (value larger than 1)
|
|
{name: "parse a matrix response with Infinity", filepath: "matrix_inf", query: matrixQuery},
|
|
{name: "parse a matrix response with very small step value", filepath: "matrix_small_step", query: matrixQuery},
|
|
|
|
// Prometheus handles the `__name__` label in a special way, but Loki should not.
|
|
{name: "parse a matrix response with __name__ label normally", filepath: "matrix_name", query: matrixQuery},
|
|
|
|
// loki adds stats to matrix-responses too
|
|
{name: "parse a matrix response with stats", filepath: "matrix_with_stats", query: matrixQuery},
|
|
|
|
{name: "parse a simple vector response", filepath: "vector_simple", query: vectorQuery},
|
|
{name: "parse a vector response with special values", filepath: "vector_special_values", query: vectorQuery},
|
|
|
|
{name: "parse a simple streams response", filepath: "streams_simple", query: streamsQuery},
|
|
|
|
{name: "parse a streams response with parse errors", filepath: "streams_parse_errors", query: streamsQuery},
|
|
|
|
{name: "parse an empty response", filepath: "empty", query: matrixQuery},
|
|
|
|
{name: "parse structured metadata", filepath: "streams_structured_metadata", query: streamsQuery},
|
|
{name: "parse structured metadata different labels each log line", filepath: "streams_structured_metadata_2", query: streamsQuery},
|
|
}
|
|
|
|
runTest := func(folder string, path string, query lokiQuery, responseOpts ResponseOpts) {
|
|
responseFileName := filepath.Join(folder, path+".json")
|
|
goldenFileName := path + ".golden"
|
|
|
|
//nolint:gosec
|
|
bytes, err := os.ReadFile(responseFileName)
|
|
require.NoError(t, err)
|
|
|
|
dr, err := runQuery(context.Background(), makeMockedAPI(http.StatusOK, "application/json", bytes, nil, false), &query, responseOpts, backend.NewLoggerWith("logger", "test"))
|
|
require.NoError(t, err)
|
|
|
|
experimental.CheckGoldenJSONResponse(t, folder, goldenFileName, dr, false)
|
|
}
|
|
|
|
for _, test := range tt {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
runTest("testdata", test.filepath, test.query, ResponseOpts{metricDataplane: false, logsDataplane: false})
|
|
runTest("testdata_metric_dataplane", test.filepath, test.query, ResponseOpts{metricDataplane: true, logsDataplane: false})
|
|
runTest("testdata_logs_dataplane", test.filepath, test.query, ResponseOpts{metricDataplane: false, logsDataplane: true})
|
|
runTest("testdata_dataplane", test.filepath, test.query, ResponseOpts{metricDataplane: true, logsDataplane: true})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrorResponse(t *testing.T) {
|
|
// NOTE: when there is an error-response, it comes with
|
|
// HTTP code 400, and the format seems to change between versions:
|
|
// 2.3.x: content-type=text/plain, content is plaintext
|
|
// 2.4.x+: content-type=application/json, content is plaintext: https://github.com/grafana/loki/issues/4844
|
|
// we should always be able to to return some kind of error message
|
|
tt := []struct {
|
|
name string
|
|
body []byte
|
|
contentType string
|
|
errorMessage string
|
|
}{
|
|
{
|
|
name: "parse an error response in JSON",
|
|
body: []byte(`
|
|
{
|
|
"status": "error",
|
|
"code": 400,
|
|
"message": "parse error at line 1, col 8: something is wrong"
|
|
}`),
|
|
contentType: "application/json; charset=utf-8",
|
|
errorMessage: "parse error at line 1, col 8: something is wrong",
|
|
},
|
|
{
|
|
name: "parse a non-json error body with json content type",
|
|
body: []byte("parse error at line 1, col 8: something is wrong"),
|
|
contentType: "application/json; charset=UTF-8",
|
|
errorMessage: "parse error at line 1, col 8: something is wrong",
|
|
},
|
|
{
|
|
name: "parse an error response in plain text",
|
|
body: []byte("parse error at line 1, col 8: something is wrong"),
|
|
contentType: "text/plain; charset=utf-8",
|
|
errorMessage: "parse error at line 1, col 8: something is wrong",
|
|
},
|
|
{
|
|
name: "parse an error response that is broken JSON",
|
|
body: []byte(`{"message":"error message but the JSON is not finished`),
|
|
contentType: "text/plain; charset=utf-8",
|
|
errorMessage: `{"message":"error message but the JSON is not finished`,
|
|
},
|
|
}
|
|
|
|
for _, test := range tt {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
dr, err := runQuery(context.Background(), makeMockedAPI(400, test.contentType, test.body, nil, false), &lokiQuery{QueryType: QueryTypeRange, Direction: DirectionBackward}, ResponseOpts{}, backend.NewLoggerWith("logger", "test"))
|
|
require.NoError(t, err)
|
|
require.Len(t, dr.Frames, 0)
|
|
require.Equal(t, dr.Error.Error(), test.errorMessage)
|
|
require.Equal(t, dr.ErrorSource, backend.ErrorSourceDownstream)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrorsFromResponseCodes(t *testing.T) {
|
|
tt := []struct {
|
|
name string
|
|
statusCode int
|
|
errorSource backend.ErrorSource
|
|
}{
|
|
{
|
|
name: "parse response with status code 400 into correct error",
|
|
statusCode: 400,
|
|
errorSource: backend.ErrorSourceDownstream,
|
|
},
|
|
{
|
|
name: "parse response with status code 406 into correct error",
|
|
statusCode: 406,
|
|
errorSource: backend.ErrorSourcePlugin,
|
|
},
|
|
{
|
|
name: "parse response with status code 413 into correct error",
|
|
statusCode: 413,
|
|
errorSource: backend.ErrorSourcePlugin,
|
|
},
|
|
{
|
|
name: "parse response with status code 500 into correct error",
|
|
statusCode: 500,
|
|
errorSource: backend.ErrorSourceDownstream,
|
|
},
|
|
{
|
|
name: "parse response with status code 501 into correct error",
|
|
statusCode: 501,
|
|
errorSource: backend.ErrorSourcePlugin,
|
|
},
|
|
}
|
|
|
|
errorString := "parse error at line 1, col 8: something is wrong"
|
|
contentType := "application/json; charset=UTF-8"
|
|
|
|
for _, test := range tt {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
dr, _ := runQuery(context.Background(), makeMockedAPI(test.statusCode, contentType, []byte(errorString), nil, false), &lokiQuery{QueryType: QueryTypeRange, Direction: DirectionBackward}, ResponseOpts{}, backend.NewLoggerWith("logger", "test"))
|
|
require.Len(t, dr.Frames, 0)
|
|
require.Equal(t, dr.Error.Error(), errorString)
|
|
require.Equal(t, dr.ErrorSource, test.errorSource)
|
|
})
|
|
}
|
|
}
|