diff --git a/pkg/infra/httpclient/httpclientprovider/host_redirect_validation_middleware_test.go b/pkg/infra/httpclient/httpclientprovider/host_redirect_validation_middleware_test.go new file mode 100644 index 00000000000..ba6a473793a --- /dev/null +++ b/pkg/infra/httpclient/httpclientprovider/host_redirect_validation_middleware_test.go @@ -0,0 +1,117 @@ +package httpclientprovider + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/stretchr/testify/require" +) + +func TestRedirectLimitMiddleware(t *testing.T) { + t.Parallel() + + t.Run("when the server responds with a status code 3xx", func(t *testing.T) { + t.Parallel() + + t.Run("but there is no Location header in the response, then the response is not validated", func(t *testing.T) { + t.Parallel() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusTemporaryRedirect) + })) + t.Cleanup(server.Close) + + mw := RedirectLimitMiddleware(&fakeValidator{err: errors.New("this wont be called")}) + rt := mw.CreateMiddleware(httpclient.Options{}, http.DefaultTransport) + require.NotNil(t, rt) + + middlewareName, ok := mw.(httpclient.MiddlewareName) + require.True(t, ok) + require.Equal(t, HostRedirectValidationMiddlewareName, middlewareName.MiddlewareName()) + + req, err := http.NewRequest(http.MethodGet, server.URL, nil) + require.NoError(t, err) + + res, err := rt.RoundTrip(req) + require.NoError(t, err) + require.NotNil(t, res) + + if res.Body != nil { + require.NoError(t, res.Body.Close()) + } + }) + + t.Run("and there is a Location header in the response", func(t *testing.T) { + t.Parallel() + + t.Run("but the validation function fails, it returns the validation error", func(t *testing.T) { + t.Parallel() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "http://www.example.com") + w.WriteHeader(http.StatusTemporaryRedirect) + })) + t.Cleanup(server.Close) + + fakeErr := errors.New("fake error") + + mw := RedirectLimitMiddleware(&fakeValidator{err: fakeErr}) + rt := mw.CreateMiddleware(httpclient.Options{}, http.DefaultTransport) + require.NotNil(t, rt) + + middlewareName, ok := mw.(httpclient.MiddlewareName) + require.True(t, ok) + require.Equal(t, HostRedirectValidationMiddlewareName, middlewareName.MiddlewareName()) + + req, err := http.NewRequest(http.MethodGet, server.URL, nil) + require.NoError(t, err) + + res, err := rt.RoundTrip(req) + require.ErrorIs(t, err, fakeErr) + require.Nil(t, res) + + if res != nil && res.Body != nil { + require.NoError(t, res.Body.Close()) + } + }) + + t.Run("and the validation function succeeds, it returns the response", func(t *testing.T) { + t.Parallel() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "http://www.example.com") + w.WriteHeader(http.StatusTemporaryRedirect) + })) + t.Cleanup(server.Close) + + mw := RedirectLimitMiddleware(&fakeValidator{}) + rt := mw.CreateMiddleware(httpclient.Options{}, http.DefaultTransport) + require.NotNil(t, rt) + + middlewareName, ok := mw.(httpclient.MiddlewareName) + require.True(t, ok) + require.Equal(t, HostRedirectValidationMiddlewareName, middlewareName.MiddlewareName()) + + req, err := http.NewRequest(http.MethodGet, server.URL, nil) + require.NoError(t, err) + + res, err := rt.RoundTrip(req) + require.NoError(t, err) + require.NotNil(t, res) + + if res.Body != nil { + require.NoError(t, res.Body.Close()) + } + }) + }) + }) +} + +type fakeValidator struct { + err error +} + +func (v *fakeValidator) Validate(_ string) error { return v.err } diff --git a/pkg/tsdb/graphite/graphite_test.go b/pkg/tsdb/graphite/graphite_test.go index 2bb94f71dea..e5946ac5cd3 100644 --- a/pkg/tsdb/graphite/graphite_test.go +++ b/pkg/tsdb/graphite/graphite_test.go @@ -6,18 +6,20 @@ import ( "fmt" "io" "net/http" + "net/http/httptest" "reflect" "strings" "testing" "time" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/infra/httpclient" + "github.com/grafana/grafana/pkg/infra/tracing" ) func TestFixIntervalFormat(t *testing.T) { @@ -141,14 +143,71 @@ func TestProcessQueries(t *testing.T) { }, } - service.im = fakeInstanceManager{} + service := ProvideService(httpclient.NewProvider(), tracing.NewNoopTracerService()) + rsp, err := service.QueryData(context.Background(), &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{ + DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ + ID: 0, + URL: "http://localhost", + }, + }, Queries: queries, }) assert.NoError(t, err) expectedResponse := backend.ErrDataResponseWithSource(400, backend.ErrorSourceDownstream, "no query target found for the alert rule") assert.Equal(t, expectedResponse, rsp.Responses["A"]) }) + + t.Run("QueryData with no queries returns an error", func(t *testing.T) { + service := &Service{} + + rsp, err := service.QueryData(context.Background(), &backend.QueryDataRequest{}) + assert.Nil(t, rsp) + assert.Error(t, err) + }) + + t.Run("QueryData happy path with service provider and plugin context", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`[ + { + "target": "target A", + "tags": { "fooTag": "fooValue", "barTag": "barValue", "int": 100, "float": 3.14 }, + "datapoints": [[50, 1], [null, 2], [100, 3]] + } + ]`)) + })) + t.Cleanup(server.Close) + + service := ProvideService(httpclient.NewProvider(), tracing.NewNoopTracerService()) + + queries := []backend.DataQuery{ + { + RefID: "A", + JSON: []byte(`{ + "target": "app.grafana.*.dashboards.views.1M.count" + }`), + }, + { + RefID: "B", + JSON: []byte(`{ + "query": "app.grafana.*.dashboards.views.1M.count" + }`), + }, + } + + rsp, err := service.QueryData(context.Background(), &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{ + DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ + ID: 0, + URL: server.URL, + }, + }, + Queries: queries, + }) + assert.NoError(t, err) + assert.NotNil(t, rsp) + }) } func TestConvertResponses(t *testing.T) { @@ -289,13 +348,3 @@ func TestConvertResponses(t *testing.T) { require.Error(t, err) }) } - -type fakeInstanceManager struct{} - -func (f fakeInstanceManager) Get(_ context.Context, _ backend.PluginContext) (instancemgmt.Instance, error) { - return datasourceInfo{}, nil -} - -func (f fakeInstanceManager) Do(_ context.Context, _ backend.PluginContext, _ instancemgmt.InstanceCallbackFunc) error { - return nil -}