grafana/pkg/services/ngalert/api/util_test.go
Michael Mandrus 5626461b3c
Caching: Refactor enterprise query caching middleware to a wire service (#65616)
* define initial service and add to wire

* update caching service interface

* add skipQueryCache header handler and update metrics query function to use it

* add caching service as a dependency to query service

* working caching impl

* propagate cache status to frontend in response

* beginning of improvements suggested by Lean - separate caching logic from query logic.

* more changes to simplify query function

* Decided to revert renaming of function

* Remove error status from cache request

* add extra documentation

* Move query caching duration metric to query package

* add a little bit of documentation

* wip: convert resource caching

* Change return type of query service QueryData to a QueryDataResponse with Headers

* update codeowners

* change X-Cache value to const

* use resource caching in endpoint handlers

* write resource headers to response even if it's not a cache hit

* fix panic caused by lack of nil check

* update unit test

* remove NONE header - shouldn't show up in OSS

* Convert everything to use the plugin middleware

* revert a few more things

* clean up unused vars

* start reverting resource caching, start to implement in plugin middleware

* revert more, fix typo

* Update caching interfaces - resource caching now has a separate cache method

* continue wiring up new resource caching conventions - still in progress

* add more safety to implementation

* remove some unused objects

* remove some code that I left in by accident

* add some comments, fix codeowners, fix duplicate registration

* fix source of panic in resource middleware

* Update client decorator test to provide an empty response object

* create tests for caching middleware

* fix unit test

* Update pkg/services/caching/service.go

Co-authored-by: Arati R. <33031346+suntala@users.noreply.github.com>

* improve error message in error log

* quick docs update

* Remove use of mockery. Update return signature to return an explicit hit/miss bool

* create unit test for empty request context

* rename caching metrics to make it clear they pertain to caching

* Update pkg/services/pluginsintegration/clientmiddleware/caching_middleware.go

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>

* Add clarifying comments to cache skip middleware func

* Add comment pointing to the resource cache update call

* fix unit tests (missing dependency)

* try to fix mystery syntax error

* fix a panic

* Caching: Introduce feature toggle to caching service refactor (#66323)

* introduce new feature toggle

* hide calls to new service behind a feature flag

* remove licensing flag from toggle (misunderstood what it was for)

* fix unit tests

* rerun toggle gen

---------

Co-authored-by: Arati R. <33031346+suntala@users.noreply.github.com>
Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2023-04-12 12:30:33 -04:00

182 lines
6.2 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"
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)
}
})
})
t.Run("if access control is disabled", func(t *testing.T) {
t.Run("should not alter user", func(t *testing.T) {
proxy := AlertingProxy{
DataProxy: nil,
ac: accesscontrolmock.New().WithDisabled(),
}
req := &http.Request{}
resp := &response.NormalResponse{}
for _, roleType := range []org.RoleType{org.RoleViewer, 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 access control is disabled and 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)
})
}