diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index 440ec55d016..4e9c226e30f 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -20,7 +20,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/eval" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/state" - "github.com/grafana/grafana/pkg/services/ngalert/store" + "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" @@ -423,7 +423,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("with many rules in a group", func(t *testing.T) { t.Run("should return sorted", func(t *testing.T) { - ruleStore := store.NewFakeRuleStore(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())) @@ -466,7 +466,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("when fine-grained access is enabled", func(t *testing.T) { t.Run("should return only rules if the user can query all data sources", func(t *testing.T) { - ruleStore := store.NewFakeRuleStore(t) + ruleStore := fakes.NewRuleStore(t) fakeAIM := NewFakeAlertInstanceManager(t) rules := ngmodels.GenerateAlertRules(rand.Intn(4)+2, ngmodels.AlertRuleGen(withOrgID(orgID))) @@ -503,8 +503,8 @@ func TestRouteGetRuleStatuses(t *testing.T) { }) } -func setupAPI(t *testing.T) (*store.FakeRuleStore, *fakeAlertInstanceManager, *acmock.Mock, PrometheusSrv) { - fakeStore := store.NewFakeRuleStore(t) +func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, *acmock.Mock, PrometheusSrv) { + fakeStore := fakes.NewRuleStore(t) fakeAIM := NewFakeAlertInstanceManager(t) acMock := acmock.New().WithDisabled() @@ -518,7 +518,7 @@ func setupAPI(t *testing.T) (*store.FakeRuleStore, *fakeAlertInstanceManager, *a return fakeStore, fakeAIM, acMock, api } -func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *store.FakeRuleStore, query func(r *ngmodels.AlertRule)) { +func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *fakes.RuleStore, query func(r *ngmodels.AlertRule)) { t.Helper() rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID), asFixture(), query)) diff --git a/pkg/services/ngalert/api/api_ruler_test.go b/pkg/services/ngalert/api/api_ruler_test.go index b8dbb4e7939..4a7b2a4cc64 100644 --- a/pkg/services/ngalert/api/api_ruler_test.go +++ b/pkg/services/ngalert/api/api_ruler_test.go @@ -23,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/schedule" "github.com/grafana/grafana/pkg/services/ngalert/store" + "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" @@ -30,22 +31,22 @@ import ( ) func TestRouteDeleteAlertRules(t *testing.T) { - getRecordedCommand := func(ruleStore *store.FakeRuleStore) []store.GenericRecordedQuery { + getRecordedCommand := func(ruleStore *fakes.RuleStore) []fakes.GenericRecordedQuery { results := ruleStore.GetRecordedCommands(func(cmd interface{}) (interface{}, bool) { - c, ok := cmd.(store.GenericRecordedQuery) + c, ok := cmd.(fakes.GenericRecordedQuery) if !ok || c.Name != "DeleteAlertRulesByUID" { return nil, false } return c, ok }) - var result []store.GenericRecordedQuery + var result []fakes.GenericRecordedQuery for _, cmd := range results { - result = append(result, cmd.(store.GenericRecordedQuery)) + result = append(result, cmd.(fakes.GenericRecordedQuery)) } return result } - assertRulesDeleted := func(t *testing.T, expectedRules []*models.AlertRule, ruleStore *store.FakeRuleStore, scheduler *schedule.FakeScheduleService) { + assertRulesDeleted := func(t *testing.T, expectedRules []*models.AlertRule, ruleStore *fakes.RuleStore, scheduler *schedule.FakeScheduleService) { deleteCommands := getRecordedCommand(ruleStore) require.Len(t, deleteCommands, 1) cmd := deleteCommands[0] @@ -73,8 +74,8 @@ func TestRouteDeleteAlertRules(t *testing.T) { orgID := rand.Int63() folder := randFolder() - initFakeRuleStore := func(t *testing.T) *store.FakeRuleStore { - ruleStore := store.NewFakeRuleStore(t) + 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)))...) @@ -267,7 +268,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) { t.Run("should return rules for which user has access to data source", func(t *testing.T) { orgID := rand.Int63() folder := randFolder() - ruleStore := store.NewFakeRuleStore(t) + ruleStore := fakes.NewRuleStore(t) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder))) ruleStore.PutRule(context.Background(), expectedRules...) @@ -303,7 +304,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) { t.Run("should return all rules from folder", func(t *testing.T) { orgID := rand.Int63() folder := randFolder() - ruleStore := store.NewFakeRuleStore(t) + ruleStore := fakes.NewRuleStore(t) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder))) ruleStore.PutRule(context.Background(), expectedRules...) @@ -337,7 +338,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) { t.Run("should return the provenance of the alert rules", func(t *testing.T) { orgID := rand.Int63() folder := randFolder() - ruleStore := store.NewFakeRuleStore(t) + 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))) ruleStore.PutRule(context.Background(), expectedRules...) @@ -378,7 +379,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) { t.Run("should enforce order of rules in the group", func(t *testing.T) { orgID := rand.Int63() folder := randFolder() - ruleStore := store.NewFakeRuleStore(t) + ruleStore := fakes.NewRuleStore(t) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) groupKey := models.GenerateGroupKey(orgID) groupKey.NamespaceUID = folder.Uid @@ -422,7 +423,7 @@ func TestRouteGetRulesConfig(t *testing.T) { t.Run("fine-grained access is enabled", func(t *testing.T) { t.Run("should check access to data source", func(t *testing.T) { orgID := rand.Int63() - ruleStore := store.NewFakeRuleStore(t) + ruleStore := fakes.NewRuleStore(t) folder1 := randFolder() folder2 := randFolder() ruleStore.Folders[orgID] = []*models2.Folder{folder1, folder2} @@ -460,7 +461,7 @@ func TestRouteGetRulesConfig(t *testing.T) { t.Run("should return rules in group sorted by group index", func(t *testing.T) { orgID := rand.Int63() folder := randFolder() - ruleStore := store.NewFakeRuleStore(t) + ruleStore := fakes.NewRuleStore(t) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) groupKey := models.GenerateGroupKey(orgID) groupKey.NamespaceUID = folder.Uid @@ -505,7 +506,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) { t.Run("should check access to data source", func(t *testing.T) { orgID := rand.Int63() folder := randFolder() - ruleStore := store.NewFakeRuleStore(t) + ruleStore := fakes.NewRuleStore(t) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) groupKey := models.GenerateGroupKey(orgID) groupKey.NamespaceUID = folder.Uid @@ -540,7 +541,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) { t.Run("should return rules in group sorted by group index", func(t *testing.T) { orgID := rand.Int63() folder := randFolder() - ruleStore := store.NewFakeRuleStore(t) + ruleStore := fakes.NewRuleStore(t) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) groupKey := models.GenerateGroupKey(orgID) groupKey.NamespaceUID = folder.Uid @@ -636,13 +637,13 @@ func TestVerifyProvisionedRulesNotAffected(t *testing.T) { }) } -func createServiceWithProvenanceStore(ac *acMock.Mock, store *store.FakeRuleStore, scheduler schedule.ScheduleService, provenanceStore provisioning.ProvisioningStore) *RulerSrv { +func createServiceWithProvenanceStore(ac *acMock.Mock, store *fakes.RuleStore, scheduler schedule.ScheduleService, provenanceStore provisioning.ProvisioningStore) *RulerSrv { svc := createService(ac, store, scheduler) svc.provenanceStore = provenanceStore return svc } -func createService(ac *acMock.Mock, store *store.FakeRuleStore, scheduler schedule.ScheduleService) *RulerSrv { +func createService(ac *acMock.Mock, store *fakes.RuleStore, scheduler schedule.ScheduleService) *RulerSrv { return &RulerSrv{ xactManager: store, store: store, diff --git a/pkg/services/ngalert/ngalert_test.go b/pkg/services/ngalert/ngalert_test.go index a1911eb25b4..30540c6f1e7 100644 --- a/pkg/services/ngalert/ngalert_test.go +++ b/pkg/services/ngalert/ngalert_test.go @@ -15,7 +15,7 @@ import ( models2 "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/schedule" - "github.com/grafana/grafana/pkg/services/ngalert/store" + "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" "github.com/grafana/grafana/pkg/util" ) @@ -29,7 +29,7 @@ func Test_subscribeToFolderChanges(t *testing.T) { rules := models.GenerateAlertRules(5, models.AlertRuleGen(models.WithOrgID(orgID), models.WithNamespace(folder))) bus := busmock.New() - db := store.NewFakeRuleStore(t) + db := fakes.NewRuleStore(t) db.Folders[orgID] = append(db.Folders[orgID], folder) db.PutRule(context.Background(), rules...) @@ -49,7 +49,7 @@ func Test_subscribeToFolderChanges(t *testing.T) { require.Eventuallyf(t, func() bool { return len(db.GetRecordedCommands(func(cmd interface{}) (interface{}, bool) { - c, ok := cmd.(store.GenericRecordedQuery) + c, ok := cmd.(fakes.GenericRecordedQuery) if !ok || c.Name != "IncreaseVersionForAllRulesInNamespace" { return nil, false } diff --git a/pkg/services/ngalert/store/deltas_test.go b/pkg/services/ngalert/store/deltas_test.go index 0cd63526482..d61943490df 100644 --- a/pkg/services/ngalert/store/deltas_test.go +++ b/pkg/services/ngalert/store/deltas_test.go @@ -9,6 +9,7 @@ import ( grafana_models "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" "github.com/grafana/grafana/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -19,7 +20,7 @@ func TestCalculateChanges(t *testing.T) { orgId := rand.Int63() t.Run("detects alerts that need to be added", func(t *testing.T) { - fakeStore := NewFakeRuleStore(t) + fakeStore := fakes.NewRuleStore(t) groupKey := models.GenerateGroupKey(orgId) submitted := models.GenerateAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withOrgID(orgId), simulateSubmitted, withoutUID)) @@ -46,7 +47,7 @@ func TestCalculateChanges(t *testing.T) { groupKey := models.GenerateGroupKey(orgId) inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withGroupKey(groupKey))) - fakeStore := NewFakeRuleStore(t) + fakeStore := fakes.NewRuleStore(t) fakeStore.PutRule(context.Background(), inDatabase...) changes, err := CalculateChanges(context.Background(), fakeStore, groupKey, make([]*models.AlertRule, 0)) @@ -70,7 +71,7 @@ func TestCalculateChanges(t *testing.T) { inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withGroupKey(groupKey))) submittedMap, submitted := models.GenerateUniqueAlertRules(len(inDatabase), models.AlertRuleGen(simulateSubmitted, withGroupKey(groupKey), withUIDs(inDatabaseMap))) - fakeStore := NewFakeRuleStore(t) + fakeStore := fakes.NewRuleStore(t) fakeStore.PutRule(context.Background(), inDatabase...) changes, err := CalculateChanges(context.Background(), fakeStore, groupKey, submitted) @@ -108,7 +109,7 @@ func TestCalculateChanges(t *testing.T) { submitted = append(submitted, r) } - fakeStore := NewFakeRuleStore(t) + fakeStore := fakes.NewRuleStore(t) fakeStore.PutRule(context.Background(), inDatabase...) changes, err := CalculateChanges(context.Background(), fakeStore, groupKey, submitted) @@ -159,7 +160,7 @@ func TestCalculateChanges(t *testing.T) { dbRule := models.AlertRuleGen(withOrgID(orgId))() - fakeStore := NewFakeRuleStore(t) + fakeStore := fakes.NewRuleStore(t) fakeStore.PutRule(context.Background(), dbRule) groupKey := models.GenerateGroupKey(orgId) @@ -185,7 +186,7 @@ func TestCalculateChanges(t *testing.T) { sourceGroupKey := models.GenerateGroupKey(orgId) inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(10)+10, models.AlertRuleGen(withGroupKey(sourceGroupKey))) - fakeStore := NewFakeRuleStore(t) + fakeStore := fakes.NewRuleStore(t) fakeStore.PutRule(context.Background(), inDatabase...) namespace := randFolder() @@ -221,7 +222,7 @@ 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 := NewFakeRuleStore(t) + fakeStore := fakes.NewRuleStore(t) groupKey := models.GenerateGroupKey(orgId) submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted)() require.NotEqual(t, "", submitted.UID) @@ -231,7 +232,7 @@ func TestCalculateChanges(t *testing.T) { }) t.Run("should fail if cannot fetch current rules in the group", func(t *testing.T) { - fakeStore := NewFakeRuleStore(t) + fakeStore := fakes.NewRuleStore(t) expectedErr := errors.New("TEST ERROR") fakeStore.Hook = func(cmd interface{}) error { switch cmd.(type) { @@ -249,7 +250,7 @@ func TestCalculateChanges(t *testing.T) { }) t.Run("should fail if cannot fetch rule by UID", func(t *testing.T) { - fakeStore := NewFakeRuleStore(t) + fakeStore := fakes.NewRuleStore(t) expectedErr := errors.New("TEST ERROR") fakeStore.Hook = func(cmd interface{}) error { switch cmd.(type) { diff --git a/pkg/services/ngalert/store/testing.go b/pkg/services/ngalert/store/testing.go index 5f6d2c3dd82..51770c100ce 100644 --- a/pkg/services/ngalert/store/testing.go +++ b/pkg/services/ngalert/store/testing.go @@ -2,16 +2,10 @@ package store import ( "context" - "fmt" - "math/rand" "strings" "sync" "testing" - "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/util" - - models2 "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/ngalert/models" ) @@ -66,331 +60,6 @@ func (s *FakeImageStore) SaveImage(_ context.Context, image *models.Image) error return nil } -func NewFakeRuleStore(t *testing.T) *FakeRuleStore { - return &FakeRuleStore{ - t: t, - Rules: map[int64][]*models.AlertRule{}, - Hook: func(interface{}) error { - return nil - }, - Folders: map[int64][]*models2.Folder{}, - } -} - -// FakeRuleStore mocks the RuleStore of the scheduler. -type FakeRuleStore struct { - t *testing.T - mtx sync.Mutex - // OrgID -> RuleGroup -> Namespace -> Rules - Rules map[int64][]*models.AlertRule - Hook func(cmd interface{}) error // use Hook if you need to intercept some query and return an error - RecordedOps []interface{} - Folders map[int64][]*models2.Folder -} - -type GenericRecordedQuery struct { - Name string - Params []interface{} -} - -// PutRule puts the rule in the Rules map. If there are existing rule in the same namespace, they will be overwritten -func (f *FakeRuleStore) PutRule(_ context.Context, rules ...*models.AlertRule) { - f.mtx.Lock() - defer f.mtx.Unlock() -mainloop: - for _, r := range rules { - rgs := f.Rules[r.OrgID] - for idx, rulePtr := range rgs { - if rulePtr.UID == r.UID { - rgs[idx] = r - continue mainloop - } - } - rgs = append(rgs, r) - f.Rules[r.OrgID] = rgs - - var existing *models2.Folder - folders := f.Folders[r.OrgID] - for _, folder := range folders { - if folder.Uid == r.NamespaceUID { - existing = folder - break - } - } - if existing == nil { - folders = append(folders, &models2.Folder{ - Id: rand.Int63(), - Uid: r.NamespaceUID, - Title: "TEST-FOLDER-" + util.GenerateShortUID(), - }) - f.Folders[r.OrgID] = folders - } - } -} - -// GetRecordedCommands filters recorded commands using predicate function. Returns the subset of the recorded commands that meet the predicate -func (f *FakeRuleStore) GetRecordedCommands(predicate func(cmd interface{}) (interface{}, bool)) []interface{} { - f.mtx.Lock() - defer f.mtx.Unlock() - - result := make([]interface{}, 0, len(f.RecordedOps)) - for _, op := range f.RecordedOps { - cmd, ok := predicate(op) - if !ok { - continue - } - result = append(result, cmd) - } - return result -} - -func (f *FakeRuleStore) DeleteAlertRulesByUID(_ context.Context, orgID int64, UIDs ...string) error { - f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{ - Name: "DeleteAlertRulesByUID", - Params: []interface{}{orgID, UIDs}, - }) - - rules := f.Rules[orgID] - - var result = make([]*models.AlertRule, 0, len(rules)) - - for _, rule := range rules { - add := true - for _, UID := range UIDs { - if rule.UID == UID { - add = false - break - } - } - if add { - result = append(result, rule) - } - } - - f.Rules[orgID] = result - return nil -} - -func (f *FakeRuleStore) GetAlertRuleByUID(_ context.Context, q *models.GetAlertRuleByUIDQuery) error { - f.mtx.Lock() - defer f.mtx.Unlock() - f.RecordedOps = append(f.RecordedOps, *q) - if err := f.Hook(*q); err != nil { - return err - } - rules, ok := f.Rules[q.OrgID] - if !ok { - return nil - } - - for _, rule := range rules { - if rule.UID == q.UID { - q.Result = rule - break - } - } - return nil -} - -func (f *FakeRuleStore) GetAlertRulesGroupByRuleUID(_ context.Context, q *models.GetAlertRulesGroupByRuleUIDQuery) error { - f.mtx.Lock() - defer f.mtx.Unlock() - f.RecordedOps = append(f.RecordedOps, *q) - if err := f.Hook(*q); err != nil { - return err - } - rules, ok := f.Rules[q.OrgID] - if !ok { - return nil - } - - var selected *models.AlertRule - for _, rule := range rules { - if rule.UID == q.UID { - selected = rule - break - } - } - if selected == nil { - return nil - } - - for _, rule := range rules { - if rule.GetGroupKey() == selected.GetGroupKey() { - q.Result = append(q.Result, rule) - } - } - return nil -} - -func (f *FakeRuleStore) ListAlertRules(_ context.Context, q *models.ListAlertRulesQuery) error { - f.mtx.Lock() - defer f.mtx.Unlock() - f.RecordedOps = append(f.RecordedOps, *q) - - if err := f.Hook(*q); err != nil { - return err - } - - hasDashboard := func(r *models.AlertRule, dashboardUID string, panelID int64) bool { - if dashboardUID != "" { - if r.DashboardUID == nil || *r.DashboardUID != dashboardUID { - return false - } - if panelID > 0 { - if r.PanelID == nil || *r.PanelID != panelID { - return false - } - } - } - return true - } - - hasNamespace := func(r *models.AlertRule, namespaceUIDs []string) bool { - if len(namespaceUIDs) > 0 { - var ok bool - for _, uid := range q.NamespaceUIDs { - if uid == r.NamespaceUID { - ok = true - break - } - } - if !ok { - return false - } - } - return true - } - - for _, r := range f.Rules[q.OrgID] { - if !hasDashboard(r, q.DashboardUID, q.PanelID) { - continue - } - if !hasNamespace(r, q.NamespaceUIDs) { - continue - } - if q.RuleGroup != "" && r.RuleGroup != q.RuleGroup { - continue - } - q.Result = append(q.Result, r) - } - - return nil -} - -func (f *FakeRuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ *user.SignedInUser) (map[string]*models2.Folder, error) { - f.mtx.Lock() - defer f.mtx.Unlock() - - namespacesMap := map[string]*models2.Folder{} - - _, ok := f.Rules[orgID] - if !ok { - return namespacesMap, nil - } - - for _, folder := range f.Folders[orgID] { - namespacesMap[folder.Uid] = folder - } - return namespacesMap, nil -} - -func (f *FakeRuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID int64, _ *user.SignedInUser, _ bool) (*models2.Folder, error) { - folders := f.Folders[orgID] - for _, folder := range folders { - if folder.Title == title { - return folder, nil - } - } - return nil, fmt.Errorf("not found") -} - -func (f *FakeRuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64, _ *user.SignedInUser) (*models2.Folder, error) { - f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{ - Name: "GetNamespaceByUID", - Params: []interface{}{orgID, uid}, - }) - - folders := f.Folders[orgID] - for _, folder := range folders { - if folder.Uid == uid { - return folder, nil - } - } - return nil, fmt.Errorf("not found") -} - -func (f *FakeRuleStore) UpdateAlertRules(_ context.Context, q []models.UpdateRule) error { - f.mtx.Lock() - defer f.mtx.Unlock() - f.RecordedOps = append(f.RecordedOps, q) - if err := f.Hook(q); err != nil { - return err - } - return nil -} - -func (f *FakeRuleStore) InsertAlertRules(_ context.Context, q []models.AlertRule) (map[string]int64, error) { - f.mtx.Lock() - defer f.mtx.Unlock() - f.RecordedOps = append(f.RecordedOps, q) - ids := make(map[string]int64, len(q)) - if err := f.Hook(q); err != nil { - return ids, err - } - return ids, nil -} - -func (f *FakeRuleStore) InTransaction(ctx context.Context, fn func(c context.Context) error) error { - return fn(ctx) -} - -func (f *FakeRuleStore) GetRuleGroupInterval(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) (int64, error) { - f.mtx.Lock() - defer f.mtx.Unlock() - for _, rule := range f.Rules[orgID] { - if rule.RuleGroup == ruleGroup && rule.NamespaceUID == namespaceUID { - return rule.IntervalSeconds, nil - } - } - return 0, ErrAlertRuleGroupNotFound -} - -func (f *FakeRuleStore) UpdateRuleGroup(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string, interval int64) error { - f.mtx.Lock() - defer f.mtx.Unlock() - for _, rule := range f.Rules[orgID] { - if rule.RuleGroup == ruleGroup && rule.NamespaceUID == namespaceUID { - rule.IntervalSeconds = interval - } - } - return nil -} - -func (f *FakeRuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, orgID int64, namespaceUID string) ([]models.AlertRuleKeyWithVersion, error) { - f.mtx.Lock() - defer f.mtx.Unlock() - - f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{ - Name: "IncreaseVersionForAllRulesInNamespace", - Params: []interface{}{orgID, namespaceUID}, - }) - - var result []models.AlertRuleKeyWithVersion - - for _, rule := range f.Rules[orgID] { - if rule.NamespaceUID == namespaceUID && rule.OrgID == orgID { - rule.Version++ - rule.Updated = TimeNow() - result = append(result, models.AlertRuleKeyWithVersion{ - Version: rule.Version, - AlertRuleKey: rule.GetKey(), - }) - } - } - return result, nil -} - func NewFakeAdminConfigStore(t *testing.T) *FakeAdminConfigStore { t.Helper() return &FakeAdminConfigStore{Configs: map[int64]*models.AdminConfiguration{}} diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go new file mode 100644 index 00000000000..3cb2f1b7210 --- /dev/null +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -0,0 +1,341 @@ +package fakes + +import ( + "context" + "errors" + "fmt" + "math/rand" + "sync" + "testing" + "time" + + models2 "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/util" +) + +// FakeRuleStore mocks the RuleStore of the scheduler. +type RuleStore struct { + t *testing.T + mtx sync.Mutex + // OrgID -> RuleGroup -> Namespace -> Rules + Rules map[int64][]*models.AlertRule + Hook func(cmd interface{}) error // use Hook if you need to intercept some query and return an error + RecordedOps []interface{} + Folders map[int64][]*models2.Folder +} + +type GenericRecordedQuery struct { + Name string + Params []interface{} +} + +func NewRuleStore(t *testing.T) *RuleStore { + return &RuleStore{ + t: t, + Rules: map[int64][]*models.AlertRule{}, + Hook: func(interface{}) error { + return nil + }, + Folders: map[int64][]*models2.Folder{}, + } +} + +// PutRule puts the rule in the Rules map. If there are existing rule in the same namespace, they will be overwritten +func (f *RuleStore) PutRule(_ context.Context, rules ...*models.AlertRule) { + f.mtx.Lock() + defer f.mtx.Unlock() +mainloop: + for _, r := range rules { + rgs := f.Rules[r.OrgID] + for idx, rulePtr := range rgs { + if rulePtr.UID == r.UID { + rgs[idx] = r + continue mainloop + } + } + rgs = append(rgs, r) + f.Rules[r.OrgID] = rgs + + var existing *models2.Folder + folders := f.Folders[r.OrgID] + for _, folder := range folders { + if folder.Uid == r.NamespaceUID { + existing = folder + break + } + } + if existing == nil { + folders = append(folders, &models2.Folder{ + Id: rand.Int63(), + Uid: r.NamespaceUID, + Title: "TEST-FOLDER-" + util.GenerateShortUID(), + }) + f.Folders[r.OrgID] = folders + } + } +} + +// GetRecordedCommands filters recorded commands using predicate function. Returns the subset of the recorded commands that meet the predicate +func (f *RuleStore) GetRecordedCommands(predicate func(cmd interface{}) (interface{}, bool)) []interface{} { + f.mtx.Lock() + defer f.mtx.Unlock() + + result := make([]interface{}, 0, len(f.RecordedOps)) + for _, op := range f.RecordedOps { + cmd, ok := predicate(op) + if !ok { + continue + } + result = append(result, cmd) + } + return result +} + +func (f *RuleStore) DeleteAlertRulesByUID(_ context.Context, orgID int64, UIDs ...string) error { + f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{ + Name: "DeleteAlertRulesByUID", + Params: []interface{}{orgID, UIDs}, + }) + + rules := f.Rules[orgID] + + var result = make([]*models.AlertRule, 0, len(rules)) + + for _, rule := range rules { + add := true + for _, UID := range UIDs { + if rule.UID == UID { + add = false + break + } + } + if add { + result = append(result, rule) + } + } + + f.Rules[orgID] = result + return nil +} + +func (f *RuleStore) GetAlertRuleByUID(_ context.Context, q *models.GetAlertRuleByUIDQuery) error { + f.mtx.Lock() + defer f.mtx.Unlock() + f.RecordedOps = append(f.RecordedOps, *q) + if err := f.Hook(*q); err != nil { + return err + } + rules, ok := f.Rules[q.OrgID] + if !ok { + return nil + } + + for _, rule := range rules { + if rule.UID == q.UID { + q.Result = rule + break + } + } + return nil +} + +func (f *RuleStore) GetAlertRulesGroupByRuleUID(_ context.Context, q *models.GetAlertRulesGroupByRuleUIDQuery) error { + f.mtx.Lock() + defer f.mtx.Unlock() + f.RecordedOps = append(f.RecordedOps, *q) + if err := f.Hook(*q); err != nil { + return err + } + rules, ok := f.Rules[q.OrgID] + if !ok { + return nil + } + + var selected *models.AlertRule + for _, rule := range rules { + if rule.UID == q.UID { + selected = rule + break + } + } + if selected == nil { + return nil + } + + for _, rule := range rules { + if rule.GetGroupKey() == selected.GetGroupKey() { + q.Result = append(q.Result, rule) + } + } + return nil +} + +func (f *RuleStore) ListAlertRules(_ context.Context, q *models.ListAlertRulesQuery) error { + f.mtx.Lock() + defer f.mtx.Unlock() + f.RecordedOps = append(f.RecordedOps, *q) + + if err := f.Hook(*q); err != nil { + return err + } + + hasDashboard := func(r *models.AlertRule, dashboardUID string, panelID int64) bool { + if dashboardUID != "" { + if r.DashboardUID == nil || *r.DashboardUID != dashboardUID { + return false + } + if panelID > 0 { + if r.PanelID == nil || *r.PanelID != panelID { + return false + } + } + } + return true + } + + hasNamespace := func(r *models.AlertRule, namespaceUIDs []string) bool { + if len(namespaceUIDs) > 0 { + var ok bool + for _, uid := range q.NamespaceUIDs { + if uid == r.NamespaceUID { + ok = true + break + } + } + if !ok { + return false + } + } + return true + } + + for _, r := range f.Rules[q.OrgID] { + if !hasDashboard(r, q.DashboardUID, q.PanelID) { + continue + } + if !hasNamespace(r, q.NamespaceUIDs) { + continue + } + if q.RuleGroup != "" && r.RuleGroup != q.RuleGroup { + continue + } + q.Result = append(q.Result, r) + } + + return nil +} + +func (f *RuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ *user.SignedInUser) (map[string]*models2.Folder, error) { + f.mtx.Lock() + defer f.mtx.Unlock() + + namespacesMap := map[string]*models2.Folder{} + + _, ok := f.Rules[orgID] + if !ok { + return namespacesMap, nil + } + + for _, folder := range f.Folders[orgID] { + namespacesMap[folder.Uid] = folder + } + return namespacesMap, nil +} + +func (f *RuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID int64, _ *user.SignedInUser, _ bool) (*models2.Folder, error) { + folders := f.Folders[orgID] + for _, folder := range folders { + if folder.Title == title { + return folder, nil + } + } + return nil, fmt.Errorf("not found") +} + +func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64, _ *user.SignedInUser) (*models2.Folder, error) { + f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{ + Name: "GetNamespaceByUID", + Params: []interface{}{orgID, uid}, + }) + + folders := f.Folders[orgID] + for _, folder := range folders { + if folder.Uid == uid { + return folder, nil + } + } + return nil, fmt.Errorf("not found") +} + +func (f *RuleStore) UpdateAlertRules(_ context.Context, q []models.UpdateRule) error { + f.mtx.Lock() + defer f.mtx.Unlock() + f.RecordedOps = append(f.RecordedOps, q) + if err := f.Hook(q); err != nil { + return err + } + return nil +} + +func (f *RuleStore) InsertAlertRules(_ context.Context, q []models.AlertRule) (map[string]int64, error) { + f.mtx.Lock() + defer f.mtx.Unlock() + f.RecordedOps = append(f.RecordedOps, q) + ids := make(map[string]int64, len(q)) + if err := f.Hook(q); err != nil { + return ids, err + } + return ids, nil +} + +func (f *RuleStore) InTransaction(ctx context.Context, fn func(c context.Context) error) error { + return fn(ctx) +} + +func (f *RuleStore) GetRuleGroupInterval(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) (int64, error) { + f.mtx.Lock() + defer f.mtx.Unlock() + for _, rule := range f.Rules[orgID] { + if rule.RuleGroup == ruleGroup && rule.NamespaceUID == namespaceUID { + return rule.IntervalSeconds, nil + } + } + return 0, errors.New("rule group not found") +} + +func (f *RuleStore) UpdateRuleGroup(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string, interval int64) error { + f.mtx.Lock() + defer f.mtx.Unlock() + for _, rule := range f.Rules[orgID] { + if rule.RuleGroup == ruleGroup && rule.NamespaceUID == namespaceUID { + rule.IntervalSeconds = interval + } + } + return nil +} + +func (f *RuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, orgID int64, namespaceUID string) ([]models.AlertRuleKeyWithVersion, error) { + f.mtx.Lock() + defer f.mtx.Unlock() + + f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{ + Name: "IncreaseVersionForAllRulesInNamespace", + Params: []interface{}{orgID, namespaceUID}, + }) + + var result []models.AlertRuleKeyWithVersion + + for _, rule := range f.Rules[orgID] { + if rule.NamespaceUID == namespaceUID && rule.OrgID == orgID { + rule.Version++ + rule.Updated = time.Now() + result = append(result, models.AlertRuleKeyWithVersion{ + Version: rule.Version, + AlertRuleKey: rule.GetKey(), + }) + } + } + return result, nil +}