grafana/pkg/services/ngalert/store/alert_rule_test.go
Kristin Laemmert ef7145e4aa
feat(nested folders): Add CountAlertRulesInFolder to ngalert store (#58269)
* chore: refactor CountDashboardsInFolder to use the more efficient Count() sql function

* feat(nested folders): Add CountAlertRulesInFolder to ngalert store

This commit adds CountAlertRulesInFolder and a new model for the CountAlertRulesQuery. It returns a count of alert rules associated with a given orgID and parent folder UID. (the namespace referenced inside alert rules is the parent folder).

I'm not sure where this belongs in the ngalert service, so that will come in a future commit.
2022-11-08 11:51:00 +01:00

199 lines
4.9 KiB
Go

package store
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/rand"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
func TestIntegrationUpdateAlertRules(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
sqlStore := db.InitTestDB(t)
store := DBstore{
SQLStore: sqlStore,
Cfg: setting.UnifiedAlertingSettings{
BaseInterval: time.Duration(rand.Int63n(100)) * time.Second,
},
}
t.Run("should increase version", func(t *testing.T) {
rule := createRule(t, store)
newRule := models.CopyRule(rule)
newRule.Title = util.GenerateShortUID()
err := store.UpdateAlertRules(context.Background(), []models.UpdateRule{{
Existing: rule,
New: *newRule,
},
})
require.NoError(t, err)
dbrule := &models.AlertRule{}
err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error {
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) {
rule := createRule(t, store)
rule.Version-- // simulate version discrepancy
newRule := models.CopyRule(rule)
newRule.Title = util.GenerateShortUID()
err := store.UpdateAlertRules(context.Background(), []models.UpdateRule{{
Existing: rule,
New: *newRule,
},
})
require.ErrorIs(t, err, ErrOptimisticLock)
})
}
func withIntervalMatching(baseInterval time.Duration) func(*models.AlertRule) {
return func(rule *models.AlertRule) {
rule.IntervalSeconds = int64(baseInterval.Seconds()) * rand.Int63n(10)
rule.For = time.Duration(rule.IntervalSeconds*rand.Int63n(9)+1) * time.Second
}
}
func TestIntegration_getFilterByOrgsString(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
testCases := []struct {
testName string
orgs map[int64]struct{}
expectedFilter string
expectedArgs []interface{}
}{
{
testName: "should return empty string if map is empty",
orgs: map[int64]struct{}{},
expectedFilter: "",
expectedArgs: nil,
},
{
testName: "should return empty string if map is nil",
orgs: nil,
expectedFilter: "",
expectedArgs: nil,
},
{
testName: "should return correct filter if single element",
orgs: map[int64]struct{}{
1: {},
},
expectedFilter: "org_id NOT IN(?)",
expectedArgs: []interface{}{int64(1)},
},
{
testName: "should return correct filter if many elements",
orgs: map[int64]struct{}{
1: {},
2: {},
3: {},
},
expectedFilter: "org_id NOT IN(?,?,?)",
expectedArgs: []interface{}{int64(1), int64(2), int64(3)},
},
}
for _, testCase := range testCases {
t.Run(testCase.testName, func(t *testing.T) {
store := &DBstore{
Cfg: setting.UnifiedAlertingSettings{
DisabledOrgs: testCase.orgs,
},
}
filter, args := store.getFilterByOrgsString()
assert.Equal(t, testCase.expectedFilter, filter)
assert.ElementsMatch(t, testCase.expectedArgs, args)
})
}
}
func TestIntegration_CountAlertRules(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
sqlStore := db.InitTestDB(t)
store := DBstore{SQLStore: sqlStore}
rule := createRule(t, store)
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) {
count, err := store.CountAlertRulesInFolder(context.Background(), test.query)
if test.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, test.expected, count)
}
})
}
}
func createRule(t *testing.T, store DBstore) *models.AlertRule {
rule := models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval))()
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
return nil
})
require.NoError(t, err)
return rule
}