mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 22:02:17 +08:00
Chore: Code cleaning with unit tests in promlib (#99542)
* update request tests * remove unused functions * add unit tests * remove commented code * fix unit tests
This commit is contained in:
@ -2,7 +2,6 @@ package promlib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -11,7 +10,6 @@ import (
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/promlib/client"
|
||||
"github.com/grafana/grafana/pkg/promlib/instrumentation"
|
||||
@ -138,18 +136,3 @@ func (s *Service) getInstance(ctx context.Context, pluginCtx backend.PluginConte
|
||||
in := i.(instance)
|
||||
return &in, nil
|
||||
}
|
||||
|
||||
// IsAPIError returns whether err is or wraps a Prometheus error.
|
||||
func IsAPIError(err error) bool {
|
||||
// Check if the right error type is in err's chain.
|
||||
var e *apiv1.Error
|
||||
return errors.As(err, &e)
|
||||
}
|
||||
|
||||
func ConvertAPIError(err error) error {
|
||||
var e *apiv1.Error
|
||||
if errors.As(err, &e) {
|
||||
return fmt.Errorf("%s: %s", e.Msg, e.Detail)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ func BenchmarkExemplarJson(b *testing.B) {
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewReader(responseBytes)),
|
||||
}
|
||||
tCtx.httpProvider.setResponse(&res)
|
||||
tCtx.httpProvider.setResponse(&res, &res)
|
||||
resp, err := tCtx.queryData.Execute(context.Background(), query)
|
||||
require.NoError(b, err)
|
||||
for _, r := range resp.Responses {
|
||||
@ -74,7 +74,7 @@ func BenchmarkRangeJson(b *testing.B) {
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewReader(body)),
|
||||
}
|
||||
tCtx.httpProvider.setResponse(&res)
|
||||
tCtx.httpProvider.setResponse(&res, &res)
|
||||
r, err = tCtx.queryData.Execute(context.Background(), q)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
|
@ -149,6 +149,6 @@ func runQuery(response []byte, q *backend.QueryDataRequest) (*backend.QueryDataR
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewReader(response)),
|
||||
}
|
||||
tCtx.httpProvider.setResponse(res)
|
||||
tCtx.httpProvider.setResponse(res, res)
|
||||
return tCtx.queryData.Execute(context.Background(), q)
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
|
||||
func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
t.Run("exemplars response should be sampled and parsed normally", func(t *testing.T) {
|
||||
t.Skip()
|
||||
exemplars := []apiv1.ExemplarQueryResult{
|
||||
{
|
||||
SeriesLabels: p.LabelSet{
|
||||
@ -60,6 +59,22 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
values := []p.SamplePair{
|
||||
{Value: 1, Timestamp: 1000},
|
||||
{Value: 4, Timestamp: 4000},
|
||||
{Value: 6, Timestamp: 7000},
|
||||
{Value: 8, Timestamp: 1100},
|
||||
}
|
||||
rangeResult := queryResult{
|
||||
Type: p.ValMatrix,
|
||||
Result: p.Matrix{
|
||||
&p.SampleStream{
|
||||
Metric: p.Metric{"app": "Application", "tag2": "tag2"},
|
||||
Values: values,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tctx, err := setup()
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -76,20 +91,20 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
RefID: "A",
|
||||
JSON: b,
|
||||
}
|
||||
res, err := execute(tctx, query, exemplars)
|
||||
res, err := execute(tctx, query, exemplars, rangeResult)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test fields
|
||||
require.Len(t, res, 1)
|
||||
// require.Equal(t, res[0].Name, "exemplar")
|
||||
require.Len(t, res, 2)
|
||||
require.Equal(t, res[0].Name, "exemplar")
|
||||
require.Equal(t, res[0].Fields[0].Name, "Time")
|
||||
require.Equal(t, res[0].Fields[1].Name, "Value")
|
||||
require.Len(t, res[0].Fields, 6)
|
||||
|
||||
// Test correct values (sampled to 2)
|
||||
require.Equal(t, res[0].Fields[1].Len(), 2)
|
||||
require.Equal(t, res[0].Fields[1].Len(), 4)
|
||||
require.Equal(t, res[0].Fields[1].At(0), 0.009545445)
|
||||
require.Equal(t, res[0].Fields[1].At(1), 0.003535405)
|
||||
require.Equal(t, res[0].Fields[1].At(3), 0.003535405)
|
||||
})
|
||||
|
||||
t.Run("matrix response should be parsed normally", func(t *testing.T) {
|
||||
@ -128,7 +143,7 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
}
|
||||
tctx, err := setup()
|
||||
require.NoError(t, err)
|
||||
res, err := execute(tctx, query, result)
|
||||
res, err := execute(tctx, query, result, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, res, 1)
|
||||
@ -177,7 +192,7 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
}
|
||||
tctx, err := setup()
|
||||
require.NoError(t, err)
|
||||
res, err := execute(tctx, query, result)
|
||||
res, err := execute(tctx, query, result, nil)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 1)
|
||||
@ -222,7 +237,7 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
}
|
||||
tctx, err := setup()
|
||||
require.NoError(t, err)
|
||||
res, err := execute(tctx, query, result)
|
||||
res, err := execute(tctx, query, result, nil)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 1)
|
||||
@ -266,7 +281,7 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
|
||||
tctx, err := setup()
|
||||
require.NoError(t, err)
|
||||
res, err := execute(tctx, query, result)
|
||||
res, err := execute(tctx, query, result, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, `{app="Application"}`, res[0].Fields[1].Config.DisplayNameFromDS)
|
||||
@ -298,7 +313,7 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
}
|
||||
tctx, err := setup()
|
||||
require.NoError(t, err)
|
||||
res, err := execute(tctx, query, qr)
|
||||
res, err := execute(tctx, query, qr, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, res, 1)
|
||||
@ -317,7 +332,6 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("scalar response should be parsed normally", func(t *testing.T) {
|
||||
t.Skip("TODO: implement scalar responses")
|
||||
qr := queryResult{
|
||||
Type: p.ValScalar,
|
||||
Result: &p.Scalar{
|
||||
@ -339,14 +353,15 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) {
|
||||
}
|
||||
tctx, err := setup()
|
||||
require.NoError(t, err)
|
||||
res, err := execute(tctx, query, qr)
|
||||
res, err := execute(tctx, query, qr, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, res, 1)
|
||||
require.Len(t, res[0].Fields, 2)
|
||||
require.Len(t, res[0].Fields[0].Labels, 0)
|
||||
require.Equal(t, res[0].Fields[0].Name, "Time")
|
||||
require.Equal(t, "1", res[0].Fields[1].Name)
|
||||
require.Equal(t, "Value", res[0].Fields[1].Name)
|
||||
require.Equal(t, float64(1), res[0].Fields[1].At(0))
|
||||
|
||||
// Ensure the timestamps are UTC zoned
|
||||
testValue := res[0].Fields[0].At(0)
|
||||
@ -360,22 +375,29 @@ type queryResult struct {
|
||||
Result any `json:"result"`
|
||||
}
|
||||
|
||||
func executeWithHeaders(tctx *testContext, query backend.DataQuery, qr any, headers map[string]string) (data.Frames, error) {
|
||||
func executeWithHeaders(tctx *testContext, query backend.DataQuery, rqr any, eqr any, headers map[string]string) (data.Frames, error) {
|
||||
req := backend.QueryDataRequest{
|
||||
Queries: []backend.DataQuery{query},
|
||||
Headers: headers,
|
||||
}
|
||||
|
||||
promRes, err := toAPIResponse(qr)
|
||||
defer func() {
|
||||
if err := promRes.Body.Close(); err != nil {
|
||||
fmt.Println(fmt.Errorf("response body close error: %v", err))
|
||||
}
|
||||
}()
|
||||
rangeRes, err := toAPIResponse(rqr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tctx.httpProvider.setResponse(promRes)
|
||||
exemplarRes, err := toAPIResponse(eqr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := rangeRes.Body.Close(); err != nil {
|
||||
fmt.Println(fmt.Errorf("rangeRes body close error: %v", err))
|
||||
}
|
||||
if err := exemplarRes.Body.Close(); err != nil {
|
||||
fmt.Println(fmt.Errorf("exemplarRes body close error: %v", err))
|
||||
}
|
||||
}()
|
||||
tctx.httpProvider.setResponse(rangeRes, exemplarRes)
|
||||
|
||||
res, err := tctx.queryData.Execute(context.Background(), &req)
|
||||
if err != nil {
|
||||
@ -385,8 +407,8 @@ func executeWithHeaders(tctx *testContext, query backend.DataQuery, qr any, head
|
||||
return res.Responses[req.Queries[0].RefID].Frames, nil
|
||||
}
|
||||
|
||||
func execute(tctx *testContext, query backend.DataQuery, qr any) (data.Frames, error) {
|
||||
return executeWithHeaders(tctx, query, qr, map[string]string{})
|
||||
func execute(tctx *testContext, query backend.DataQuery, rqr any, eqr any) (data.Frames, error) {
|
||||
return executeWithHeaders(tctx, query, rqr, eqr, map[string]string{})
|
||||
}
|
||||
|
||||
type apiResponse struct {
|
||||
@ -426,7 +448,11 @@ func setup() (*testContext, error) {
|
||||
opts: httpclient.Options{
|
||||
Timeouts: &httpclient.DefaultTimeoutOptions,
|
||||
},
|
||||
res: &http.Response{
|
||||
rangeRes: &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewReader([]byte(`{}`))),
|
||||
},
|
||||
exemplarRes: &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewReader([]byte(`{}`))),
|
||||
},
|
||||
@ -456,9 +482,10 @@ func setup() (*testContext, error) {
|
||||
|
||||
type fakeHttpClientProvider struct {
|
||||
httpclient.Provider
|
||||
opts httpclient.Options
|
||||
req *http.Request
|
||||
res *http.Response
|
||||
opts httpclient.Options
|
||||
req *http.Request
|
||||
rangeRes *http.Response
|
||||
exemplarRes *http.Response
|
||||
}
|
||||
|
||||
func (p *fakeHttpClientProvider) New(opts ...httpclient.Options) (*http.Client, error) {
|
||||
@ -476,11 +503,18 @@ func (p *fakeHttpClientProvider) GetTransport(opts ...httpclient.Options) (http.
|
||||
return http.DefaultTransport, nil
|
||||
}
|
||||
|
||||
func (p *fakeHttpClientProvider) setResponse(res *http.Response) {
|
||||
p.res = res
|
||||
func (p *fakeHttpClientProvider) setResponse(rangeRes *http.Response, exemplarRes *http.Response) {
|
||||
p.rangeRes = rangeRes
|
||||
p.exemplarRes = exemplarRes
|
||||
}
|
||||
|
||||
func (p *fakeHttpClientProvider) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
p.req = req
|
||||
return p.res, nil
|
||||
switch req.URL.Path {
|
||||
case "/api/v1/query_range", "/api/v1/query":
|
||||
return p.rangeRes, nil
|
||||
case "/api/v1/query_exemplars":
|
||||
return p.exemplarRes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no such path: %s", req.URL.Path)
|
||||
}
|
||||
|
@ -83,15 +83,6 @@ func (r *Resource) Execute(ctx context.Context, req *backend.CallResourceRequest
|
||||
return callResponse, err
|
||||
}
|
||||
|
||||
func (r *Resource) DetectVersion(ctx context.Context, req *backend.CallResourceRequest) (*backend.CallResourceResponse, error) {
|
||||
newReq := &backend.CallResourceRequest{
|
||||
PluginContext: req.PluginContext,
|
||||
Path: "/api/v1/status/buildinfo",
|
||||
}
|
||||
|
||||
return r.Execute(ctx, newReq)
|
||||
}
|
||||
|
||||
func getSelectors(expr string) ([]string, error) {
|
||||
parsed, err := parser.ParseExpr(expr)
|
||||
if err != nil {
|
||||
|
109
pkg/promlib/resource/resource_test.go
Normal file
109
pkg/promlib/resource/resource_test.go
Normal file
@ -0,0 +1,109 @@
|
||||
package resource_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/promlib/resource"
|
||||
)
|
||||
|
||||
type mockRoundTripper struct {
|
||||
Response *http.Response
|
||||
Err error
|
||||
}
|
||||
|
||||
func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return m.Response, m.Err
|
||||
}
|
||||
|
||||
func setup() (*http.Client, backend.DataSourceInstanceSettings, log.Logger) {
|
||||
// Mock HTTP Response
|
||||
mockResponse := &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewReader([]byte(`{"message": "success"}`))),
|
||||
Header: make(http.Header),
|
||||
}
|
||||
|
||||
// Create a mock RoundTripper
|
||||
mockTransport := &mockRoundTripper{
|
||||
Response: mockResponse,
|
||||
}
|
||||
|
||||
// Create a mock HTTP client using the mock RoundTripper
|
||||
mockClient := &http.Client{
|
||||
Transport: mockTransport,
|
||||
}
|
||||
|
||||
settings := backend.DataSourceInstanceSettings{
|
||||
ID: 1,
|
||||
URL: "http://mock-server",
|
||||
JSONData: []byte(`{}`),
|
||||
}
|
||||
|
||||
logger := log.DefaultLogger
|
||||
|
||||
return mockClient, settings, logger
|
||||
}
|
||||
|
||||
func TestNewResource(t *testing.T) {
|
||||
mockClient, settings, logger := setup()
|
||||
res, err := resource.New(mockClient, settings, logger)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, res)
|
||||
}
|
||||
|
||||
func TestResource_Execute(t *testing.T) {
|
||||
mockClient, settings, logger := setup()
|
||||
res, err := resource.New(mockClient, settings, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &backend.CallResourceRequest{
|
||||
URL: "/test",
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
resp, err := res.Execute(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
}
|
||||
|
||||
func TestResource_GetSuggestions(t *testing.T) {
|
||||
mockClient, _, logger := setup()
|
||||
settings := backend.DataSourceInstanceSettings{
|
||||
ID: 1,
|
||||
URL: "http://localhost:9090",
|
||||
JSONData: []byte(`{"httpMethod": "GET"}`),
|
||||
}
|
||||
|
||||
res, err := resource.New(mockClient, settings, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
suggestionReq := resource.SuggestionRequest{
|
||||
LabelName: "instance",
|
||||
Queries: []string{"up"},
|
||||
Start: "1609459200",
|
||||
End: "1609462800",
|
||||
Limit: 10,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(suggestionReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := &backend.CallResourceRequest{
|
||||
Body: body,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
resp, err := res.GetSuggestions(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
}
|
Reference in New Issue
Block a user