2022-06-13 11:15:28 -05:00
package store
import (
"context"
"errors"
"fmt"
2023-06-08 17:51:50 -05:00
"strings"
2024-02-15 08:45:10 -06:00
"sync"
2022-06-13 11:15:28 -05:00
"testing"
"time"
2023-10-04 12:13:31 -05:00
"github.com/google/uuid"
2024-02-15 08:45:10 -06:00
"github.com/stretchr/testify/assert"
2023-10-04 12:13:31 -05:00
2023-04-13 06:55:42 -05:00
"github.com/grafana/grafana/pkg/bus"
2023-06-02 09:38:02 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2023-06-08 17:51:50 -05:00
"github.com/grafana/grafana/pkg/infra/log/logtest"
2023-04-13 06:55:42 -05:00
"github.com/grafana/grafana/pkg/infra/tracing"
2023-12-04 03:34:38 -06:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2024-02-06 16:12:13 -06:00
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
2023-12-04 03:34:38 -06:00
"github.com/grafana/grafana/pkg/services/dashboards"
2023-06-16 12:55:49 -05:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2023-04-13 06:55:42 -05:00
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
2023-06-02 09:38:02 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/testutil"
2023-04-13 06:55:42 -05:00
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
2022-06-13 11:15:28 -05:00
"github.com/stretchr/testify/require"
"golang.org/x/exp/rand"
2022-10-19 08:02:15 -05:00
"github.com/grafana/grafana/pkg/infra/db"
2023-12-04 03:34:38 -06:00
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
2022-06-13 11:15:28 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/models"
2022-07-15 13:13:30 -05:00
"github.com/grafana/grafana/pkg/setting"
2022-06-13 11:15:28 -05:00
"github.com/grafana/grafana/pkg/util"
)
2022-10-10 00:54:54 -05:00
func TestIntegrationUpdateAlertRules ( t * testing . T ) {
2022-11-04 09:14:21 -05:00
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
2023-04-13 06:55:42 -05:00
cfg := setting . NewCfg ( )
2023-05-25 15:00:06 -05:00
cfg . UnifiedAlerting = setting . UnifiedAlertingSettings { BaseInterval : time . Duration ( rand . Int63n ( 100 ) + 1 ) * time . Second }
2022-10-19 08:02:15 -05:00
sqlStore := db . InitTestDB ( t )
2022-11-11 07:28:24 -06:00
store := & DBstore {
2023-04-13 06:55:42 -05:00
SQLStore : sqlStore ,
Cfg : cfg . UnifiedAlerting ,
2024-02-06 16:12:13 -06:00
FolderService : setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( ) ) ,
2023-06-08 17:51:50 -05:00
Logger : & logtest . Fake { } ,
2022-06-13 11:15:28 -05:00
}
2023-05-25 15:00:06 -05:00
generator := models . AlertRuleGen ( withIntervalMatching ( store . Cfg . BaseInterval ) , models . WithUniqueID ( ) )
2022-06-13 11:15:28 -05:00
t . Run ( "should increase version" , func ( t * testing . T ) {
2023-05-25 15:00:06 -05:00
rule := createRule ( t , store , generator )
2022-06-13 11:15:28 -05:00
newRule := models . CopyRule ( rule )
newRule . Title = util . GenerateShortUID ( )
2022-09-29 15:47:56 -05:00
err := store . UpdateAlertRules ( context . Background ( ) , [ ] models . UpdateRule { {
2022-06-13 11:15:28 -05:00
Existing : rule ,
New : * newRule ,
} ,
} )
require . NoError ( t , err )
dbrule := & models . AlertRule { }
2022-10-19 08:02:15 -05:00
err = sqlStore . WithDbSession ( context . Background ( ) , func ( sess * db . Session ) error {
2022-06-13 11:15:28 -05:00
exist , err := sess . Table ( models . AlertRule { } ) . ID ( rule . ID ) . Get ( dbrule )
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule . ID ) )
return err
} )
require . NoError ( t , err )
require . Equal ( t , rule . Version + 1 , dbrule . Version )
} )
t . Run ( "should fail due to optimistic locking if version does not match" , func ( t * testing . T ) {
2023-05-25 15:00:06 -05:00
rule := createRule ( t , store , generator )
2022-06-13 11:15:28 -05:00
rule . Version -- // simulate version discrepancy
newRule := models . CopyRule ( rule )
newRule . Title = util . GenerateShortUID ( )
2022-09-29 15:47:56 -05:00
err := store . UpdateAlertRules ( context . Background ( ) , [ ] models . UpdateRule { {
2022-06-13 11:15:28 -05:00
Existing : rule ,
New : * newRule ,
} ,
} )
require . ErrorIs ( t , err , ErrOptimisticLock )
} )
}
2023-06-08 17:51:50 -05:00
func TestIntegrationUpdateAlertRulesWithUniqueConstraintViolation ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
cfg := setting . NewCfg ( )
cfg . UnifiedAlerting = setting . UnifiedAlertingSettings { BaseInterval : time . Duration ( rand . Int63n ( 100 ) + 1 ) * time . Second }
sqlStore := db . InitTestDB ( t )
store := & DBstore {
SQLStore : sqlStore ,
Cfg : cfg . UnifiedAlerting ,
2024-02-06 16:12:13 -06:00
FolderService : setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( ) ) ,
2023-06-08 17:51:50 -05:00
Logger : & logtest . Fake { } ,
}
idMutator := models . WithUniqueID ( )
createRuleInFolder := func ( title string , orgID int64 , namespaceUID string ) * models . AlertRule {
generator := models . AlertRuleGen ( withIntervalMatching ( store . Cfg . BaseInterval ) , idMutator , models . WithNamespace ( & folder . Folder {
UID : namespaceUID ,
Title : namespaceUID ,
} ) , withOrgID ( orgID ) , models . WithTitle ( title ) )
return createRule ( t , store , generator )
}
t . Run ( "should handle update chains without unique constraint violation" , func ( t * testing . T ) {
rule1 := createRuleInFolder ( "chain-rule1" , 1 , "my-namespace" )
rule2 := createRuleInFolder ( "chain-rule2" , 1 , "my-namespace" )
newRule1 := models . CopyRule ( rule1 )
newRule2 := models . CopyRule ( rule2 )
newRule1 . Title = rule2 . Title
newRule2 . Title = util . GenerateShortUID ( )
err := store . UpdateAlertRules ( context . Background ( ) , [ ] models . UpdateRule { {
Existing : rule1 ,
New : * newRule1 ,
} , {
Existing : rule2 ,
New : * newRule2 ,
} ,
} )
require . NoError ( t , err )
dbrule1 := & models . AlertRule { }
dbrule2 := & models . AlertRule { }
err = sqlStore . WithDbSession ( context . Background ( ) , func ( sess * db . Session ) error {
exist , err := sess . Table ( models . AlertRule { } ) . ID ( rule1 . ID ) . Get ( dbrule1 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule1 . ID ) )
exist , err = sess . Table ( models . AlertRule { } ) . ID ( rule2 . ID ) . Get ( dbrule2 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule2 . ID ) )
return nil
} )
require . NoError ( t , err )
require . Equal ( t , newRule1 . Title , dbrule1 . Title )
require . Equal ( t , newRule2 . Title , dbrule2 . Title )
} )
t . Run ( "should handle update chains with cycle without unique constraint violation" , func ( t * testing . T ) {
rule1 := createRuleInFolder ( "cycle-rule1" , 1 , "my-namespace" )
rule2 := createRuleInFolder ( "cycle-rule2" , 1 , "my-namespace" )
rule3 := createRuleInFolder ( "cycle-rule3" , 1 , "my-namespace" )
newRule1 := models . CopyRule ( rule1 )
newRule2 := models . CopyRule ( rule2 )
newRule3 := models . CopyRule ( rule3 )
newRule1 . Title = rule2 . Title
newRule2 . Title = rule3 . Title
newRule3 . Title = rule1 . Title
err := store . UpdateAlertRules ( context . Background ( ) , [ ] models . UpdateRule { {
Existing : rule1 ,
New : * newRule1 ,
} , {
Existing : rule2 ,
New : * newRule2 ,
} , {
Existing : rule3 ,
New : * newRule3 ,
} ,
} )
require . NoError ( t , err )
dbrule1 := & models . AlertRule { }
dbrule2 := & models . AlertRule { }
dbrule3 := & models . AlertRule { }
err = sqlStore . WithDbSession ( context . Background ( ) , func ( sess * db . Session ) error {
exist , err := sess . Table ( models . AlertRule { } ) . ID ( rule1 . ID ) . Get ( dbrule1 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule1 . ID ) )
exist , err = sess . Table ( models . AlertRule { } ) . ID ( rule2 . ID ) . Get ( dbrule2 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule2 . ID ) )
exist , err = sess . Table ( models . AlertRule { } ) . ID ( rule3 . ID ) . Get ( dbrule3 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule3 . ID ) )
return nil
} )
require . NoError ( t , err )
require . Equal ( t , newRule1 . Title , dbrule1 . Title )
require . Equal ( t , newRule2 . Title , dbrule2 . Title )
require . Equal ( t , newRule3 . Title , dbrule3 . Title )
} )
t . Run ( "should handle case-insensitive intermediate collision without unique constraint violation" , func ( t * testing . T ) {
rule1 := createRuleInFolder ( "case-cycle-rule1" , 1 , "my-namespace" )
rule2 := createRuleInFolder ( "case-cycle-rule2" , 1 , "my-namespace" )
newRule1 := models . CopyRule ( rule1 )
newRule2 := models . CopyRule ( rule2 )
newRule1 . Title = strings . ToUpper ( rule2 . Title )
newRule2 . Title = strings . ToUpper ( rule1 . Title )
err := store . UpdateAlertRules ( context . Background ( ) , [ ] models . UpdateRule { {
Existing : rule1 ,
New : * newRule1 ,
} , {
Existing : rule2 ,
New : * newRule2 ,
} ,
} )
require . NoError ( t , err )
dbrule1 := & models . AlertRule { }
dbrule2 := & models . AlertRule { }
err = sqlStore . WithDbSession ( context . Background ( ) , func ( sess * db . Session ) error {
exist , err := sess . Table ( models . AlertRule { } ) . ID ( rule1 . ID ) . Get ( dbrule1 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule1 . ID ) )
exist , err = sess . Table ( models . AlertRule { } ) . ID ( rule2 . ID ) . Get ( dbrule2 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule2 . ID ) )
return nil
} )
require . NoError ( t , err )
require . Equal ( t , newRule1 . Title , dbrule1 . Title )
require . Equal ( t , newRule2 . Title , dbrule2 . Title )
} )
t . Run ( "should handle update multiple chains in different folders without unique constraint violation" , func ( t * testing . T ) {
rule1 := createRuleInFolder ( "multi-cycle-rule1" , 1 , "my-namespace" )
rule2 := createRuleInFolder ( "multi-cycle-rule2" , 1 , "my-namespace" )
rule3 := createRuleInFolder ( "multi-cycle-rule1" , 1 , "my-namespace2" )
rule4 := createRuleInFolder ( "multi-cycle-rule2" , 1 , "my-namespace2" )
newRule1 := models . CopyRule ( rule1 )
newRule2 := models . CopyRule ( rule2 )
newRule3 := models . CopyRule ( rule3 )
newRule4 := models . CopyRule ( rule4 )
newRule1 . Title = rule2 . Title
newRule2 . Title = rule1 . Title
newRule3 . Title = rule4 . Title
newRule4 . Title = rule3 . Title
err := store . UpdateAlertRules ( context . Background ( ) , [ ] models . UpdateRule { {
Existing : rule1 ,
New : * newRule1 ,
} , {
Existing : rule2 ,
New : * newRule2 ,
} , {
Existing : rule3 ,
New : * newRule3 ,
} , {
Existing : rule4 ,
New : * newRule4 ,
} ,
} )
require . NoError ( t , err )
dbrule1 := & models . AlertRule { }
dbrule2 := & models . AlertRule { }
dbrule3 := & models . AlertRule { }
dbrule4 := & models . AlertRule { }
err = sqlStore . WithDbSession ( context . Background ( ) , func ( sess * db . Session ) error {
exist , err := sess . Table ( models . AlertRule { } ) . ID ( rule1 . ID ) . Get ( dbrule1 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule1 . ID ) )
exist , err = sess . Table ( models . AlertRule { } ) . ID ( rule2 . ID ) . Get ( dbrule2 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule2 . ID ) )
exist , err = sess . Table ( models . AlertRule { } ) . ID ( rule3 . ID ) . Get ( dbrule3 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule3 . ID ) )
exist , err = sess . Table ( models . AlertRule { } ) . ID ( rule4 . ID ) . Get ( dbrule4 )
if err != nil {
return err
}
require . Truef ( t , exist , fmt . Sprintf ( "rule with ID %d does not exist" , rule4 . ID ) )
return nil
} )
require . NoError ( t , err )
require . Equal ( t , newRule1 . Title , dbrule1 . Title )
require . Equal ( t , newRule2 . Title , dbrule2 . Title )
require . Equal ( t , newRule3 . Title , dbrule3 . Title )
require . Equal ( t , newRule4 . Title , dbrule4 . Title )
} )
2024-02-07 11:55:48 -06:00
t . Run ( "should fail with unique constraint violation" , func ( t * testing . T ) {
rule1 := createRuleInFolder ( "unique-rule1" , 1 , "my-namespace" )
rule2 := createRuleInFolder ( "unique-rule2" , 1 , "my-namespace" )
newRule1 := models . CopyRule ( rule1 )
newRule2 := models . CopyRule ( rule2 )
newRule2 . Title = newRule1 . Title
err := store . UpdateAlertRules ( context . Background ( ) , [ ] models . UpdateRule { {
Existing : rule2 ,
New : * newRule2 ,
} ,
} )
require . ErrorIs ( t , err , models . ErrAlertRuleUniqueConstraintViolation )
require . NotEqual ( t , newRule2 . UID , "" )
require . NotEqual ( t , newRule2 . Title , "" )
require . NotEqual ( t , newRule2 . NamespaceUID , "" )
require . ErrorContains ( t , err , newRule2 . UID )
require . ErrorContains ( t , err , newRule2 . Title )
require . ErrorContains ( t , err , newRule2 . NamespaceUID )
} )
2023-06-08 17:51:50 -05:00
}
2023-04-13 06:55:42 -05:00
func TestIntegration_GetAlertRulesForScheduling ( t * testing . T ) {
2022-11-04 09:14:21 -05:00
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
2023-04-13 06:55:42 -05:00
cfg := setting . NewCfg ( )
cfg . UnifiedAlerting = setting . UnifiedAlertingSettings {
BaseInterval : time . Duration ( rand . Int63n ( 100 ) ) * time . Second ,
}
sqlStore := db . InitTestDB ( t )
store := & DBstore {
2023-06-16 12:55:49 -05:00
SQLStore : sqlStore ,
Cfg : cfg . UnifiedAlerting ,
2024-02-06 16:12:13 -06:00
FolderService : setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( ) ) ,
2023-06-16 12:55:49 -05:00
FeatureToggles : featuremgmt . WithFeatures ( ) ,
2023-04-13 06:55:42 -05:00
}
2023-05-25 12:51:38 -05:00
generator := models . AlertRuleGen ( withIntervalMatching ( store . Cfg . BaseInterval ) , models . WithUniqueID ( ) , models . WithUniqueOrgID ( ) )
rule1 := createRule ( t , store , generator )
rule2 := createRule ( t , store , generator )
2023-04-13 06:55:42 -05:00
2024-01-17 03:07:39 -06:00
parentFolderUid := uuid . NewString ( )
2024-02-06 16:12:13 -06:00
parentFolderTitle := "Very Parent Folder"
createFolder ( t , store , parentFolderUid , parentFolderTitle , rule1 . OrgID , "" )
rule1FolderTitle := "folder-" + rule1 . Title
rule2FolderTitle := "folder-" + rule2 . Title
createFolder ( t , store , rule1 . NamespaceUID , rule1FolderTitle , rule1 . OrgID , parentFolderUid )
createFolder ( t , store , rule2 . NamespaceUID , rule2FolderTitle , rule2 . OrgID , "" )
2024-01-17 03:07:39 -06:00
createFolder ( t , store , rule2 . NamespaceUID , "same UID folder" , generator ( ) . OrgID , "" ) // create a folder with the same UID but in the different org
2024-01-11 08:21:03 -06:00
2023-04-13 06:55:42 -05:00
tc := [ ] struct {
name string
rules [ ] string
ruleGroups [ ] string
disabledOrgs [ ] int64
2024-01-30 16:14:11 -06:00
folders map [ models . FolderKey ] string
2024-02-06 16:12:13 -06:00
flags [ ] string
2022-08-31 10:08:19 -05:00
} {
{
2023-04-13 06:55:42 -05:00
name : "without a rule group filter, it returns all created rules" ,
rules : [ ] string { rule1 . Title , rule2 . Title } ,
2022-08-31 10:08:19 -05:00
} ,
{
2023-04-13 06:55:42 -05:00
name : "with a rule group filter, it only returns the rules that match on rule group" ,
ruleGroups : [ ] string { rule1 . RuleGroup } ,
rules : [ ] string { rule1 . Title } ,
2022-08-31 10:08:19 -05:00
} ,
{
2023-04-13 06:55:42 -05:00
name : "with a filter on orgs, it returns rules that do not belong to that org" ,
rules : [ ] string { rule1 . Title } ,
disabledOrgs : [ ] int64 { rule2 . OrgID } ,
2022-08-31 10:08:19 -05:00
} ,
{
2023-04-13 06:55:42 -05:00
name : "with populate folders enabled, it returns them" ,
rules : [ ] string { rule1 . Title , rule2 . Title } ,
2024-02-06 16:12:13 -06:00
folders : map [ models . FolderKey ] string { rule1 . GetFolderKey ( ) : rule1FolderTitle , rule2 . GetFolderKey ( ) : rule2FolderTitle } ,
2023-04-13 06:55:42 -05:00
} ,
{
name : "with populate folders enabled and a filter on orgs, it only returns selected information" ,
rules : [ ] string { rule1 . Title } ,
disabledOrgs : [ ] int64 { rule2 . OrgID } ,
2024-02-06 16:12:13 -06:00
folders : map [ models . FolderKey ] string { rule1 . GetFolderKey ( ) : rule1FolderTitle } ,
2022-08-31 10:08:19 -05:00
} ,
}
2023-04-13 06:55:42 -05:00
for _ , tt := range tc {
t . Run ( tt . name , func ( t * testing . T ) {
if len ( tt . disabledOrgs ) > 0 {
store . Cfg . DisabledOrgs = map [ int64 ] struct { } { }
for _ , orgID := range tt . disabledOrgs {
store . Cfg . DisabledOrgs [ orgID ] = struct { } { }
t . Cleanup ( func ( ) {
delete ( store . Cfg . DisabledOrgs , orgID )
} )
}
}
populateFolders := len ( tt . folders ) > 0
query := & models . GetAlertRulesForSchedulingQuery {
RuleGroups : tt . ruleGroups ,
PopulateFolders : populateFolders ,
}
require . NoError ( t , store . GetAlertRulesForScheduling ( context . Background ( ) , query ) )
require . Len ( t , query . ResultRules , len ( tt . rules ) )
r := make ( [ ] string , 0 , len ( query . ResultRules ) )
for _ , rule := range query . ResultRules {
r = append ( r , rule . Title )
}
require . ElementsMatch ( t , r , tt . rules )
if populateFolders {
require . Equal ( t , tt . folders , query . ResultFoldersTitles )
2022-08-31 10:08:19 -05:00
}
} )
}
2024-02-06 16:12:13 -06:00
t . Run ( "when nested folders are enabled folders should contain full path" , func ( t * testing . T ) {
store . FolderService = setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( featuremgmt . FlagNestedFolders ) )
query := & models . GetAlertRulesForSchedulingQuery {
PopulateFolders : true ,
}
require . NoError ( t , store . GetAlertRulesForScheduling ( context . Background ( ) , query ) )
expected := map [ models . FolderKey ] string {
rule1 . GetFolderKey ( ) : parentFolderTitle + "/" + rule1FolderTitle ,
rule2 . GetFolderKey ( ) : rule2FolderTitle ,
}
require . Equal ( t , expected , query . ResultFoldersTitles )
} )
2022-08-31 10:08:19 -05:00
}
2022-11-08 04:51:00 -06:00
2023-04-13 06:55:42 -05:00
func withIntervalMatching ( baseInterval time . Duration ) func ( * models . AlertRule ) {
return func ( rule * models . AlertRule ) {
2023-05-25 15:00:06 -05:00
rule . IntervalSeconds = int64 ( baseInterval . Seconds ( ) ) * ( rand . Int63n ( 10 ) + 1 )
2023-04-13 06:55:42 -05:00
rule . For = time . Duration ( rule . IntervalSeconds * rand . Int63n ( 9 ) + 1 ) * time . Second
}
}
2022-11-08 04:51:00 -06:00
func TestIntegration_CountAlertRules ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
sqlStore := db . InitTestDB ( t )
2023-04-13 06:55:42 -05:00
cfg := setting . NewCfg ( )
2024-02-06 16:12:13 -06:00
store := & DBstore { SQLStore : sqlStore , FolderService : setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( ) ) }
2023-05-25 12:51:38 -05:00
rule := createRule ( t , store , nil )
2022-11-08 04:51:00 -06:00
tests := map [ string ] struct {
query * models . CountAlertRulesQuery
expected int64
expectErr bool
} {
"basic success" : {
& models . CountAlertRulesQuery {
NamespaceUID : rule . NamespaceUID ,
OrgID : rule . OrgID ,
} ,
1 ,
false ,
} ,
"successfully returning no results" : {
& models . CountAlertRulesQuery {
NamespaceUID : "probably not a uid we'd generate" ,
OrgID : rule . OrgID ,
} ,
0 ,
false ,
} ,
}
for name , test := range tests {
t . Run ( name , func ( t * testing . T ) {
2024-01-30 10:26:34 -06:00
count , err := store . CountInFolders ( context . Background ( ) ,
test . query . OrgID , [ ] string { test . query . NamespaceUID } , nil )
2022-11-08 04:51:00 -06:00
if test . expectErr {
require . Error ( t , err )
} else {
require . NoError ( t , err )
require . Equal ( t , test . expected , count )
}
} )
}
}
2023-06-02 09:38:02 -05:00
func TestIntegration_DeleteInFolder ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
sqlStore := db . InitTestDB ( t )
cfg := setting . NewCfg ( )
store := & DBstore {
SQLStore : sqlStore ,
2024-02-06 16:12:13 -06:00
FolderService : setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( ) ) ,
2023-06-02 09:38:02 -05:00
Logger : log . New ( "test-dbstore" ) ,
}
rule := createRule ( t , store , nil )
2023-12-04 03:34:38 -06:00
t . Run ( "should not be able to delete folder without permissions to delete rules" , func ( t * testing . T ) {
store . AccessControl = acmock . New ( )
2024-01-30 10:26:34 -06:00
err := store . DeleteInFolders ( context . Background ( ) , rule . OrgID , [ ] string { rule . NamespaceUID } , & user . SignedInUser { } )
2023-12-04 03:34:38 -06:00
require . ErrorIs ( t , err , dashboards . ErrFolderAccessDenied )
} )
t . Run ( "should be able to delete folder with permissions to delete rules" , func ( t * testing . T ) {
store . AccessControl = acmock . New ( ) . WithPermissions ( [ ] accesscontrol . Permission {
{ Action : accesscontrol . ActionAlertingRuleDelete , Scope : dashboards . ScopeFoldersAll } ,
} )
2024-01-30 10:26:34 -06:00
err := store . DeleteInFolders ( context . Background ( ) , rule . OrgID , [ ] string { rule . NamespaceUID } , & user . SignedInUser { } )
2023-12-04 03:34:38 -06:00
require . NoError ( t , err )
2023-06-02 09:38:02 -05:00
2024-01-30 10:26:34 -06:00
c , err := store . CountInFolders ( context . Background ( ) , rule . OrgID , [ ] string { rule . NamespaceUID } , & user . SignedInUser { } )
2023-12-04 03:34:38 -06:00
require . NoError ( t , err )
require . Equal ( t , int64 ( 0 ) , c )
} )
2023-06-02 09:38:02 -05:00
}
2023-10-04 12:13:31 -05:00
func TestIntegration_GetNamespaceByUID ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
sqlStore := db . InitTestDB ( t )
cfg := setting . NewCfg ( )
store := & DBstore {
SQLStore : sqlStore ,
2024-02-06 16:12:13 -06:00
FolderService : setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( ) ) ,
2023-10-04 12:13:31 -05:00
Logger : log . New ( "test-dbstore" ) ,
}
u := & user . SignedInUser {
UserID : 1 ,
OrgID : 1 ,
OrgRole : org . RoleAdmin ,
IsGrafanaAdmin : true ,
}
uid := uuid . NewString ( )
2024-02-06 16:12:13 -06:00
parentUid := uuid . NewString ( )
title := "folder/title"
parentTitle := "parent-title"
createFolder ( t , store , parentUid , parentTitle , 1 , "" )
createFolder ( t , store , uid , title , 1 , parentUid )
2023-10-04 12:13:31 -05:00
actual , err := store . GetNamespaceByUID ( context . Background ( ) , uid , 1 , u )
require . NoError ( t , err )
require . Equal ( t , title , actual . Title )
require . Equal ( t , uid , actual . UID )
2024-02-06 16:12:13 -06:00
require . Equal ( t , title , actual . Fullpath )
t . Run ( "error when user does not have permissions" , func ( t * testing . T ) {
someUser := & user . SignedInUser {
UserID : 2 ,
OrgID : 1 ,
OrgRole : org . RoleViewer ,
}
_ , err = store . GetNamespaceByUID ( context . Background ( ) , uid , 1 , someUser )
require . ErrorIs ( t , err , dashboards . ErrFolderAccessDenied )
} )
t . Run ( "when nested folders are enabled full path should be populated with correct value" , func ( t * testing . T ) {
store . FolderService = setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( featuremgmt . FlagNestedFolders ) )
actual , err := store . GetNamespaceByUID ( context . Background ( ) , uid , 1 , u )
require . NoError ( t , err )
require . Equal ( t , title , actual . Title )
require . Equal ( t , uid , actual . UID )
require . Equal ( t , "parent-title/folder\\/title" , actual . Fullpath )
} )
2023-10-04 12:13:31 -05:00
}
2023-10-06 17:11:24 -05:00
func TestIntegrationInsertAlertRules ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
sqlStore := db . InitTestDB ( t )
cfg := setting . NewCfg ( )
cfg . UnifiedAlerting . BaseInterval = 1 * time . Second
store := & DBstore {
SQLStore : sqlStore ,
2024-02-06 16:12:13 -06:00
FolderService : setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( ) ) ,
2023-10-06 17:11:24 -05:00
Logger : log . New ( "test-dbstore" ) ,
Cfg : cfg . UnifiedAlerting ,
}
rules := models . GenerateAlertRules ( 5 , models . AlertRuleGen ( models . WithOrgID ( 1 ) , withIntervalMatching ( store . Cfg . BaseInterval ) ) )
deref := make ( [ ] models . AlertRule , 0 , len ( rules ) )
for _ , rule := range rules {
deref = append ( deref , * rule )
}
ids , err := store . InsertAlertRules ( context . Background ( ) , deref )
require . NoError ( t , err )
require . Len ( t , ids , len ( rules ) )
dbRules , err := store . ListAlertRules ( context . Background ( ) , & models . ListAlertRulesQuery {
OrgID : 1 ,
} )
require . NoError ( t , err )
for idx , keyWithID := range ids {
found := false
for _ , rule := range dbRules {
if rule . GetKey ( ) == keyWithID . AlertRuleKey {
expected := rules [ idx ]
require . Equal ( t , keyWithID . ID , rule . ID )
require . Equal ( t , expected . Title , rule . Title )
found = true
break
}
}
require . Truef ( t , found , "Rule with key %#v was not found in database" , keyWithID )
}
2024-02-07 11:55:48 -06:00
_ , err = store . InsertAlertRules ( context . Background ( ) , [ ] models . AlertRule { deref [ 0 ] } )
require . ErrorIs ( t , err , models . ErrAlertRuleUniqueConstraintViolation )
require . NotEqual ( t , deref [ 0 ] . UID , "" )
require . NotEqual ( t , deref [ 0 ] . Title , "" )
require . NotEqual ( t , deref [ 0 ] . NamespaceUID , "" )
require . ErrorContains ( t , err , deref [ 0 ] . UID )
require . ErrorContains ( t , err , deref [ 0 ] . Title )
require . ErrorContains ( t , err , deref [ 0 ] . NamespaceUID )
2023-10-06 17:11:24 -05:00
}
2024-02-15 08:45:10 -06:00
func TestIntegrationAlertRulesNotificationSettings ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
sqlStore := db . InitTestDB ( t )
cfg := setting . NewCfg ( )
cfg . UnifiedAlerting . BaseInterval = 1 * time . Second
store := & DBstore {
SQLStore : sqlStore ,
FolderService : setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( ) ) ,
Logger : log . New ( "test-dbstore" ) ,
Cfg : cfg . UnifiedAlerting ,
}
uniqueUids := & sync . Map { }
receiverName := "receiver\"-" + uuid . NewString ( )
rules := models . GenerateAlertRules ( 3 , models . AlertRuleGen ( models . WithOrgID ( 1 ) , withIntervalMatching ( store . Cfg . BaseInterval ) , models . WithUniqueUID ( uniqueUids ) ) )
receiveRules := models . GenerateAlertRules ( 3 ,
models . AlertRuleGen (
models . WithOrgID ( 1 ) ,
withIntervalMatching ( store . Cfg . BaseInterval ) ,
models . WithUniqueUID ( uniqueUids ) ,
models . WithNotificationSettingsGen ( models . NotificationSettingsGen ( models . NSMuts . WithReceiver ( receiverName ) ) ) ) )
noise := models . GenerateAlertRules ( 3 ,
models . AlertRuleGen (
models . WithOrgID ( 1 ) ,
withIntervalMatching ( store . Cfg . BaseInterval ) ,
models . WithUniqueUID ( uniqueUids ) ,
models . WithNotificationSettingsGen ( models . NotificationSettingsGen ( models . NSMuts . WithMuteTimeIntervals ( receiverName ) ) ) ) ) // simulate collision of names of receiver and mute timing
deref := make ( [ ] models . AlertRule , 0 , len ( rules ) + len ( receiveRules ) + len ( noise ) )
for _ , rule := range append ( append ( rules , receiveRules ... ) , noise ... ) {
r := * rule
r . ID = 0
deref = append ( deref , r )
}
_ , err := store . InsertAlertRules ( context . Background ( ) , deref )
require . NoError ( t , err )
t . Run ( "should find rules by receiver name" , func ( t * testing . T ) {
expectedUIDs := map [ string ] struct { } { }
for _ , rule := range receiveRules {
expectedUIDs [ rule . UID ] = struct { } { }
}
actual , err := store . ListAlertRules ( context . Background ( ) , & models . ListAlertRulesQuery {
OrgID : 1 ,
ReceiverName : receiverName ,
} )
require . NoError ( t , err )
assert . Len ( t , actual , len ( expectedUIDs ) )
for _ , rule := range actual {
assert . Contains ( t , expectedUIDs , rule . UID )
}
} )
t . Run ( "RenameReceiverInNotificationSettings should update all rules that refer to the old receiver" , func ( t * testing . T ) {
newName := "new-receiver"
affected , err := store . RenameReceiverInNotificationSettings ( context . Background ( ) , 1 , receiverName , newName )
require . NoError ( t , err )
require . Equal ( t , len ( receiveRules ) , affected )
expectedUIDs := map [ string ] struct { } { }
for _ , rule := range receiveRules {
expectedUIDs [ rule . UID ] = struct { } { }
}
actual , err := store . ListAlertRules ( context . Background ( ) , & models . ListAlertRulesQuery {
OrgID : 1 ,
ReceiverName : newName ,
} )
require . NoError ( t , err )
assert . Len ( t , actual , len ( expectedUIDs ) )
for _ , rule := range actual {
assert . Contains ( t , expectedUIDs , rule . UID )
}
actual , err = store . ListAlertRules ( context . Background ( ) , & models . ListAlertRulesQuery {
OrgID : 1 ,
ReceiverName : receiverName ,
} )
require . NoError ( t , err )
require . Empty ( t , actual )
} )
}
func TestIntegrationListNotificationSettings ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
sqlStore := db . InitTestDB ( t )
cfg := setting . NewCfg ( )
cfg . UnifiedAlerting . BaseInterval = 1 * time . Second
store := & DBstore {
SQLStore : sqlStore ,
FolderService : setupFolderService ( t , sqlStore , cfg , featuremgmt . WithFeatures ( ) ) ,
Logger : log . New ( "test-dbstore" ) ,
Cfg : cfg . UnifiedAlerting ,
}
uids := & sync . Map { }
titles := & sync . Map { }
receiverName := ` receiver%"-👍'test `
rulesWithNotifications := models . GenerateAlertRules ( 5 , models . AlertRuleGen (
models . WithOrgID ( 1 ) ,
models . WithUniqueUID ( uids ) ,
models . WithUniqueTitle ( titles ) ,
withIntervalMatching ( store . Cfg . BaseInterval ) ,
models . WithNotificationSettingsGen ( models . NotificationSettingsGen ( models . NSMuts . WithReceiver ( receiverName ) ) ) ,
) )
rulesInOtherOrg := models . GenerateAlertRules ( 5 , models . AlertRuleGen (
models . WithOrgID ( 2 ) ,
models . WithUniqueUID ( uids ) ,
models . WithUniqueTitle ( titles ) ,
withIntervalMatching ( store . Cfg . BaseInterval ) ,
models . WithNotificationSettingsGen ( models . NotificationSettingsGen ( ) ) ,
) )
rulesWithNoNotifications := models . GenerateAlertRules ( 5 , models . AlertRuleGen (
models . WithOrgID ( 1 ) ,
models . WithUniqueUID ( uids ) ,
models . WithUniqueTitle ( titles ) ,
withIntervalMatching ( store . Cfg . BaseInterval ) ,
models . WithNoNotificationSettings ( ) ,
) )
deref := make ( [ ] models . AlertRule , 0 , len ( rulesWithNotifications ) + len ( rulesWithNoNotifications ) + len ( rulesInOtherOrg ) )
for _ , rule := range append ( append ( rulesWithNotifications , rulesWithNoNotifications ... ) , rulesInOtherOrg ... ) {
r := * rule
r . ID = 0
deref = append ( deref , r )
}
_ , err := store . InsertAlertRules ( context . Background ( ) , deref )
require . NoError ( t , err )
result , err := store . ListNotificationSettings ( context . Background ( ) , models . ListNotificationSettingsQuery { OrgID : 1 } )
require . NoError ( t , err )
require . Len ( t , result , len ( rulesWithNotifications ) )
for _ , rule := range rulesWithNotifications {
if ! assert . Contains ( t , result , rule . GetKey ( ) ) {
continue
}
assert . EqualValues ( t , rule . NotificationSettings , result [ rule . GetKey ( ) ] )
}
t . Run ( "should list notification settings by receiver name" , func ( t * testing . T ) {
expectedUIDs := map [ models . AlertRuleKey ] struct { } { }
for _ , rule := range rulesWithNotifications {
expectedUIDs [ rule . GetKey ( ) ] = struct { } { }
}
actual , err := store . ListNotificationSettings ( context . Background ( ) , models . ListNotificationSettingsQuery {
OrgID : 1 ,
ReceiverName : receiverName ,
} )
require . NoError ( t , err )
assert . Len ( t , actual , len ( expectedUIDs ) )
for ruleKey := range actual {
assert . Contains ( t , expectedUIDs , ruleKey )
}
} )
}
2024-01-29 10:22:43 -06:00
// createAlertRule creates an alert rule in the database and returns it.
// If a generator is not specified, uniqueness of primary key is not guaranteed.
2023-05-25 12:51:38 -05:00
func createRule ( t * testing . T , store * DBstore , generate func ( ) * models . AlertRule ) * models . AlertRule {
2023-04-13 06:55:42 -05:00
t . Helper ( )
2023-05-25 12:51:38 -05:00
if generate == nil {
2024-01-29 10:22:43 -06:00
generate = models . AlertRuleGen ( withIntervalMatching ( store . Cfg . BaseInterval ) )
2023-05-25 12:51:38 -05:00
}
rule := generate ( )
2022-11-08 04:51:00 -06:00
err := store . SQLStore . WithDbSession ( context . Background ( ) , func ( sess * db . Session ) error {
_ , err := sess . Table ( models . AlertRule { } ) . InsertOne ( rule )
if err != nil {
return err
}
dbRule := & models . AlertRule { }
exist , err := sess . Table ( models . AlertRule { } ) . ID ( rule . ID ) . Get ( dbRule )
if err != nil {
return err
}
if ! exist {
return errors . New ( "cannot read inserted record" )
}
rule = dbRule
2023-04-13 06:55:42 -05:00
require . NoError ( t , err )
2022-11-08 04:51:00 -06:00
return nil
} )
require . NoError ( t , err )
2023-04-13 06:55:42 -05:00
2022-11-08 04:51:00 -06:00
return rule
}
2023-04-13 06:55:42 -05:00
2024-01-17 03:07:39 -06:00
func createFolder ( t * testing . T , store * DBstore , uid , title string , orgID int64 , parentUID string ) {
2023-04-13 06:55:42 -05:00
t . Helper ( )
u := & user . SignedInUser {
UserID : 1 ,
OrgID : orgID ,
OrgRole : org . RoleAdmin ,
IsGrafanaAdmin : true ,
}
_ , err := store . FolderService . Create ( context . Background ( ) , & folder . CreateFolderCommand {
2023-10-04 12:13:31 -05:00
UID : uid ,
2023-04-13 06:55:42 -05:00
OrgID : orgID ,
Title : title ,
Description : "" ,
SignedInUser : u ,
2024-01-17 03:07:39 -06:00
ParentUID : parentUID ,
2023-04-13 06:55:42 -05:00
} )
require . NoError ( t , err )
}
2024-02-06 16:12:13 -06:00
func setupFolderService ( t * testing . T , sqlStore * sqlstore . SQLStore , cfg * setting . Cfg , features featuremgmt . FeatureToggles ) folder . Service {
2023-04-13 06:55:42 -05:00
tracer := tracing . InitializeTracerForTest ( )
inProcBus := bus . ProvideBus ( tracer )
folderStore := folderimpl . ProvideDashboardFolderStore ( sqlStore )
2023-06-02 09:38:02 -05:00
_ , dashboardStore := testutil . SetupDashboardService ( t , sqlStore , folderStore , cfg )
2023-04-13 06:55:42 -05:00
2024-02-06 16:12:13 -06:00
return testutil . SetupFolderService ( t , cfg , sqlStore , dashboardStore , folderStore , inProcBus , features , & actest . FakeAccessControl { } )
2023-04-13 06:55:42 -05:00
}