2022-02-23 10:30:04 -06:00
package api
import (
"context"
2022-04-11 16:37:44 -05:00
"encoding/json"
2022-02-23 10:30:04 -06:00
"errors"
2022-06-22 09:52:46 -05:00
"fmt"
2022-02-23 10:30:04 -06:00
"math/rand"
2022-03-25 11:39:24 -05:00
"net/http"
2022-06-17 12:55:31 -05:00
"net/url"
2022-02-23 10:30:04 -06:00
"testing"
"time"
2022-04-11 16:37:44 -05:00
"github.com/stretchr/testify/assert"
2022-03-25 11:39:24 -05:00
"github.com/stretchr/testify/mock"
2022-02-23 10:30:04 -06:00
"github.com/stretchr/testify/require"
2022-03-25 11:39:24 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2022-02-23 10:30:04 -06:00
models2 "github.com/grafana/grafana/pkg/models"
2022-03-25 11:39:24 -05:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/datasources"
2022-04-11 16:37:44 -05:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
2022-02-23 10:30:04 -06:00
"github.com/grafana/grafana/pkg/services/ngalert/models"
2022-04-28 14:27:34 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
2022-03-25 11:39:24 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
2022-02-23 10:30:04 -06:00
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/util"
2022-03-25 11:39:24 -05:00
"github.com/grafana/grafana/pkg/web"
2022-02-23 10:30:04 -06:00
)
func TestCalculateChanges ( t * testing . T ) {
orgId := rand . Int63 ( )
t . Run ( "detects alerts that need to be added" , func ( t * testing . T ) {
fakeStore := store . NewFakeRuleStore ( t )
2022-05-16 14:45:45 -05:00
groupKey := models . GenerateGroupKey ( orgId )
2022-02-23 10:30:04 -06:00
submitted := models . GenerateAlertRules ( rand . Intn ( 5 ) + 1 , models . AlertRuleGen ( withOrgID ( orgId ) , simulateSubmitted , withoutUID ) )
2022-05-16 14:45:45 -05:00
changes , err := calculateChanges ( context . Background ( ) , fakeStore , groupKey , submitted )
2022-02-23 10:30:04 -06:00
require . NoError ( t , err )
2022-03-04 15:16:33 -06:00
require . Len ( t , changes . New , len ( submitted ) )
2022-02-23 10:30:04 -06:00
require . Empty ( t , changes . Delete )
2022-03-04 15:16:33 -06:00
require . Empty ( t , changes . Update )
2022-02-23 10:30:04 -06:00
outerloop :
for _ , expected := range submitted {
2022-03-04 15:16:33 -06:00
for _ , rule := range changes . New {
if len ( expected . Diff ( rule ) ) == 0 {
2022-02-23 10:30:04 -06:00
continue outerloop
}
}
require . Fail ( t , "changes did not contain rule that was submitted" )
}
} )
t . Run ( "detects alerts that need to be deleted" , func ( t * testing . T ) {
2022-05-16 14:45:45 -05:00
groupKey := models . GenerateGroupKey ( orgId )
inDatabaseMap , inDatabase := models . GenerateUniqueAlertRules ( rand . Intn ( 5 ) + 1 , models . AlertRuleGen ( withGroupKey ( groupKey ) ) )
2022-02-23 10:30:04 -06:00
fakeStore := store . NewFakeRuleStore ( t )
fakeStore . PutRule ( context . Background ( ) , inDatabase ... )
2022-05-16 14:45:45 -05:00
changes , err := calculateChanges ( context . Background ( ) , fakeStore , groupKey , make ( [ ] * models . AlertRule , 0 ) )
2022-02-23 10:30:04 -06:00
require . NoError ( t , err )
2022-06-01 09:23:54 -05:00
require . Equal ( t , groupKey , changes . GroupKey )
2022-03-04 15:16:33 -06:00
require . Empty ( t , changes . New )
require . Empty ( t , changes . Update )
2022-02-23 10:30:04 -06:00
require . Len ( t , changes . Delete , len ( inDatabaseMap ) )
for _ , toDelete := range changes . Delete {
require . Contains ( t , inDatabaseMap , toDelete . UID )
db := inDatabaseMap [ toDelete . UID ]
require . Equal ( t , db , toDelete )
}
2022-06-01 09:23:54 -05:00
require . Contains ( t , changes . AffectedGroups , groupKey )
2022-06-22 09:52:46 -05:00
require . Equal ( t , models . RulesGroup ( inDatabase ) , changes . AffectedGroups [ groupKey ] )
2022-02-23 10:30:04 -06:00
} )
t . Run ( "should detect alerts that needs to be updated" , func ( t * testing . T ) {
2022-05-16 14:45:45 -05:00
groupKey := models . GenerateGroupKey ( orgId )
inDatabaseMap , inDatabase := models . GenerateUniqueAlertRules ( rand . Intn ( 5 ) + 1 , models . AlertRuleGen ( withGroupKey ( groupKey ) ) )
submittedMap , submitted := models . GenerateUniqueAlertRules ( len ( inDatabase ) , models . AlertRuleGen ( simulateSubmitted , withGroupKey ( groupKey ) , withUIDs ( inDatabaseMap ) ) )
2022-02-23 10:30:04 -06:00
fakeStore := store . NewFakeRuleStore ( t )
fakeStore . PutRule ( context . Background ( ) , inDatabase ... )
2022-05-16 14:45:45 -05:00
changes , err := calculateChanges ( context . Background ( ) , fakeStore , groupKey , submitted )
2022-02-23 10:30:04 -06:00
require . NoError ( t , err )
2022-06-01 09:23:54 -05:00
require . Equal ( t , groupKey , changes . GroupKey )
2022-03-04 15:16:33 -06:00
require . Len ( t , changes . Update , len ( inDatabase ) )
for _ , upsert := range changes . Update {
2022-02-23 10:30:04 -06:00
require . NotNil ( t , upsert . Existing )
require . Equal ( t , upsert . Existing . UID , upsert . New . UID )
require . Equal ( t , inDatabaseMap [ upsert . Existing . UID ] , upsert . Existing )
2022-03-04 15:16:33 -06:00
require . Equal ( t , submittedMap [ upsert . Existing . UID ] , upsert . New )
require . NotEmpty ( t , upsert . Diff )
}
require . Empty ( t , changes . Delete )
require . Empty ( t , changes . New )
2022-06-01 09:23:54 -05:00
require . Contains ( t , changes . AffectedGroups , groupKey )
2022-06-22 09:52:46 -05:00
require . Equal ( t , models . RulesGroup ( inDatabase ) , changes . AffectedGroups [ groupKey ] )
2022-03-04 15:16:33 -06:00
} )
t . Run ( "should include only if there are changes ignoring specific fields" , func ( t * testing . T ) {
2022-05-16 14:45:45 -05:00
groupKey := models . GenerateGroupKey ( orgId )
_ , inDatabase := models . GenerateUniqueAlertRules ( rand . Intn ( 5 ) + 1 , models . AlertRuleGen ( withGroupKey ( groupKey ) ) )
2022-03-04 15:16:33 -06:00
submitted := make ( [ ] * models . AlertRule , 0 , len ( inDatabase ) )
for _ , rule := range inDatabase {
r := models . CopyRule ( rule )
// Ignore difference in the following fields as submitted models do not have them set
r . ID = rand . Int63 ( )
r . Version = rand . Int63 ( )
r . Updated = r . Updated . Add ( 1 * time . Minute )
submitted = append ( submitted , r )
2022-02-23 10:30:04 -06:00
}
2022-03-04 15:16:33 -06:00
fakeStore := store . NewFakeRuleStore ( t )
fakeStore . PutRule ( context . Background ( ) , inDatabase ... )
2022-05-16 14:45:45 -05:00
changes , err := calculateChanges ( context . Background ( ) , fakeStore , groupKey , submitted )
2022-03-04 15:16:33 -06:00
require . NoError ( t , err )
require . Empty ( t , changes . Update )
require . Empty ( t , changes . Delete )
require . Empty ( t , changes . New )
2022-02-23 10:30:04 -06:00
} )
t . Run ( "should patch rule with UID specified by existing rule" , func ( t * testing . T ) {
testCases := [ ] struct {
name string
mutator func ( r * models . AlertRule )
} {
{
name : "title is empty" ,
mutator : func ( r * models . AlertRule ) {
r . Title = ""
} ,
} ,
{
name : "condition and data are empty" ,
mutator : func ( r * models . AlertRule ) {
r . Condition = ""
r . Data = nil
} ,
} ,
{
name : "ExecErrState is empty" ,
mutator : func ( r * models . AlertRule ) {
r . ExecErrState = ""
} ,
} ,
{
name : "NoDataState is empty" ,
mutator : func ( r * models . AlertRule ) {
r . NoDataState = ""
} ,
} ,
{
name : "For is 0" ,
mutator : func ( r * models . AlertRule ) {
r . For = 0
} ,
} ,
}
dbRule := models . AlertRuleGen ( withOrgID ( orgId ) ) ( )
fakeStore := store . NewFakeRuleStore ( t )
fakeStore . PutRule ( context . Background ( ) , dbRule )
2022-05-16 14:45:45 -05:00
groupKey := models . GenerateGroupKey ( orgId )
2022-02-23 10:30:04 -06:00
for _ , testCase := range testCases {
t . Run ( testCase . name , func ( t * testing . T ) {
expected := models . AlertRuleGen ( simulateSubmitted , testCase . mutator ) ( )
expected . UID = dbRule . UID
submitted := * expected
2022-05-16 14:45:45 -05:00
changes , err := calculateChanges ( context . Background ( ) , fakeStore , groupKey , [ ] * models . AlertRule { & submitted } )
2022-02-23 10:30:04 -06:00
require . NoError ( t , err )
2022-03-04 15:16:33 -06:00
require . Len ( t , changes . Update , 1 )
ch := changes . Update [ 0 ]
2022-02-23 10:30:04 -06:00
require . Equal ( t , ch . Existing , dbRule )
fixed := * expected
models . PatchPartialAlertRule ( dbRule , & fixed )
2022-03-04 15:16:33 -06:00
require . Equal ( t , fixed , * ch . New )
2022-02-23 10:30:04 -06:00
} )
}
} )
t . Run ( "should be able to find alerts by UID in other group/namespace" , func ( t * testing . T ) {
2022-06-01 09:23:54 -05:00
sourceGroupKey := models . GenerateGroupKey ( orgId )
inDatabaseMap , inDatabase := models . GenerateUniqueAlertRules ( rand . Intn ( 10 ) + 10 , models . AlertRuleGen ( withGroupKey ( sourceGroupKey ) ) )
2022-02-23 10:30:04 -06:00
fakeStore := store . NewFakeRuleStore ( t )
fakeStore . PutRule ( context . Background ( ) , inDatabase ... )
namespace := randFolder ( )
groupName := util . GenerateShortUID ( )
2022-05-16 14:45:45 -05:00
groupKey := models . AlertRuleGroupKey {
OrgID : orgId ,
NamespaceUID : namespace . Uid ,
RuleGroup : groupName ,
}
submittedMap , submitted := models . GenerateUniqueAlertRules ( rand . Intn ( len ( inDatabase ) - 5 ) + 5 , models . AlertRuleGen ( simulateSubmitted , withGroupKey ( groupKey ) , withUIDs ( inDatabaseMap ) ) )
changes , err := calculateChanges ( context . Background ( ) , fakeStore , groupKey , submitted )
2022-02-23 10:30:04 -06:00
require . NoError ( t , err )
2022-06-01 09:23:54 -05:00
require . Equal ( t , groupKey , changes . GroupKey )
2022-03-04 15:16:33 -06:00
require . Empty ( t , changes . Delete )
require . Empty ( t , changes . New )
require . Len ( t , changes . Update , len ( submitted ) )
for _ , update := range changes . Update {
require . NotNil ( t , update . Existing )
require . Equal ( t , update . Existing . UID , update . New . UID )
require . Equal ( t , inDatabaseMap [ update . Existing . UID ] , update . Existing )
require . Equal ( t , submittedMap [ update . Existing . UID ] , update . New )
require . NotEmpty ( t , update . Diff )
2022-02-23 10:30:04 -06:00
}
2022-06-01 09:23:54 -05:00
require . Contains ( t , changes . AffectedGroups , sourceGroupKey )
require . NotContains ( t , changes . AffectedGroups , groupKey ) // because there is no such group in database yet
require . Len ( t , changes . AffectedGroups [ sourceGroupKey ] , len ( inDatabase ) )
2022-02-23 10:30:04 -06:00
} )
t . Run ( "should fail when submitted rule has UID that does not exist in db" , func ( t * testing . T ) {
fakeStore := store . NewFakeRuleStore ( t )
2022-05-16 14:45:45 -05:00
groupKey := models . GenerateGroupKey ( orgId )
2022-02-23 10:30:04 -06:00
submitted := models . AlertRuleGen ( withOrgID ( orgId ) , simulateSubmitted ) ( )
require . NotEqual ( t , "" , submitted . UID )
2022-05-16 14:45:45 -05:00
_ , err := calculateChanges ( context . Background ( ) , fakeStore , groupKey , [ ] * models . AlertRule { submitted } )
2022-02-23 10:30:04 -06:00
require . Error ( t , err )
} )
t . Run ( "should fail if cannot fetch current rules in the group" , func ( t * testing . T ) {
fakeStore := store . NewFakeRuleStore ( t )
expectedErr := errors . New ( "TEST ERROR" )
fakeStore . Hook = func ( cmd interface { } ) error {
switch cmd . ( type ) {
2022-04-25 05:42:42 -05:00
case models . ListAlertRulesQuery :
2022-02-23 10:30:04 -06:00
return expectedErr
}
return nil
}
2022-05-16 14:45:45 -05:00
groupKey := models . GenerateGroupKey ( orgId )
2022-02-23 10:30:04 -06:00
submitted := models . AlertRuleGen ( withOrgID ( orgId ) , simulateSubmitted , withoutUID ) ( )
2022-05-16 14:45:45 -05:00
_ , err := calculateChanges ( context . Background ( ) , fakeStore , groupKey , [ ] * models . AlertRule { submitted } )
2022-02-23 10:30:04 -06:00
require . ErrorIs ( t , err , expectedErr )
} )
t . Run ( "should fail if cannot fetch rule by UID" , func ( t * testing . T ) {
fakeStore := store . NewFakeRuleStore ( t )
expectedErr := errors . New ( "TEST ERROR" )
fakeStore . Hook = func ( cmd interface { } ) error {
switch cmd . ( type ) {
2022-06-01 09:23:54 -05:00
case models . GetAlertRulesGroupByRuleUIDQuery :
2022-02-23 10:30:04 -06:00
return expectedErr
}
return nil
}
2022-05-16 14:45:45 -05:00
groupKey := models . GenerateGroupKey ( orgId )
2022-02-23 10:30:04 -06:00
submitted := models . AlertRuleGen ( withOrgID ( orgId ) , simulateSubmitted ) ( )
2022-05-16 14:45:45 -05:00
_ , err := calculateChanges ( context . Background ( ) , fakeStore , groupKey , [ ] * models . AlertRule { submitted } )
2022-06-01 09:23:54 -05:00
require . ErrorIs ( t , err , expectedErr )
2022-02-23 10:30:04 -06:00
} )
}
2022-03-25 11:39:24 -05:00
func TestRouteDeleteAlertRules ( t * testing . T ) {
getRecordedCommand := func ( ruleStore * store . FakeRuleStore ) [ ] store . GenericRecordedQuery {
results := ruleStore . GetRecordedCommands ( func ( cmd interface { } ) ( interface { } , bool ) {
c , ok := cmd . ( store . GenericRecordedQuery )
if ! ok || c . Name != "DeleteAlertRulesByUID" {
return nil , false
}
return c , ok
} )
var result [ ] store . GenericRecordedQuery
for _ , cmd := range results {
result = append ( result , cmd . ( store . GenericRecordedQuery ) )
}
return result
}
assertRulesDeleted := func ( t * testing . T , expectedRules [ ] * models . AlertRule , ruleStore * store . FakeRuleStore , scheduler * schedule . FakeScheduleService ) {
deleteCommands := getRecordedCommand ( ruleStore )
require . Len ( t , deleteCommands , 1 )
cmd := deleteCommands [ 0 ]
actualUIDs := cmd . Params [ 1 ] . ( [ ] string )
require . Len ( t , actualUIDs , len ( expectedRules ) )
for _ , rule := range expectedRules {
require . Containsf ( t , actualUIDs , rule . UID , "Rule %s was expected to be deleted but it wasn't" , rule . UID )
}
require . Len ( t , scheduler . Calls , len ( expectedRules ) )
for _ , call := range scheduler . Calls {
require . Equal ( t , "DeleteAlertRule" , call . Method )
key , ok := call . Arguments . Get ( 0 ) . ( models . AlertRuleKey )
require . Truef ( t , ok , "Expected AlertRuleKey but got something else" )
found := false
for _ , rule := range expectedRules {
if rule . GetKey ( ) == key {
found = true
break
}
}
require . Truef ( t , found , "Key %v was not expected to be submitted to scheduler" , key )
}
}
t . Run ( "when fine-grained access is disabled" , func ( t * testing . T ) {
t . Run ( "viewer should not be authorized" , func ( t * testing . T ) {
ruleStore := store . NewFakeRuleStore ( t )
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) ) ... )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) ) ) ... )
scheduler := & schedule . FakeScheduleService { }
scheduler . On ( "DeleteAlertRule" , mock . Anything ) . Panic ( "should not be called" )
ac := acMock . New ( ) . WithDisabled ( )
2022-06-23 15:13:39 -05:00
request := createRequestContext ( orgID , models2 . ROLE_VIEWER , nil )
response := createService ( ac , ruleStore , scheduler ) . RouteDeleteAlertRules ( request , folder . Title , "" )
2022-03-25 11:39:24 -05:00
require . Equalf ( t , 401 , response . Status ( ) , "Expected 403 but got %d: %v" , response . Status ( ) , string ( response . Body ( ) ) )
scheduler . AssertNotCalled ( t , "DeleteAlertRule" )
require . Empty ( t , getRecordedCommand ( ruleStore ) )
} )
t . Run ( "editor should be able to delete all rules in folder" , func ( t * testing . T ) {
ruleStore := store . NewFakeRuleStore ( t )
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
rulesInFolder := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) )
ruleStore . PutRule ( context . Background ( ) , rulesInFolder ... )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) ) ) ... )
scheduler := & schedule . FakeScheduleService { }
scheduler . On ( "DeleteAlertRule" , mock . Anything )
ac := acMock . New ( ) . WithDisabled ( )
2022-06-23 15:13:39 -05:00
request := createRequestContext ( orgID , models2 . ROLE_EDITOR , nil )
response := createService ( ac , ruleStore , scheduler ) . RouteDeleteAlertRules ( request , folder . Title , "" )
2022-03-25 11:39:24 -05:00
require . Equalf ( t , 202 , response . Status ( ) , "Expected 202 but got %d: %v" , response . Status ( ) , string ( response . Body ( ) ) )
assertRulesDeleted ( t , rulesInFolder , ruleStore , scheduler )
} )
t . Run ( "editor should be able to delete rules in a group in a folder" , func ( t * testing . T ) {
ruleStore := store . NewFakeRuleStore ( t )
orgID := rand . Int63 ( )
groupName := util . GenerateShortUID ( )
folder := randFolder ( )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
rulesInFolderInGroup := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) , withGroup ( groupName ) ) )
ruleStore . PutRule ( context . Background ( ) , rulesInFolderInGroup ... )
// rules in different groups but in the same namespace
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) ) ... )
// rules in the same group but different folder
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withGroup ( groupName ) ) ) ... )
scheduler := & schedule . FakeScheduleService { }
scheduler . On ( "DeleteAlertRule" , mock . Anything )
ac := acMock . New ( ) . WithDisabled ( )
2022-06-23 15:13:39 -05:00
request := createRequestContext ( orgID , models2 . ROLE_EDITOR , nil )
response := createService ( ac , ruleStore , scheduler ) . RouteDeleteAlertRules ( request , folder . Title , groupName )
2022-03-25 11:39:24 -05:00
require . Equalf ( t , 202 , response . Status ( ) , "Expected 202 but got %d: %v" , response . Status ( ) , string ( response . Body ( ) ) )
assertRulesDeleted ( t , rulesInFolderInGroup , ruleStore , scheduler )
} )
2022-05-06 13:55:27 -05:00
t . Run ( "editor shouldn't be able to delete provisioned rules" , func ( t * testing . T ) {
ruleStore := store . NewFakeRuleStore ( t )
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
rulesInFolder := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) )
ruleStore . PutRule ( context . Background ( ) , rulesInFolder ... )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) ) ) ... )
scheduler := & schedule . FakeScheduleService { }
scheduler . On ( "DeleteAlertRule" , mock . Anything )
ac := acMock . New ( ) . WithDisabled ( )
svc := createService ( ac , ruleStore , scheduler )
err := svc . provenanceStore . SetProvenance ( context . Background ( ) , rulesInFolder [ 0 ] , orgID , models . ProvenanceAPI )
require . NoError ( t , err )
2022-06-23 15:13:39 -05:00
request := createRequestContext ( orgID , models2 . ROLE_EDITOR , nil )
response := svc . RouteDeleteAlertRules ( request , folder . Title , "" )
2022-05-06 13:55:27 -05:00
require . Equalf ( t , 202 , response . Status ( ) , "Expected 202 but got %d: %v" , response . Status ( ) , string ( response . Body ( ) ) )
assertRulesDeleted ( t , rulesInFolder [ 1 : ] , ruleStore , scheduler )
} )
2022-03-25 11:39:24 -05:00
} )
t . Run ( "when fine-grained access is enabled" , func ( t * testing . T ) {
t . Run ( "and user does not have access to any of data sources used by alert rules" , func ( t * testing . T ) {
ruleStore := store . NewFakeRuleStore ( t )
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) ) ... )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) ) ) ... )
scheduler := & schedule . FakeScheduleService { }
scheduler . On ( "DeleteAlertRule" , mock . Anything ) . Panic ( "should not be called" )
ac := acMock . New ( )
2022-06-23 15:13:39 -05:00
request := createRequestContext ( orgID , "None" , nil )
response := createService ( ac , ruleStore , scheduler ) . RouteDeleteAlertRules ( request , folder . Title , "" )
2022-03-25 11:39:24 -05:00
require . Equalf ( t , 401 , response . Status ( ) , "Expected 403 but got %d: %v" , response . Status ( ) , string ( response . Body ( ) ) )
scheduler . AssertNotCalled ( t , "DeleteAlertRule" )
require . Empty ( t , getRecordedCommand ( ruleStore ) )
} )
t . Run ( "and user has access to all alert rules" , func ( t * testing . T ) {
t . Run ( "should delete all rules" , func ( t * testing . T ) {
ruleStore := store . NewFakeRuleStore ( t )
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
rulesInFolder := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) )
ruleStore . PutRule ( context . Background ( ) , rulesInFolder ... )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) ) ) ... )
scheduler := & schedule . FakeScheduleService { }
scheduler . On ( "DeleteAlertRule" , mock . Anything )
2022-04-11 16:37:44 -05:00
ac := acMock . New ( ) . WithPermissions ( createPermissionsForRules ( rulesInFolder ) )
2022-06-23 15:13:39 -05:00
request := createRequestContext ( orgID , "None" , nil )
2022-03-25 11:39:24 -05:00
2022-06-23 15:13:39 -05:00
response := createService ( ac , ruleStore , scheduler ) . RouteDeleteAlertRules ( request , folder . Title , "" )
2022-03-25 11:39:24 -05:00
require . Equalf ( t , 202 , response . Status ( ) , "Expected 202 but got %d: %v" , response . Status ( ) , string ( response . Body ( ) ) )
assertRulesDeleted ( t , rulesInFolder , ruleStore , scheduler )
} )
2022-05-06 13:55:27 -05:00
t . Run ( "shouldn't be able to delete provisioned rules" , func ( t * testing . T ) {
ruleStore := store . NewFakeRuleStore ( t )
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
rulesInFolder := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) )
ruleStore . PutRule ( context . Background ( ) , rulesInFolder ... )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) ) ) ... )
scheduler := & schedule . FakeScheduleService { }
scheduler . On ( "DeleteAlertRule" , mock . Anything )
ac := acMock . New ( ) . WithPermissions ( createPermissionsForRules ( rulesInFolder ) )
svc := createService ( ac , ruleStore , scheduler )
err := svc . provenanceStore . SetProvenance ( context . Background ( ) , rulesInFolder [ 0 ] , orgID , models . ProvenanceAPI )
require . NoError ( t , err )
2022-06-23 15:13:39 -05:00
request := createRequestContext ( orgID , "None" , nil )
2022-05-06 13:55:27 -05:00
2022-06-23 15:13:39 -05:00
response := svc . RouteDeleteAlertRules ( request , folder . Title , "" )
2022-05-06 13:55:27 -05:00
require . Equalf ( t , 202 , response . Status ( ) , "Expected 202 but got %d: %v" , response . Status ( ) , string ( response . Body ( ) ) )
assertRulesDeleted ( t , rulesInFolder [ 1 : ] , ruleStore , scheduler )
} )
2022-03-25 11:39:24 -05:00
} )
t . Run ( "and user has access to data sources of some of alert rules" , func ( t * testing . T ) {
t . Run ( "should delete only those that are accessible in folder" , func ( t * testing . T ) {
ruleStore := store . NewFakeRuleStore ( t )
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
authorizedRulesInFolder := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) )
ruleStore . PutRule ( context . Background ( ) , authorizedRulesInFolder ... )
// more rules in the same namespace but user does not have access to them
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) ) ... )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) ) ) ... )
scheduler := & schedule . FakeScheduleService { }
scheduler . On ( "DeleteAlertRule" , mock . Anything )
2022-04-11 16:37:44 -05:00
ac := acMock . New ( ) . WithPermissions ( createPermissionsForRules ( authorizedRulesInFolder ) )
2022-06-23 15:13:39 -05:00
request := createRequestContext ( orgID , "None" , nil )
2022-03-25 11:39:24 -05:00
2022-06-23 15:13:39 -05:00
response := createService ( ac , ruleStore , scheduler ) . RouteDeleteAlertRules ( request , folder . Title , "" )
2022-03-25 11:39:24 -05:00
require . Equalf ( t , 202 , response . Status ( ) , "Expected 202 but got %d: %v" , response . Status ( ) , string ( response . Body ( ) ) )
assertRulesDeleted ( t , authorizedRulesInFolder , ruleStore , scheduler )
} )
t . Run ( "should delete only rules in a group that are authorized" , func ( t * testing . T ) {
ruleStore := store . NewFakeRuleStore ( t )
orgID := rand . Int63 ( )
groupName := util . GenerateShortUID ( )
folder := randFolder ( )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
authorizedRulesInGroup := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) , withGroup ( groupName ) ) )
ruleStore . PutRule ( context . Background ( ) , authorizedRulesInGroup ... )
// more rules in the same group but user is not authorized to access them
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) , withGroup ( groupName ) ) ) ... )
// rules in different groups but in the same namespace
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) ) ... )
// rules in the same group but different folder
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withGroup ( groupName ) ) ) ... )
scheduler := & schedule . FakeScheduleService { }
scheduler . On ( "DeleteAlertRule" , mock . Anything )
2022-04-11 16:37:44 -05:00
ac := acMock . New ( ) . WithPermissions ( createPermissionsForRules ( authorizedRulesInGroup ) )
2022-06-23 15:13:39 -05:00
request := createRequestContext ( orgID , "None" , nil )
response := createService ( ac , ruleStore , scheduler ) . RouteDeleteAlertRules ( request , folder . Title , groupName )
2022-03-25 11:39:24 -05:00
require . Equalf ( t , 202 , response . Status ( ) , "Expected 202 but got %d: %v" , response . Status ( ) , string ( response . Body ( ) ) )
assertRulesDeleted ( t , authorizedRulesInGroup , ruleStore , scheduler )
} )
} )
} )
}
2022-04-11 16:37:44 -05:00
func TestRouteGetNamespaceRulesConfig ( t * testing . T ) {
t . Run ( "fine-grained access is enabled" , func ( t * testing . T ) {
t . Run ( "should return rules for which user has access to data source" , func ( t * testing . T ) {
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore := store . NewFakeRuleStore ( t )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
expectedRules := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) )
ruleStore . PutRule ( context . Background ( ) , expectedRules ... )
ruleStore . PutRule ( context . Background ( ) , models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) ) ... )
ac := acMock . New ( ) . WithPermissions ( createPermissionsForRules ( expectedRules ) )
2022-06-23 15:13:39 -05:00
req := createRequestContext ( orgID , "" , nil )
response := createService ( ac , ruleStore , nil ) . RouteGetNamespaceRulesConfig ( req , folder . Title )
2022-04-11 16:37:44 -05:00
require . Equal ( t , http . StatusAccepted , response . Status ( ) )
result := & apimodels . NamespaceConfigResponse { }
require . NoError ( t , json . Unmarshal ( response . Body ( ) , result ) )
require . NotNil ( t , result )
for namespace , groups := range * result {
require . Equal ( t , folder . Title , namespace )
for _ , group := range groups {
grouploop :
for _ , actualRule := range group . Rules {
for i , expected := range expectedRules {
if actualRule . GrafanaManagedAlert . UID == expected . UID {
expectedRules = append ( expectedRules [ : i ] , expectedRules [ i + 1 : ] ... )
continue grouploop
}
}
assert . Failf ( t , "rule in a group was not found in expected" , "rule %s group %s" , actualRule . GrafanaManagedAlert . Title , group . Name )
}
}
}
assert . Emptyf ( t , expectedRules , "not all expected rules were returned" )
} )
} )
t . Run ( "fine-grained access is disabled" , func ( t * testing . T ) {
t . Run ( "should return all rules from folder" , func ( t * testing . T ) {
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore := store . NewFakeRuleStore ( t )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
expectedRules := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) )
ruleStore . PutRule ( context . Background ( ) , expectedRules ... )
ac := acMock . New ( ) . WithDisabled ( )
2022-06-23 15:13:39 -05:00
req := createRequestContext ( orgID , models2 . ROLE_VIEWER , nil )
response := createService ( ac , ruleStore , nil ) . RouteGetNamespaceRulesConfig ( req , folder . Title )
2022-04-11 16:37:44 -05:00
require . Equal ( t , http . StatusAccepted , response . Status ( ) )
result := & apimodels . NamespaceConfigResponse { }
require . NoError ( t , json . Unmarshal ( response . Body ( ) , result ) )
require . NotNil ( t , result )
for namespace , groups := range * result {
require . Equal ( t , folder . Title , namespace )
for _ , group := range groups {
grouploop :
for _ , actualRule := range group . Rules {
for i , expected := range expectedRules {
if actualRule . GrafanaManagedAlert . UID == expected . UID {
expectedRules = append ( expectedRules [ : i ] , expectedRules [ i + 1 : ] ... )
continue grouploop
}
}
assert . Failf ( t , "rule in a group was not found in expected" , "rule %s group %s" , actualRule . GrafanaManagedAlert . Title , group . Name )
}
}
}
assert . Emptyf ( t , expectedRules , "not all expected rules were returned" )
} )
} )
2022-04-28 14:27:34 -05:00
t . Run ( "should return the provenance of the alert rules" , func ( t * testing . T ) {
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore := store . NewFakeRuleStore ( t )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
expectedRules := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withOrgID ( orgID ) , withNamespace ( folder ) ) )
ruleStore . PutRule ( context . Background ( ) , expectedRules ... )
ac := acMock . New ( ) . WithDisabled ( )
svc := createService ( ac , ruleStore , nil )
// add provenance to the first generated rule
rule := & models . AlertRule {
UID : expectedRules [ 0 ] . UID ,
}
err := svc . provenanceStore . SetProvenance ( context . Background ( ) , rule , orgID , models . ProvenanceAPI )
require . NoError ( t , err )
2022-06-23 15:13:39 -05:00
req := createRequestContext ( orgID , models2 . ROLE_VIEWER , nil )
response := svc . RouteGetNamespaceRulesConfig ( req , folder . Title )
2022-04-28 14:27:34 -05:00
require . Equal ( t , http . StatusAccepted , response . Status ( ) )
result := & apimodels . NamespaceConfigResponse { }
require . NoError ( t , json . Unmarshal ( response . Body ( ) , result ) )
require . NotNil ( t , result )
found := false
for namespace , groups := range * result {
require . Equal ( t , folder . Title , namespace )
for _ , group := range groups {
for _ , actualRule := range group . Rules {
if actualRule . GrafanaManagedAlert . UID == expectedRules [ 0 ] . UID {
require . Equal ( t , models . ProvenanceAPI , actualRule . GrafanaManagedAlert . Provenance )
found = true
} else {
require . Equal ( t , models . ProvenanceNone , actualRule . GrafanaManagedAlert . Provenance )
}
}
}
}
require . True ( t , found )
} )
2022-06-22 09:52:46 -05:00
t . Run ( "should enforce order of rules in the group" , func ( t * testing . T ) {
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore := store . NewFakeRuleStore ( t )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
groupKey := models . GenerateGroupKey ( orgID )
groupKey . NamespaceUID = folder . Uid
expectedRules := models . GenerateAlertRules ( rand . Intn ( 5 ) + 5 , models . AlertRuleGen ( withGroupKey ( groupKey ) , models . WithUniqueGroupIndex ( ) ) )
ruleStore . PutRule ( context . Background ( ) , expectedRules ... )
ac := acMock . New ( ) . WithDisabled ( )
2022-06-23 15:13:39 -05:00
response := createService ( ac , ruleStore , nil ) . RouteGetNamespaceRulesConfig ( createRequestContext ( orgID , models2 . ROLE_VIEWER , nil ) , folder . Title )
2022-06-22 09:52:46 -05:00
require . Equal ( t , http . StatusAccepted , response . Status ( ) )
result := & apimodels . NamespaceConfigResponse { }
require . NoError ( t , json . Unmarshal ( response . Body ( ) , result ) )
require . NotNil ( t , result )
models . RulesGroup ( expectedRules ) . SortByGroupIndex ( )
require . Contains ( t , * result , folder . Title )
groups := ( * result ) [ folder . Title ]
require . Len ( t , groups , 1 )
group := groups [ 0 ]
require . Equal ( t , groupKey . RuleGroup , group . Name )
for i , actual := range groups [ 0 ] . Rules {
expected := expectedRules [ i ]
if actual . GrafanaManagedAlert . UID != expected . UID {
var actualUIDs [ ] string
var expectedUIDs [ ] string
for _ , rule := range group . Rules {
actualUIDs = append ( actualUIDs , rule . GrafanaManagedAlert . UID )
}
for _ , rule := range expectedRules {
expectedUIDs = append ( expectedUIDs , rule . UID )
}
require . Fail ( t , fmt . Sprintf ( "rules are not sorted by group index. Expected: %v. Actual: %v" , expectedUIDs , actualUIDs ) )
}
}
} )
2022-04-11 16:37:44 -05:00
}
2022-06-17 12:55:31 -05:00
func TestRouteGetRulesConfig ( t * testing . T ) {
t . Run ( "fine-grained access is enabled" , func ( t * testing . T ) {
t . Run ( "should check access to data source" , func ( t * testing . T ) {
orgID := rand . Int63 ( )
ruleStore := store . NewFakeRuleStore ( t )
folder1 := randFolder ( )
folder2 := randFolder ( )
ruleStore . Folders [ orgID ] = [ ] * models2 . Folder { folder1 , folder2 }
group1Key := models . GenerateGroupKey ( orgID )
group1Key . NamespaceUID = folder1 . Uid
group2Key := models . GenerateGroupKey ( orgID )
group2Key . NamespaceUID = folder2 . Uid
group1 := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withGroupKey ( group1Key ) ) )
group2 := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withGroupKey ( group2Key ) ) )
ruleStore . PutRule ( context . Background ( ) , append ( group1 , group2 ... ) ... )
request := createRequestContext ( orgID , "" , nil )
t . Run ( "and do not return group if user does not have access to one of rules" , func ( t * testing . T ) {
ac := acMock . New ( ) . WithPermissions ( createPermissionsForRules ( append ( group1 , group2 [ 1 : ] ... ) ) )
response := createService ( ac , ruleStore , nil ) . RouteGetRulesConfig ( request )
require . Equal ( t , http . StatusOK , response . Status ( ) )
result := & apimodels . NamespaceConfigResponse { }
require . NoError ( t , json . Unmarshal ( response . Body ( ) , result ) )
require . NotNil ( t , result )
require . Contains ( t , * result , folder1 . Title )
require . NotContains ( t , * result , folder2 . Title )
groups := ( * result ) [ folder1 . Title ]
require . Len ( t , groups , 1 )
require . Equal ( t , group1Key . RuleGroup , groups [ 0 ] . Name )
require . Len ( t , groups [ 0 ] . Rules , len ( group1 ) )
} )
} )
} )
2022-06-22 09:52:46 -05:00
t . Run ( "should return rules in group sorted by group index" , func ( t * testing . T ) {
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore := store . NewFakeRuleStore ( t )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
groupKey := models . GenerateGroupKey ( orgID )
groupKey . NamespaceUID = folder . Uid
expectedRules := models . GenerateAlertRules ( rand . Intn ( 5 ) + 5 , models . AlertRuleGen ( withGroupKey ( groupKey ) , models . WithUniqueGroupIndex ( ) ) )
ruleStore . PutRule ( context . Background ( ) , expectedRules ... )
ac := acMock . New ( ) . WithDisabled ( )
response := createService ( ac , ruleStore , nil ) . RouteGetRulesConfig ( createRequestContext ( orgID , models2 . ROLE_VIEWER , nil ) )
require . Equal ( t , http . StatusOK , response . Status ( ) )
result := & apimodels . NamespaceConfigResponse { }
require . NoError ( t , json . Unmarshal ( response . Body ( ) , result ) )
require . NotNil ( t , result )
models . RulesGroup ( expectedRules ) . SortByGroupIndex ( )
require . Contains ( t , * result , folder . Title )
groups := ( * result ) [ folder . Title ]
require . Len ( t , groups , 1 )
group := groups [ 0 ]
require . Equal ( t , groupKey . RuleGroup , group . Name )
for i , actual := range groups [ 0 ] . Rules {
expected := expectedRules [ i ]
if actual . GrafanaManagedAlert . UID != expected . UID {
var actualUIDs [ ] string
var expectedUIDs [ ] string
for _ , rule := range group . Rules {
actualUIDs = append ( actualUIDs , rule . GrafanaManagedAlert . UID )
}
for _ , rule := range expectedRules {
expectedUIDs = append ( expectedUIDs , rule . UID )
}
require . Fail ( t , fmt . Sprintf ( "rules are not sorted by group index. Expected: %v. Actual: %v" , expectedUIDs , actualUIDs ) )
}
}
} )
2022-06-17 12:55:31 -05:00
}
func TestRouteGetRulesGroupConfig ( t * testing . T ) {
t . Run ( "fine-grained access is enabled" , func ( t * testing . T ) {
t . Run ( "should check access to data source" , func ( t * testing . T ) {
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore := store . NewFakeRuleStore ( t )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
groupKey := models . GenerateGroupKey ( orgID )
groupKey . NamespaceUID = folder . Uid
expectedRules := models . GenerateAlertRules ( rand . Intn ( 4 ) + 2 , models . AlertRuleGen ( withGroupKey ( groupKey ) ) )
ruleStore . PutRule ( context . Background ( ) , expectedRules ... )
request := createRequestContext ( orgID , "" , map [ string ] string {
":Namespace" : folder . Title ,
":Groupname" : groupKey . RuleGroup ,
} )
t . Run ( "and return 401 if user does not have access one of rules" , func ( t * testing . T ) {
ac := acMock . New ( ) . WithPermissions ( createPermissionsForRules ( expectedRules [ 1 : ] ) )
2022-06-23 15:13:39 -05:00
response := createService ( ac , ruleStore , nil ) . RouteGetRulesGroupConfig ( request , folder . Title , groupKey . RuleGroup )
2022-06-17 12:55:31 -05:00
require . Equal ( t , http . StatusUnauthorized , response . Status ( ) )
} )
t . Run ( "and return rules if user has access to all of them" , func ( t * testing . T ) {
ac := acMock . New ( ) . WithPermissions ( createPermissionsForRules ( expectedRules ) )
2022-06-23 15:13:39 -05:00
response := createService ( ac , ruleStore , nil ) . RouteGetRulesGroupConfig ( request , folder . Title , groupKey . RuleGroup )
2022-06-17 12:55:31 -05:00
require . Equal ( t , http . StatusAccepted , response . Status ( ) )
result := & apimodels . RuleGroupConfigResponse { }
require . NoError ( t , json . Unmarshal ( response . Body ( ) , result ) )
require . NotNil ( t , result )
require . Len ( t , result . Rules , len ( expectedRules ) )
} )
} )
} )
2022-06-22 09:52:46 -05:00
t . Run ( "should return rules in group sorted by group index" , func ( t * testing . T ) {
orgID := rand . Int63 ( )
folder := randFolder ( )
ruleStore := store . NewFakeRuleStore ( t )
ruleStore . Folders [ orgID ] = append ( ruleStore . Folders [ orgID ] , folder )
groupKey := models . GenerateGroupKey ( orgID )
groupKey . NamespaceUID = folder . Uid
expectedRules := models . GenerateAlertRules ( rand . Intn ( 5 ) + 5 , models . AlertRuleGen ( withGroupKey ( groupKey ) , models . WithUniqueGroupIndex ( ) ) )
ruleStore . PutRule ( context . Background ( ) , expectedRules ... )
ac := acMock . New ( ) . WithDisabled ( )
2022-06-23 15:13:39 -05:00
response := createService ( ac , ruleStore , nil ) . RouteGetRulesGroupConfig ( createRequestContext ( orgID , models2 . ROLE_VIEWER , nil ) , folder . Title , groupKey . RuleGroup )
2022-06-22 09:52:46 -05:00
require . Equal ( t , http . StatusAccepted , response . Status ( ) )
result := & apimodels . RuleGroupConfigResponse { }
require . NoError ( t , json . Unmarshal ( response . Body ( ) , result ) )
require . NotNil ( t , result )
models . RulesGroup ( expectedRules ) . SortByGroupIndex ( )
for i , actual := range result . Rules {
expected := expectedRules [ i ]
if actual . GrafanaManagedAlert . UID != expected . UID {
var actualUIDs [ ] string
var expectedUIDs [ ] string
for _ , rule := range result . Rules {
actualUIDs = append ( actualUIDs , rule . GrafanaManagedAlert . UID )
}
for _ , rule := range expectedRules {
expectedUIDs = append ( expectedUIDs , rule . UID )
}
require . Fail ( t , fmt . Sprintf ( "rules are not sorted by group index. Expected: %v. Actual: %v" , expectedUIDs , actualUIDs ) )
}
}
} )
2022-06-17 12:55:31 -05:00
}
2022-06-15 15:01:14 -05:00
func TestVerifyProvisionedRulesNotAffected ( t * testing . T ) {
orgID := rand . Int63 ( )
group := models . GenerateGroupKey ( orgID )
2022-06-22 09:52:46 -05:00
affectedGroups := make ( map [ models . AlertRuleGroupKey ] models . RulesGroup )
2022-06-15 15:01:14 -05:00
var allRules [ ] * models . AlertRule
{
rules := models . GenerateAlertRules ( rand . Intn ( 3 ) + 1 , models . AlertRuleGen ( withGroupKey ( group ) ) )
allRules = append ( allRules , rules ... )
affectedGroups [ group ] = rules
for i := 0 ; i < rand . Intn ( 3 ) + 1 ; i ++ {
g := models . GenerateGroupKey ( orgID )
rules := models . GenerateAlertRules ( rand . Intn ( 3 ) + 1 , models . AlertRuleGen ( withGroupKey ( g ) ) )
allRules = append ( allRules , rules ... )
affectedGroups [ g ] = rules
}
}
ch := & changes {
GroupKey : group ,
AffectedGroups : affectedGroups ,
}
t . Run ( "should return error if at least one rule in affected groups is provisioned" , func ( t * testing . T ) {
rand . Shuffle ( len ( allRules ) , func ( i , j int ) {
allRules [ j ] , allRules [ i ] = allRules [ i ] , allRules [ j ]
} )
storeResult := make ( map [ string ] models . Provenance , len ( allRules ) )
storeResult [ allRules [ 0 ] . UID ] = models . ProvenanceAPI
storeResult [ allRules [ 1 ] . UID ] = models . ProvenanceFile
provenanceStore := & provisioning . MockProvisioningStore { }
provenanceStore . EXPECT ( ) . GetProvenances ( mock . Anything , orgID , "alertRule" ) . Return ( storeResult , nil )
result := verifyProvisionedRulesNotAffected ( context . Background ( ) , provenanceStore , orgID , ch )
require . Error ( t , result )
require . ErrorIs ( t , result , errProvisionedResource )
assert . Contains ( t , result . Error ( ) , allRules [ 0 ] . GetGroupKey ( ) . String ( ) )
assert . Contains ( t , result . Error ( ) , allRules [ 1 ] . GetGroupKey ( ) . String ( ) )
} )
t . Run ( "should return nil if all have ProvenanceNone" , func ( t * testing . T ) {
storeResult := make ( map [ string ] models . Provenance , len ( allRules ) )
for _ , rule := range allRules {
storeResult [ rule . UID ] = models . ProvenanceNone
}
provenanceStore := & provisioning . MockProvisioningStore { }
provenanceStore . EXPECT ( ) . GetProvenances ( mock . Anything , orgID , "alertRule" ) . Return ( storeResult , nil )
result := verifyProvisionedRulesNotAffected ( context . Background ( ) , provenanceStore , orgID , ch )
require . NoError ( t , result )
} )
t . Run ( "should return nil if no alerts have provisioning status" , func ( t * testing . T ) {
provenanceStore := & provisioning . MockProvisioningStore { }
provenanceStore . EXPECT ( ) . GetProvenances ( mock . Anything , orgID , "alertRule" ) . Return ( make ( map [ string ] models . Provenance , len ( allRules ) ) , nil )
result := verifyProvisionedRulesNotAffected ( context . Background ( ) , provenanceStore , orgID , ch )
require . NoError ( t , result )
} )
}
2022-06-22 09:52:46 -05:00
func TestCalculateAutomaticChanges ( t * testing . T ) {
orgID := rand . Int63 ( )
t . Run ( "should mark all rules in affected groups" , func ( t * testing . T ) {
group := models . GenerateGroupKey ( orgID )
rules := models . GenerateAlertRules ( 10 , models . AlertRuleGen ( withGroupKey ( group ) ) )
// copy rules to make sure that the function does not modify the original rules
copies := make ( [ ] * models . AlertRule , 0 , len ( rules ) )
for _ , rule := range rules {
copies = append ( copies , models . CopyRule ( rule ) )
}
var updates [ ] ruleUpdate
for i := 0 ; i < 5 ; i ++ {
ruleCopy := models . CopyRule ( copies [ i ] )
ruleCopy . Title += util . GenerateShortUID ( )
updates = append ( updates , ruleUpdate {
Existing : copies [ i ] ,
New : ruleCopy ,
} )
}
// simulate adding new rules, updating a few existing and delete some from the same rule
ch := & changes {
GroupKey : group ,
AffectedGroups : map [ models . AlertRuleGroupKey ] models . RulesGroup {
group : copies ,
} ,
New : models . GenerateAlertRules ( 2 , models . AlertRuleGen ( withGroupKey ( group ) ) ) ,
Update : updates ,
Delete : rules [ 5 : 7 ] ,
}
result := calculateAutomaticChanges ( ch )
require . NotEqual ( t , ch , result )
require . Equal ( t , ch . GroupKey , result . GroupKey )
require . Equal ( t , map [ models . AlertRuleGroupKey ] models . RulesGroup {
group : rules ,
} , result . AffectedGroups )
require . Equal ( t , ch . New , result . New )
require . Equal ( t , rules [ 5 : 7 ] , result . Delete )
var expected [ ] ruleUpdate
expected = append ( expected , updates ... )
// all rules that were not updated directly by user should be added to the
for _ , rule := range rules [ 7 : ] {
expected = append ( expected , ruleUpdate {
Existing : rule ,
New : rule ,
} )
}
require . Equal ( t , expected , result . Update )
} )
t . Run ( "should re-index rules in affected groups other than updated" , func ( t * testing . T ) {
group := models . GenerateGroupKey ( orgID )
rules := models . GenerateAlertRules ( 3 , models . AlertRuleGen ( withGroupKey ( group ) , models . WithSequentialGroupIndex ( ) ) )
group2 := models . GenerateGroupKey ( orgID )
rules2 := models . GenerateAlertRules ( 4 , models . AlertRuleGen ( withGroupKey ( group2 ) , models . WithSequentialGroupIndex ( ) ) )
movedIndex := rand . Intn ( len ( rules2 ) - 1 )
movedRule := rules2 [ movedIndex ]
copyRule := models . CopyRule ( movedRule )
copyRule . RuleGroup = group . RuleGroup
copyRule . NamespaceUID = group . NamespaceUID
copyRule . RuleGroupIndex = len ( rules )
update := ruleUpdate {
Existing : movedRule ,
New : copyRule ,
}
shuffled := make ( [ ] * models . AlertRule , 0 , len ( rules2 ) )
copy ( shuffled , rules2 )
rand . Shuffle ( len ( shuffled ) , func ( i , j int ) {
shuffled [ i ] , shuffled [ j ] = shuffled [ j ] , shuffled [ i ]
} )
// simulate moving a rule from one group to another.
ch := & changes {
GroupKey : group ,
AffectedGroups : map [ models . AlertRuleGroupKey ] models . RulesGroup {
group : rules ,
group2 : shuffled ,
} ,
Update : [ ] ruleUpdate {
update ,
} ,
}
result := calculateAutomaticChanges ( ch )
require . NotEqual ( t , ch , result )
require . Equal ( t , ch . GroupKey , result . GroupKey )
require . Equal ( t , ch . AffectedGroups , result . AffectedGroups )
require . Equal ( t , ch . New , result . New )
require . Equal ( t , ch . Delete , result . Delete )
require . Equal ( t , ch . Update , result . Update [ 0 : 1 ] )
require . Contains ( t , result . Update , update )
for _ , rule := range rules {
assert . Containsf ( t , result . Update , ruleUpdate {
Existing : rule ,
New : rule ,
} , "automatic changes expected to contain all rules of the updated group" )
}
// calculate expected index of the rules in the source group after the move
expectedReindex := make ( map [ string ] int , len ( rules2 ) - 1 )
idx := 1
for _ , rule := range rules2 {
if rule . UID == movedRule . UID {
continue
}
expectedReindex [ rule . UID ] = idx
idx ++
}
for _ , upd := range result . Update {
expectedIdx , ok := expectedReindex [ upd . Existing . UID ]
if ! ok {
continue
}
diff := upd . Existing . Diff ( upd . New )
if upd . Existing . RuleGroupIndex != expectedIdx {
require . Lenf ( t , diff , 1 , fmt . Sprintf ( "the rule in affected group should be re-indexed to %d but it still has index %d. Moved rule with index %d" , expectedIdx , upd . Existing . RuleGroupIndex , movedIndex ) )
require . Equal ( t , "RuleGroupIndex" , diff [ 0 ] . Path )
require . Equal ( t , expectedIdx , upd . New . RuleGroupIndex )
} else {
require . Empty ( t , diff )
}
}
} )
}
2022-04-11 16:37:44 -05:00
func createService ( ac * acMock . Mock , store * store . FakeRuleStore , scheduler schedule . ScheduleService ) * RulerSrv {
return & RulerSrv {
xactManager : store ,
store : store ,
DatasourceCache : nil ,
QuotaService : nil ,
2022-04-28 14:27:34 -05:00
provenanceStore : provisioning . NewFakeProvisioningStore ( ) ,
2022-04-11 16:37:44 -05:00
scheduleService : scheduler ,
log : log . New ( "test" ) ,
cfg : nil ,
ac : ac ,
}
}
2022-03-25 11:39:24 -05:00
func createRequestContext ( orgID int64 , role models2 . RoleType , params map [ string ] string ) * models2 . ReqContext {
2022-06-17 12:55:31 -05:00
uri , _ := url . Parse ( "http://localhost" )
ctx := web . Context { Req : & http . Request {
URL : uri ,
} }
2022-06-23 15:13:39 -05:00
if params != nil {
ctx . Req = web . SetURLParams ( ctx . Req , params )
}
2022-03-25 11:39:24 -05:00
return & models2 . ReqContext {
2022-04-11 16:37:44 -05:00
IsSignedIn : true ,
2022-03-25 11:39:24 -05:00
SignedInUser : & models2 . SignedInUser {
OrgRole : role ,
OrgId : orgID ,
} ,
Context : & ctx ,
}
}
2022-06-14 03:17:48 -05:00
func createPermissionsForRules ( rules [ ] * models . AlertRule ) [ ] accesscontrol . Permission {
var permissions [ ] accesscontrol . Permission
2022-04-11 16:37:44 -05:00
for _ , rule := range rules {
for _ , query := range rule . Data {
2022-06-14 03:17:48 -05:00
permissions = append ( permissions , accesscontrol . Permission {
2022-04-11 16:37:44 -05:00
Action : datasources . ActionQuery , Scope : datasources . ScopeProvider . GetResourceScopeUID ( query . DatasourceUID ) ,
} )
}
}
return permissions
}
2022-02-23 10:30:04 -06:00
func withOrgID ( orgId int64 ) func ( rule * models . AlertRule ) {
return func ( rule * models . AlertRule ) {
rule . OrgID = orgId
}
}
func withGroup ( groupName string ) func ( rule * models . AlertRule ) {
return func ( rule * models . AlertRule ) {
rule . RuleGroup = groupName
}
}
func withNamespace ( namespace * models2 . Folder ) func ( rule * models . AlertRule ) {
return func ( rule * models . AlertRule ) {
rule . NamespaceUID = namespace . Uid
}
}
2022-05-16 14:45:45 -05:00
func withGroupKey ( groupKey models . AlertRuleGroupKey ) func ( rule * models . AlertRule ) {
return func ( rule * models . AlertRule ) {
rule . RuleGroup = groupKey . RuleGroup
rule . OrgID = groupKey . OrgID
rule . NamespaceUID = groupKey . NamespaceUID
}
}
2022-02-23 10:30:04 -06:00
// simulateSubmitted resets some fields of the structure that are not populated by API model to model conversion
func simulateSubmitted ( rule * models . AlertRule ) {
rule . ID = 0
rule . Version = 0
rule . Updated = time . Time { }
}
func withoutUID ( rule * models . AlertRule ) {
rule . UID = ""
}
func withUIDs ( uids map [ string ] * models . AlertRule ) func ( rule * models . AlertRule ) {
unused := make ( [ ] string , 0 , len ( uids ) )
for s := range uids {
unused = append ( unused , s )
}
return func ( rule * models . AlertRule ) {
if len ( unused ) == 0 {
return
}
rule . UID = unused [ 0 ]
unused = unused [ 1 : ]
}
}