mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			220 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			220 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
| package expr
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"net/http"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/grafana/grafana-plugin-sdk-go/backend"
 | |
| 	"github.com/grafana/grafana-plugin-sdk-go/data"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/expr/ml"
 | |
| 	"github.com/grafana/grafana/pkg/plugins"
 | |
| 	"github.com/grafana/grafana/pkg/services/user"
 | |
| )
 | |
| 
 | |
| func TestMLNodeExecute(t *testing.T) {
 | |
| 	timeNow := time.Now()
 | |
| 	expectedOrgID := int64(123)
 | |
| 	timeRange := RelativeTimeRange{
 | |
| 		From: -10 * time.Hour,
 | |
| 		To:   0,
 | |
| 	}
 | |
| 	request := &Request{
 | |
| 		Headers: map[string]string{
 | |
| 			"test": "test",
 | |
| 		},
 | |
| 		Debug:   false,
 | |
| 		OrgId:   expectedOrgID,
 | |
| 		Queries: nil,
 | |
| 		User: &user.SignedInUser{
 | |
| 			UserID: 1,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	expectedResponse := &backend.CallResourceResponse{
 | |
| 		Status:  200,
 | |
| 		Headers: nil,
 | |
| 		Body:    []byte("test-response"),
 | |
| 	}
 | |
| 
 | |
| 	pluginsClient := &recordingCallResourceHandler{
 | |
| 		response: expectedResponse,
 | |
| 	}
 | |
| 
 | |
| 	pluginCtx := &fakePluginContextProvider{
 | |
| 		result: map[string]*backend.AppInstanceSettings{
 | |
| 			mlPluginID: {
 | |
| 				JSONData: json.RawMessage(`{ "initialized": true }`),
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	s := &Service{
 | |
| 		cfg:           nil,
 | |
| 		dataService:   nil,
 | |
| 		pCtxProvider:  pluginCtx,
 | |
| 		features:      nil,
 | |
| 		pluginsClient: pluginsClient,
 | |
| 		tracer:        nil,
 | |
| 		metrics:       newMetrics(nil),
 | |
| 	}
 | |
| 
 | |
| 	cmdResponse := data.NewFrame("test",
 | |
| 		data.NewField("Time", nil, []time.Time{time.Unix(1, 0)}),
 | |
| 		data.NewField("Value", nil, []*float64{fp(1)}),
 | |
| 	)
 | |
| 
 | |
| 	cmd := &ml.FakeCommand{
 | |
| 		Method:  http.MethodPost,
 | |
| 		Path:    "/test/ml",
 | |
| 		Payload: []byte(`{}`),
 | |
| 		Response: &backend.QueryDataResponse{
 | |
| 			Responses: map[string]backend.DataResponse{
 | |
| 				"A": {
 | |
| 					Frames: data.Frames{
 | |
| 						cmdResponse,
 | |
| 					},
 | |
| 					Status: backend.StatusOK,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		Error: nil,
 | |
| 	}
 | |
| 
 | |
| 	node := &MLNode{
 | |
| 		baseNode:  baseNode{},
 | |
| 		command:   cmd,
 | |
| 		TimeRange: timeRange,
 | |
| 		request:   request,
 | |
| 	}
 | |
| 
 | |
| 	result, err := node.Execute(context.Background(), timeNow, nil, s)
 | |
| 
 | |
| 	require.NoError(t, err)
 | |
| 	require.NotNil(t, result)
 | |
| 	require.NotEmpty(t, result.Values)
 | |
| 
 | |
| 	t.Run("should get plugin context", func(t *testing.T) {
 | |
| 		require.NotEmpty(t, pluginCtx.recordings)
 | |
| 		require.Equal(t, "Get", pluginCtx.recordings[0].method)
 | |
| 		require.Equal(t, mlPluginID, pluginCtx.recordings[0].params[0])
 | |
| 		require.Equal(t, request.User, pluginCtx.recordings[0].params[1])
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should call command execute with correct parameters", func(t *testing.T) {
 | |
| 		require.NotEmpty(t, cmd.Recordings)
 | |
| 		rec := cmd.Recordings[0]
 | |
| 		require.Equal(t, timeRange.AbsoluteTime(timeNow).From, rec.From)
 | |
| 		require.Equal(t, timeRange.AbsoluteTime(timeNow).To, rec.To)
 | |
| 		require.NotNil(t, rec.Response)
 | |
| 		require.Equal(t, expectedResponse.Status, rec.Response.Status())
 | |
| 		require.Equal(t, expectedResponse.Body, rec.Response.Body())
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should call plugin API", func(t *testing.T) {
 | |
| 		require.NotEmpty(t, pluginsClient.recordings)
 | |
| 		req := pluginsClient.recordings[0]
 | |
| 		require.Equal(t, cmd.Payload, req.Body)
 | |
| 		require.Equal(t, cmd.Path, req.Path)
 | |
| 		require.Equal(t, cmd.Method, req.Method)
 | |
| 
 | |
| 		require.NotNil(t, req.PluginContext)
 | |
| 		require.Equal(t, mlPluginID, req.PluginContext.PluginID)
 | |
| 
 | |
| 		t.Run("should append request headers to API call", func(t *testing.T) {
 | |
| 			for key, value := range request.Headers {
 | |
| 				require.Contains(t, req.Headers, key)
 | |
| 				require.Equal(t, value, req.Headers[key][0])
 | |
| 			}
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should fail if plugin is not installed", func(t *testing.T) {
 | |
| 		s := &Service{
 | |
| 			cfg:         nil,
 | |
| 			dataService: nil,
 | |
| 			pCtxProvider: &fakePluginContextProvider{
 | |
| 				errorResult: plugins.ErrPluginNotRegistered,
 | |
| 			},
 | |
| 			features:      nil,
 | |
| 			pluginsClient: nil,
 | |
| 			tracer:        nil,
 | |
| 			metrics:       nil,
 | |
| 		}
 | |
| 
 | |
| 		_, err := node.Execute(context.Background(), timeNow, nil, s)
 | |
| 		require.ErrorIs(t, err, errMLPluginDoesNotExist)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should fail if plugin settings cannot be retrieved", func(t *testing.T) {
 | |
| 		expectedErr := errors.New("test-error")
 | |
| 		s := &Service{
 | |
| 			cfg:         nil,
 | |
| 			dataService: nil,
 | |
| 			pCtxProvider: &fakePluginContextProvider{
 | |
| 				errorResult: expectedErr,
 | |
| 			},
 | |
| 			features:      nil,
 | |
| 			pluginsClient: nil,
 | |
| 			tracer:        nil,
 | |
| 			metrics:       nil,
 | |
| 		}
 | |
| 
 | |
| 		_, err := node.Execute(context.Background(), timeNow, nil, s)
 | |
| 		require.ErrorIs(t, err, expectedErr)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should fail if plugin is not initialized", func(t *testing.T) {
 | |
| 		s := &Service{
 | |
| 			cfg:         nil,
 | |
| 			dataService: nil,
 | |
| 			pCtxProvider: &fakePluginContextProvider{
 | |
| 				result: map[string]*backend.AppInstanceSettings{
 | |
| 					mlPluginID: {
 | |
| 						JSONData: json.RawMessage(`{}`),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			features:      nil,
 | |
| 			pluginsClient: nil,
 | |
| 			tracer:        nil,
 | |
| 			metrics:       nil,
 | |
| 		}
 | |
| 
 | |
| 		_, err := node.Execute(context.Background(), timeNow, nil, s)
 | |
| 		require.ErrorIs(t, err, errMLPluginDoesNotExist)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should return QueryError if command failed", func(t *testing.T) {
 | |
| 		s := &Service{
 | |
| 			cfg:           nil,
 | |
| 			dataService:   nil,
 | |
| 			pCtxProvider:  pluginCtx,
 | |
| 			features:      nil,
 | |
| 			pluginsClient: pluginsClient,
 | |
| 			tracer:        nil,
 | |
| 			metrics:       newMetrics(nil),
 | |
| 		}
 | |
| 
 | |
| 		cmd := &ml.FakeCommand{
 | |
| 			Error: errors.New("failed to execute command"),
 | |
| 		}
 | |
| 
 | |
| 		node := &MLNode{
 | |
| 			baseNode:  baseNode{},
 | |
| 			command:   cmd,
 | |
| 			TimeRange: timeRange,
 | |
| 			request:   request,
 | |
| 		}
 | |
| 
 | |
| 		_, err := node.Execute(context.Background(), timeNow, nil, s)
 | |
| 		require.IsType(t, err, MakeQueryError("A", "", expectedError{}))
 | |
| 		require.ErrorIs(t, err, cmd.Error)
 | |
| 	})
 | |
| }
 |