mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			568 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			568 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
| package api
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"math/rand"
 | |
| 	"net/http"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/stretchr/testify/mock"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/infra/log"
 | |
| 	models2 "github.com/grafana/grafana/pkg/models"
 | |
| 	"github.com/grafana/grafana/pkg/services/accesscontrol"
 | |
| 	acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
 | |
| 	"github.com/grafana/grafana/pkg/services/datasources"
 | |
| 	"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/util"
 | |
| 	"github.com/grafana/grafana/pkg/web"
 | |
| )
 | |
| 
 | |
| func TestCalculateChanges(t *testing.T) {
 | |
| 	orgId := rand.Int63()
 | |
| 
 | |
| 	t.Run("detects alerts that need to be added", func(t *testing.T) {
 | |
| 		fakeStore := store.NewFakeRuleStore(t)
 | |
| 
 | |
| 		namespace := randFolder()
 | |
| 		groupName := util.GenerateShortUID()
 | |
| 		submitted := models.GenerateAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withOrgID(orgId), simulateSubmitted, withoutUID))
 | |
| 
 | |
| 		changes, err := calculateChanges(context.Background(), fakeStore, orgId, namespace, groupName, submitted)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Len(t, changes.New, len(submitted))
 | |
| 		require.Empty(t, changes.Delete)
 | |
| 		require.Empty(t, changes.Update)
 | |
| 
 | |
| 	outerloop:
 | |
| 		for _, expected := range submitted {
 | |
| 			for _, rule := range changes.New {
 | |
| 				if len(expected.Diff(rule)) == 0 {
 | |
| 					continue outerloop
 | |
| 				}
 | |
| 			}
 | |
| 			require.Fail(t, "changes did not contain rule that was submitted")
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	t.Run("detects alerts that need to be deleted", func(t *testing.T) {
 | |
| 		namespace := randFolder()
 | |
| 		groupName := util.GenerateShortUID()
 | |
| 		inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withOrgID(orgId), withGroup(groupName), withNamespace(namespace)))
 | |
| 
 | |
| 		fakeStore := store.NewFakeRuleStore(t)
 | |
| 		fakeStore.PutRule(context.Background(), inDatabase...)
 | |
| 
 | |
| 		changes, err := calculateChanges(context.Background(), fakeStore, orgId, namespace, groupName, make([]*models.AlertRule, 0))
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Empty(t, changes.New)
 | |
| 		require.Empty(t, changes.Update)
 | |
| 		require.Len(t, changes.Delete, len(inDatabaseMap))
 | |
| 		for _, toDelete := range changes.Delete {
 | |
| 			require.Contains(t, inDatabaseMap, toDelete.UID)
 | |
| 			db := inDatabaseMap[toDelete.UID]
 | |
| 			require.Equal(t, db, toDelete)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should detect alerts that needs to be updated", func(t *testing.T) {
 | |
| 		namespace := randFolder()
 | |
| 		groupName := util.GenerateShortUID()
 | |
| 		inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withOrgID(orgId), withGroup(groupName), withNamespace(namespace)))
 | |
| 		submittedMap, submitted := models.GenerateUniqueAlertRules(len(inDatabase), models.AlertRuleGen(simulateSubmitted, withOrgID(orgId), withGroup(groupName), withNamespace(namespace), withUIDs(inDatabaseMap)))
 | |
| 
 | |
| 		fakeStore := store.NewFakeRuleStore(t)
 | |
| 		fakeStore.PutRule(context.Background(), inDatabase...)
 | |
| 
 | |
| 		changes, err := calculateChanges(context.Background(), fakeStore, orgId, namespace, groupName, submitted)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Len(t, changes.Update, len(inDatabase))
 | |
| 		for _, upsert := range changes.Update {
 | |
| 			require.NotNil(t, upsert.Existing)
 | |
| 			require.Equal(t, upsert.Existing.UID, upsert.New.UID)
 | |
| 			require.Equal(t, inDatabaseMap[upsert.Existing.UID], upsert.Existing)
 | |
| 			require.Equal(t, submittedMap[upsert.Existing.UID], upsert.New)
 | |
| 			require.NotEmpty(t, upsert.Diff)
 | |
| 		}
 | |
| 		require.Empty(t, changes.Delete)
 | |
| 		require.Empty(t, changes.New)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should include only if there are changes ignoring specific fields", func(t *testing.T) {
 | |
| 		namespace := randFolder()
 | |
| 		groupName := util.GenerateShortUID()
 | |
| 		_, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withOrgID(orgId), withGroup(groupName), withNamespace(namespace)))
 | |
| 
 | |
| 		submitted := make([]*models.AlertRule, 0, len(inDatabase))
 | |
| 		for _, rule := range inDatabase {
 | |
| 			r := models.CopyRule(rule)
 | |
| 
 | |
| 			// Ignore difference in the following fields as submitted models do not have them set
 | |
| 			r.ID = rand.Int63()
 | |
| 			r.Version = rand.Int63()
 | |
| 			r.Updated = r.Updated.Add(1 * time.Minute)
 | |
| 
 | |
| 			submitted = append(submitted, r)
 | |
| 		}
 | |
| 
 | |
| 		fakeStore := store.NewFakeRuleStore(t)
 | |
| 		fakeStore.PutRule(context.Background(), inDatabase...)
 | |
| 
 | |
| 		changes, err := calculateChanges(context.Background(), fakeStore, orgId, namespace, groupName, submitted)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Empty(t, changes.Update)
 | |
| 		require.Empty(t, changes.Delete)
 | |
| 		require.Empty(t, changes.New)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should patch rule with UID specified by existing rule", func(t *testing.T) {
 | |
| 		testCases := []struct {
 | |
| 			name    string
 | |
| 			mutator func(r *models.AlertRule)
 | |
| 		}{
 | |
| 			{
 | |
| 				name: "title is empty",
 | |
| 				mutator: func(r *models.AlertRule) {
 | |
| 					r.Title = ""
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				name: "condition and data are empty",
 | |
| 				mutator: func(r *models.AlertRule) {
 | |
| 					r.Condition = ""
 | |
| 					r.Data = nil
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				name: "ExecErrState is empty",
 | |
| 				mutator: func(r *models.AlertRule) {
 | |
| 					r.ExecErrState = ""
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				name: "NoDataState is empty",
 | |
| 				mutator: func(r *models.AlertRule) {
 | |
| 					r.NoDataState = ""
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				name: "For is 0",
 | |
| 				mutator: func(r *models.AlertRule) {
 | |
| 					r.For = 0
 | |
| 				},
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		dbRule := models.AlertRuleGen(withOrgID(orgId))()
 | |
| 
 | |
| 		fakeStore := store.NewFakeRuleStore(t)
 | |
| 		fakeStore.PutRule(context.Background(), dbRule)
 | |
| 
 | |
| 		namespace := randFolder()
 | |
| 		groupName := util.GenerateShortUID()
 | |
| 
 | |
| 		for _, testCase := range testCases {
 | |
| 			t.Run(testCase.name, func(t *testing.T) {
 | |
| 				expected := models.AlertRuleGen(simulateSubmitted, testCase.mutator)()
 | |
| 				expected.UID = dbRule.UID
 | |
| 				submitted := *expected
 | |
| 				changes, err := calculateChanges(context.Background(), fakeStore, orgId, namespace, groupName, []*models.AlertRule{&submitted})
 | |
| 				require.NoError(t, err)
 | |
| 				require.Len(t, changes.Update, 1)
 | |
| 				ch := changes.Update[0]
 | |
| 				require.Equal(t, ch.Existing, dbRule)
 | |
| 				fixed := *expected
 | |
| 				models.PatchPartialAlertRule(dbRule, &fixed)
 | |
| 				require.Equal(t, fixed, *ch.New)
 | |
| 			})
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should be able to find alerts by UID in other group/namespace", func(t *testing.T) {
 | |
| 		inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(10)+10, models.AlertRuleGen(withOrgID(orgId)))
 | |
| 
 | |
| 		fakeStore := store.NewFakeRuleStore(t)
 | |
| 		fakeStore.PutRule(context.Background(), inDatabase...)
 | |
| 
 | |
| 		namespace := randFolder()
 | |
| 		groupName := util.GenerateShortUID()
 | |
| 		submittedMap, submitted := models.GenerateUniqueAlertRules(rand.Intn(len(inDatabase)-5)+5, models.AlertRuleGen(simulateSubmitted, withOrgID(orgId), withGroup(groupName), withNamespace(namespace), withUIDs(inDatabaseMap)))
 | |
| 
 | |
| 		changes, err := calculateChanges(context.Background(), fakeStore, orgId, namespace, groupName, submitted)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Empty(t, changes.Delete)
 | |
| 		require.Empty(t, changes.New)
 | |
| 		require.Len(t, changes.Update, len(submitted))
 | |
| 		for _, update := range changes.Update {
 | |
| 			require.NotNil(t, update.Existing)
 | |
| 			require.Equal(t, update.Existing.UID, update.New.UID)
 | |
| 			require.Equal(t, inDatabaseMap[update.Existing.UID], update.Existing)
 | |
| 			require.Equal(t, submittedMap[update.Existing.UID], update.New)
 | |
| 			require.NotEmpty(t, update.Diff)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should fail when submitted rule has UID that does not exist in db", func(t *testing.T) {
 | |
| 		fakeStore := store.NewFakeRuleStore(t)
 | |
| 
 | |
| 		namespace := randFolder()
 | |
| 		groupName := util.GenerateShortUID()
 | |
| 		submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted)()
 | |
| 		require.NotEqual(t, "", submitted.UID)
 | |
| 
 | |
| 		_, err := calculateChanges(context.Background(), fakeStore, orgId, namespace, groupName, []*models.AlertRule{submitted})
 | |
| 		require.Error(t, err)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should fail if cannot fetch current rules in the group", func(t *testing.T) {
 | |
| 		fakeStore := store.NewFakeRuleStore(t)
 | |
| 		expectedErr := errors.New("TEST ERROR")
 | |
| 		fakeStore.Hook = func(cmd interface{}) error {
 | |
| 			switch cmd.(type) {
 | |
| 			case models.GetAlertRulesQuery:
 | |
| 				return expectedErr
 | |
| 			}
 | |
| 			return nil
 | |
| 		}
 | |
| 
 | |
| 		namespace := randFolder()
 | |
| 		groupName := util.GenerateShortUID()
 | |
| 		submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted, withoutUID)()
 | |
| 
 | |
| 		_, err := calculateChanges(context.Background(), fakeStore, orgId, namespace, groupName, []*models.AlertRule{submitted})
 | |
| 		require.ErrorIs(t, err, expectedErr)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should fail if cannot fetch rule by UID", func(t *testing.T) {
 | |
| 		fakeStore := store.NewFakeRuleStore(t)
 | |
| 		expectedErr := errors.New("TEST ERROR")
 | |
| 		fakeStore.Hook = func(cmd interface{}) error {
 | |
| 			switch cmd.(type) {
 | |
| 			case models.GetAlertRuleByUIDQuery:
 | |
| 				return expectedErr
 | |
| 			}
 | |
| 			return nil
 | |
| 		}
 | |
| 
 | |
| 		namespace := randFolder()
 | |
| 		groupName := util.GenerateShortUID()
 | |
| 		submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted)()
 | |
| 
 | |
| 		_, err := calculateChanges(context.Background(), fakeStore, orgId, namespace, groupName, []*models.AlertRule{submitted})
 | |
| 		require.Error(t, err, expectedErr)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestRouteDeleteAlertRules(t *testing.T) {
 | |
| 	createService := func(ac *acMock.Mock, store *store.FakeRuleStore, scheduler schedule.ScheduleService) *RulerSrv {
 | |
| 		return &RulerSrv{
 | |
| 			xactManager:     store,
 | |
| 			store:           store,
 | |
| 			DatasourceCache: nil,
 | |
| 			QuotaService:    nil,
 | |
| 			scheduleService: scheduler,
 | |
| 			log:             log.New("test"),
 | |
| 			cfg:             nil,
 | |
| 			ac:              ac,
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	getRecordedCommand := func(ruleStore *store.FakeRuleStore) []store.GenericRecordedQuery {
 | |
| 		results := ruleStore.GetRecordedCommands(func(cmd interface{}) (interface{}, bool) {
 | |
| 			c, ok := cmd.(store.GenericRecordedQuery)
 | |
| 			if !ok || c.Name != "DeleteAlertRulesByUID" {
 | |
| 				return nil, false
 | |
| 			}
 | |
| 			return c, ok
 | |
| 		})
 | |
| 		var result []store.GenericRecordedQuery
 | |
| 		for _, cmd := range results {
 | |
| 			result = append(result, cmd.(store.GenericRecordedQuery))
 | |
| 		}
 | |
| 		return result
 | |
| 	}
 | |
| 
 | |
| 	assertRulesDeleted := func(t *testing.T, expectedRules []*models.AlertRule, ruleStore *store.FakeRuleStore, scheduler *schedule.FakeScheduleService) {
 | |
| 		deleteCommands := getRecordedCommand(ruleStore)
 | |
| 		require.Len(t, deleteCommands, 1)
 | |
| 		cmd := deleteCommands[0]
 | |
| 		actualUIDs := cmd.Params[1].([]string)
 | |
| 		require.Len(t, actualUIDs, len(expectedRules))
 | |
| 		for _, rule := range expectedRules {
 | |
| 			require.Containsf(t, actualUIDs, rule.UID, "Rule %s was expected to be deleted but it wasn't", rule.UID)
 | |
| 		}
 | |
| 
 | |
| 		require.Len(t, scheduler.Calls, len(expectedRules))
 | |
| 		for _, call := range scheduler.Calls {
 | |
| 			require.Equal(t, "DeleteAlertRule", call.Method)
 | |
| 			key, ok := call.Arguments.Get(0).(models.AlertRuleKey)
 | |
| 			require.Truef(t, ok, "Expected AlertRuleKey but got something else")
 | |
| 			found := false
 | |
| 			for _, rule := range expectedRules {
 | |
| 				if rule.GetKey() == key {
 | |
| 					found = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			require.Truef(t, found, "Key %v was not expected to be submitted to scheduler", key)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	t.Run("when fine-grained access is disabled", func(t *testing.T) {
 | |
| 		t.Run("viewer should not be authorized", func(t *testing.T) {
 | |
| 			ruleStore := store.NewFakeRuleStore(t)
 | |
| 			orgID := rand.Int63()
 | |
| 			folder := randFolder()
 | |
| 			ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
 | |
| 			ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
 | |
| 			ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
 | |
| 
 | |
| 			scheduler := &schedule.FakeScheduleService{}
 | |
| 			scheduler.On("DeleteAlertRule", mock.Anything).Panic("should not be called")
 | |
| 
 | |
| 			ac := acMock.New().WithDisabled()
 | |
| 			request := createRequestContext(orgID, models2.ROLE_VIEWER, map[string]string{
 | |
| 				":Namespace": folder.Title,
 | |
| 			})
 | |
| 			response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
 | |
| 			require.Equalf(t, 401, response.Status(), "Expected 403 but got %d: %v", response.Status(), string(response.Body()))
 | |
| 
 | |
| 			scheduler.AssertNotCalled(t, "DeleteAlertRule")
 | |
| 			require.Empty(t, getRecordedCommand(ruleStore))
 | |
| 		})
 | |
| 		t.Run("editor should be able to delete all rules in folder", func(t *testing.T) {
 | |
| 			ruleStore := store.NewFakeRuleStore(t)
 | |
| 			orgID := rand.Int63()
 | |
| 			folder := randFolder()
 | |
| 			ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
 | |
| 			rulesInFolder := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
 | |
| 			ruleStore.PutRule(context.Background(), rulesInFolder...)
 | |
| 			ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
 | |
| 
 | |
| 			scheduler := &schedule.FakeScheduleService{}
 | |
| 			scheduler.On("DeleteAlertRule", mock.Anything)
 | |
| 
 | |
| 			ac := acMock.New().WithDisabled()
 | |
| 			request := createRequestContext(orgID, models2.ROLE_EDITOR, map[string]string{
 | |
| 				":Namespace": folder.Title,
 | |
| 			})
 | |
| 			response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
 | |
| 			require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
 | |
| 			assertRulesDeleted(t, rulesInFolder, ruleStore, scheduler)
 | |
| 		})
 | |
| 		t.Run("editor should be able to delete rules in a group in a folder", func(t *testing.T) {
 | |
| 			ruleStore := store.NewFakeRuleStore(t)
 | |
| 			orgID := rand.Int63()
 | |
| 			groupName := util.GenerateShortUID()
 | |
| 			folder := randFolder()
 | |
| 			ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
 | |
| 			rulesInFolderInGroup := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))
 | |
| 			ruleStore.PutRule(context.Background(), rulesInFolderInGroup...)
 | |
| 			// rules in different groups but in the same namespace
 | |
| 			ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
 | |
| 			// rules in the same group but different folder
 | |
| 			ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withGroup(groupName)))...)
 | |
| 
 | |
| 			scheduler := &schedule.FakeScheduleService{}
 | |
| 			scheduler.On("DeleteAlertRule", mock.Anything)
 | |
| 
 | |
| 			ac := acMock.New().WithDisabled()
 | |
| 			request := createRequestContext(orgID, models2.ROLE_EDITOR, map[string]string{
 | |
| 				":Namespace": folder.Title,
 | |
| 				":Groupname": groupName,
 | |
| 			})
 | |
| 			response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
 | |
| 			require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
 | |
| 			assertRulesDeleted(t, rulesInFolderInGroup, ruleStore, scheduler)
 | |
| 		})
 | |
| 	})
 | |
| 	t.Run("when fine-grained access is enabled", func(t *testing.T) {
 | |
| 		t.Run("and user does not have access to any of data sources used by alert rules", func(t *testing.T) {
 | |
| 			ruleStore := store.NewFakeRuleStore(t)
 | |
| 			orgID := rand.Int63()
 | |
| 			folder := randFolder()
 | |
| 			ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
 | |
| 			ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
 | |
| 			ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
 | |
| 
 | |
| 			scheduler := &schedule.FakeScheduleService{}
 | |
| 			scheduler.On("DeleteAlertRule", mock.Anything).Panic("should not be called")
 | |
| 
 | |
| 			ac := acMock.New()
 | |
| 			request := createRequestContext(orgID, "None", map[string]string{
 | |
| 				":Namespace": folder.Title,
 | |
| 			})
 | |
| 			response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
 | |
| 			require.Equalf(t, 401, response.Status(), "Expected 403 but got %d: %v", response.Status(), string(response.Body()))
 | |
| 
 | |
| 			scheduler.AssertNotCalled(t, "DeleteAlertRule")
 | |
| 			require.Empty(t, getRecordedCommand(ruleStore))
 | |
| 		})
 | |
| 		t.Run("and user has access to all alert rules", func(t *testing.T) {
 | |
| 			t.Run("should delete all rules", func(t *testing.T) {
 | |
| 				ruleStore := store.NewFakeRuleStore(t)
 | |
| 				orgID := rand.Int63()
 | |
| 				folder := randFolder()
 | |
| 				ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
 | |
| 				rulesInFolder := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
 | |
| 				ruleStore.PutRule(context.Background(), rulesInFolder...)
 | |
| 				ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
 | |
| 
 | |
| 				scheduler := &schedule.FakeScheduleService{}
 | |
| 				scheduler.On("DeleteAlertRule", mock.Anything)
 | |
| 
 | |
| 				var permissions []*accesscontrol.Permission
 | |
| 				for _, rule := range rulesInFolder {
 | |
| 					for _, query := range rule.Data {
 | |
| 						permissions = append(permissions, &accesscontrol.Permission{
 | |
| 							Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID),
 | |
| 						})
 | |
| 					}
 | |
| 				}
 | |
| 				ac := acMock.New().WithPermissions(permissions)
 | |
| 				request := createRequestContext(orgID, "None", map[string]string{
 | |
| 					":Namespace": folder.Title,
 | |
| 				})
 | |
| 
 | |
| 				response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
 | |
| 				require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
 | |
| 				assertRulesDeleted(t, rulesInFolder, ruleStore, scheduler)
 | |
| 			})
 | |
| 		})
 | |
| 		t.Run("and user has access to data sources of some of alert rules", func(t *testing.T) {
 | |
| 			t.Run("should delete only those that are accessible in folder", func(t *testing.T) {
 | |
| 				ruleStore := store.NewFakeRuleStore(t)
 | |
| 				orgID := rand.Int63()
 | |
| 				folder := randFolder()
 | |
| 				ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
 | |
| 				authorizedRulesInFolder := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
 | |
| 				ruleStore.PutRule(context.Background(), authorizedRulesInFolder...)
 | |
| 				// more rules in the same namespace but user does not have access to them
 | |
| 				ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
 | |
| 				ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
 | |
| 
 | |
| 				scheduler := &schedule.FakeScheduleService{}
 | |
| 				scheduler.On("DeleteAlertRule", mock.Anything)
 | |
| 
 | |
| 				var permissions []*accesscontrol.Permission
 | |
| 				for _, rule := range authorizedRulesInFolder {
 | |
| 					for _, query := range rule.Data {
 | |
| 						permissions = append(permissions, &accesscontrol.Permission{
 | |
| 							Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID),
 | |
| 						})
 | |
| 					}
 | |
| 				}
 | |
| 				ac := acMock.New().WithPermissions(permissions)
 | |
| 				request := createRequestContext(orgID, "None", map[string]string{
 | |
| 					":Namespace": folder.Title,
 | |
| 				})
 | |
| 
 | |
| 				response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
 | |
| 				require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
 | |
| 				assertRulesDeleted(t, authorizedRulesInFolder, ruleStore, scheduler)
 | |
| 			})
 | |
| 			t.Run("should delete only rules in a group that are authorized", func(t *testing.T) {
 | |
| 				ruleStore := store.NewFakeRuleStore(t)
 | |
| 				orgID := rand.Int63()
 | |
| 				groupName := util.GenerateShortUID()
 | |
| 				folder := randFolder()
 | |
| 				ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
 | |
| 				authorizedRulesInGroup := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))
 | |
| 				ruleStore.PutRule(context.Background(), authorizedRulesInGroup...)
 | |
| 				// more rules in the same group but user is not authorized to access them
 | |
| 				ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))...)
 | |
| 				// rules in different groups but in the same namespace
 | |
| 				ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
 | |
| 				// rules in the same group but different folder
 | |
| 				ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withGroup(groupName)))...)
 | |
| 
 | |
| 				scheduler := &schedule.FakeScheduleService{}
 | |
| 				scheduler.On("DeleteAlertRule", mock.Anything)
 | |
| 
 | |
| 				var permissions []*accesscontrol.Permission
 | |
| 				for _, rule := range authorizedRulesInGroup {
 | |
| 					for _, query := range rule.Data {
 | |
| 						permissions = append(permissions, &accesscontrol.Permission{
 | |
| 							Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID),
 | |
| 						})
 | |
| 					}
 | |
| 				}
 | |
| 				ac := acMock.New().WithPermissions(permissions)
 | |
| 				request := createRequestContext(orgID, "None", map[string]string{
 | |
| 					":Namespace": folder.Title,
 | |
| 					":Groupname": groupName,
 | |
| 				})
 | |
| 				response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
 | |
| 				require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
 | |
| 				assertRulesDeleted(t, authorizedRulesInGroup, ruleStore, scheduler)
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func createRequestContext(orgID int64, role models2.RoleType, params map[string]string) *models2.ReqContext {
 | |
| 	ctx := web.Context{Req: &http.Request{}}
 | |
| 	ctx.Req = web.SetURLParams(ctx.Req, params)
 | |
| 
 | |
| 	return &models2.ReqContext{
 | |
| 		SignedInUser: &models2.SignedInUser{
 | |
| 			OrgRole: role,
 | |
| 			OrgId:   orgID,
 | |
| 		},
 | |
| 		Context: &ctx,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func withOrgID(orgId int64) func(rule *models.AlertRule) {
 | |
| 	return func(rule *models.AlertRule) {
 | |
| 		rule.OrgID = orgId
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func withGroup(groupName string) func(rule *models.AlertRule) {
 | |
| 	return func(rule *models.AlertRule) {
 | |
| 		rule.RuleGroup = groupName
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func withNamespace(namespace *models2.Folder) func(rule *models.AlertRule) {
 | |
| 	return func(rule *models.AlertRule) {
 | |
| 		rule.NamespaceUID = namespace.Uid
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // simulateSubmitted resets some fields of the structure that are not populated by API model to model conversion
 | |
| func simulateSubmitted(rule *models.AlertRule) {
 | |
| 	rule.ID = 0
 | |
| 	rule.Version = 0
 | |
| 	rule.Updated = time.Time{}
 | |
| }
 | |
| 
 | |
| func withoutUID(rule *models.AlertRule) {
 | |
| 	rule.UID = ""
 | |
| }
 | |
| 
 | |
| func withUIDs(uids map[string]*models.AlertRule) func(rule *models.AlertRule) {
 | |
| 	unused := make([]string, 0, len(uids))
 | |
| 	for s := range uids {
 | |
| 		unused = append(unused, s)
 | |
| 	}
 | |
| 	return func(rule *models.AlertRule) {
 | |
| 		if len(unused) == 0 {
 | |
| 			return
 | |
| 		}
 | |
| 		rule.UID = unused[0]
 | |
| 		unused = unused[1:]
 | |
| 	}
 | |
| }
 |