mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 04:59:15 -06:00
195 lines
6.2 KiB
Go
195 lines
6.2 KiB
Go
package ml
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
)
|
|
|
|
func TestOutlierExec(t *testing.T) {
|
|
outlier := OutlierCommand{
|
|
config: OutlierCommandConfiguration{
|
|
DatasourceType: "prometheus",
|
|
DatasourceUID: "a4ce599c-4c93-44b9-be5b-76385b8c01be",
|
|
QueryParams: map[string]any{
|
|
"expr": "go_goroutines{}",
|
|
"range": true,
|
|
"refId": "A",
|
|
},
|
|
Algorithm: map[string]any{
|
|
"name": "dbscan",
|
|
"config": map[string]any{
|
|
"epsilon": 7.667,
|
|
},
|
|
"sensitivity": 0.83,
|
|
},
|
|
ResponseType: "binary",
|
|
},
|
|
appURL: "https://grafana.com",
|
|
interval: 1000 * time.Second,
|
|
}
|
|
|
|
t.Run("should generate expected parameters for request", func(t *testing.T) {
|
|
to := time.Now().UTC()
|
|
from := to.Add(-10 * time.Hour)
|
|
|
|
called := false
|
|
_, err := outlier.Execute(from, to, func(method string, path string, payload []byte) (response.Response, error) {
|
|
require.Equal(t, "POST", method)
|
|
require.Equal(t, "/proxy/api/v1/outlier", path)
|
|
|
|
assert.JSONEq(t, fmt.Sprintf(`{
|
|
"data": {
|
|
"attributes": {
|
|
"datasource_type": "prometheus",
|
|
"datasource_uid": "a4ce599c-4c93-44b9-be5b-76385b8c01be",
|
|
"query_params": {
|
|
"expr": "go_goroutines{}",
|
|
"range": true,
|
|
"refId": "A"
|
|
},
|
|
"algorithm": {
|
|
"config": {
|
|
"epsilon": 7.667
|
|
},
|
|
"name": "dbscan",
|
|
"sensitivity": 0.83
|
|
},
|
|
"response_type": "binary",
|
|
"grafana_url": "https://grafana.com",
|
|
"start_end_attributes": {
|
|
"start": "%s",
|
|
"end": "%s",
|
|
"interval": 1000000
|
|
}
|
|
}
|
|
}
|
|
}`, from.Format(timeFormat), to.Format(timeFormat)), string(payload))
|
|
|
|
called = true
|
|
return nil, nil
|
|
})
|
|
require.Truef(t, called, "request function was not called")
|
|
require.ErrorContains(t, err, "response is nil")
|
|
})
|
|
|
|
successResponse := `{"status":"success","data":{"results":{"A":{"status":200,"frames":[{"schema":{"name":"test","fields":[{"name":"Time","type":"time","typeInfo":{"frame":"time.Time"}},{"name":"Value","type":"number","typeInfo":{"frame":"float64"},"labels":{"instance":"test"}}]},"data":{"values":[[1686945300000],[0]]}}]}}}}`
|
|
|
|
testCases := []struct {
|
|
name string
|
|
response response.Response
|
|
assert func(t *testing.T, r *backend.QueryDataResponse, err error)
|
|
}{
|
|
{
|
|
name: "should return parsed frames when 200",
|
|
response: response.CreateNormalResponse(nil, []byte(successResponse), http.StatusOK),
|
|
assert: func(t *testing.T, r *backend.QueryDataResponse, err error) {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, r)
|
|
require.Contains(t, r.Responses, "A")
|
|
require.NoError(t, r.Responses["A"].Error)
|
|
require.Equal(t, backend.StatusOK, r.Responses["A"].Status)
|
|
require.Len(t, r.Responses["A"].Frames, 1)
|
|
},
|
|
},
|
|
{
|
|
name: "should return nil if 204",
|
|
response: response.CreateNormalResponse(nil, []byte(`{"status": "success"}`), http.StatusNoContent),
|
|
assert: func(t *testing.T, r *backend.QueryDataResponse, err error) {
|
|
require.NoError(t, err)
|
|
require.Nil(t, r)
|
|
},
|
|
},
|
|
{
|
|
name: "should return error if any status and body has status error",
|
|
response: response.CreateNormalResponse(nil, []byte(`{"status": "error"}`), 0),
|
|
assert: func(t *testing.T, r *backend.QueryDataResponse, err error) {
|
|
require.ErrorContains(t, err, "ML API responded with error")
|
|
require.Nil(t, r)
|
|
},
|
|
},
|
|
{
|
|
name: "should return error with explanations if any status and body has status error",
|
|
response: response.CreateNormalResponse(nil, []byte(`{"status": "error", "error": "test-error"}`), 0),
|
|
assert: func(t *testing.T, r *backend.QueryDataResponse, err error) {
|
|
require.ErrorContains(t, err, "ML API responded with error")
|
|
require.ErrorContains(t, err, "test-error")
|
|
require.Nil(t, r)
|
|
},
|
|
},
|
|
{
|
|
name: "should return error response is empty",
|
|
response: response.CreateNormalResponse(nil, []byte(``), 0),
|
|
assert: func(t *testing.T, r *backend.QueryDataResponse, err error) {
|
|
require.ErrorContains(t, err, "unexpected format of the response from ML API")
|
|
require.Nil(t, r)
|
|
},
|
|
},
|
|
{
|
|
name: "should return error response is not a valid JSON",
|
|
response: response.CreateNormalResponse(nil, []byte(`{`), 0),
|
|
assert: func(t *testing.T, r *backend.QueryDataResponse, err error) {
|
|
require.ErrorContains(t, err, "unexpected format of the response from ML API")
|
|
require.Nil(t, r)
|
|
},
|
|
},
|
|
{
|
|
name: "should error if response is nil and no error",
|
|
response: nil,
|
|
assert: func(t *testing.T, r *backend.QueryDataResponse, err error) {
|
|
require.ErrorContains(t, err, "response is nil")
|
|
require.Nil(t, r)
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
to := time.Now()
|
|
from := to.Add(-10 * time.Hour)
|
|
|
|
resp, err := outlier.Execute(from, to, func(method string, path string, payload []byte) (response.Response, error) {
|
|
return tc.response, nil
|
|
})
|
|
tc.assert(t, resp, err)
|
|
})
|
|
}
|
|
|
|
t.Run("should return error if status is not known", func(t *testing.T) {
|
|
knownStatuses := []int{http.StatusOK, http.StatusNoContent}
|
|
|
|
for status := 100; status < 600; status++ {
|
|
if http.StatusText(status) == "" || slices.Contains(knownStatuses, status) {
|
|
continue
|
|
}
|
|
to := time.Now()
|
|
from := to.Add(-10 * time.Hour)
|
|
|
|
resp, err := outlier.Execute(from, to, func(method string, path string, payload []byte) (response.Response, error) {
|
|
return response.CreateNormalResponse(nil, []byte(successResponse), status), nil
|
|
})
|
|
require.ErrorContains(t, err, fmt.Sprintf("unexpected status %d returned by ML API", status))
|
|
require.Nil(t, resp)
|
|
}
|
|
})
|
|
|
|
t.Run("should propagate error from request function", func(t *testing.T) {
|
|
to := time.Now()
|
|
from := to.Add(-10 * time.Hour)
|
|
|
|
resp, err := outlier.Execute(from, to, func(method string, path string, payload []byte) (response.Response, error) {
|
|
return nil, errors.New("test-error")
|
|
})
|
|
require.ErrorContains(t, err, "test-error")
|
|
require.Nil(t, resp)
|
|
})
|
|
}
|