mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 12:14:08 -06:00
b963defa44
* replace condition validation with just structural validation * validate conditions of only new and updated rules * add integration tests for rule update\delete API Co-authored-by: George Robinson <george.robinson@grafana.com>
178 lines
6.0 KiB
Go
178 lines
6.0 KiB
Go
package api
|
|
|
|
import (
|
|
"math/rand"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
|
"github.com/grafana/grafana/pkg/services/auth"
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
|
models2 "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
"github.com/grafana/grafana/pkg/web"
|
|
)
|
|
|
|
func TestToMacaronPath(t *testing.T) {
|
|
testCases := []struct {
|
|
inputPath string
|
|
expectedOutputPath string
|
|
}{
|
|
{
|
|
inputPath: "",
|
|
expectedOutputPath: "",
|
|
},
|
|
{
|
|
inputPath: "/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}",
|
|
expectedOutputPath: "/ruler/:DatasourceID/api/v1/rules/:Namespace/:Groupname",
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
outputPath := toMacaronPath(tc.inputPath)
|
|
assert.Equal(t, tc.expectedOutputPath, outputPath)
|
|
}
|
|
}
|
|
|
|
func TestAlertingProxy_createProxyContext(t *testing.T) {
|
|
ctx := &contextmodel.ReqContext{
|
|
Context: &web.Context{
|
|
Req: &http.Request{},
|
|
},
|
|
SignedInUser: &user.SignedInUser{},
|
|
UserToken: &auth.UserToken{},
|
|
IsSignedIn: rand.Int63()%2 == 1,
|
|
IsRenderCall: rand.Int63()%2 == 1,
|
|
AllowAnonymous: rand.Int63()%2 == 1,
|
|
SkipDSCache: rand.Int63()%2 == 1,
|
|
SkipQueryCache: rand.Int63()%2 == 1,
|
|
Logger: log.New("test"),
|
|
RequestNonce: util.GenerateShortUID(),
|
|
IsPublicDashboardView: rand.Int63()%2 == 1,
|
|
}
|
|
|
|
t.Run("should create a copy of request context", func(t *testing.T) {
|
|
for _, mock := range []*accesscontrolmock.Mock{
|
|
accesscontrolmock.New(), accesscontrolmock.New().WithDisabled(),
|
|
} {
|
|
proxy := AlertingProxy{
|
|
DataProxy: nil,
|
|
ac: mock,
|
|
}
|
|
|
|
req := &http.Request{}
|
|
resp := &response.NormalResponse{}
|
|
|
|
newCtx := proxy.createProxyContext(ctx, req, resp)
|
|
|
|
require.NotEqual(t, ctx, newCtx)
|
|
require.Equal(t, ctx.UserToken, newCtx.UserToken)
|
|
require.Equal(t, ctx.IsSignedIn, newCtx.IsSignedIn)
|
|
require.Equal(t, ctx.IsRenderCall, newCtx.IsRenderCall)
|
|
require.Equal(t, ctx.AllowAnonymous, newCtx.AllowAnonymous)
|
|
require.Equal(t, ctx.SkipDSCache, newCtx.SkipDSCache)
|
|
require.Equal(t, ctx.SkipQueryCache, newCtx.SkipQueryCache)
|
|
require.Equal(t, ctx.Logger, newCtx.Logger)
|
|
require.Equal(t, ctx.RequestNonce, newCtx.RequestNonce)
|
|
require.Equal(t, ctx.IsPublicDashboardView, newCtx.IsPublicDashboardView)
|
|
}
|
|
})
|
|
t.Run("should overwrite response writer", func(t *testing.T) {
|
|
proxy := AlertingProxy{
|
|
DataProxy: nil,
|
|
ac: accesscontrolmock.New(),
|
|
}
|
|
|
|
req := &http.Request{}
|
|
resp := &response.NormalResponse{}
|
|
|
|
newCtx := proxy.createProxyContext(ctx, req, resp)
|
|
|
|
require.NotEqual(t, ctx.Context.Resp, newCtx.Context.Resp)
|
|
require.Equal(t, ctx.Context.Req, newCtx.Context.Req)
|
|
|
|
require.NotEqual(t, 123, resp.Status())
|
|
newCtx.Context.Resp.WriteHeader(123)
|
|
require.Equal(t, 123, resp.Status())
|
|
})
|
|
t.Run("if access control is enabled", func(t *testing.T) {
|
|
t.Run("should elevate permissions to Editor for Viewer", func(t *testing.T) {
|
|
proxy := AlertingProxy{
|
|
DataProxy: nil,
|
|
ac: accesscontrolmock.New(),
|
|
}
|
|
|
|
req := &http.Request{}
|
|
resp := &response.NormalResponse{}
|
|
|
|
viewerCtx := *ctx
|
|
viewerCtx.SignedInUser = &user.SignedInUser{
|
|
OrgRole: org.RoleViewer,
|
|
}
|
|
|
|
newCtx := proxy.createProxyContext(&viewerCtx, req, resp)
|
|
require.NotEqual(t, viewerCtx.SignedInUser, newCtx.SignedInUser)
|
|
require.Truef(t, newCtx.SignedInUser.HasRole(org.RoleEditor), "user of the proxy request should have at least Editor role but has %s", newCtx.SignedInUser.OrgRole)
|
|
})
|
|
t.Run("should not alter user if it is Editor", func(t *testing.T) {
|
|
proxy := AlertingProxy{
|
|
DataProxy: nil,
|
|
ac: accesscontrolmock.New(),
|
|
}
|
|
|
|
req := &http.Request{}
|
|
resp := &response.NormalResponse{}
|
|
|
|
for _, roleType := range []org.RoleType{org.RoleEditor, org.RoleAdmin} {
|
|
roleCtx := *ctx
|
|
roleCtx.SignedInUser = &user.SignedInUser{
|
|
OrgRole: roleType,
|
|
}
|
|
newCtx := proxy.createProxyContext(&roleCtx, req, resp)
|
|
require.Equalf(t, roleCtx.SignedInUser, newCtx.SignedInUser, "user should not be altered if role is %s", roleType)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func Test_containsProvisionedAlerts(t *testing.T) {
|
|
t.Run("should return true if at least one rule is provisioned", func(t *testing.T) {
|
|
_, rules := models2.GenerateUniqueAlertRules(rand.Intn(4)+2, models2.AlertRuleGen())
|
|
provenance := map[string]models2.Provenance{
|
|
rules[rand.Intn(len(rules))].UID: []models2.Provenance{models2.ProvenanceAPI, models2.ProvenanceFile}[rand.Intn(2)],
|
|
}
|
|
require.Truef(t, containsProvisionedAlerts(provenance, rules), "the group of rules is expected to be considered as provisioned but it isn't. Provenances: %v", provenance)
|
|
})
|
|
t.Run("should return false if map does not contain or has ProvenanceNone", func(t *testing.T) {
|
|
_, rules := models2.GenerateUniqueAlertRules(rand.Intn(5)+1, models2.AlertRuleGen())
|
|
provenance := make(map[string]models2.Provenance)
|
|
numProvenanceNone := rand.Intn(len(rules))
|
|
for i := 0; i < numProvenanceNone; i++ {
|
|
provenance[rules[i].UID] = models2.ProvenanceNone
|
|
}
|
|
require.Falsef(t, containsProvisionedAlerts(provenance, rules), "the group of rules is not expected to be provisioned but it is. Provenances: %v", provenance)
|
|
})
|
|
}
|
|
|
|
type recordingConditionValidator struct {
|
|
recorded []models2.Condition
|
|
hook func(c models2.Condition) error
|
|
}
|
|
|
|
func (r *recordingConditionValidator) Validate(_ eval.EvaluationContext, condition models2.Condition) error {
|
|
r.recorded = append(r.recorded, condition)
|
|
if r.hook != nil {
|
|
return r.hook(condition)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var _ ConditionValidator = &recordingConditionValidator{}
|