mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			284 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| package api
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"net/http"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/grafana/grafana-plugin-sdk-go/backend"
 | |
| 	"github.com/stretchr/testify/mock"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/infra/tracing"
 | |
| 	ac "github.com/grafana/grafana/pkg/services/accesscontrol"
 | |
| 	acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
 | |
| 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
 | |
| 	"github.com/grafana/grafana/pkg/services/datasources"
 | |
| 	fakes "github.com/grafana/grafana/pkg/services/datasources/fakes"
 | |
| 	"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
 | |
| 	"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
 | |
| 	"github.com/grafana/grafana/pkg/services/ngalert/eval"
 | |
| 	"github.com/grafana/grafana/pkg/services/ngalert/eval/eval_mocks"
 | |
| 	"github.com/grafana/grafana/pkg/services/ngalert/models"
 | |
| 	"github.com/grafana/grafana/pkg/services/user"
 | |
| 	"github.com/grafana/grafana/pkg/web"
 | |
| )
 | |
| 
 | |
| func Test(t *testing.T) {
 | |
| 	text := `{
 | |
|     "rule": {
 | |
| "grafana_alert" : {
 | |
|         "condition": "C",
 | |
|         "data": [
 | |
|             {
 | |
|                 "refId": "A",
 | |
|                 "relativeTimeRange": {
 | |
|                     "from": 600,
 | |
|                     "to": 0
 | |
|                 },
 | |
|                 "queryType": "",
 | |
|                 "datasourceUid": "PD8C576611E62080A",
 | |
|                 "model": {
 | |
|                     "refId": "A",
 | |
|                     "hide": false,
 | |
|                     "datasource": {
 | |
|                         "type": "testdata",
 | |
|                         "uid": "PD8C576611E62080A"
 | |
|                     },
 | |
|                     "scenarioId": "random_walk",
 | |
|                     "seriesCount": 5,
 | |
|                     "labels": "series=series-$seriesIndex"
 | |
|                 }
 | |
|             },
 | |
|             {
 | |
|                 "refId": "B",
 | |
|                 "datasourceUid": "__expr__",
 | |
|                 "queryType": "",
 | |
|                 "model": {
 | |
|                     "refId": "B",
 | |
|                     "hide": false,
 | |
|                     "type": "reduce",
 | |
|                     "datasource": {
 | |
|                         "uid": "__expr__",
 | |
|                         "type": "__expr__"
 | |
|                     },
 | |
|                     "reducer": "last",
 | |
|                     "expression": "A"
 | |
|                 },
 | |
|                 "relativeTimeRange": {
 | |
|                     "from": 600,
 | |
|                     "to": 0
 | |
|                 }
 | |
|             },
 | |
|             {
 | |
|                 "refId": "C",
 | |
|                 "datasourceUid": "__expr__",
 | |
|                 "queryType": "",
 | |
|                 "model": {
 | |
|                     "refId": "C",
 | |
|                     "hide": false,
 | |
|                     "type": "threshold",
 | |
|                     "datasource": {
 | |
|                         "uid": "__expr__",
 | |
|                         "type": "__expr__"
 | |
|                     },
 | |
|                     "conditions": [
 | |
|                         {
 | |
|                             "type": "query",
 | |
|                             "evaluator": {
 | |
|                                 "params": [
 | |
|                                     0
 | |
|                                 ],
 | |
|                                 "type": "gt"
 | |
|                             }
 | |
|                         }
 | |
|                     ],
 | |
|                     "expression": "B"
 | |
|                 },
 | |
|                 "relativeTimeRange": {
 | |
|                     "from": 600,
 | |
|                     "to": 0
 | |
|                 }
 | |
|             }
 | |
|         ],
 | |
|         "no_data_state": "Alerting",
 | |
| "title": "string"
 | |
| },
 | |
|         "for": "0s",
 | |
|         "labels": {
 | |
|             "additionalProp1": "string",
 | |
|             "additionalProp2": "string",
 | |
|             "additionalProp3": "string"
 | |
|         },
 | |
|  "annotations": {
 | |
|             "additionalProp1": "string",
 | |
|             "additionalProp2": "string",
 | |
|             "additionalProp3": "string"
 | |
|         }
 | |
|     },
 | |
|     "folderUid": "test-uid",
 | |
|     "folderTitle": "test-folder"
 | |
| }`
 | |
| 	var conf definitions.PostableExtendedRuleNodeExtended
 | |
| 	require.NoError(t, json.Unmarshal([]byte(text), &conf))
 | |
| 
 | |
| 	require.Equal(t, "test-folder", conf.NamespaceTitle)
 | |
| }
 | |
| 
 | |
| func TestRouteTestGrafanaRuleConfig(t *testing.T) {
 | |
| 	t.Run("when fine-grained access is enabled", func(t *testing.T) {
 | |
| 		rc := &contextmodel.ReqContext{
 | |
| 			Context: &web.Context{
 | |
| 				Req: &http.Request{},
 | |
| 			},
 | |
| 			SignedInUser: &user.SignedInUser{
 | |
| 				OrgID: 1,
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		t.Run("should return 401 if user cannot query a data source", func(t *testing.T) {
 | |
| 			data1 := models.GenerateAlertQuery()
 | |
| 			data2 := models.GenerateAlertQuery()
 | |
| 
 | |
| 			ac := acMock.New().WithPermissions([]ac.Permission{
 | |
| 				{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
 | |
| 			})
 | |
| 
 | |
| 			srv := createTestingApiSrv(t, nil, ac, eval_mocks.NewEvaluatorFactory(&eval_mocks.ConditionEvaluatorMock{}))
 | |
| 
 | |
| 			rule := validRule()
 | |
| 			rule.GrafanaManagedAlert.Data = ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2})
 | |
| 			rule.GrafanaManagedAlert.Condition = data2.RefID
 | |
| 			response := srv.RouteTestGrafanaRuleConfig(rc, definitions.PostableExtendedRuleNodeExtended{
 | |
| 				Rule:           rule,
 | |
| 				NamespaceUID:   "test-folder",
 | |
| 				NamespaceTitle: "test-folder",
 | |
| 			})
 | |
| 
 | |
| 			require.Equal(t, http.StatusUnauthorized, response.Status())
 | |
| 		})
 | |
| 
 | |
| 		t.Run("should return 200 if user can query all data sources", func(t *testing.T) {
 | |
| 			data1 := models.GenerateAlertQuery()
 | |
| 			data2 := models.GenerateAlertQuery()
 | |
| 
 | |
| 			ac := acMock.New().WithPermissions([]ac.Permission{
 | |
| 				{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
 | |
| 				{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data2.DatasourceUID)},
 | |
| 			})
 | |
| 
 | |
| 			ds := &fakes.FakeCacheService{DataSources: []*datasources.DataSource{
 | |
| 				{UID: data1.DatasourceUID},
 | |
| 				{UID: data2.DatasourceUID},
 | |
| 			}}
 | |
| 
 | |
| 			var result []eval.Result
 | |
| 			evaluator := &eval_mocks.ConditionEvaluatorMock{}
 | |
| 			evaluator.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(result, nil)
 | |
| 
 | |
| 			evalFactory := eval_mocks.NewEvaluatorFactory(evaluator)
 | |
| 
 | |
| 			srv := createTestingApiSrv(t, ds, ac, evalFactory)
 | |
| 
 | |
| 			rule := validRule()
 | |
| 			rule.GrafanaManagedAlert.Data = ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2})
 | |
| 			rule.GrafanaManagedAlert.Condition = data2.RefID
 | |
| 			response := srv.RouteTestGrafanaRuleConfig(rc, definitions.PostableExtendedRuleNodeExtended{
 | |
| 				Rule:           rule,
 | |
| 				NamespaceUID:   "test-folder",
 | |
| 				NamespaceTitle: "test-folder",
 | |
| 			})
 | |
| 
 | |
| 			require.Equal(t, http.StatusOK, response.Status())
 | |
| 
 | |
| 			evaluator.AssertCalled(t, "Evaluate", mock.Anything, mock.Anything)
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestRouteEvalQueries(t *testing.T) {
 | |
| 	t.Run("when fine-grained access is enabled", func(t *testing.T) {
 | |
| 		rc := &contextmodel.ReqContext{
 | |
| 			Context: &web.Context{
 | |
| 				Req: &http.Request{},
 | |
| 			},
 | |
| 			SignedInUser: &user.SignedInUser{
 | |
| 				OrgID: 1,
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		t.Run("should return 401 if user cannot query a data source", func(t *testing.T) {
 | |
| 			data1 := models.GenerateAlertQuery()
 | |
| 			data2 := models.GenerateAlertQuery()
 | |
| 
 | |
| 			srv := &TestingApiSrv{
 | |
| 				authz: accesscontrol.NewRuleService(acMock.New().WithPermissions([]ac.Permission{
 | |
| 					{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
 | |
| 				})),
 | |
| 				tracer: tracing.InitializeTracerForTest(),
 | |
| 			}
 | |
| 
 | |
| 			response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
 | |
| 				Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2}),
 | |
| 				Now:  time.Time{},
 | |
| 			})
 | |
| 
 | |
| 			require.Equal(t, http.StatusUnauthorized, response.Status())
 | |
| 		})
 | |
| 
 | |
| 		t.Run("should return 200 if user can query all data sources", func(t *testing.T) {
 | |
| 			data1 := models.GenerateAlertQuery()
 | |
| 			data2 := models.GenerateAlertQuery()
 | |
| 
 | |
| 			currentTime := time.Now()
 | |
| 
 | |
| 			ac := acMock.New().WithPermissions([]ac.Permission{
 | |
| 				{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
 | |
| 				{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data2.DatasourceUID)},
 | |
| 			})
 | |
| 
 | |
| 			ds := &fakes.FakeCacheService{DataSources: []*datasources.DataSource{
 | |
| 				{UID: data1.DatasourceUID},
 | |
| 				{UID: data2.DatasourceUID},
 | |
| 			}}
 | |
| 
 | |
| 			evaluator := &eval_mocks.ConditionEvaluatorMock{}
 | |
| 			result := &backend.QueryDataResponse{
 | |
| 				Responses: map[string]backend.DataResponse{
 | |
| 					"test": {
 | |
| 						Frames: nil,
 | |
| 						Error:  nil,
 | |
| 					},
 | |
| 				},
 | |
| 			}
 | |
| 			evaluator.EXPECT().EvaluateRaw(mock.Anything, mock.Anything).Return(result, nil)
 | |
| 
 | |
| 			srv := createTestingApiSrv(t, ds, ac, eval_mocks.NewEvaluatorFactory(evaluator))
 | |
| 
 | |
| 			response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
 | |
| 				Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2}),
 | |
| 				Now:  currentTime,
 | |
| 			})
 | |
| 
 | |
| 			require.Equal(t, http.StatusOK, response.Status())
 | |
| 
 | |
| 			evaluator.AssertCalled(t, "EvaluateRaw", mock.Anything, currentTime)
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func createTestingApiSrv(t *testing.T, ds *fakes.FakeCacheService, ac *acMock.Mock, evaluator eval.EvaluatorFactory) *TestingApiSrv {
 | |
| 	if ac == nil {
 | |
| 		ac = acMock.New()
 | |
| 	}
 | |
| 
 | |
| 	return &TestingApiSrv{
 | |
| 		DatasourceCache: ds,
 | |
| 		authz:           accesscontrol.NewRuleService(ac),
 | |
| 		evaluator:       evaluator,
 | |
| 		cfg:             config(t),
 | |
| 		tracer:          tracing.InitializeTracerForTest(),
 | |
| 	}
 | |
| }
 |