2021-03-12 03:28:00 +08:00
package api
import (
2022-08-12 21:56:18 +08:00
"math/rand"
"net/http"
2021-03-12 03:28:00 +08:00
"testing"
"github.com/stretchr/testify/assert"
2022-08-12 21:56:18 +08:00
"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"
2022-11-18 16:56:06 +08:00
"github.com/grafana/grafana/pkg/services/auth"
2023-01-27 15:50:36 +08:00
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2025-04-11 03:49:11 +08:00
"github.com/grafana/grafana/pkg/services/datasources"
2023-06-16 01:33:42 +08:00
"github.com/grafana/grafana/pkg/services/ngalert/eval"
2022-08-25 03:33:33 +08:00
models2 "github.com/grafana/grafana/pkg/services/ngalert/models"
2022-08-12 21:56:18 +08:00
"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"
2021-03-12 03:28:00 +08:00
)
func TestToMacaronPath ( t * testing . T ) {
testCases := [ ] struct {
inputPath string
expectedOutputPath string
} {
{
inputPath : "" ,
expectedOutputPath : "" ,
} ,
{
2022-04-20 21:20:17 +08:00
inputPath : "/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}" ,
expectedOutputPath : "/ruler/:DatasourceID/api/v1/rules/:Namespace/:Groupname" ,
2021-03-12 03:28:00 +08:00
} ,
}
for _ , tc := range testCases {
outputPath := toMacaronPath ( tc . inputPath )
assert . Equal ( t , tc . expectedOutputPath , outputPath )
}
}
2022-08-12 21:56:18 +08:00
func TestAlertingProxy_createProxyContext ( t * testing . T ) {
2023-01-27 15:50:36 +08:00
ctx := & contextmodel . ReqContext {
2022-08-12 21:56:18 +08:00
Context : & web . Context {
2022-08-16 18:25:27 +08:00
Req : & http . Request { } ,
2022-08-12 21:56:18 +08:00
} ,
2023-08-26 02:56:02 +08:00
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 ( ) ,
PublicDashboardAccessToken : util . GenerateShortUID ( ) ,
2022-08-12 21:56:18 +08:00
}
t . Run ( "should create a copy of request context" , func ( t * testing . T ) {
for _ , mock := range [ ] * accesscontrolmock . Mock {
2023-09-05 18:04:39 +08:00
accesscontrolmock . New ( ) , accesscontrolmock . New ( ) ,
2022-08-12 21:56:18 +08:00
} {
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 )
2023-04-13 00:30:33 +08:00
require . Equal ( t , ctx . SkipDSCache , newCtx . SkipDSCache )
require . Equal ( t , ctx . SkipQueryCache , newCtx . SkipQueryCache )
2022-08-12 21:56:18 +08:00
require . Equal ( t , ctx . Logger , newCtx . Logger )
require . Equal ( t , ctx . RequestNonce , newCtx . RequestNonce )
2023-08-26 02:56:02 +08:00
require . Equal ( t , ctx . PublicDashboardAccessToken , newCtx . PublicDashboardAccessToken )
2022-08-12 21:56:18 +08:00
}
} )
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 )
2025-04-10 20:42:23 +08:00
require . NotEqual ( t , ctx . Resp , newCtx . Resp )
require . Equal ( t , ctx . Req , newCtx . Req )
2022-08-12 21:56:18 +08:00
require . NotEqual ( t , 123 , resp . Status ( ) )
2025-04-10 20:42:23 +08:00
newCtx . Resp . WriteHeader ( 123 )
2022-08-12 21:56:18 +08:00
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 )
2025-04-10 20:42:23 +08:00
require . Truef ( t , newCtx . HasRole ( org . RoleEditor ) , "user of the proxy request should have at least Editor role but has %s" , newCtx . OrgRole )
2022-08-12 21:56:18 +08:00
} )
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 )
}
} )
} )
}
2022-08-25 03:33:33 +08:00
func Test_containsProvisionedAlerts ( t * testing . T ) {
2024-04-30 09:52:15 +08:00
gen := models2 . RuleGen
2022-08-25 03:33:33 +08:00
t . Run ( "should return true if at least one rule is provisioned" , func ( t * testing . T ) {
2024-04-30 09:52:15 +08:00
rules := gen . GenerateManyRef ( 2 , 6 )
2022-08-25 03:33:33 +08:00
provenance := map [ string ] models2 . Provenance {
2023-01-25 02:07:37 +08:00
rules [ rand . Intn ( len ( rules ) ) ] . UID : [ ] models2 . Provenance { models2 . ProvenanceAPI , models2 . ProvenanceFile } [ rand . Intn ( 2 ) ] ,
2022-08-25 03:33:33 +08:00
}
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 ) {
2024-04-30 09:52:15 +08:00
rules := gen . GenerateManyRef ( 1 , 6 )
2022-08-25 03:33:33 +08:00
provenance := make ( map [ string ] models2 . Provenance )
2023-01-25 02:07:37 +08:00
numProvenanceNone := rand . Intn ( len ( rules ) )
for i := 0 ; i < numProvenanceNone ; i ++ {
2022-08-25 03:33:33 +08:00
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 )
} )
}
2023-06-16 01:33:42 +08:00
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 { }
2025-04-11 03:49:11 +08:00
func TestIsPrometheusCompatible ( t * testing . T ) {
testCases := [ ] struct {
name string
dsType string
expected bool
} {
{
name : "prometheus datasource should be compatible" ,
dsType : datasources . DS_PROMETHEUS ,
expected : true ,
} ,
{
name : "amazon prometheus datasource should be compatible" ,
dsType : datasources . DS_AMAZON_PROMETHEUS ,
expected : true ,
} ,
{
name : "azure prometheus datasource should be compatible" ,
dsType : datasources . DS_AZURE_PROMETHEUS ,
expected : true ,
} ,
{
name : "loki datasource should not be prometheus compatible" ,
dsType : datasources . DS_LOKI ,
expected : false ,
} ,
{
name : "other datasource types should not be compatible" ,
dsType : "some-other-datasource" ,
expected : false ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
result := isPrometheusCompatible ( tc . dsType )
assert . Equal ( t , tc . expected , result )
} )
}
}
func TestIsLotexRulerCompatible ( t * testing . T ) {
testCases := [ ] struct {
name string
dsType string
expected bool
} {
{
name : "prometheus datasource should be compatible" ,
dsType : datasources . DS_PROMETHEUS ,
expected : true ,
} ,
{
name : "amazon prometheus datasource should be compatible" ,
dsType : datasources . DS_AMAZON_PROMETHEUS ,
expected : true ,
} ,
{
name : "azure prometheus datasource should be compatible" ,
dsType : datasources . DS_AZURE_PROMETHEUS ,
expected : true ,
} ,
{
name : "loki datasource should be compatible" ,
dsType : datasources . DS_LOKI ,
expected : true ,
} ,
{
name : "other datasource types should not be compatible" ,
dsType : "some-other-datasource" ,
expected : false ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
result := isLotexRulerCompatible ( tc . dsType )
assert . Equal ( t , tc . expected , result )
} )
}
}