2022-04-01 19:00:23 -05:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2023-06-08 17:59:54 -05:00
|
|
|
"encoding/json"
|
2022-04-01 19:00:23 -05:00
|
|
|
"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/services/accesscontrol"
|
|
|
|
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
2023-01-27 01:50:36 -06:00
|
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
2022-04-01 19:00:23 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
2022-04-25 11:57:45 -05:00
|
|
|
fakes "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
2022-04-01 19:00:23 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
2022-11-02 09:13:39 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/eval/eval_mocks"
|
2022-04-01 19:00:23 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
2022-08-10 04:56:48 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/user"
|
2022-04-01 19:00:23 -05:00
|
|
|
"github.com/grafana/grafana/pkg/web"
|
|
|
|
)
|
|
|
|
|
2023-06-08 17:59:54 -05:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2022-04-01 19:00:23 -05:00
|
|
|
func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
|
|
|
t.Run("when fine-grained access is enabled", func(t *testing.T) {
|
2023-01-27 01:50:36 -06:00
|
|
|
rc := &contextmodel.ReqContext{
|
2022-04-01 19:00:23 -05:00
|
|
|
Context: &web.Context{
|
|
|
|
Req: &http.Request{},
|
|
|
|
},
|
2022-08-10 04:56:48 -05:00
|
|
|
SignedInUser: &user.SignedInUser{
|
2022-08-11 06:28:55 -05:00
|
|
|
OrgID: 1,
|
2022-04-01 19:00:23 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("should return 401 if user cannot query a data source", func(t *testing.T) {
|
|
|
|
data1 := models.GenerateAlertQuery()
|
|
|
|
data2 := models.GenerateAlertQuery()
|
|
|
|
|
2022-06-14 03:17:48 -05:00
|
|
|
ac := acMock.New().WithPermissions([]accesscontrol.Permission{
|
2022-04-01 19:00:23 -05:00
|
|
|
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
|
|
|
|
})
|
|
|
|
|
2023-06-08 17:59:54 -05:00
|
|
|
srv := createTestingApiSrv(t, nil, ac, eval_mocks.NewEvaluatorFactory(&eval_mocks.ConditionEvaluatorMock{}))
|
2022-04-01 19:00:23 -05:00
|
|
|
|
2023-06-08 17:59:54 -05:00
|
|
|
rule := validRule()
|
|
|
|
rule.GrafanaManagedAlert.Data = ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2})
|
2023-06-15 12:33:42 -05:00
|
|
|
rule.GrafanaManagedAlert.Condition = data2.RefID
|
2023-06-08 17:59:54 -05:00
|
|
|
response := srv.RouteTestGrafanaRuleConfig(rc, definitions.PostableExtendedRuleNodeExtended{
|
|
|
|
Rule: rule,
|
|
|
|
NamespaceUID: "test-folder",
|
|
|
|
NamespaceTitle: "test-folder",
|
2022-04-01 19:00:23 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
2022-06-14 03:17:48 -05:00
|
|
|
ac := acMock.New().WithPermissions([]accesscontrol.Permission{
|
2022-04-01 19:00:23 -05:00
|
|
|
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
|
|
|
|
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data2.DatasourceUID)},
|
|
|
|
})
|
|
|
|
|
2022-06-27 11:23:15 -05:00
|
|
|
ds := &fakes.FakeCacheService{DataSources: []*datasources.DataSource{
|
2023-02-02 10:22:43 -06:00
|
|
|
{UID: data1.DatasourceUID},
|
|
|
|
{UID: data2.DatasourceUID},
|
2022-04-01 19:00:23 -05:00
|
|
|
}}
|
|
|
|
|
|
|
|
var result []eval.Result
|
2022-11-02 09:13:39 -05:00
|
|
|
evaluator := &eval_mocks.ConditionEvaluatorMock{}
|
|
|
|
evaluator.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(result, nil)
|
|
|
|
|
|
|
|
evalFactory := eval_mocks.NewEvaluatorFactory(evaluator)
|
2022-04-01 19:00:23 -05:00
|
|
|
|
2023-06-08 17:59:54 -05:00
|
|
|
srv := createTestingApiSrv(t, ds, ac, evalFactory)
|
2022-04-01 19:00:23 -05:00
|
|
|
|
2023-06-08 17:59:54 -05:00
|
|
|
rule := validRule()
|
|
|
|
rule.GrafanaManagedAlert.Data = ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2})
|
2023-06-15 12:33:42 -05:00
|
|
|
rule.GrafanaManagedAlert.Condition = data2.RefID
|
2023-06-08 17:59:54 -05:00
|
|
|
response := srv.RouteTestGrafanaRuleConfig(rc, definitions.PostableExtendedRuleNodeExtended{
|
|
|
|
Rule: rule,
|
|
|
|
NamespaceUID: "test-folder",
|
|
|
|
NamespaceTitle: "test-folder",
|
2022-04-01 19:00:23 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
require.Equal(t, http.StatusOK, response.Status())
|
|
|
|
|
2023-06-08 17:59:54 -05:00
|
|
|
evaluator.AssertCalled(t, "Evaluate", mock.Anything, mock.Anything)
|
2022-04-01 19:00:23 -05:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRouteEvalQueries(t *testing.T) {
|
|
|
|
t.Run("when fine-grained access is enabled", func(t *testing.T) {
|
2023-01-27 01:50:36 -06:00
|
|
|
rc := &contextmodel.ReqContext{
|
2022-04-01 19:00:23 -05:00
|
|
|
Context: &web.Context{
|
|
|
|
Req: &http.Request{},
|
|
|
|
},
|
2022-08-10 04:56:48 -05:00
|
|
|
SignedInUser: &user.SignedInUser{
|
2022-08-11 06:28:55 -05:00
|
|
|
OrgID: 1,
|
2022-04-01 19:00:23 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("should return 401 if user cannot query a data source", func(t *testing.T) {
|
|
|
|
data1 := models.GenerateAlertQuery()
|
|
|
|
data2 := models.GenerateAlertQuery()
|
|
|
|
|
2022-06-14 03:17:48 -05:00
|
|
|
ac := acMock.New().WithPermissions([]accesscontrol.Permission{
|
2022-04-01 19:00:23 -05:00
|
|
|
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
|
|
|
|
})
|
|
|
|
|
|
|
|
srv := &TestingApiSrv{
|
|
|
|
accessControl: ac,
|
|
|
|
}
|
|
|
|
|
|
|
|
response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
|
2023-03-27 10:55:13 -05:00
|
|
|
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2}),
|
2022-04-01 19:00:23 -05:00
|
|
|
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()
|
|
|
|
|
2022-11-02 09:13:39 -05:00
|
|
|
currentTime := time.Now()
|
|
|
|
|
2022-06-14 03:17:48 -05:00
|
|
|
ac := acMock.New().WithPermissions([]accesscontrol.Permission{
|
2022-04-01 19:00:23 -05:00
|
|
|
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
|
|
|
|
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data2.DatasourceUID)},
|
|
|
|
})
|
|
|
|
|
2022-06-27 11:23:15 -05:00
|
|
|
ds := &fakes.FakeCacheService{DataSources: []*datasources.DataSource{
|
2023-02-02 10:22:43 -06:00
|
|
|
{UID: data1.DatasourceUID},
|
|
|
|
{UID: data2.DatasourceUID},
|
2022-04-01 19:00:23 -05:00
|
|
|
}}
|
|
|
|
|
2022-11-02 09:13:39 -05:00
|
|
|
evaluator := &eval_mocks.ConditionEvaluatorMock{}
|
2022-04-01 19:00:23 -05:00
|
|
|
result := &backend.QueryDataResponse{
|
|
|
|
Responses: map[string]backend.DataResponse{
|
|
|
|
"test": {
|
|
|
|
Frames: nil,
|
|
|
|
Error: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2022-11-02 09:13:39 -05:00
|
|
|
evaluator.EXPECT().EvaluateRaw(mock.Anything, mock.Anything).Return(result, nil)
|
2022-04-01 19:00:23 -05:00
|
|
|
|
2023-06-08 17:59:54 -05:00
|
|
|
srv := createTestingApiSrv(t, ds, ac, eval_mocks.NewEvaluatorFactory(evaluator))
|
2022-04-01 19:00:23 -05:00
|
|
|
|
|
|
|
response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
|
2023-03-27 10:55:13 -05:00
|
|
|
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2}),
|
2022-11-02 09:13:39 -05:00
|
|
|
Now: currentTime,
|
2022-04-01 19:00:23 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
require.Equal(t, http.StatusOK, response.Status())
|
|
|
|
|
2022-11-02 09:13:39 -05:00
|
|
|
evaluator.AssertCalled(t, "EvaluateRaw", mock.Anything, currentTime)
|
2022-04-01 19:00:23 -05:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-06-08 17:59:54 -05:00
|
|
|
func createTestingApiSrv(t *testing.T, ds *fakes.FakeCacheService, ac *acMock.Mock, evaluator eval.EvaluatorFactory) *TestingApiSrv {
|
2022-04-01 19:00:23 -05:00
|
|
|
if ac == nil {
|
|
|
|
ac = acMock.New().WithDisabled()
|
|
|
|
}
|
|
|
|
|
|
|
|
return &TestingApiSrv{
|
|
|
|
DatasourceCache: ds,
|
|
|
|
accessControl: ac,
|
|
|
|
evaluator: evaluator,
|
2023-06-08 17:59:54 -05:00
|
|
|
cfg: config(t),
|
2022-04-01 19:00:23 -05:00
|
|
|
}
|
|
|
|
}
|