Alerting: Refactor Alert Rule Generators (#86813)

This commit is contained in:
Yuri Tseretyan 2024-04-29 21:52:15 -04:00 committed by GitHub
parent 8bb9b06e48
commit 052082a927
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 739 additions and 648 deletions

View File

@ -7,10 +7,11 @@ import (
"math/rand"
"net/url"
"strconv"
"sync"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
@ -28,7 +29,6 @@ import (
historymodel "github.com/grafana/grafana/pkg/services/ngalert/state/historian/model"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
)
@ -59,21 +59,15 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
"title": "Dashboard 2",
}),
})
knownUIDs := &sync.Map{}
generator := ngmodels.AlertRuleGen(
ngmodels.WithUniqueUID(knownUIDs),
ngmodels.WithUniqueID(),
ngmodels.WithOrgID(1),
)
gen := ngmodels.RuleGen.With(ngmodels.RuleGen.WithOrgID(1))
dashboardRules := map[string][]*ngmodels.AlertRule{
dashboard1.UID: {
createAlertRuleFromDashboard(t, sql, "Test Rule 1", *dashboard1, generator),
createAlertRuleFromDashboard(t, sql, "Test Rule 2", *dashboard1, generator),
createAlertRuleFromDashboard(t, sql, "Test Rule 1", *dashboard1, gen),
createAlertRuleFromDashboard(t, sql, "Test Rule 2", *dashboard1, gen),
},
dashboard2.UID: {
createAlertRuleFromDashboard(t, sql, "Test Rule 3", *dashboard2, generator),
createAlertRuleFromDashboard(t, sql, "Test Rule 3", *dashboard2, gen),
},
}
@ -343,7 +337,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
rule := dashboardRules[dashboard1.UID][0]
stream1 := historian.StatesToStream(ruleMetaFromRule(t, rule), transitions, map[string]string{}, log.NewNopLogger())
rule = createAlertRule(t, sql, "Test rule", generator)
rule = createAlertRule(t, sql, "Test rule", gen)
stream2 := historian.StatesToStream(ruleMetaFromRule(t, rule), transitions, map[string]string{}, log.NewNopLogger())
stream := historian.Stream{
@ -591,14 +585,15 @@ func createTestLokiStore(t *testing.T, sql db.DB, client lokiQueryClient) *LokiH
// createAlertRule creates an alert rule in the database and returns it.
// If a generator is not specified, uniqueness of primary key is not guaranteed.
func createAlertRule(t *testing.T, sql db.DB, title string, generator func() *ngmodels.AlertRule) *ngmodels.AlertRule {
func createAlertRule(t *testing.T, sql db.DB, title string, generator *ngmodels.AlertRuleGenerator) *ngmodels.AlertRule {
t.Helper()
if generator == nil {
generator = ngmodels.AlertRuleGen(ngmodels.WithTitle(title), withDashboardUID(nil), withPanelID(nil), ngmodels.WithOrgID(1))
g := ngmodels.RuleGen
generator = g.With(g.WithTitle(title), g.WithDashboardAndPanel(nil, nil), g.WithOrgID(1))
}
rule := generator()
rule := generator.GenerateRef()
// ensure rule has correct values
if rule.Title != title {
rule.Title = title
@ -632,17 +627,18 @@ func createAlertRule(t *testing.T, sql db.DB, title string, generator func() *ng
// createAlertRuleFromDashboard creates an alert rule with a linked dashboard and panel in the database and returns it.
// If a generator is not specified, uniqueness of primary key is not guaranteed.
func createAlertRuleFromDashboard(t *testing.T, sql db.DB, title string, dashboard dashboards.Dashboard, generator func() *ngmodels.AlertRule) *ngmodels.AlertRule {
func createAlertRuleFromDashboard(t *testing.T, sql db.DB, title string, dashboard dashboards.Dashboard, generator *ngmodels.AlertRuleGenerator) *ngmodels.AlertRule {
t.Helper()
panelID := new(int64)
*panelID = 123
if generator == nil {
generator = ngmodels.AlertRuleGen(ngmodels.WithTitle(title), ngmodels.WithOrgID(1), withDashboardUID(&dashboard.UID), withPanelID(panelID))
g := ngmodels.RuleGen
generator = g.With(g.WithTitle(title), g.WithDashboardAndPanel(&dashboard.UID, panelID), g.WithOrgID(1))
}
rule := generator()
rule := generator.GenerateRef()
// ensure rule has correct values
if rule.Title != title {
rule.Title = title
@ -741,18 +737,6 @@ func genStateTransitions(t *testing.T, num int, start time.Time) []state.StateTr
return transitions
}
func withDashboardUID(dashboardUID *string) ngmodels.AlertRuleMutator {
return func(rule *ngmodels.AlertRule) {
rule.DashboardUID = dashboardUID
}
}
func withPanelID(panelID *int64) ngmodels.AlertRuleMutator {
return func(rule *ngmodels.AlertRule) {
rule.PanelID = panelID
}
}
func compareAnnotationItem(t *testing.T, expected, actual *annotations.ItemDTO) {
require.Equal(t, expected.AlertID, actual.AlertID)
require.Equal(t, expected.AlertName, actual.AlertName)

View File

@ -92,6 +92,8 @@ func createUserWithPermissions(permissions map[string][]string) identity.Request
func TestAuthorizeRuleChanges(t *testing.T) {
groupKey := models.GenerateGroupKey(rand.Int63())
namespaceIdScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(groupKey.NamespaceUID)
gen := models.RuleGen
genWithGroupKey := gen.With(gen.WithGroupKey(groupKey))
testCases := []struct {
name string
@ -103,7 +105,7 @@ func TestAuthorizeRuleChanges(t *testing.T) {
changes: func() *store.GroupDelta {
return &store.GroupDelta{
GroupKey: groupKey,
New: models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey))),
New: genWithGroupKey.GenerateManyRef(1, 5),
Update: nil,
Delete: nil,
}
@ -132,8 +134,8 @@ func TestAuthorizeRuleChanges(t *testing.T) {
{
name: "if there are rules to delete it should check delete action and query for datasource",
changes: func() *store.GroupDelta {
rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey)))
rules2 := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey)))
rules := genWithGroupKey.GenerateManyRef(1, 5)
rules2 := genWithGroupKey.GenerateManyRef(1, 5)
return &store.GroupDelta{
GroupKey: groupKey,
AffectedGroups: map[models.AlertRuleGroupKey]models.RulesGroup{
@ -162,8 +164,8 @@ func TestAuthorizeRuleChanges(t *testing.T) {
{
name: "if there are rules to update within the same namespace it should check update action and access to datasource",
changes: func() *store.GroupDelta {
rules1 := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey)))
rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey)))
rules1 := genWithGroupKey.GenerateManyRef(1, 5)
rules := genWithGroupKey.GenerateManyRef(1, 5)
updates := make([]store.RuleDelta, 0, len(rules))
for _, rule := range rules {
@ -207,18 +209,14 @@ func TestAuthorizeRuleChanges(t *testing.T) {
{
name: "if there are rules that are moved between namespaces it should check delete+add action and access to group where rules come from",
changes: func() *store.GroupDelta {
rules1 := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey)))
rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey)))
rules1 := genWithGroupKey.GenerateManyRef(1, 5)
rules := genWithGroupKey.GenerateManyRef(1, 5)
targetGroupKey := models.GenerateGroupKey(groupKey.OrgID)
updates := make([]store.RuleDelta, 0, len(rules))
for _, rule := range rules {
cp := models.CopyRule(rule)
models.WithGroupKey(targetGroupKey)(cp)
cp.Data = []models.AlertQuery{
models.GenerateAlertQuery(),
}
cp := models.CopyRule(rule, gen.WithGroupKey(targetGroupKey), gen.WithQuery(gen.GenerateQuery()))
updates = append(updates, store.RuleDelta{
Existing: rule,
@ -269,8 +267,8 @@ func TestAuthorizeRuleChanges(t *testing.T) {
NamespaceUID: groupKey.NamespaceUID,
RuleGroup: util.GenerateShortUID(),
}
sourceGroup := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey)))
targetGroup := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(targetGroupKey)))
sourceGroup := genWithGroupKey.GenerateManyRef(1, 5)
targetGroup := gen.With(gen.WithGroupKey(targetGroupKey)).GenerateManyRef(1, 5)
updates := make([]store.RuleDelta, 0, len(sourceGroup))
toCopy := len(sourceGroup)
@ -279,11 +277,7 @@ func TestAuthorizeRuleChanges(t *testing.T) {
}
for i := 0; i < toCopy; i++ {
rule := sourceGroup[0]
cp := models.CopyRule(rule)
models.WithGroupKey(targetGroupKey)(cp)
cp.Data = []models.AlertQuery{
models.GenerateAlertQuery(),
}
cp := models.CopyRule(rule, gen.WithGroupKey(targetGroupKey), gen.WithQuery(models.GenerateAlertQuery()))
updates = append(updates, store.RuleDelta{
Existing: rule,
@ -379,7 +373,7 @@ func TestAuthorizeRuleChanges(t *testing.T) {
}
func TestCheckDatasourcePermissionsForRule(t *testing.T) {
rule := models.AlertRuleGen()()
rule := models.RuleGen.GenerateRef()
expressionByType := models.GenerateAlertQuery()
expressionByType.QueryType = expr.DatasourceType
@ -442,7 +436,7 @@ func TestCheckDatasourcePermissionsForRule(t *testing.T) {
func Test_authorizeAccessToRuleGroup(t *testing.T) {
t.Run("should return true if user has access to all datasources of all rules in group", func(t *testing.T) {
rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen())
rules := models.RuleGen.GenerateManyRef(1, 5)
var scopes []string
for _, rule := range rules {
for _, query := range rule.Data {
@ -470,7 +464,9 @@ func Test_authorizeAccessToRuleGroup(t *testing.T) {
})
t.Run("should return false if user does not have access to at least one rule in group", func(t *testing.T) {
f := &folder.Folder{UID: "test-folder"}
rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithNamespace(f)))
gen := models.RuleGen
genWithFolder := gen.With(gen.WithNamespace(f))
rules := genWithFolder.GenerateManyRef(1, 5)
var scopes []string
for _, rule := range rules {
for _, query := range rule.Data {
@ -487,7 +483,7 @@ func Test_authorizeAccessToRuleGroup(t *testing.T) {
datasources.ActionQuery: scopes,
}
rule := models.AlertRuleGen(models.WithNamespace(f))()
rule := genWithFolder.GenerateRef()
rules = append(rules, rule)
ac := &recordingAccessControlFake{}

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"testing"
"time"
@ -21,7 +20,6 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
@ -287,6 +285,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
timeNow = func() time.Time { return time.Date(2022, 3, 10, 14, 0, 0, 0, time.UTC) }
orgID := int64(1)
gen := ngmodels.RuleGen
gen = gen.With(gen.WithOrgID(orgID))
queryPermissions := map[int64]map[string][]string{1: {datasources.ActionQuery: {datasources.ScopeAll}}}
req, err := http.NewRequest("GET", "/api/v1/rules", nil)
@ -496,7 +496,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
ruleStore := fakes.NewRuleStore(t)
fakeAIM := NewFakeAlertInstanceManager(t)
groupKey := ngmodels.GenerateGroupKey(orgID)
_, rules := ngmodels.GenerateUniqueAlertRules(rand.Intn(5)+5, ngmodels.AlertRuleGen(withGroupKey(groupKey), ngmodels.WithUniqueGroupIndex()))
gen := ngmodels.RuleGen
rules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10)
ruleStore.PutRule(context.Background(), rules...)
api := PrometheusSrv{
@ -539,9 +540,9 @@ func TestRouteGetRuleStatuses(t *testing.T) {
ruleStore := fakes.NewRuleStore(t)
fakeAIM := NewFakeAlertInstanceManager(t)
rules := ngmodels.GenerateAlertRules(rand.Intn(4)+2, ngmodels.AlertRuleGen(withOrgID(orgID)))
rules := gen.GenerateManyRef(2, 6)
ruleStore.PutRule(context.Background(), rules...)
ruleStore.PutRule(context.Background(), ngmodels.GenerateAlertRules(rand.Intn(4)+2, ngmodels.AlertRuleGen(withOrgID(orgID)))...)
ruleStore.PutRule(context.Background(), gen.GenerateManyRef(2, 6)...)
api := PrometheusSrv{
log: log.NewNopLogger(),
@ -575,9 +576,11 @@ func TestRouteGetRuleStatuses(t *testing.T) {
t.Run("test totals are expected", func(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
// Create rules in the same Rule Group to keep assertions simple
rules := ngmodels.GenerateAlertRules(3, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{
Title: "Folder-1",
})))
rules := gen.With(gen.WithGroupKey(ngmodels.AlertRuleGroupKey{
RuleGroup: "Rule-Group-1",
NamespaceUID: "Folder-1",
OrgID: orgID,
})).GenerateManyRef(3)
// Need to sort these so we add alerts to the rules as ordered in the response
ngmodels.AlertRulesBy(ngmodels.AlertRulesByIndex).Sort(rules)
// The last two rules will have errors, however the first will be alerting
@ -635,7 +638,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
t.Run("test time of first firing alert", func(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
// Create rules in the same Rule Group to keep assertions simple
rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID)))
rules := gen.GenerateManyRef(1)
fakeStore.PutRule(context.Background(), rules...)
getRuleResponse := func() apimodels.RuleResponse {
@ -691,7 +694,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
t.Run("test with limit on Rule Groups", func(t *testing.T) {
fakeStore, _, api := setupAPI(t)
rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID)))
rules := gen.GenerateManyRef(2)
fakeStore.PutRule(context.Background(), rules...)
t.Run("first without limit", func(t *testing.T) {
@ -763,7 +766,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
t.Run("test with limit rules", func(t *testing.T) {
fakeStore, _, api := setupAPI(t)
rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1")))
rules := gen.With(gen.WithGroupName("Rule-Group-1")).GenerateManyRef(2)
fakeStore.PutRule(context.Background(), rules...)
t.Run("first without limit", func(t *testing.T) {
@ -836,7 +839,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
t.Run("test with limit alerts", func(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1")))
rules := gen.With(gen.WithGroupName("Rule-Group-1")).GenerateManyRef(2)
fakeStore.PutRule(context.Background(), rules...)
// create a normal and firing alert for each rule
for _, r := range rules {
@ -927,9 +930,11 @@ func TestRouteGetRuleStatuses(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
// create two rules in the same Rule Group to keep assertions simple
rules := ngmodels.GenerateAlertRules(3, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{
Title: "Folder-1",
})))
rules := gen.With(gen.WithGroupKey(ngmodels.AlertRuleGroupKey{
NamespaceUID: "Folder-1",
RuleGroup: "Rule-Group-1",
OrgID: orgID,
})).GenerateManyRef(2)
// Need to sort these so we add alerts to the rules as ordered in the response
ngmodels.AlertRulesBy(ngmodels.AlertRulesByIndex).Sort(rules)
// The last two rules will have errors, however the first will be alerting
@ -1084,9 +1089,11 @@ func TestRouteGetRuleStatuses(t *testing.T) {
t.Run("test with matcher on labels", func(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
// create two rules in the same Rule Group to keep assertions simple
rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{
Title: "Folder-1",
})))
rules := gen.With(gen.WithGroupKey(ngmodels.AlertRuleGroupKey{
NamespaceUID: "Folder-1",
RuleGroup: "Rule-Group-1",
OrgID: orgID,
})).GenerateManyRef(1)
fakeStore.PutRule(context.Background(), rules...)
// create a normal and alerting state for each rule
@ -1268,12 +1275,13 @@ func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, Promet
return fakeStore, fakeAIM, api
}
func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *fakes.RuleStore, query func(r *ngmodels.AlertRule)) {
func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *fakes.RuleStore, query ngmodels.AlertRuleMutator) {
t.Helper()
rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID), asFixture(), query))
gen := ngmodels.RuleGen
r := gen.With(gen.WithOrgID(orgID), asFixture(), query).GenerateRef()
fakeAIM.GenerateAlertInstances(orgID, rules[0].UID, 1, func(s *state.State) *state.State {
fakeAIM.GenerateAlertInstances(orgID, r.UID, 1, func(s *state.State) *state.State {
s.Labels = data.Labels{
"job": "prometheus",
alertingModels.NamespaceUIDLabel: "test_namespace_uid",
@ -1283,14 +1291,12 @@ func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAl
return s
})
for _, r := range rules {
fakeStore.PutRule(context.Background(), r)
}
fakeStore.PutRule(context.Background(), r)
}
// asFixture removes variable values of the alert rule.
// we're not too interested in variability of the rule in this scenario.
func asFixture() func(r *ngmodels.AlertRule) {
func asFixture() ngmodels.AlertRuleMutator {
return func(r *ngmodels.AlertRule) {
r.Title = "AlwaysFiring"
r.NamespaceUID = "namespaceUID"
@ -1306,7 +1312,7 @@ func asFixture() func(r *ngmodels.AlertRule) {
}
}
func withClassicConditionSingleQuery() func(r *ngmodels.AlertRule) {
func withClassicConditionSingleQuery() ngmodels.AlertRuleMutator {
return func(r *ngmodels.AlertRule) {
queries := []ngmodels.AlertQuery{
{
@ -1328,7 +1334,7 @@ func withClassicConditionSingleQuery() func(r *ngmodels.AlertRule) {
}
}
func withExpressionsMultiQuery() func(r *ngmodels.AlertRule) {
func withExpressionsMultiQuery() ngmodels.AlertRuleMutator {
return func(r *ngmodels.AlertRule) {
queries := []ngmodels.AlertQuery{
{

View File

@ -9,7 +9,6 @@ import (
"path"
"sort"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
@ -198,7 +197,6 @@ func TestExportFromPayload(t *testing.T) {
}
func TestExportRules(t *testing.T) {
uids := sync.Map{}
orgID := int64(1)
f1 := randFolder()
f2 := randFolder()
@ -210,33 +208,20 @@ func TestExportRules(t *testing.T) {
NamespaceUID: f1.UID,
RuleGroup: "HAS-ACCESS-1",
}
accessQuery := ngmodels.GenerateAlertQuery()
noAccessQuery := ngmodels.GenerateAlertQuery()
_, hasAccess1 := ngmodels.GenerateUniqueAlertRules(5,
ngmodels.AlertRuleGen(
ngmodels.WithUniqueUID(&uids),
withGroupKey(hasAccessKey1),
ngmodels.WithQuery(accessQuery),
ngmodels.WithUniqueGroupIndex(),
))
gen := ngmodels.RuleGen
accessQuery := gen.GenerateQuery()
noAccessQuery := gen.GenerateQuery()
hasAccess1 := gen.With(gen.WithGroupKey(hasAccessKey1), gen.WithQuery(accessQuery), gen.WithUniqueGroupIndex()).GenerateManyRef(5)
ruleStore.PutRule(context.Background(), hasAccess1...)
noAccessKey1 := ngmodels.AlertRuleGroupKey{
OrgID: orgID,
NamespaceUID: f1.UID,
RuleGroup: "NO-ACCESS",
}
_, noAccess1 := ngmodels.GenerateUniqueAlertRules(5,
ngmodels.AlertRuleGen(
ngmodels.WithUniqueUID(&uids),
withGroupKey(noAccessKey1),
ngmodels.WithQuery(noAccessQuery),
))
noAccessRule := ngmodels.AlertRuleGen(
ngmodels.WithUniqueUID(&uids),
withGroupKey(noAccessKey1),
ngmodels.WithQuery(accessQuery),
)()
noAccess1 := gen.With(gen.WithGroupKey(noAccessKey1), gen.WithQuery(noAccessQuery)).GenerateManyRef(5)
noAccessRule := gen.With(gen.WithGroupKey(noAccessKey1), gen.WithQuery(accessQuery)).GenerateRef()
noAccess1 = append(noAccess1, noAccessRule)
ruleStore.PutRule(context.Background(), noAccess1...)
@ -245,21 +230,10 @@ func TestExportRules(t *testing.T) {
NamespaceUID: f2.UID,
RuleGroup: "HAS-ACCESS-2",
}
_, hasAccess2 := ngmodels.GenerateUniqueAlertRules(5,
ngmodels.AlertRuleGen(
ngmodels.WithUniqueUID(&uids),
withGroupKey(hasAccessKey2),
ngmodels.WithQuery(accessQuery),
ngmodels.WithUniqueGroupIndex(),
))
hasAccess2 := gen.With(gen.WithGroupKey(hasAccessKey2), gen.WithQuery(accessQuery), gen.WithUniqueGroupIndex()).GenerateManyRef(5)
ruleStore.PutRule(context.Background(), hasAccess2...)
_, noAccessByFolder := ngmodels.GenerateUniqueAlertRules(10,
ngmodels.AlertRuleGen(
ngmodels.WithUniqueUID(&uids),
ngmodels.WithQuery(accessQuery), // no access because of folder
ngmodels.WithNamespaceUIDNotIn(f1.UID, f2.UID),
))
noAccessByFolder := gen.With(gen.WithQuery(accessQuery), gen.WithNamespaceUIDNotIn(f1.UID, f2.UID)).GenerateManyRef(10)
ruleStore.PutRule(context.Background(), noAccessByFolder...)
// overwrite the folders visible to user because PutRule automatically creates folders in the fake store.

View File

@ -33,7 +33,6 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/cmputil"
"github.com/grafana/grafana/pkg/web"
)
@ -67,12 +66,13 @@ func TestRouteDeleteAlertRules(t *testing.T) {
orgID := rand.Int63()
folder := randFolder()
gen := models.RuleGen.With(models.RuleGen.WithOrgID(orgID))
initFakeRuleStore := func(t *testing.T) *fakes.RuleStore {
ruleStore := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
// add random data
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID)))...)
ruleStore.PutRule(context.Background(), gen.GenerateManyRef(1, 5)...)
return ruleStore
}
@ -80,7 +80,7 @@ func TestRouteDeleteAlertRules(t *testing.T) {
t.Run("and group argument is empty", func(t *testing.T) {
t.Run("return Forbidden if user is not authorized to access any group in the folder", func(t *testing.T) {
ruleStore := initFakeRuleStore(t)
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
ruleStore.PutRule(context.Background(), gen.With(gen.WithNamespace(folder)).GenerateManyRef(1, 5)...)
request := createRequestContextWithPerms(orgID, map[int64]map[string][]string{}, nil)
@ -93,16 +93,20 @@ func TestRouteDeleteAlertRules(t *testing.T) {
ruleStore := initFakeRuleStore(t)
provisioningStore := fakes.NewFakeProvisioningStore()
authorizedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("authz_"+util.GenerateShortUID())))
folderGen := gen.With(gen.WithNamespace(folder))
provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("provisioned_"+util.GenerateShortUID())))
err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI)
require.NoError(t, err)
authorizedRulesInFolder := folderGen.With(gen.WithGroupPrefix("authz-")).GenerateManyRef(1, 5)
provisionedRulesInFolder := folderGen.With(gen.WithGroupPrefix("provisioned-")).GenerateManyRef(1, 5)
for _, rule := range provisionedRulesInFolder {
err := provisioningStore.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI)
require.NoError(t, err)
}
ruleStore.PutRule(context.Background(), authorizedRulesInFolder...)
ruleStore.PutRule(context.Background(), provisionedRulesInFolder...)
// more rules in the same namespace but user does not have access to them
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("unauthz"+util.GenerateShortUID())))...)
ruleStore.PutRule(context.Background(), folderGen.With(gen.WithGroupPrefix("unauthz")).GenerateManyRef(1, 5)...)
permissions := createPermissionsForRules(append(authorizedRulesInFolder, provisionedRulesInFolder...), orgID)
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
@ -116,13 +120,15 @@ func TestRouteDeleteAlertRules(t *testing.T) {
ruleStore := initFakeRuleStore(t)
provisioningStore := fakes.NewFakeProvisioningStore()
provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(util.GenerateShortUID())))
folderGen := gen.With(gen.WithNamespace(folder))
provisionedRulesInFolder := folderGen.With(gen.WithSameGroup()).GenerateManyRef(1, 5)
err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI)
require.NoError(t, err)
ruleStore.PutRule(context.Background(), provisionedRulesInFolder...)
// more rules in the same namespace but user does not have access to them
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(util.GenerateShortUID())))...)
ruleStore.PutRule(context.Background(), folderGen.With(gen.WithSameGroup()).GenerateManyRef(1, 5)...)
permissions := createPermissionsForRules(provisionedRulesInFolder, orgID)
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
@ -143,19 +149,20 @@ func TestRouteDeleteAlertRules(t *testing.T) {
})
})
t.Run("and group argument is not empty", func(t *testing.T) {
groupName := util.GenerateShortUID()
t.Run("return Forbidden if user is not authorized to access the group", func(t *testing.T) {
ruleStore := initFakeRuleStore(t)
authorizedRulesInGroup := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))
groupGen := gen.With(gen.WithNamespace(folder), gen.WithSameGroup())
authorizedRulesInGroup := groupGen.GenerateManyRef(1, 5)
ruleStore.PutRule(context.Background(), authorizedRulesInGroup...)
// more rules in the same group but user is not authorized to access them
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))...)
ruleStore.PutRule(context.Background(), groupGen.GenerateManyRef(1, 5)...)
permissions := createPermissionsForRules(authorizedRulesInGroup, orgID)
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.UID, groupName)
response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.UID, authorizedRulesInGroup[0].RuleGroup)
require.Equalf(t, http.StatusForbidden, response.Status(), "Expected 403 but got %d: %v", response.Status(), string(response.Body()))
deleteCommands := getRecordedCommand(ruleStore)
@ -165,7 +172,9 @@ func TestRouteDeleteAlertRules(t *testing.T) {
ruleStore := initFakeRuleStore(t)
provisioningStore := fakes.NewFakeProvisioningStore()
provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))
groupGen := gen.With(gen.WithNamespace(folder), gen.WithSameGroup())
provisionedRulesInFolder := groupGen.GenerateManyRef(1, 5)
err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI)
require.NoError(t, err)
@ -174,7 +183,7 @@ func TestRouteDeleteAlertRules(t *testing.T) {
permissions := createPermissionsForRules(provisionedRulesInFolder, orgID)
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.UID, groupName)
response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.UID, provisionedRulesInFolder[0].RuleGroup)
require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body()))
deleteCommands := getRecordedCommand(ruleStore)
@ -185,15 +194,17 @@ func TestRouteDeleteAlertRules(t *testing.T) {
}
func TestRouteGetNamespaceRulesConfig(t *testing.T) {
gen := models.RuleGen
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 := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
folderGen := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder))
expectedRules := folderGen.GenerateManyRef(2, 6)
ruleStore.PutRule(context.Background(), expectedRules...)
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
ruleStore.PutRule(context.Background(), folderGen.GenerateManyRef(2, 6)...)
permissions := createPermissionsForRules(expectedRules, orgID)
req := createRequestContextWithPerms(orgID, permissions, nil)
@ -227,7 +238,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
folder := randFolder()
ruleStore := fakes.NewRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
expectedRules := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder)).GenerateManyRef(2, 6)
ruleStore.PutRule(context.Background(), expectedRules...)
svc := createService(ruleStore)
@ -271,7 +282,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.UID
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
expectedRules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10)
ruleStore.PutRule(context.Background(), expectedRules...)
perms := createPermissionsForRules(expectedRules, orgID)
@ -308,6 +319,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
}
func TestRouteGetRulesConfig(t *testing.T) {
gen := models.RuleGen
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()
@ -321,8 +333,8 @@ func TestRouteGetRulesConfig(t *testing.T) {
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)))
group1 := gen.With(gen.WithGroupKey(group1Key)).GenerateManyRef(2, 6)
group2 := gen.With(gen.WithGroupKey(group2Key)).GenerateManyRef(2, 6)
ruleStore.PutRule(context.Background(), append(group1, group2...)...)
t.Run("and do not return group if user does not have access to one of rules", func(t *testing.T) {
@ -355,7 +367,7 @@ func TestRouteGetRulesConfig(t *testing.T) {
groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.UID
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
expectedRules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10)
ruleStore.PutRule(context.Background(), expectedRules...)
perms := createPermissionsForRules(expectedRules, orgID)
@ -392,6 +404,7 @@ func TestRouteGetRulesConfig(t *testing.T) {
}
func TestRouteGetRulesGroupConfig(t *testing.T) {
gen := models.RuleGen
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()
@ -401,7 +414,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.UID
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey)))
expectedRules := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(2, 6)
ruleStore.PutRule(context.Background(), expectedRules...)
t.Run("and return Forbidden if user does not have access one of rules", func(t *testing.T) {
@ -439,7 +452,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.UID
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
expectedRules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10)
ruleStore.PutRule(context.Background(), expectedRules...)
perms := createPermissionsForRules(expectedRules, orgID)
@ -475,14 +488,15 @@ func TestVerifyProvisionedRulesNotAffected(t *testing.T) {
orgID := rand.Int63()
group := models.GenerateGroupKey(orgID)
affectedGroups := make(map[models.AlertRuleGroupKey]models.RulesGroup)
gen := models.RuleGen
var allRules []*models.AlertRule
{
rules := models.GenerateAlertRules(rand.Intn(3)+1, models.AlertRuleGen(withGroupKey(group)))
rules := gen.With(gen.WithGroupKey(group)).GenerateManyRef(1, 4)
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)))
rules := gen.With(gen.WithGroupKey(g)).GenerateManyRef(1, 4)
allRules = append(allRules, rules...)
affectedGroups[g] = rules
}
@ -533,20 +547,15 @@ func TestVerifyProvisionedRulesNotAffected(t *testing.T) {
}
func TestValidateQueries(t *testing.T) {
gen := models.RuleGen
delta := store.GroupDelta{
New: []*models.AlertRule{
models.AlertRuleGen(func(rule *models.AlertRule) {
rule.Condition = "New"
})(),
gen.With(gen.WithCondition("New")).GenerateRef(),
},
Update: []store.RuleDelta{
{
Existing: models.AlertRuleGen(func(rule *models.AlertRule) {
rule.Condition = "Update_Existing"
})(),
New: models.AlertRuleGen(func(rule *models.AlertRule) {
rule.Condition = "Update_New"
})(),
Existing: gen.With(gen.WithCondition("New")).GenerateRef(),
New: gen.With(gen.WithCondition("Update_New")).GenerateRef(),
Diff: cmputil.DiffReport{
cmputil.Diff{
Path: "SomeField",
@ -554,12 +563,8 @@ func TestValidateQueries(t *testing.T) {
},
},
{
Existing: models.AlertRuleGen(func(rule *models.AlertRule) {
rule.Condition = "Update_Index_Existing"
})(),
New: models.AlertRuleGen(func(rule *models.AlertRule) {
rule.Condition = "Update_Index_New"
})(),
Existing: gen.With(gen.WithCondition("Update_Index_Existing")).GenerateRef(),
New: gen.With(gen.WithCondition("Update_Index_New")).GenerateRef(),
Diff: cmputil.DiffReport{
cmputil.Diff{
Path: "RuleGroupIndex",
@ -567,11 +572,7 @@ func TestValidateQueries(t *testing.T) {
},
},
},
Delete: []*models.AlertRule{
models.AlertRuleGen(func(rule *models.AlertRule) {
rule.Condition = "Deleted"
})(),
},
Delete: gen.With(gen.WithCondition("Deleted")).GenerateManyRef(1),
}
t.Run("should not validate deleted rules or updated rules with ignored fields", func(t *testing.T) {
@ -694,29 +695,3 @@ func createPermissionsForRules(rules []*models.AlertRule, orgID int64) map[int64
}
return map[int64]map[string][]string{orgID: permissions}
}
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 *folder.Folder) func(rule *models.AlertRule) {
return func(rule *models.AlertRule) {
rule.NamespaceUID = namespace.UID
}
}
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
}
}

View File

@ -174,8 +174,9 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
})
t.Run("should return Forbidden if user cannot query a data source", func(t *testing.T) {
data1 := models.GenerateAlertQuery()
data2 := models.GenerateAlertQuery()
gen := models.RuleGen
data1 := gen.GenerateQuery()
data2 := gen.GenerateQuery()
ac := acMock.New().WithPermissions([]ac.Permission{
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
@ -195,12 +196,14 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
NamespaceTitle: f.Title,
})
t.Log(string(response.Body()))
require.Equal(t, http.StatusForbidden, response.Status())
})
t.Run("should return 200 if user can query all data sources", func(t *testing.T) {
data1 := models.GenerateAlertQuery()
data2 := models.GenerateAlertQuery()
gen := models.RuleGen
data1 := gen.GenerateQuery()
data2 := gen.GenerateQuery()
ac := acMock.New().WithPermissions([]ac.Permission{
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
@ -252,8 +255,9 @@ func TestRouteEvalQueries(t *testing.T) {
}
t.Run("should return Forbidden if user cannot query a data source", func(t *testing.T) {
data1 := models.GenerateAlertQuery()
data2 := models.GenerateAlertQuery()
g := models.RuleGen
data1 := g.GenerateQuery()
data2 := g.GenerateQuery()
srv := &TestingApiSrv{
authz: accesscontrol.NewRuleService(acMock.New().WithPermissions([]ac.Permission{

View File

@ -143,15 +143,16 @@ func TestAlertingProxy_createProxyContext(t *testing.T) {
}
func Test_containsProvisionedAlerts(t *testing.T) {
gen := models2.RuleGen
t.Run("should return true if at least one rule is provisioned", func(t *testing.T) {
_, rules := models2.GenerateUniqueAlertRules(rand.Intn(4)+2, models2.AlertRuleGen())
rules := gen.GenerateManyRef(2, 6)
provenance := map[string]models2.Provenance{
rules[rand.Intn(len(rules))].UID: []models2.Provenance{models2.ProvenanceAPI, models2.ProvenanceFile}[rand.Intn(2)],
}
require.Truef(t, containsProvisionedAlerts(provenance, rules), "the group of rules is expected to be considered as provisioned but it isn't. Provenances: %v", provenance)
})
t.Run("should return false if map does not contain or has ProvenanceNone", func(t *testing.T) {
_, rules := models2.GenerateUniqueAlertRules(rand.Intn(5)+1, models2.AlertRuleGen())
rules := gen.GenerateManyRef(1, 6)
provenance := make(map[string]models2.Provenance)
numProvenanceNone := rand.Intn(len(rules))
for i := 0; i < numProvenanceNone; i++ {

View File

@ -189,7 +189,8 @@ func TestEvaluatorTest(t *testing.T) {
return manager
},
}
rule := models.AlertRuleGen(models.WithInterval(time.Second))()
gen := models.RuleGen
rule := gen.With(gen.WithInterval(time.Second)).GenerateRef()
ruleInterval := time.Duration(rule.IntervalSeconds) * time.Second
t.Run("should return data frame in specific format", func(t *testing.T) {

View File

@ -197,11 +197,10 @@ func TestSetDashboardAndPanelFromAnnotations(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
rule := AlertRuleGen(func(rule *AlertRule) {
rule.Annotations = tc.annotations
rule.DashboardUID = nil
rule.PanelID = nil
})()
rule := RuleGen.With(
RuleMuts.WithDashboardAndPanel(nil, nil),
RuleMuts.WithAnnotations(tc.annotations),
).Generate()
err := rule.SetDashboardAndPanelFromAnnotations()
require.Equal(t, tc.expectedError, err)
@ -256,14 +255,16 @@ func TestPatchPartialAlertRule(t *testing.T) {
},
}
gen := RuleGen.With(
RuleMuts.WithFor(time.Duration(rand.Int63n(1000) + 1)),
)
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
var existing *AlertRuleWithOptionals
for {
rule := AlertRuleGen(func(rule *AlertRule) {
rule.For = time.Duration(rand.Int63n(1000) + 1)
})()
existing = &AlertRuleWithOptionals{AlertRule: *rule}
for i := 0; i < 10; i++ {
rule := gen.Generate()
existing = &AlertRuleWithOptionals{AlertRule: rule}
cloned := *existing
testCase.mutator(&cloned)
if !cmp.Equal(existing, cloned, cmp.FilterPath(func(path cmp.Path) bool {
@ -343,15 +344,20 @@ func TestPatchPartialAlertRule(t *testing.T) {
},
}
gen := RuleGen.With(
RuleMuts.WithUniqueID(),
RuleMuts.WithFor(time.Duration(rand.Int63n(1000)+1)),
)
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
var existing *AlertRule
for {
existing = AlertRuleGen(WithUniqueID())()
cloned := *existing
existing = gen.GenerateRef()
cloned := CopyRule(existing)
// make sure the generated rule does not match the mutated one
testCase.mutator(&cloned)
if !cmp.Equal(*existing, cloned, cmp.FilterPath(func(path cmp.Path) bool {
testCase.mutator(cloned)
if !cmp.Equal(existing, cloned, cmp.FilterPath(func(path cmp.Path) bool {
return path.String() == "Data.modelProps"
}, cmp.Ignore())) {
break
@ -368,14 +374,14 @@ func TestPatchPartialAlertRule(t *testing.T) {
func TestDiff(t *testing.T) {
t.Run("should return nil if there is no diff", func(t *testing.T) {
rule1 := AlertRuleGen()()
rule1 := RuleGen.GenerateRef()
rule2 := CopyRule(rule1)
result := rule1.Diff(rule2)
require.Emptyf(t, result, "expected diff to be empty. rule1: %#v, rule2: %#v\ndiff: %s", rule1, rule2, result)
})
t.Run("should respect fields to ignore", func(t *testing.T) {
rule1 := AlertRuleGen()()
rule1 := RuleGen.GenerateRef()
rule2 := CopyRule(rule1)
rule2.ID = rule1.ID/2 + 1
rule2.Version = rule1.Version/2 + 1
@ -385,8 +391,8 @@ func TestDiff(t *testing.T) {
})
t.Run("should find diff in simple fields", func(t *testing.T) {
rule1 := AlertRuleGen()()
rule2 := AlertRuleGen()()
rule1 := RuleGen.GenerateRef()
rule2 := RuleGen.GenerateRef()
diffs := rule1.Diff(rule2, "Data", "Annotations", "Labels", "NotificationSettings") // these fields will be tested separately
@ -508,7 +514,7 @@ func TestDiff(t *testing.T) {
})
t.Run("should not see difference between nil and empty Annotations", func(t *testing.T) {
rule1 := AlertRuleGen()()
rule1 := RuleGen.GenerateRef()
rule1.Annotations = make(map[string]string)
rule2 := CopyRule(rule1)
rule2.Annotations = nil
@ -518,7 +524,7 @@ func TestDiff(t *testing.T) {
})
t.Run("should detect changes in Annotations", func(t *testing.T) {
rule1 := AlertRuleGen()()
rule1 := RuleGen.GenerateRef()
rule2 := CopyRule(rule1)
rule1.Annotations = map[string]string{
@ -555,7 +561,7 @@ func TestDiff(t *testing.T) {
})
t.Run("should not see difference between nil and empty Labels", func(t *testing.T) {
rule1 := AlertRuleGen()()
rule1 := RuleGen.GenerateRef()
rule1.Annotations = make(map[string]string)
rule2 := CopyRule(rule1)
rule2.Annotations = nil
@ -565,7 +571,7 @@ func TestDiff(t *testing.T) {
})
t.Run("should detect changes in Labels", func(t *testing.T) {
rule1 := AlertRuleGen()()
rule1 := RuleGen.GenerateRef()
rule2 := CopyRule(rule1)
rule1.Labels = map[string]string{
@ -602,7 +608,7 @@ func TestDiff(t *testing.T) {
})
t.Run("should detect changes in Data", func(t *testing.T) {
rule1 := AlertRuleGen()()
rule1 := RuleGen.GenerateRef()
rule2 := CopyRule(rule1)
query1 := AlertQuery{
@ -658,11 +664,11 @@ func TestDiff(t *testing.T) {
t.Run("should correctly detect no change with '<' and '>' in query", func(t *testing.T) {
old := query1
new := query1
newQuery := query1
old.Model = json.RawMessage(`{"field1": "$A \u003c 1"}`)
new.Model = json.RawMessage(`{"field1": "$A < 1"}`)
newQuery.Model = json.RawMessage(`{"field1": "$A < 1"}`)
rule1.Data = []AlertQuery{old}
rule2.Data = []AlertQuery{new}
rule2.Data = []AlertQuery{newQuery}
diff := rule1.Diff(rule2)
assert.Nil(t, diff)
@ -699,7 +705,7 @@ func TestDiff(t *testing.T) {
})
t.Run("should detect changes in NotificationSettings", func(t *testing.T) {
rule1 := AlertRuleGen()()
rule1 := RuleGen.GenerateRef()
baseSettings := NotificationSettingsGen(NSMuts.WithGroupBy("test1", "test2"))()
rule1.NotificationSettings = []NotificationSettings{baseSettings}
@ -824,7 +830,9 @@ func TestSortByGroupIndex(t *testing.T) {
}
t.Run("should sort rules by GroupIndex", func(t *testing.T) {
rules := GenerateAlertRules(rand.Intn(15)+5, AlertRuleGen(WithUniqueGroupIndex()))
rules := RuleGen.With(
RuleMuts.WithUniqueGroupIndex(),
).GenerateManyRef(5, 20)
ensureNotSorted(t, rules, func(i, j int) bool {
return rules[i].RuleGroupIndex < rules[j].RuleGroupIndex
})
@ -835,7 +843,10 @@ func TestSortByGroupIndex(t *testing.T) {
})
t.Run("should sort by ID if same GroupIndex", func(t *testing.T) {
rules := GenerateAlertRules(rand.Intn(15)+5, AlertRuleGen(WithUniqueID(), WithGroupIndex(rand.Int())))
rules := RuleGen.With(
RuleMuts.WithUniqueID(),
RuleMuts.WithGroupIndex(rand.Int()),
).GenerateManyRef(5, 20)
ensureNotSorted(t, rules, func(i, j int) bool {
return rules[i].ID < rules[j].ID
})

View File

@ -9,7 +9,6 @@ import (
"testing"
"time"
"github.com/google/uuid"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
@ -20,222 +19,312 @@ import (
"github.com/grafana/grafana/pkg/util"
)
type AlertRuleMutator func(*AlertRule)
var (
RuleMuts = AlertRuleMutators{}
NSMuts = NotificationSettingsMutators{}
RuleGen = &AlertRuleGenerator{
mutators: []AlertRuleMutator{
RuleMuts.WithUniqueUID(), RuleMuts.WithUniqueTitle(),
},
}
)
// AlertRuleGen provides a factory function that generates a random AlertRule.
// The mutators arguments allows changing fields of the resulting structure
func AlertRuleGen(mutators ...AlertRuleMutator) func() *AlertRule {
return func() *AlertRule {
randNoDataState := func() NoDataState {
s := [...]NoDataState{
Alerting,
NoData,
OK,
}
return s[rand.Intn(len(s))]
}
type AlertRuleMutator func(r *AlertRule)
randErrState := func() ExecutionErrorState {
s := [...]ExecutionErrorState{
AlertingErrState,
ErrorErrState,
OkErrState,
}
return s[rand.Intn(len(s))]
}
type AlertRuleGenerator struct {
AlertRuleMutators
mutators []AlertRuleMutator
}
interval := (rand.Int63n(6) + 1) * 10
forInterval := time.Duration(interval*rand.Int63n(6)) * time.Second
var annotations map[string]string = nil
if rand.Int63()%2 == 0 {
annotations = GenerateAlertLabels(rand.Intn(5), "ann-")
}
var labels map[string]string = nil
if rand.Int63()%2 == 0 {
labels = GenerateAlertLabels(rand.Intn(5), "lbl-")
}
var dashUID *string = nil
var panelID *int64 = nil
if rand.Int63()%2 == 0 {
d := util.GenerateShortUID()
dashUID = &d
p := rand.Int63n(1500)
panelID = &p
}
var ns []NotificationSettings
if rand.Int63()%2 == 0 {
ns = append(ns, NotificationSettingsGen()())
}
rule := &AlertRule{
ID: 0,
OrgID: rand.Int63n(1500) + 1, // Prevent OrgID=0 as this does not pass alert rule validation.
Title: "TEST-ALERT-" + util.GenerateShortUID(),
Condition: "A",
Data: []AlertQuery{GenerateAlertQuery()},
Updated: time.Now().Add(-time.Duration(rand.Intn(100) + 1)),
IntervalSeconds: rand.Int63n(60) + 1,
Version: rand.Int63n(1500), // Don't generate a rule ID too big for postgres
UID: util.GenerateShortUID(),
NamespaceUID: util.GenerateShortUID(),
DashboardUID: dashUID,
PanelID: panelID,
RuleGroup: "TEST-GROUP-" + util.GenerateShortUID(),
RuleGroupIndex: rand.Intn(1500),
NoDataState: randNoDataState(),
ExecErrState: randErrState(),
For: forInterval,
Annotations: annotations,
Labels: labels,
NotificationSettings: ns,
}
for _, mutator := range mutators {
mutator(rule)
}
return rule
func (g *AlertRuleGenerator) With(mutators ...AlertRuleMutator) *AlertRuleGenerator {
return &AlertRuleGenerator{
AlertRuleMutators: g.AlertRuleMutators,
mutators: append(g.mutators, mutators...),
}
}
func WithNotEmptyLabels(count int, prefix string) AlertRuleMutator {
func (g *AlertRuleGenerator) Generate() AlertRule {
randNoDataState := func() NoDataState {
s := [...]NoDataState{
Alerting,
NoData,
OK,
}
return s[rand.Intn(len(s))]
}
randErrState := func() ExecutionErrorState {
s := [...]ExecutionErrorState{
AlertingErrState,
ErrorErrState,
OkErrState,
}
return s[rand.Intn(len(s))]
}
interval := (rand.Int63n(6) + 1) * 10
forInterval := time.Duration(interval*rand.Int63n(6)) * time.Second
var annotations map[string]string = nil
if rand.Int63()%2 == 0 {
annotations = GenerateAlertLabels(rand.Intn(5), "ann-")
}
var labels map[string]string = nil
if rand.Int63()%2 == 0 {
labels = GenerateAlertLabels(rand.Intn(5), "lbl-")
}
var dashUID *string = nil
var panelID *int64 = nil
if rand.Int63()%2 == 0 {
d := util.GenerateShortUID()
dashUID = &d
p := rand.Int63n(1500)
panelID = &p
}
var ns []NotificationSettings
if rand.Int63()%2 == 0 {
ns = append(ns, NotificationSettingsGen()())
}
rule := AlertRule{
ID: 0,
OrgID: rand.Int63n(1500) + 1, // Prevent OrgID=0 as this does not pass alert rule validation.
Title: fmt.Sprintf("title-%s", util.GenerateShortUID()),
Condition: "A",
Data: []AlertQuery{g.GenerateQuery()},
Updated: time.Now().Add(-time.Duration(rand.Intn(100) + 1)),
IntervalSeconds: rand.Int63n(60) + 1,
Version: rand.Int63n(1500), // Don't generate a rule ID too big for postgres
UID: util.GenerateShortUID(),
NamespaceUID: util.GenerateShortUID(),
DashboardUID: dashUID,
PanelID: panelID,
RuleGroup: fmt.Sprintf("group-%s,", util.GenerateShortUID()),
RuleGroupIndex: rand.Intn(1500),
NoDataState: randNoDataState(),
ExecErrState: randErrState(),
For: forInterval,
Annotations: annotations,
Labels: labels,
NotificationSettings: ns,
}
for _, mutator := range g.mutators {
mutator(&rule)
}
return rule
}
func (g *AlertRuleGenerator) GenerateRef() *AlertRule {
r := g.Generate()
return &r
}
func (g *AlertRuleGenerator) getCount(bounds ...int) int {
count := 0
if len(bounds) == 0 {
count = rand.Intn(5) + 1
}
if len(bounds) == 1 {
count = bounds[0]
}
if len(bounds) == 2 {
if bounds[0] > bounds[1] {
panic("min should not be greater than max")
} else if bounds[0] < bounds[1] {
count = rand.Intn(bounds[1]-bounds[0]) + bounds[0]
} else {
count = bounds[0]
}
}
if len(bounds) > 2 {
panic("invalid number of parameter must be up to 2")
}
return count
}
func (g *AlertRuleGenerator) GenerateMany(bounds ...int) []AlertRule {
count := g.getCount(bounds...)
result := make([]AlertRule, 0, count)
for i := 0; i < count; i++ {
result = append(result, g.Generate())
}
return result
}
func (g *AlertRuleGenerator) GenerateManyRef(bounds ...int) []*AlertRule {
count := g.getCount(bounds...)
result := make([]*AlertRule, 0)
for i := 0; i < count; i++ {
r := g.Generate()
result = append(result, &r)
}
return result
}
type AlertRuleMutators struct {
}
func (a *AlertRuleMutators) WithNotEmptyLabels(count int, prefix string) AlertRuleMutator {
return func(rule *AlertRule) {
rule.Labels = GenerateAlertLabels(count, prefix)
}
}
func WithUniqueID() AlertRuleMutator {
usedID := make(map[int64]struct{})
func (a *AlertRuleMutators) WithUniqueID() AlertRuleMutator {
ids := sync.Map{}
return func(rule *AlertRule) {
id := rule.ID
for {
id := rand.Int63n(1500) + 1
if _, ok := usedID[id]; !ok {
usedID[id] = struct{}{}
_, exists := ids.LoadOrStore(id, struct{}{})
if !exists {
rule.ID = id
return
}
id = rand.Int63n(1500) + 1
}
}
}
func WithGroupIndex(groupIndex int) AlertRuleMutator {
func (a *AlertRuleMutators) WithGroupIndex(groupIndex int) AlertRuleMutator {
return func(rule *AlertRule) {
rule.RuleGroupIndex = groupIndex
}
}
func WithUniqueGroupIndex() AlertRuleMutator {
usedIdx := make(map[int]struct{})
func (a *AlertRuleMutators) WithUniqueGroupIndex() AlertRuleMutator {
usedIdx := sync.Map{}
return func(rule *AlertRule) {
idx := rule.RuleGroupIndex
for {
idx := rand.Int()
if _, ok := usedIdx[idx]; !ok {
usedIdx[idx] = struct{}{}
if _, exists := usedIdx.LoadOrStore(idx, struct{}{}); !exists {
rule.RuleGroupIndex = idx
return
}
idx = rand.Int()
}
}
}
func WithSequentialGroupIndex() AlertRuleMutator {
func (a *AlertRuleMutators) WithSequentialGroupIndex() AlertRuleMutator {
idx := 1
m := sync.Mutex{}
return func(rule *AlertRule) {
m.Lock()
defer m.Unlock()
rule.RuleGroupIndex = idx
idx++
}
}
func WithOrgID(orgId int64) AlertRuleMutator {
func (a *AlertRuleMutators) WithOrgID(orgId int64) AlertRuleMutator {
return func(rule *AlertRule) {
rule.OrgID = orgId
}
}
func WithUniqueOrgID() AlertRuleMutator {
orgs := map[int64]struct{}{}
func (a *AlertRuleMutators) WithUniqueOrgID() AlertRuleMutator {
orgs := sync.Map{}
return func(rule *AlertRule) {
var orgID int64
orgID := rule.OrgID
for {
orgID = rand.Int63()
if _, ok := orgs[orgID]; !ok {
break
if _, exist := orgs.LoadOrStore(orgID, struct{}{}); !exist {
rule.OrgID = orgID
return
}
orgID = rand.Int63()
}
orgs[orgID] = struct{}{}
rule.OrgID = orgID
}
}
// WithNamespaceUIDNotIn generates a random namespace UID if it is among excluded
func WithNamespaceUIDNotIn(exclude ...string) AlertRuleMutator {
func (a *AlertRuleMutators) WithNamespaceUIDNotIn(exclude ...string) AlertRuleMutator {
return func(rule *AlertRule) {
for {
if !slices.Contains(exclude, rule.NamespaceUID) {
return
}
rule.NamespaceUID = uuid.NewString()
rule.NamespaceUID = util.GenerateShortUID()
}
}
}
func WithNamespace(namespace *folder.Folder) AlertRuleMutator {
func (a *AlertRuleMutators) WithNamespaceUID(namespaceUID string) AlertRuleMutator {
return func(rule *AlertRule) {
rule.NamespaceUID = namespace.UID
rule.NamespaceUID = namespaceUID
}
}
func WithInterval(interval time.Duration) AlertRuleMutator {
func (a *AlertRuleMutators) WithNamespace(namespace *folder.Folder) AlertRuleMutator {
return a.WithNamespaceUID(namespace.UID)
}
func (a *AlertRuleMutators) WithInterval(interval time.Duration) AlertRuleMutator {
return func(rule *AlertRule) {
rule.IntervalSeconds = int64(interval.Seconds())
}
}
func WithIntervalBetween(min, max int64) AlertRuleMutator {
func (a *AlertRuleMutators) WithIntervalSeconds(seconds int64) AlertRuleMutator {
return func(rule *AlertRule) {
rule.IntervalSeconds = seconds
}
}
// WithIntervalMatching mutator that generates random interval and `for` duration that are times of the provided base interval.
func (a *AlertRuleMutators) WithIntervalMatching(baseInterval time.Duration) AlertRuleMutator {
return func(rule *AlertRule) {
rule.IntervalSeconds = int64(baseInterval.Seconds()) * (rand.Int63n(10) + 1)
rule.For = time.Duration(rule.IntervalSeconds*rand.Int63n(9)+1) * time.Second
}
}
func (a *AlertRuleMutators) WithIntervalBetween(min, max int64) AlertRuleMutator {
return func(rule *AlertRule) {
rule.IntervalSeconds = rand.Int63n(max-min) + min
}
}
func WithTitle(title string) AlertRuleMutator {
func (a *AlertRuleMutators) WithTitle(title string) AlertRuleMutator {
return func(rule *AlertRule) {
rule.Title = title
}
}
func WithFor(duration time.Duration) AlertRuleMutator {
func (a *AlertRuleMutators) WithFor(duration time.Duration) AlertRuleMutator {
return func(rule *AlertRule) {
rule.For = duration
}
}
func WithForNTimes(timesOfInterval int64) AlertRuleMutator {
func (a *AlertRuleMutators) WithForNTimes(timesOfInterval int64) AlertRuleMutator {
return func(rule *AlertRule) {
rule.For = time.Duration(rule.IntervalSeconds*timesOfInterval) * time.Second
}
}
func WithNoDataExecAs(nodata NoDataState) AlertRuleMutator {
func (a *AlertRuleMutators) WithNoDataExecAs(nodata NoDataState) AlertRuleMutator {
return func(rule *AlertRule) {
rule.NoDataState = nodata
}
}
func WithErrorExecAs(err ExecutionErrorState) AlertRuleMutator {
func (a *AlertRuleMutators) WithErrorExecAs(err ExecutionErrorState) AlertRuleMutator {
return func(rule *AlertRule) {
rule.ExecErrState = err
}
}
func WithAnnotations(a data.Labels) AlertRuleMutator {
func (a *AlertRuleMutators) WithAnnotations(lbls data.Labels) AlertRuleMutator {
return func(rule *AlertRule) {
rule.Annotations = a
rule.Annotations = lbls
}
}
func WithAnnotation(key, value string) AlertRuleMutator {
func (a *AlertRuleMutators) WithAnnotation(key, value string) AlertRuleMutator {
return func(rule *AlertRule) {
if rule.Annotations == nil {
rule.Annotations = data.Labels{}
@ -244,13 +333,13 @@ func WithAnnotation(key, value string) AlertRuleMutator {
}
}
func WithLabels(a data.Labels) AlertRuleMutator {
func (a *AlertRuleMutators) WithLabels(lbls data.Labels) AlertRuleMutator {
return func(rule *AlertRule) {
rule.Labels = a
rule.Labels = lbls
}
}
func WithLabel(key, value string) AlertRuleMutator {
func (a *AlertRuleMutators) WithLabel(key, value string) AlertRuleMutator {
return func(rule *AlertRule) {
if rule.Labels == nil {
rule.Labels = data.Labels{}
@ -259,35 +348,80 @@ func WithLabel(key, value string) AlertRuleMutator {
}
}
func WithUniqueUID(knownUids *sync.Map) AlertRuleMutator {
func (a *AlertRuleMutators) WithDashboardAndPanel(dashboardUID *string, panelID *int64) AlertRuleMutator {
return func(rule *AlertRule) {
rule.DashboardUID = dashboardUID
rule.PanelID = panelID
}
}
// WithUniqueUID returns AlertRuleMutator that generates a random UID if it is among UIDs known by the instance of mutator.
// NOTE: two instances of the mutator do not share known UID.
// Example #1 reuse mutator instance:
//
// mut := WithUniqueUID()
// rule1 := RuleGen.With(mut).Generate()
// rule2 := RuleGen.With(mut).Generate()
//
// Example #2 reuse generator:
//
// gen := RuleGen.With(WithUniqueUID())
// rule1 := gen.Generate()
// rule2 := gen.Generate()
//
// Example #3 non-unique:
//
// rule1 := RuleGen.With(WithUniqueUID()).Generate
// rule2 := RuleGen.With(WithUniqueUID()).Generate
func (a *AlertRuleMutators) WithUniqueUID() AlertRuleMutator {
uids := sync.Map{}
return func(rule *AlertRule) {
uid := rule.UID
for {
_, ok := knownUids.LoadOrStore(uid, struct{}{})
if !ok {
_, exist := uids.LoadOrStore(uid, struct{}{})
if !exist {
rule.UID = uid
return
}
uid = uuid.NewString()
uid = util.GenerateShortUID()
}
}
}
func WithUniqueTitle(knownTitles *sync.Map) AlertRuleMutator {
// WithUniqueTitle returns AlertRuleMutator that generates a random title if the rule's title is among titles known by the instance of mutator.
// Two instances of the mutator do not share known titles.
// Example #1 reuse mutator instance:
//
// mut := WithUniqueTitle()
// rule1 := RuleGen.With(mut).Generate()
// rule2 := RuleGen.With(mut).Generate()
//
// Example #2 reuse generator:
//
// gen := RuleGen.With(WithUniqueTitle())
// rule1 := gen.Generate()
// rule2 := gen.Generate()
//
// Example #3 non-unique:
//
// rule1 := RuleGen.With(WithUniqueTitle()).Generate
// rule2 := RuleGen.With(WithUniqueTitle()).Generate
func (a *AlertRuleMutators) WithUniqueTitle() AlertRuleMutator {
titles := sync.Map{}
return func(rule *AlertRule) {
title := rule.Title
for {
_, ok := knownTitles.LoadOrStore(title, struct{}{})
if !ok {
_, exist := titles.LoadOrStore(title, struct{}{})
if !exist {
rule.Title = title
return
}
title = uuid.NewString()
title = fmt.Sprintf("title-%s", util.GenerateShortUID())
}
}
}
func WithQuery(query ...AlertQuery) AlertRuleMutator {
func (a *AlertRuleMutators) WithQuery(query ...AlertQuery) AlertRuleMutator {
return func(rule *AlertRule) {
rule.Data = query
if len(query) > 1 {
@ -296,7 +430,19 @@ func WithQuery(query ...AlertQuery) AlertRuleMutator {
}
}
func WithGroupKey(groupKey AlertRuleGroupKey) AlertRuleMutator {
func (a *AlertRuleMutators) WithGroupName(groupName string) AlertRuleMutator {
return func(rule *AlertRule) {
rule.RuleGroup = groupName
}
}
func (a *AlertRuleMutators) WithGroupPrefix(prefix string) AlertRuleMutator {
return func(rule *AlertRule) {
rule.RuleGroup = fmt.Sprintf("%s%s", prefix, util.GenerateShortUID())
}
}
func (a *AlertRuleMutators) WithGroupKey(groupKey AlertRuleGroupKey) AlertRuleMutator {
return func(rule *AlertRule) {
rule.RuleGroup = groupKey.RuleGroup
rule.OrgID = groupKey.OrgID
@ -304,19 +450,48 @@ func WithGroupKey(groupKey AlertRuleGroupKey) AlertRuleMutator {
}
}
func WithNotificationSettingsGen(ns func() NotificationSettings) AlertRuleMutator {
// WithSameGroup generates a random group name and assigns it to all rules passed to it
func (a *AlertRuleMutators) WithSameGroup() AlertRuleMutator {
once := sync.Once{}
name := ""
return func(rule *AlertRule) {
once.Do(func() {
name = util.GenerateShortUID()
})
rule.RuleGroup = name
}
}
func (a *AlertRuleMutators) WithNotificationSettingsGen(ns func() NotificationSettings) AlertRuleMutator {
return func(rule *AlertRule) {
rule.NotificationSettings = []NotificationSettings{ns()}
}
}
func (a *AlertRuleMutators) WithNotificationSettings(ns NotificationSettings) AlertRuleMutator {
return func(rule *AlertRule) {
rule.NotificationSettings = []NotificationSettings{ns}
}
}
func WithNoNotificationSettings() AlertRuleMutator {
func (a *AlertRuleMutators) WithNoNotificationSettings() AlertRuleMutator {
return func(rule *AlertRule) {
rule.NotificationSettings = nil
}
}
func GenerateAlertLabels(count int, prefix string) data.Labels {
func (a *AlertRuleMutators) WithIsPaused(paused bool) AlertRuleMutator {
return func(rule *AlertRule) {
rule.IsPaused = paused
}
}
func (g *AlertRuleGenerator) GenerateLabels(min, max int, prefix string) data.Labels {
count := max
if min > max {
panic("min should not be greater than max")
} else if min < max {
count = rand.Intn(max-min) + min
}
labels := make(data.Labels, count)
for i := 0; i < count; i++ {
labels[prefix+"key-"+util.GenerateShortUID()] = prefix + "value-" + util.GenerateShortUID()
@ -324,7 +499,15 @@ func GenerateAlertLabels(count int, prefix string) data.Labels {
return labels
}
func GenerateAlertLabels(count int, prefix string) data.Labels {
return RuleGen.GenerateLabels(count, count, prefix)
}
func GenerateAlertQuery() AlertQuery {
return RuleGen.GenerateQuery()
}
func (g *AlertRuleGenerator) GenerateQuery() AlertQuery {
f := rand.Intn(10) + 5
t := rand.Intn(f)
@ -343,35 +526,10 @@ func GenerateAlertQuery() AlertQuery {
}
}
// GenerateUniqueAlertRules generates many random alert rules and makes sure that they have unique UID.
// It returns a tuple where first element is a map where keys are UID of alert rule and the second element is a slice of the same rules
func GenerateUniqueAlertRules(count int, f func() *AlertRule) (map[string]*AlertRule, []*AlertRule) {
uIDs := make(map[string]*AlertRule, count)
result := make([]*AlertRule, 0, count)
for len(result) < count {
rule := f()
if _, ok := uIDs[rule.UID]; ok {
continue
}
result = append(result, rule)
uIDs[rule.UID] = rule
func (g *AlertRuleGenerator) WithCondition(condition string) AlertRuleMutator {
return func(r *AlertRule) {
r.Condition = condition
}
return uIDs, result
}
// GenerateAlertRulesSmallNonEmpty generates 1 to 5 rules using the provided generator
func GenerateAlertRulesSmallNonEmpty(f func() *AlertRule) []*AlertRule {
return GenerateAlertRules(rand.Intn(4)+1, f)
}
// GenerateAlertRules generates many random alert rules. Does not guarantee that rules are unique (by UID)
func GenerateAlertRules(count int, f func() *AlertRule) []*AlertRule {
result := make([]*AlertRule, 0, count)
for len(result) < count {
rule := f()
result = append(result, rule)
}
return result
}
// GenerateRuleKey generates a random alert rule key
@ -392,7 +550,7 @@ func GenerateGroupKey(orgID int64) AlertRuleGroupKey {
}
// CopyRule creates a deep copy of AlertRule
func CopyRule(r *AlertRule) *AlertRule {
func CopyRule(r *AlertRule, mutators ...AlertRuleMutator) *AlertRule {
result := AlertRule{
ID: r.ID,
OrgID: r.OrgID,
@ -449,6 +607,12 @@ func CopyRule(r *AlertRule) *AlertRule {
result.NotificationSettings = append(result.NotificationSettings, CopyNotificationSettings(s))
}
if len(mutators) > 0 {
for _, mutator := range mutators {
mutator(&result)
}
}
return &result
}
@ -687,10 +851,6 @@ func NotificationSettingsGen(mutators ...Mutator[NotificationSettings]) func() N
}
}
var (
NSMuts = NotificationSettingsMutators{}
)
type NotificationSettingsMutators struct{}
func (n NotificationSettingsMutators) WithReceiver(receiver string) Mutator[NotificationSettings] {

View File

@ -29,7 +29,8 @@ func Test_subscribeToFolderChanges(t *testing.T) {
UID: util.GenerateShortUID(),
Title: "Folder" + util.GenerateShortUID(),
}
rules := models.GenerateAlertRules(5, models.AlertRuleGen(models.WithOrgID(orgID), models.WithNamespace(folder)))
gen := models.RuleGen
rules := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder)).GenerateManyRef(5)
bus := bus.ProvideBus(tracing.InitializeTracerForTest())
db := fakes.NewRuleStore(t)

View File

@ -84,7 +84,7 @@ func TestCanWriteAllRules(t *testing.T) {
func TestAuthorizeAccessToRuleGroup(t *testing.T) {
testUser := &user.SignedInUser{}
rules := models.GenerateAlertRules(1, models.AlertRuleGen())
rules := models.RuleGen.GenerateManyRef(1)
t.Run("should return nil when user has provisioning permissions", func(t *testing.T) {
rs := &fakes.FakeRuleService{}

View File

@ -551,7 +551,8 @@ func TestCreateAlertRule(t *testing.T) {
u := &user.SignedInUser{OrgID: orgID}
groupKey := models.GenerateGroupKey(orgID)
groupIntervalSeconds := int64(30)
rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second)))
gen := models.RuleGen
rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3)
groupProvenance := models.ProvenanceAPI
initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) {
@ -567,14 +568,14 @@ func TestCreateAlertRule(t *testing.T) {
t.Run("when user can write all rules", func(t *testing.T) {
t.Run("and a new rule creates a new group", func(t *testing.T) {
rule := models.AlertRuleGen(models.WithOrgID(orgID))()
rule := gen.With(gen.WithOrgID(orgID)).Generate()
service, ruleStore, provenanceStore, ac := initServiceWithData(t)
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
return true, nil
}
actualRule, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceFile)
actualRule, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceFile)
require.NoError(t, err)
require.Len(t, ac.Calls, 1)
@ -601,14 +602,14 @@ func TestCreateAlertRule(t *testing.T) {
})
})
t.Run("and it adds a rule to a group", func(t *testing.T) {
rule := models.AlertRuleGen(models.WithGroupKey(groupKey))()
rule := gen.With(gen.WithGroupKey(groupKey)).Generate()
service, ruleStore, provenanceStore, ac := initServiceWithData(t)
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
return true, nil
}
actualRule, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceNone)
actualRule, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
require.NoError(t, err)
require.Len(t, ac.Calls, 1)
@ -637,7 +638,7 @@ func TestCreateAlertRule(t *testing.T) {
})
t.Run("when user cannot write all rules", func(t *testing.T) {
t.Run("and it creates a new group", func(t *testing.T) {
rule := models.AlertRuleGen(models.WithOrgID(orgID))()
rule := gen.With(gen.WithOrgID(orgID)).Generate()
t.Run("it should authorize the change", func(t *testing.T) {
service, ruleStore, provenanceStore, ac := initServiceWithData(t)
@ -654,7 +655,7 @@ func TestCreateAlertRule(t *testing.T) {
return nil
}
actualRule, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceFile)
actualRule, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceFile)
require.NoError(t, err)
require.Len(t, ac.Calls, 2)
@ -683,7 +684,7 @@ func TestCreateAlertRule(t *testing.T) {
})
})
t.Run("and it adds a rule to a group", func(t *testing.T) {
rule := models.AlertRuleGen(models.WithGroupKey(groupKey))()
rule := gen.With(gen.WithGroupKey(groupKey)).Generate()
t.Run("it should authorize the change to whole group", func(t *testing.T) {
service, ruleStore, provenanceStore, ac := initServiceWithData(t)
@ -701,7 +702,7 @@ func TestCreateAlertRule(t *testing.T) {
return nil
}
actualRule, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceNone)
actualRule, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
require.NoError(t, err)
require.Len(t, ac.Calls, 2)
@ -730,7 +731,7 @@ func TestCreateAlertRule(t *testing.T) {
})
})
t.Run("it should not insert if not authorized", func(t *testing.T) {
rule := models.AlertRuleGen(models.WithGroupKey(groupKey))()
rule := gen.With(gen.WithGroupKey(groupKey)).Generate()
service, ruleStore, _, ac := initServiceWithData(t)
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
@ -741,7 +742,7 @@ func TestCreateAlertRule(t *testing.T) {
return expectedErr
}
_, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceFile)
_, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceFile)
require.ErrorIs(t, expectedErr, err)
require.Len(t, ac.Calls, 2)
@ -797,7 +798,8 @@ func TestUpdateAlertRule(t *testing.T) {
u := &user.SignedInUser{OrgID: orgID}
groupKey := models.GenerateGroupKey(orgID)
groupIntervalSeconds := int64(30)
rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second)))
gen := models.RuleGen
rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3)
groupProvenance := models.ProvenanceAPI
initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) {
@ -899,7 +901,8 @@ func TestDeleteAlertRule(t *testing.T) {
u := &user.SignedInUser{OrgID: orgID}
groupKey := models.GenerateGroupKey(orgID)
groupIntervalSeconds := int64(30)
rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second)))
gen := models.RuleGen
rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3)
groupProvenance := models.ProvenanceAPI
initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) {
@ -990,7 +993,8 @@ func TestGetAlertRule(t *testing.T) {
orgID := rand.Int63()
u := &user.SignedInUser{OrgID: orgID}
groupKey := models.GenerateGroupKey(orgID)
rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey)))
gen := models.RuleGen
rules := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(3)
rule := rules[0]
expectedProvenance := models.ProvenanceAPI
@ -1118,7 +1122,8 @@ func TestGetRuleGroup(t *testing.T) {
u := &user.SignedInUser{OrgID: orgID}
groupKey := models.GenerateGroupKey(orgID)
intervalSeconds := int64(30)
rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(intervalSeconds)*time.Second)))
gen := models.RuleGen
rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(intervalSeconds)).GenerateManyRef(3)
derefRules := make([]models.AlertRule, 0, len(rules))
for _, rule := range rules {
derefRules = append(derefRules, *rule)
@ -1226,9 +1231,10 @@ func TestGetAlertRules(t *testing.T) {
u := &user.SignedInUser{OrgID: orgID}
groupKey1 := models.GenerateGroupKey(orgID)
groupKey2 := models.GenerateGroupKey(orgID)
rules1 := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey1)))
gen := models.RuleGen
rules1 := gen.With(gen.WithGroupKey(groupKey1), gen.WithUniqueGroupIndex()).GenerateManyRef(3)
models.RulesGroup(rules1).SortByGroupIndex()
rules2 := models.GenerateAlertRules(4, models.AlertRuleGen(models.WithGroupKey(groupKey2)))
rules2 := gen.With(gen.WithGroupKey(groupKey2), gen.WithUniqueGroupIndex()).GenerateManyRef(4)
models.RulesGroup(rules2).SortByGroupIndex()
allRules := append(rules1, rules2...)
expectedProvenance := models.ProvenanceAPI
@ -1325,7 +1331,8 @@ func TestReplaceGroup(t *testing.T) {
u := &user.SignedInUser{OrgID: orgID}
groupKey := models.GenerateGroupKey(orgID)
groupIntervalSeconds := int64(30)
rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second)))
gen := models.RuleGen
rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3)
groupProvenance := models.ProvenanceAPI
initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) {
@ -1438,7 +1445,8 @@ func TestDeleteRuleGroup(t *testing.T) {
u := &user.SignedInUser{OrgID: orgID}
groupKey := models.GenerateGroupKey(orgID)
groupIntervalSeconds := int64(30)
rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second)))
gen := models.RuleGen
rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3)
groupProvenance := models.ProvenanceAPI
initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) {

View File

@ -13,20 +13,22 @@ import (
alertingModels "github.com/grafana/alerting/models"
"github.com/grafana/grafana-plugin-sdk-go/data"
definitions "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
models "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/state"
"github.com/grafana/grafana/pkg/util"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
prometheusModel "github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
mock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
definitions "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
models "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/state"
"github.com/grafana/grafana/pkg/util"
)
func TestAlertRule(t *testing.T) {
gen := models.RuleGen
type evalResponse struct {
success bool
droppedEval *Evaluation
@ -78,7 +80,7 @@ func TestAlertRule(t *testing.T) {
resultCh := make(chan evalResponse)
data := &Evaluation{
scheduledAt: expected,
rule: models.AlertRuleGen()(),
rule: gen.GenerateRef(),
folderTitle: util.GenerateShortUID(),
}
go func() {
@ -103,7 +105,7 @@ func TestAlertRule(t *testing.T) {
resultCh2 := make(chan evalResponse)
data := &Evaluation{
scheduledAt: time1,
rule: models.AlertRuleGen()(),
rule: gen.GenerateRef(),
folderTitle: util.GenerateShortUID(),
}
data2 := &Evaluation{
@ -146,7 +148,7 @@ func TestAlertRule(t *testing.T) {
resultCh := make(chan evalResponse)
data := &Evaluation{
scheduledAt: time.Now(),
rule: models.AlertRuleGen()(),
rule: gen.GenerateRef(),
folderTitle: util.GenerateShortUID(),
}
go func() {
@ -176,7 +178,7 @@ func TestAlertRule(t *testing.T) {
r.Stop(nil)
data := &Evaluation{
scheduledAt: time.Now(),
rule: models.AlertRuleGen()(),
rule: gen.GenerateRef(),
folderTitle: util.GenerateShortUID(),
}
success, dropped := r.Eval(data)
@ -225,7 +227,7 @@ func TestAlertRule(t *testing.T) {
case 2:
r.Eval(&Evaluation{
scheduledAt: time.Now(),
rule: models.AlertRuleGen()(),
rule: gen.GenerateRef(),
folderTitle: util.GenerateShortUID(),
})
case 3:
@ -245,6 +247,7 @@ func blankRuleForTests(ctx context.Context) *alertRule {
}
func TestRuleRoutine(t *testing.T) {
gen := models.RuleGen
createSchedule := func(
evalAppliedChan chan time.Time,
senderMock *SyncAlertsSenderMock,
@ -270,7 +273,7 @@ func TestRuleRoutine(t *testing.T) {
evalAppliedChan := make(chan time.Time)
sch, ruleStore, instanceStore, reg := createSchedule(evalAppliedChan, nil)
rule := models.AlertRuleGen(withQueryForState(t, evalState))()
rule := gen.With(withQueryForState(t, evalState)).GenerateRef()
ruleStore.PutRule(context.Background(), rule)
folderTitle := ruleStore.getNamespaceTitle(rule.NamespaceUID)
factory := ruleFactoryFromScheduler(sch)
@ -432,7 +435,7 @@ func TestRuleRoutine(t *testing.T) {
stoppedChan := make(chan error)
sch, _, _, _ := createSchedule(make(chan time.Time), nil)
rule := models.AlertRuleGen()()
rule := gen.GenerateRef()
_ = sch.stateManager.ProcessEvalResults(context.Background(), sch.clock.Now(), rule, eval.GenerateResults(rand.Intn(5)+1, eval.ResultGen(eval.WithEvaluatedAt(sch.clock.Now()))), nil)
expectedStates := sch.stateManager.GetStatesForRuleUID(rule.OrgID, rule.UID)
require.NotEmpty(t, expectedStates)
@ -454,7 +457,7 @@ func TestRuleRoutine(t *testing.T) {
stoppedChan := make(chan error)
sch, _, _, _ := createSchedule(make(chan time.Time), nil)
rule := models.AlertRuleGen()()
rule := gen.GenerateRef()
_ = sch.stateManager.ProcessEvalResults(context.Background(), sch.clock.Now(), rule, eval.GenerateResults(rand.Intn(5)+1, eval.ResultGen(eval.WithEvaluatedAt(sch.clock.Now()))), nil)
require.NotEmpty(t, sch.stateManager.GetStatesForRuleUID(rule.OrgID, rule.UID))
@ -474,7 +477,7 @@ func TestRuleRoutine(t *testing.T) {
})
t.Run("when a message is sent to update channel", func(t *testing.T) {
rule := models.AlertRuleGen(withQueryForState(t, eval.Normal))()
rule := gen.With(withQueryForState(t, eval.Normal)).GenerateRef()
folderTitle := "folderName"
ruleFp := ruleWithFolder{rule, folderTitle}.Fingerprint()
@ -557,7 +560,7 @@ func TestRuleRoutine(t *testing.T) {
})
t.Run("when evaluation fails", func(t *testing.T) {
rule := models.AlertRuleGen(withQueryForState(t, eval.Error))()
rule := gen.With(withQueryForState(t, eval.Error)).GenerateRef()
rule.ExecErrState = models.ErrorErrState
evalAppliedChan := make(chan time.Time)
@ -678,7 +681,7 @@ func TestRuleRoutine(t *testing.T) {
t.Run("when there are alerts that should be firing", func(t *testing.T) {
t.Run("it should call sender", func(t *testing.T) {
// eval.Alerting makes state manager to create notifications for alertmanagers
rule := models.AlertRuleGen(withQueryForState(t, eval.Alerting))()
rule := gen.With(withQueryForState(t, eval.Alerting)).GenerateRef()
evalAppliedChan := make(chan time.Time)
@ -712,7 +715,7 @@ func TestRuleRoutine(t *testing.T) {
})
t.Run("when there are no alerts to send it should not call notifiers", func(t *testing.T) {
rule := models.AlertRuleGen(withQueryForState(t, eval.Normal))()
rule := gen.With(withQueryForState(t, eval.Normal)).GenerateRef()
evalAppliedChan := make(chan time.Time)

View File

@ -4,14 +4,17 @@ import (
"testing"
"time"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/stretchr/testify/require"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
)
func TestJitter(t *testing.T) {
gen := ngmodels.RuleGen
genWithInterval10to600 := gen.With(gen.WithIntervalBetween(10, 600))
t.Run("when strategy is JitterNever", func(t *testing.T) {
t.Run("offset is always zero", func(t *testing.T) {
rules := createTestRules(100, ngmodels.WithIntervalBetween(10, 600))
rules := genWithInterval10to600.GenerateManyRef(100)
baseInterval := 10 * time.Second
for _, r := range rules {
@ -23,7 +26,7 @@ func TestJitter(t *testing.T) {
t.Run("when strategy is JitterByGroup", func(t *testing.T) {
t.Run("offset is stable for the same rule", func(t *testing.T) {
rule := ngmodels.AlertRuleGen(ngmodels.WithIntervalBetween(10, 600))()
rule := genWithInterval10to600.GenerateRef()
baseInterval := 10 * time.Second
original := jitterOffsetInTicks(rule, baseInterval, JitterByGroup)
@ -35,7 +38,7 @@ func TestJitter(t *testing.T) {
t.Run("offset is on the interval [0, interval/baseInterval)", func(t *testing.T) {
baseInterval := 10 * time.Second
rules := createTestRules(1000, ngmodels.WithIntervalBetween(10, 600))
rules := genWithInterval10to600.GenerateManyRef(1000)
for _, r := range rules {
offset := jitterOffsetInTicks(r, baseInterval, JitterByGroup)
@ -49,8 +52,8 @@ func TestJitter(t *testing.T) {
baseInterval := 10 * time.Second
group1 := ngmodels.AlertRuleGroupKey{}
group2 := ngmodels.AlertRuleGroupKey{}
rules1 := createTestRules(1000, ngmodels.WithInterval(60*time.Second), ngmodels.WithGroupKey(group1))
rules2 := createTestRules(1000, ngmodels.WithInterval(1*time.Hour), ngmodels.WithGroupKey(group2))
rules1 := gen.With(gen.WithInterval(60*time.Second), gen.WithGroupKey(group1)).GenerateManyRef(1000)
rules2 := gen.With(gen.WithInterval(1*time.Hour), gen.WithGroupKey(group2)).GenerateManyRef(1000)
group1Offset := jitterOffsetInTicks(rules1[0], baseInterval, JitterByGroup)
for _, r := range rules1 {
@ -67,7 +70,7 @@ func TestJitter(t *testing.T) {
t.Run("when strategy is JitterByRule", func(t *testing.T) {
t.Run("offset is stable for the same rule", func(t *testing.T) {
rule := ngmodels.AlertRuleGen(ngmodels.WithIntervalBetween(10, 600))()
rule := genWithInterval10to600.GenerateRef()
baseInterval := 10 * time.Second
original := jitterOffsetInTicks(rule, baseInterval, JitterByRule)
@ -79,7 +82,7 @@ func TestJitter(t *testing.T) {
t.Run("offset is on the interval [0, interval/baseInterval)", func(t *testing.T) {
baseInterval := 10 * time.Second
rules := createTestRules(1000, ngmodels.WithIntervalBetween(10, 600))
rules := genWithInterval10to600.GenerateManyRef(1000)
for _, r := range rules {
offset := jitterOffsetInTicks(r, baseInterval, JitterByRule)
@ -90,11 +93,3 @@ func TestJitter(t *testing.T) {
})
})
}
func createTestRules(n int, mutators ...ngmodels.AlertRuleMutator) []*ngmodels.AlertRule {
result := make([]*ngmodels.AlertRule, 0, n)
for i := 0; i < n; i++ {
result = append(result, ngmodels.AlertRuleGen(mutators...)())
}
return result
}

View File

@ -13,7 +13,7 @@ import (
)
func TestLoadedResultsFromRuleState(t *testing.T) {
rule := ngmodels.AlertRuleGen()()
rule := ngmodels.RuleGen.GenerateRef()
p := &FakeRuleStateProvider{
map[ngmodels.AlertRuleKey][]*state.State{
rule.GetKey(): {

View File

@ -12,12 +12,13 @@ import (
)
func BenchmarkRuleWithFolderFingerprint(b *testing.B) {
rules := models.GenerateAlertRules(b.N, models.AlertRuleGen(func(rule *models.AlertRule) {
gen := models.RuleGen
rules := gen.With(func(rule *models.AlertRule) {
rule.Data = make([]models.AlertQuery, 0, 5)
for i := 0; i < rand.Intn(5)+1; i++ {
rule.Data = append(rule.Data, models.GenerateAlertQuery())
rule.Data = append(rule.Data, gen.GenerateQuery())
}
}))
}).GenerateManyRef(b.N)
folder := uuid.NewString()
b.ReportAllocs()
b.ResetTimer()

View File

@ -78,7 +78,8 @@ func TestSchedulableAlertRulesRegistry(t *testing.T) {
}
func TestSchedulableAlertRulesRegistry_set(t *testing.T) {
_, initialRules := models.GenerateUniqueAlertRules(100, models.AlertRuleGen())
gen := models.RuleGen
initialRules := gen.GenerateManyRef(100)
init := make(map[models.AlertRuleKey]*models.AlertRule, len(initialRules))
for _, rule := range initialRules {
init[rule.GetKey()] = rule
@ -95,7 +96,7 @@ func TestSchedulableAlertRulesRegistry_set(t *testing.T) {
t.Run("should return empty diff if version does not change", func(t *testing.T) {
newRules := make([]*models.AlertRule, 0, len(initialRules))
// generate random and then override rule key + version
_, randomNew := models.GenerateUniqueAlertRules(len(initialRules), models.AlertRuleGen())
randomNew := gen.GenerateManyRef(len(initialRules))
for i := 0; i < len(initialRules); i++ {
rule := randomNew[i]
oldRule := initialRules[i]
@ -128,7 +129,7 @@ func TestSchedulableAlertRulesRegistry_set(t *testing.T) {
}
func TestRuleWithFolderFingerprint(t *testing.T) {
rule := models.AlertRuleGen()()
rule := models.RuleGen.GenerateRef()
title := uuid.NewString()
f := ruleWithFolder{rule: rule, folderTitle: title}.Fingerprint()
t.Run("should calculate a fingerprint", func(t *testing.T) {

View File

@ -101,9 +101,9 @@ func TestProcessTicks(t *testing.T) {
}
tick := time.Time{}
gen := models.RuleGen
// create alert rule under main org with one second interval
alertRule1 := models.AlertRuleGen(models.WithOrgID(mainOrgID), models.WithInterval(cfg.BaseInterval), models.WithTitle("rule-1"))()
alertRule1 := gen.With(gen.WithOrgID(mainOrgID), gen.WithInterval(cfg.BaseInterval), gen.WithTitle("rule-1")).GenerateRef()
ruleStore.PutRule(ctx, alertRule1)
t.Run("on 1st tick alert rule should be evaluated", func(t *testing.T) {
@ -132,7 +132,7 @@ func TestProcessTicks(t *testing.T) {
})
// add alert rule under main org with three base intervals
alertRule2 := models.AlertRuleGen(models.WithOrgID(mainOrgID), models.WithInterval(3*cfg.BaseInterval), models.WithTitle("rule-2"))()
alertRule2 := gen.With(gen.WithOrgID(mainOrgID), gen.WithInterval(3*cfg.BaseInterval), gen.WithTitle("rule-2")).GenerateRef()
ruleStore.PutRule(ctx, alertRule2)
t.Run("on 2nd tick first alert rule should be evaluated", func(t *testing.T) {
@ -317,7 +317,7 @@ func TestProcessTicks(t *testing.T) {
})
// create alert rule with one base interval
alertRule3 := models.AlertRuleGen(models.WithOrgID(mainOrgID), models.WithInterval(cfg.BaseInterval), models.WithTitle("rule-3"))()
alertRule3 := gen.With(gen.WithOrgID(mainOrgID), gen.WithInterval(cfg.BaseInterval), gen.WithTitle("rule-3")).GenerateRef()
ruleStore.PutRule(ctx, alertRule3)
t.Run("on 10th tick a new alert rule should be evaluated", func(t *testing.T) {
@ -361,7 +361,7 @@ func TestSchedule_deleteAlertRule(t *testing.T) {
t.Run("it should stop evaluation loop and remove the controller from registry", func(t *testing.T) {
sch := setupScheduler(t, nil, nil, nil, nil, nil)
ruleFactory := ruleFactoryFromScheduler(sch)
rule := models.AlertRuleGen()()
rule := models.RuleGen.GenerateRef()
key := rule.GetKey()
info, _ := sch.registry.getOrCreate(context.Background(), key, ruleFactory)
sch.deleteAlertRule(key)

View File

@ -16,7 +16,7 @@ import (
func BenchmarkGetOrCreateTest(b *testing.B) {
cache := newCache()
rule := models.AlertRuleGen(func(rule *models.AlertRule) {
rule := models.RuleGen.With(func(rule *models.AlertRule) {
for i := 0; i < 2; i++ {
rule.Labels = data.Labels{
"label-1": "{{ $value }}",
@ -27,7 +27,7 @@ func BenchmarkGetOrCreateTest(b *testing.B) {
"anno-2": "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
}
}
})()
}).GenerateRef()
result := eval.ResultGen(func(r *eval.Result) {
r.Values = map[string]eval.NumberValueCapture{
"A": {

View File

@ -126,7 +126,8 @@ func Test_getOrCreate(t *testing.T) {
l := log.New("test")
c := newCache()
generateRule := models.AlertRuleGen(models.WithNotEmptyLabels(5, "rule-"))
gen := models.RuleGen
generateRule := gen.With(gen.WithNotEmptyLabels(5, "rule-")).GenerateRef
t.Run("should combine all labels", func(t *testing.T) {
rule := generateRule()

View File

@ -14,6 +14,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/annotations"
@ -128,7 +129,7 @@ func createTestAnnotationSutWithStore(t *testing.T, annotations AnnotationStore)
met := metrics.NewHistorianMetrics(prometheus.NewRegistry(), metrics.Subsystem)
rules := fakes.NewRuleStore(t)
rules.Rules[1] = []*models.AlertRule{
models.AlertRuleGen(withOrgID(1), withUID("my-rule"))(),
models.RuleGen.With(models.RuleMuts.WithOrgID(1), withUID("my-rule")).GenerateRef(),
}
return NewAnnotationBackend(annotations, rules, met)
}
@ -138,7 +139,7 @@ func createTestAnnotationBackendSutWithMetrics(t *testing.T, met *metrics.Histor
fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo()
rules := fakes.NewRuleStore(t)
rules.Rules[1] = []*models.AlertRule{
models.AlertRuleGen(withOrgID(1), withUID("my-rule"))(),
models.RuleGen.With(models.RuleMuts.WithOrgID(1), withUID("my-rule")).GenerateRef(),
}
dbs := &dashboards.FakeDashboardService{}
dbs.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
@ -150,7 +151,7 @@ func createFailingAnnotationSut(t *testing.T, met *metrics.Historian) *Annotatio
fakeAnnoRepo := &failingAnnotationRepo{}
rules := fakes.NewRuleStore(t)
rules.Rules[1] = []*models.AlertRule{
models.AlertRuleGen(withOrgID(1), withUID("my-rule"))(),
models.RuleGen.With(models.RuleMuts.WithOrgID(1), withUID("my-rule")).GenerateRef(),
}
dbs := &dashboards.FakeDashboardService{}
dbs.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
@ -169,12 +170,6 @@ func createAnnotation() annotations.Item {
}
}
func withOrgID(orgId int64) func(rule *models.AlertRule) {
return func(rule *models.AlertRule) {
rule.OrgID = orgId
}
}
func TestBuildAnnotations(t *testing.T) {
t.Run("data wraps nil values when values are nil", func(t *testing.T) {
logger := log.NewNopLogger()
@ -230,7 +225,7 @@ func makeStateTransition() state.StateTransition {
}
}
func withUID(uid string) func(rule *models.AlertRule) {
func withUID(uid string) models.AlertRuleMutator {
return func(rule *models.AlertRule) {
rule.UID = uid
}

View File

@ -146,11 +146,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
}
baseRuleWith := func(mutators ...ngmodels.AlertRuleMutator) *ngmodels.AlertRule {
r := ngmodels.CopyRule(baseRule)
for _, mutator := range mutators {
mutator(r)
}
return r
return ngmodels.CopyRule(baseRule, mutators...)
}
newEvaluation := func(evalTime time.Time, evalState eval.State) Evaluation {
@ -397,7 +393,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[1:alerting,2:normal] and 'for'>0 at t1",
alertRule: baseRuleWith(ngmodels.WithForNTimes(3)),
alertRule: baseRuleWith(ngmodels.RuleMuts.WithForNTimes(3)),
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
@ -483,7 +479,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[1:alerting] t2[1:alerting] t3[1:alerting] and 'for'=2 at t1,t2,t3",
alertRule: baseRuleWith(ngmodels.WithForNTimes(2)),
alertRule: baseRuleWith(ngmodels.RuleMuts.WithForNTimes(2)),
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
@ -547,7 +543,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[1:alerting], t2[1:normal] and 'for'=2 at t2",
alertRule: baseRuleWith(ngmodels.WithForNTimes(2)),
alertRule: baseRuleWith(ngmodels.RuleMuts.WithForNTimes(2)),
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
@ -740,7 +736,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[{}:alerting] and 'for'>0 at t1",
alertRule: baseRuleWith(ngmodels.WithForNTimes(3)),
alertRule: baseRuleWith(ngmodels.RuleMuts.WithForNTimes(3)),
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting)),
@ -809,10 +805,10 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
t.Run("no-data", func(t *testing.T) {
rules := map[ngmodels.NoDataState]*ngmodels.AlertRule{
ngmodels.NoData: baseRuleWith(ngmodels.WithNoDataExecAs(ngmodels.NoData)),
ngmodels.Alerting: baseRuleWith(ngmodels.WithNoDataExecAs(ngmodels.Alerting)),
ngmodels.OK: baseRuleWith(ngmodels.WithNoDataExecAs(ngmodels.OK)),
ngmodels.KeepLast: baseRuleWith(ngmodels.WithNoDataExecAs(ngmodels.KeepLast)),
ngmodels.NoData: baseRuleWith(ngmodels.RuleMuts.WithNoDataExecAs(ngmodels.NoData)),
ngmodels.Alerting: baseRuleWith(ngmodels.RuleMuts.WithNoDataExecAs(ngmodels.Alerting)),
ngmodels.OK: baseRuleWith(ngmodels.RuleMuts.WithNoDataExecAs(ngmodels.OK)),
ngmodels.KeepLast: baseRuleWith(ngmodels.RuleMuts.WithNoDataExecAs(ngmodels.KeepLast)),
}
type noDataTestCase struct {
@ -829,10 +825,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
for stateExec, rule := range rules {
r := rule
if len(tc.ruleMutators) > 0 {
r = ngmodels.CopyRule(r)
for _, mutateRule := range tc.ruleMutators {
mutateRule(r)
}
r = ngmodels.CopyRule(r, tc.ruleMutators...)
}
t.Run(fmt.Sprintf("execute as %s", stateExec), func(t *testing.T) {
expectedTransitions, ok := tc.expectedTransitionsApplyNoDataErrorToAllStates[stateExec]
@ -1524,7 +1517,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[1:normal,2:alerting] t2[NoData] t3[NoData] and 'for'=1 at t2*,t3",
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(1)},
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(1)},
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -1953,7 +1946,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[1:alerting] t2[NoData] t3[1:alerting] and 'for'=2 at t3",
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(2)},
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(2)},
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
@ -2678,7 +2671,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[{}:alerting] t2[NoData] t3[{}:alerting] and 'for'=2 at t2*,t3",
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(2)},
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(2)},
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting)),
@ -2850,10 +2843,10 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
t.Run("error", func(t *testing.T) {
rules := map[ngmodels.ExecutionErrorState]*ngmodels.AlertRule{
ngmodels.ErrorErrState: baseRuleWith(ngmodels.WithErrorExecAs(ngmodels.ErrorErrState)),
ngmodels.AlertingErrState: baseRuleWith(ngmodels.WithErrorExecAs(ngmodels.AlertingErrState)),
ngmodels.OkErrState: baseRuleWith(ngmodels.WithErrorExecAs(ngmodels.OkErrState)),
ngmodels.KeepLastErrState: baseRuleWith(ngmodels.WithErrorExecAs(ngmodels.KeepLastErrState)),
ngmodels.ErrorErrState: baseRuleWith(ngmodels.RuleMuts.WithErrorExecAs(ngmodels.ErrorErrState)),
ngmodels.AlertingErrState: baseRuleWith(ngmodels.RuleMuts.WithErrorExecAs(ngmodels.AlertingErrState)),
ngmodels.OkErrState: baseRuleWith(ngmodels.RuleMuts.WithErrorExecAs(ngmodels.OkErrState)),
ngmodels.KeepLastErrState: baseRuleWith(ngmodels.RuleMuts.WithErrorExecAs(ngmodels.KeepLastErrState)),
}
cacheID := func(lbls data.Labels) string {
@ -2879,10 +2872,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
for stateExec, rule := range rules {
r := rule
if len(tc.ruleMutators) > 0 {
r = ngmodels.CopyRule(r)
for _, mutateRule := range tc.ruleMutators {
mutateRule(r)
}
r = ngmodels.CopyRule(r, tc.ruleMutators...)
}
t.Run(fmt.Sprintf("execute as %s", stateExec), func(t *testing.T) {
expectedTransitions, ok := tc.expectedTransitionsApplyNoDataErrorToAllStates[stateExec]
@ -3084,7 +3074,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[1:alerting] t2[QueryError] and 'for'=1 at t2",
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(1)},
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(1)},
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
@ -3630,7 +3620,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[{}:alerting] t2[QueryError] and 'for'=1 at t1*,t2",
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(1)},
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(1)},
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting)),
@ -3736,7 +3726,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
},
{
desc: "t1[{}:alerting] t2[QueryError] t3[{}:alerting] and 'for'=2 at t2,t3",
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(2)},
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(2)},
results: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting)),

View File

@ -311,7 +311,7 @@ func TestProcessEvalResults(t *testing.T) {
t2 := tn(2)
t3 := tn(3)
m := models.RuleMuts
baseRule := &models.AlertRule{
OrgID: 1,
Title: "test_title",
@ -340,10 +340,7 @@ func TestProcessEvalResults(t *testing.T) {
}
baseRuleWith := func(mutators ...models.AlertRuleMutator) *models.AlertRule {
r := models.CopyRule(baseRule)
for _, mutator := range mutators {
mutator(r)
}
r := models.CopyRule(baseRule, mutators...)
return r
}
@ -500,7 +497,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> alerting when For is set",
alertRule: baseRuleWith(models.WithForNTimes(2)),
alertRule: baseRuleWith(m.WithForNTimes(2)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -533,7 +530,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> alerting -> noData -> alerting when For is set",
alertRule: baseRuleWith(models.WithForNTimes(2)),
alertRule: baseRuleWith(m.WithForNTimes(2)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -569,7 +566,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "pending -> alerting -> noData when For is set and NoDataState is NoData",
alertRule: baseRuleWith(models.WithForNTimes(2)),
alertRule: baseRuleWith(m.WithForNTimes(2)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
@ -602,7 +599,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> pending when For is set but not exceeded and first result is normal",
alertRule: baseRuleWith(models.WithForNTimes(2)),
alertRule: baseRuleWith(m.WithForNTimes(2)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -630,7 +627,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> pending when For is set but not exceeded and first result is alerting",
alertRule: baseRuleWith(models.WithForNTimes(6)),
alertRule: baseRuleWith(m.WithForNTimes(6)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
@ -657,7 +654,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> pending when For is set but not exceeded, result is NoData and NoDataState is alerting",
alertRule: baseRuleWith(models.WithForNTimes(6), models.WithNoDataExecAs(models.Alerting)),
alertRule: baseRuleWith(m.WithForNTimes(6), m.WithNoDataExecAs(models.Alerting)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -685,7 +682,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> alerting when For is exceeded, result is NoData and NoDataState is alerting",
alertRule: baseRuleWith(models.WithForNTimes(3), models.WithNoDataExecAs(models.Alerting)),
alertRule: baseRuleWith(m.WithForNTimes(3), m.WithNoDataExecAs(models.Alerting)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -877,7 +874,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> normal (NoData, KeepLastState) -> alerting -> alerting (NoData, KeepLastState) - keeps last state when result is NoData and NoDataState is KeepLast",
alertRule: baseRuleWith(models.WithForNTimes(0), models.WithNoDataExecAs(models.KeepLast)),
alertRule: baseRuleWith(m.WithForNTimes(0), m.WithNoDataExecAs(models.KeepLast)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -913,7 +910,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> pending -> pending (NoData, KeepLastState) -> alerting (NoData, KeepLastState) - keep last state respects For when result is NoData",
alertRule: baseRuleWith(models.WithForNTimes(2), models.WithNoDataExecAs(models.KeepLast)),
alertRule: baseRuleWith(m.WithForNTimes(2), m.WithNoDataExecAs(models.KeepLast)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -947,7 +944,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> normal when result is NoData and NoDataState is ok",
alertRule: baseRuleWith(models.WithNoDataExecAs(models.OK)),
alertRule: baseRuleWith(m.WithNoDataExecAs(models.OK)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -975,7 +972,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> pending when For is set but not exceeded, result is Error and ExecErrState is Alerting",
alertRule: baseRuleWith(models.WithForNTimes(6), models.WithErrorExecAs(models.AlertingErrState)),
alertRule: baseRuleWith(m.WithForNTimes(6), m.WithErrorExecAs(models.AlertingErrState)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -1004,7 +1001,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> alerting when For is exceeded, result is Error and ExecErrState is Alerting",
alertRule: baseRuleWith(models.WithForNTimes(3), models.WithErrorExecAs(models.AlertingErrState)),
alertRule: baseRuleWith(m.WithForNTimes(3), m.WithErrorExecAs(models.AlertingErrState)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -1043,7 +1040,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> error when result is Error and ExecErrState is Error",
alertRule: baseRuleWith(models.WithForNTimes(6), models.WithErrorExecAs(models.ErrorErrState)),
alertRule: baseRuleWith(m.WithForNTimes(6), m.WithErrorExecAs(models.ErrorErrState)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -1084,7 +1081,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> normal (Error, KeepLastState) -> alerting -> alerting (Error, KeepLastState) - keeps last state when result is Error and ExecErrState is KeepLast",
alertRule: baseRuleWith(models.WithForNTimes(0), models.WithErrorExecAs(models.KeepLastErrState)),
alertRule: baseRuleWith(m.WithForNTimes(0), m.WithErrorExecAs(models.KeepLastErrState)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -1120,7 +1117,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> pending -> pending (Error, KeepLastState) -> alerting (Error, KeepLastState) - keep last state respects For when result is Error",
alertRule: baseRuleWith(models.WithForNTimes(2), models.WithErrorExecAs(models.KeepLastErrState)),
alertRule: baseRuleWith(m.WithForNTimes(2), m.WithErrorExecAs(models.KeepLastErrState)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -1154,7 +1151,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> normal when result is Error and ExecErrState is OK",
alertRule: baseRuleWith(models.WithForNTimes(6), models.WithErrorExecAs(models.OkErrState)),
alertRule: baseRuleWith(m.WithForNTimes(6), m.WithErrorExecAs(models.OkErrState)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -1182,7 +1179,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "alerting -> normal when result is Error and ExecErrState is OK",
alertRule: baseRuleWith(models.WithForNTimes(6), models.WithErrorExecAs(models.OkErrState)),
alertRule: baseRuleWith(m.WithForNTimes(6), m.WithErrorExecAs(models.OkErrState)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
@ -1210,7 +1207,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> alerting -> error when result is Error and ExecErrorState is Error",
alertRule: baseRuleWith(models.WithForNTimes(2)),
alertRule: baseRuleWith(m.WithForNTimes(2)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
@ -1250,7 +1247,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> alerting -> error -> alerting - it should clear the error",
alertRule: baseRuleWith(models.WithForNTimes(3)),
alertRule: baseRuleWith(m.WithForNTimes(3)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -1284,7 +1281,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "normal -> alerting -> error -> no data - it should clear the error",
alertRule: baseRuleWith(models.WithForNTimes(3)),
alertRule: baseRuleWith(m.WithForNTimes(3)),
evalResults: map[time.Time]eval.Results{
t1: {
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
@ -1319,8 +1316,8 @@ func TestProcessEvalResults(t *testing.T) {
{
desc: "template is correctly expanded",
alertRule: baseRuleWith(
models.WithAnnotations(map[string]string{"summary": "{{$labels.pod}} is down in {{$labels.cluster}} cluster -> {{$labels.namespace}} namespace"}),
models.WithLabels(map[string]string{"label": "test", "job": "{{$labels.namespace}}/{{$labels.pod}}"}),
m.WithAnnotations(map[string]string{"summary": "{{$labels.pod}} is down in {{$labels.cluster}} cluster -> {{$labels.namespace}} namespace"}),
m.WithLabels(map[string]string{"label": "test", "job": "{{$labels.namespace}}/{{$labels.pod}}"}),
),
evalResults: map[time.Time]eval.Results{
t1: {
@ -1359,7 +1356,7 @@ func TestProcessEvalResults(t *testing.T) {
},
{
desc: "classic condition, execution Error as Error (alerting -> query error -> alerting)",
alertRule: baseRuleWith(models.WithErrorExecAs(models.ErrorErrState)),
alertRule: baseRuleWith(m.WithErrorExecAs(models.ErrorErrState)),
expectedAnnotations: 3,
evalResults: map[time.Time]eval.Results{
t1: {
@ -1512,7 +1509,7 @@ func TestProcessEvalResults(t *testing.T) {
}
statePersister := state.NewSyncStatePersisiter(log.New("ngalert.state.manager.persist"), cfg)
st := state.NewManager(cfg, statePersister)
rule := models.AlertRuleGen()()
rule := models.RuleGen.GenerateRef()
var results = eval.GenerateResults(rand.Intn(4)+1, eval.ResultGen(eval.WithEvaluatedAt(clk.Now())))
states := st.ProcessEvalResults(context.Background(), clk.Now(), rule, results, make(data.Labels))
@ -1747,7 +1744,8 @@ func TestStaleResults(t *testing.T) {
}
st := state.NewManager(cfg, state.NewNoopPersister())
rule := models.AlertRuleGen(models.WithFor(0))()
gen := models.RuleGen
rule := gen.With(gen.WithFor(0)).GenerateRef()
initResults := eval.Results{
eval.ResultGen(eval.WithEvaluatedAt(clk.Now()))(),

View File

@ -706,8 +706,7 @@ func TestParseFormattedState(t *testing.T) {
func TestGetRuleExtraLabels(t *testing.T) {
logger := log.New()
rule := ngmodels.AlertRuleGen()()
rule.NotificationSettings = nil
rule := ngmodels.RuleGen.With(ngmodels.RuleMuts.WithNoNotificationSettings()).GenerateRef()
folderTitle := uuid.NewString()
ns := ngmodels.NotificationSettings{

View File

@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"strings"
"sync"
"testing"
"time"
@ -49,10 +48,11 @@ func TestIntegrationUpdateAlertRules(t *testing.T) {
FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()),
Logger: &logtest.Fake{},
}
generator := models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval), models.WithUniqueID())
gen := models.RuleGen
gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval))
t.Run("should increase version", func(t *testing.T) {
rule := createRule(t, store, generator)
rule := createRule(t, store, gen)
newRule := models.CopyRule(rule)
newRule.Title = util.GenerateShortUID()
err := store.UpdateAlertRules(context.Background(), []models.UpdateRule{{
@ -74,7 +74,7 @@ func TestIntegrationUpdateAlertRules(t *testing.T) {
})
t.Run("should fail due to optimistic locking if version does not match", func(t *testing.T) {
rule := createRule(t, store, generator)
rule := createRule(t, store, gen)
rule.Version-- // simulate version discrepancy
newRule := models.CopyRule(rule)
@ -104,13 +104,14 @@ func TestIntegrationUpdateAlertRulesWithUniqueConstraintViolation(t *testing.T)
Logger: &logtest.Fake{},
}
idMutator := models.WithUniqueID()
gen := models.RuleGen
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)
gen := gen.With(
gen.WithOrgID(orgID),
gen.WithIntervalMatching(store.Cfg.BaseInterval),
gen.WithNamespaceUID(namespaceUID),
)
return createRule(t, store, gen)
}
t.Run("should handle update chains without unique constraint violation", func(t *testing.T) {
@ -360,9 +361,11 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
FeatureToggles: featuremgmt.WithFeatures(),
}
generator := models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval), models.WithUniqueID(), models.WithUniqueOrgID())
rule1 := createRule(t, store, generator)
rule2 := createRule(t, store, generator)
gen := models.RuleGen
gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval), gen.WithUniqueOrgID())
rule1 := createRule(t, store, gen)
rule2 := createRule(t, store, gen)
parentFolderUid := uuid.NewString()
parentFolderTitle := "Very Parent Folder"
@ -372,7 +375,7 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
createFolder(t, store, rule1.NamespaceUID, rule1FolderTitle, rule1.OrgID, parentFolderUid)
createFolder(t, store, rule2.NamespaceUID, rule2FolderTitle, rule2.OrgID, "")
createFolder(t, store, rule2.NamespaceUID, "same UID folder", generator().OrgID, "") // create a folder with the same UID but in the different org
createFolder(t, store, rule2.NamespaceUID, "same UID folder", gen.GenerateRef().OrgID, "") // create a folder with the same UID but in the different org
tc := []struct {
name string
@ -458,13 +461,6 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
})
}
func withIntervalMatching(baseInterval time.Duration) func(*models.AlertRule) {
return func(rule *models.AlertRule) {
rule.IntervalSeconds = int64(baseInterval.Seconds()) * (rand.Int63n(10) + 1)
rule.For = time.Duration(rule.IntervalSeconds*rand.Int63n(9)+1) * time.Second
}
}
func TestIntegration_CountAlertRules(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
@ -614,7 +610,12 @@ func TestIntegrationInsertAlertRules(t *testing.T) {
Cfg: cfg.UnifiedAlerting,
}
rules := models.GenerateAlertRules(5, models.AlertRuleGen(models.WithOrgID(orgID), withIntervalMatching(store.Cfg.BaseInterval)))
gen := models.RuleGen
rules := gen.With(
gen.WithOrgID(orgID),
gen.WithIntervalMatching(store.Cfg.BaseInterval),
).GenerateManyRef(5)
deref := make([]models.AlertRule, 0, len(rules))
for _, rule := range rules {
deref = append(deref, *rule)
@ -683,21 +684,14 @@ func TestIntegrationAlertRulesNotificationSettings(t *testing.T) {
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
gen := models.RuleGen
gen = gen.With(gen.WithOrgID(1), gen.WithIntervalMatching(store.Cfg.BaseInterval))
rules := gen.GenerateManyRef(3)
receiveRules := gen.With(gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithReceiver(receiverName)))).GenerateManyRef(3)
noise := gen.With(gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithMuteTimeIntervals(receiverName)))).GenerateManyRef(3)
deref := make([]models.AlertRule, 0, len(rules)+len(receiveRules)+len(noise))
for _, rule := range append(append(rules, receiveRules...), noise...) {
r := *rule
@ -768,36 +762,21 @@ func TestIntegrationListNotificationSettings(t *testing.T) {
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)
}
gen := models.RuleGen
gen = gen.With(gen.WithOrgID(1), gen.WithIntervalMatching(store.Cfg.BaseInterval))
rulesWithNotifications := gen.With(
gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithReceiver(receiverName))),
).GenerateMany(5)
rulesInOtherOrg := gen.With(
gen.WithOrgID(2),
gen.WithNotificationSettingsGen(models.NotificationSettingsGen()),
).GenerateMany(5)
rulesWithNoNotifications := gen.With(gen.WithNoNotificationSettings()).GenerateMany(5)
deref := append(append(rulesWithNotifications, rulesWithNoNotifications...), rulesInOtherOrg...)
_, err := store.InsertAlertRules(context.Background(), deref)
require.NoError(t, err)
@ -832,12 +811,12 @@ func TestIntegrationListNotificationSettings(t *testing.T) {
// createAlertRule creates an alert rule in the database and returns it.
// If a generator is not specified, uniqueness of primary key is not guaranteed.
func createRule(t *testing.T, store *DBstore, generate func() *models.AlertRule) *models.AlertRule {
func createRule(t *testing.T, store *DBstore, generator *models.AlertRuleGenerator) *models.AlertRule {
t.Helper()
if generate == nil {
generate = models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval))
if generator == nil {
generator = models.RuleGen.With(models.RuleMuts.WithIntervalMatching(store.Cfg.BaseInterval))
}
rule := generate()
rule := generator.GenerateRef()
err := store.SQLStore.WithDbSession(context.Background(), func(sess *db.Session) error {
_, err := sess.Table(models.AlertRule{}).InsertOne(rule)
if err != nil {

View File

@ -19,15 +19,16 @@ import (
func TestCalculateChanges(t *testing.T) {
orgId := int64(rand.Int31())
gen := models.RuleGen
t.Run("detects alerts that need to be added", func(t *testing.T) {
fakeStore := fakes.NewRuleStore(t)
groupKey := models.GenerateGroupKey(orgId)
rules := models.GenerateAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withOrgID(orgId), simulateSubmitted, withoutUID))
rules := gen.With(gen.WithOrgID(orgId), simulateSubmitted, withoutUID).GenerateMany(1, 5)
submitted := make([]*models.AlertRuleWithOptionals, 0, len(rules))
for _, rule := range rules {
submitted = append(submitted, &models.AlertRuleWithOptionals{AlertRule: *rule})
submitted = append(submitted, &models.AlertRuleWithOptionals{AlertRule: rule})
}
changes, err := CalculateChanges(context.Background(), fakeStore, groupKey, submitted)
@ -50,8 +51,8 @@ func TestCalculateChanges(t *testing.T) {
t.Run("detects alerts that need to be deleted", func(t *testing.T) {
groupKey := models.GenerateGroupKey(orgId)
inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withGroupKey(groupKey)))
inDatabase := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(1, 5)
inDatabaseMap := groupByUID(t, inDatabase)
fakeStore := fakes.NewRuleStore(t)
fakeStore.PutRule(context.Background(), inDatabase...)
@ -73,8 +74,11 @@ func TestCalculateChanges(t *testing.T) {
t.Run("should detect alerts that needs to be updated", func(t *testing.T) {
groupKey := models.GenerateGroupKey(orgId)
inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withGroupKey(groupKey)))
submittedMap, rules := models.GenerateUniqueAlertRules(len(inDatabase), models.AlertRuleGen(simulateSubmitted, withGroupKey(groupKey), withUIDs(inDatabaseMap)))
inDatabase := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(1, 5)
inDatabaseMap := groupByUID(t, inDatabase)
rules := gen.With(simulateSubmitted, gen.WithGroupKey(groupKey), withUIDs(inDatabaseMap)).GenerateManyRef(len(inDatabase), len(inDatabase))
submittedMap := groupByUID(t, rules)
submitted := make([]*models.AlertRuleWithOptionals, 0, len(rules))
for _, rule := range rules {
submitted = append(submitted, &models.AlertRuleWithOptionals{AlertRule: *rule})
@ -104,7 +108,7 @@ func TestCalculateChanges(t *testing.T) {
t.Run("should include only if there are changes ignoring specific fields", func(t *testing.T) {
groupKey := models.GenerateGroupKey(orgId)
_, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withGroupKey(groupKey)))
inDatabase := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(1, 5)
submitted := make([]*models.AlertRuleWithOptionals, 0, len(inDatabase))
for _, rule := range inDatabase {
@ -132,7 +136,7 @@ func TestCalculateChanges(t *testing.T) {
t.Run("should patch rule with UID specified by existing rule", func(t *testing.T) {
testCases := []struct {
name string
mutator func(r *models.AlertRule)
mutator models.AlertRuleMutator
}{
{
name: "title is empty",
@ -167,7 +171,7 @@ func TestCalculateChanges(t *testing.T) {
},
}
dbRule := models.AlertRuleGen(withOrgID(orgId))()
dbRule := gen.With(gen.WithOrgID(orgId)).GenerateRef()
fakeStore := fakes.NewRuleStore(t)
fakeStore.PutRule(context.Background(), dbRule)
@ -176,7 +180,7 @@ func TestCalculateChanges(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
expected := models.AlertRuleGen(simulateSubmitted, testCase.mutator)()
expected := gen.With(simulateSubmitted, testCase.mutator).GenerateRef()
expected.UID = dbRule.UID
submitted := *expected
changes, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}})
@ -193,7 +197,8 @@ func TestCalculateChanges(t *testing.T) {
t.Run("should be able to find alerts by UID in other group/namespace", func(t *testing.T) {
sourceGroupKey := models.GenerateGroupKey(orgId)
inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(10)+10, models.AlertRuleGen(withGroupKey(sourceGroupKey)))
inDatabase := gen.With(gen.WithGroupKey(sourceGroupKey)).GenerateManyRef(10, 20)
inDatabaseMap := groupByUID(t, inDatabase)
fakeStore := fakes.NewRuleStore(t)
fakeStore.PutRule(context.Background(), inDatabase...)
@ -207,7 +212,8 @@ func TestCalculateChanges(t *testing.T) {
RuleGroup: groupName,
}
submittedMap, rules := models.GenerateUniqueAlertRules(rand.Intn(len(inDatabase)-5)+5, models.AlertRuleGen(simulateSubmitted, withGroupKey(groupKey), withUIDs(inDatabaseMap)))
rules := gen.With(simulateSubmitted, gen.WithGroupKey(groupKey), withUIDs(inDatabaseMap)).GenerateManyRef(5, len(inDatabase))
submittedMap := groupByUID(t, rules)
submitted := make([]*models.AlertRuleWithOptionals, 0, len(rules))
for _, rule := range rules {
submitted = append(submitted, &models.AlertRuleWithOptionals{AlertRule: *rule})
@ -237,10 +243,10 @@ func TestCalculateChanges(t *testing.T) {
t.Run("should fail when submitted rule has UID that does not exist in db", func(t *testing.T) {
fakeStore := fakes.NewRuleStore(t)
groupKey := models.GenerateGroupKey(orgId)
submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted)()
submitted := gen.With(gen.WithOrgID(orgId), simulateSubmitted).Generate()
require.NotEqual(t, "", submitted.UID)
_, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: *submitted}})
_, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}})
require.Error(t, err)
})
@ -256,9 +262,9 @@ func TestCalculateChanges(t *testing.T) {
}
groupKey := models.GenerateGroupKey(orgId)
submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted, withoutUID)()
submitted := gen.With(gen.WithOrgID(orgId), simulateSubmitted, withoutUID).Generate()
_, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: *submitted}})
_, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}})
require.ErrorIs(t, err, expectedErr)
})
@ -274,19 +280,20 @@ func TestCalculateChanges(t *testing.T) {
}
groupKey := models.GenerateGroupKey(orgId)
submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted)()
submitted := gen.With(gen.WithOrgID(orgId), simulateSubmitted).Generate()
_, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: *submitted}})
_, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}})
require.ErrorIs(t, err, expectedErr)
})
}
func TestCalculateAutomaticChanges(t *testing.T) {
orgID := rand.Int63()
gen := models.RuleGen
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)))
rules := gen.With(gen.WithGroupKey(group)).GenerateManyRef(10)
// 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 {
@ -309,7 +316,7 @@ func TestCalculateAutomaticChanges(t *testing.T) {
AffectedGroups: map[models.AlertRuleGroupKey]models.RulesGroup{
group: copies,
},
New: models.GenerateAlertRules(2, models.AlertRuleGen(withGroupKey(group))),
New: gen.With(gen.WithGroupKey(group)).GenerateManyRef(2),
Update: updates,
Delete: rules[5:7],
}
@ -337,9 +344,9 @@ func TestCalculateAutomaticChanges(t *testing.T) {
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()))
rules := gen.With(gen.WithGroupKey(group), gen.WithSequentialGroupIndex()).GenerateManyRef(3)
group2 := models.GenerateGroupKey(orgID)
rules2 := models.GenerateAlertRules(4, models.AlertRuleGen(withGroupKey(group2), models.WithSequentialGroupIndex()))
rules2 := gen.With(gen.WithGroupKey(group2), gen.WithSequentialGroupIndex()).GenerateManyRef(4)
movedIndex := rand.Intn(len(rules2))
movedRule := rules2[movedIndex]
@ -417,9 +424,10 @@ func TestCalculateAutomaticChanges(t *testing.T) {
}
func TestCalculateRuleGroupDelete(t *testing.T) {
gen := models.RuleGen
fakeStore := fakes.NewRuleStore(t)
groupKey := models.GenerateGroupKey(1)
otherRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithOrgID(groupKey.OrgID), models.WithNamespaceUIDNotIn(groupKey.NamespaceUID)))
otherRules := gen.With(gen.WithOrgID(groupKey.OrgID), gen.WithNamespaceUIDNotIn(groupKey.NamespaceUID)).GenerateManyRef(3)
fakeStore.Rules[groupKey.OrgID] = otherRules
t.Run("NotFound when group does not exist", func(t *testing.T) {
@ -429,7 +437,7 @@ func TestCalculateRuleGroupDelete(t *testing.T) {
})
t.Run("set AffectedGroups when a rule refers to an existing group", func(t *testing.T) {
groupRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey)))
groupRules := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(3)
fakeStore.Rules[groupKey.OrgID] = append(fakeStore.Rules[groupKey.OrgID], groupRules...)
delta, err := CalculateRuleGroupDelete(context.Background(), fakeStore, groupKey)
@ -447,9 +455,10 @@ func TestCalculateRuleGroupDelete(t *testing.T) {
}
func TestCalculateRuleDelete(t *testing.T) {
gen := models.RuleGen
fakeStore := fakes.NewRuleStore(t)
rule := models.AlertRuleGen()()
otherRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithOrgID(rule.OrgID), models.WithNamespaceUIDNotIn(rule.NamespaceUID)))
rule := gen.GenerateRef()
otherRules := gen.With(gen.WithOrgID(rule.OrgID), gen.WithNamespaceUIDNotIn(rule.NamespaceUID)).GenerateManyRef(3)
fakeStore.Rules[rule.OrgID] = otherRules
t.Run("nil when a rule does not exist", func(t *testing.T) {
@ -459,7 +468,7 @@ func TestCalculateRuleDelete(t *testing.T) {
})
t.Run("set AffectedGroups when a rule refers to an existing group", func(t *testing.T) {
groupRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(rule.GetGroupKey())))
groupRules := gen.With(gen.WithGroupKey(rule.GetGroupKey())).GenerateManyRef(3)
groupRules = append(groupRules, rule)
fakeStore.Rules[rule.OrgID] = append(fakeStore.Rules[rule.OrgID], groupRules...)
@ -479,10 +488,11 @@ func TestCalculateRuleDelete(t *testing.T) {
}
func TestCalculateRuleUpdate(t *testing.T) {
gen := models.RuleGen
fakeStore := fakes.NewRuleStore(t)
rule := models.AlertRuleGen()()
otherRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithOrgID(rule.OrgID), models.WithNamespaceUIDNotIn(rule.NamespaceUID)))
groupRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(rule.GetGroupKey())))
rule := gen.GenerateRef()
otherRules := gen.With(gen.WithOrgID(rule.OrgID), gen.WithNamespaceUIDNotIn(rule.NamespaceUID)).GenerateManyRef(3)
groupRules := gen.With(gen.WithGroupKey(rule.GetGroupKey())).GenerateManyRef(3)
groupRules = append(groupRules, rule)
fakeStore.Rules[rule.OrgID] = append(otherRules, groupRules...)
@ -520,7 +530,7 @@ func TestCalculateRuleUpdate(t *testing.T) {
t.Run("when a rule is moved between groups", func(t *testing.T) {
sourceGroupKey := rule.GetGroupKey()
targetGroupKey := models.GenerateGroupKey(rule.OrgID)
targetGroup := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(targetGroupKey)))
targetGroup := gen.With(gen.WithGroupKey(targetGroupKey)).GenerateManyRef(3)
fakeStore.Rules[rule.OrgID] = append(fakeStore.Rules[rule.OrgID], targetGroup...)
cp := models.CopyRule(rule)
@ -548,9 +558,10 @@ func TestCalculateRuleUpdate(t *testing.T) {
}
func TestCalculateRuleCreate(t *testing.T) {
gen := models.RuleGen
t.Run("when a rule refers to a new group", func(t *testing.T) {
fakeStore := fakes.NewRuleStore(t)
rule := models.AlertRuleGen()()
rule := gen.GenerateRef()
delta, err := CalculateRuleCreate(context.Background(), fakeStore, rule)
require.NoError(t, err)
@ -565,10 +576,10 @@ func TestCalculateRuleCreate(t *testing.T) {
t.Run("when a rule refers to an existing group", func(t *testing.T) {
fakeStore := fakes.NewRuleStore(t)
rule := models.AlertRuleGen()()
rule := gen.GenerateRef()
groupRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(rule.GetGroupKey())))
otherRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithOrgID(rule.OrgID), models.WithNamespaceUIDNotIn(rule.NamespaceUID)))
groupRules := gen.With(gen.WithGroupKey(rule.GetGroupKey())).GenerateManyRef(3)
otherRules := gen.With(gen.WithGroupKey(rule.GetGroupKey()), gen.WithNamespaceUIDNotIn(rule.NamespaceUID)).GenerateManyRef(3)
fakeStore.Rules[rule.OrgID] = append(groupRules, otherRules...)
delta, err := CalculateRuleCreate(context.Background(), fakeStore, rule)
@ -591,25 +602,11 @@ func simulateSubmitted(rule *models.AlertRule) {
rule.Updated = time.Time{}
}
func withOrgID(orgId int64) func(rule *models.AlertRule) {
return func(rule *models.AlertRule) {
rule.OrgID = orgId
}
}
func withoutUID(rule *models.AlertRule) {
rule.UID = ""
}
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
}
}
func withUIDs(uids map[string]*models.AlertRule) func(rule *models.AlertRule) {
func withUIDs(uids map[string]*models.AlertRule) models.AlertRuleMutator {
unused := make([]string, 0, len(uids))
for s := range uids {
unused = append(unused, s)
@ -635,3 +632,14 @@ func randFolder() *folder.Folder {
CreatedBy: 0,
}
}
func groupByUID(t *testing.T, list []*models.AlertRule) map[string]*models.AlertRule {
result := make(map[string]*models.AlertRule, len(list))
for _, rule := range list {
if _, ok := result[rule.UID]; ok {
t.Fatalf("expected unique UID for rule %s but duplicate", rule.UID)
}
result[rule.UID] = rule
}
return result
}