| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | package api | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 	"math/rand" | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2023-10-02 23:47:59 +08:00
										 |  |  | 	"net/http/httptest" | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2023-10-02 23:47:59 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 	"github.com/stretchr/testify/mock" | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/datasources" | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/folder" | 
					
						
							| 
									
										
										
										
											2023-11-16 00:54:54 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol" | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 	apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/ngalert/models" | 
					
						
							| 
									
										
										
										
											2022-04-29 03:27:34 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/ngalert/provisioning" | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/ngalert/store" | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/user" | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util" | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/web" | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | func TestRouteDeleteAlertRules(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 	getRecordedCommand := func(ruleStore *fakes.RuleStore) []fakes.GenericRecordedQuery { | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 		results := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) { | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 			c, ok := cmd.(fakes.GenericRecordedQuery) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 			if !ok || c.Name != "DeleteAlertRulesByUID" { | 
					
						
							|  |  |  | 				return nil, false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return c, ok | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 		var result []fakes.GenericRecordedQuery | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 		for _, cmd := range results { | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 			result = append(result, cmd.(fakes.GenericRecordedQuery)) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		return result | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-15 06:02:51 +08:00
										 |  |  | 	assertRulesDeleted := func(t *testing.T, expectedRules []*models.AlertRule, ruleStore *fakes.RuleStore) { | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 		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) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	orgID := rand.Int63() | 
					
						
							|  |  |  | 	folder := randFolder() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 	initFakeRuleStore := func(t *testing.T) *fakes.RuleStore { | 
					
						
							|  |  |  | 		ruleStore := fakes.NewRuleStore(t) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) | 
					
						
							|  |  |  | 		// add random data
 | 
					
						
							|  |  |  | 		ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID)))...) | 
					
						
							|  |  |  | 		return ruleStore | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("when fine-grained access is enabled", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 		t.Run("and group argument is empty", func(t *testing.T) { | 
					
						
							|  |  |  | 			t.Run("return 401 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)))...) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				request := createRequestContextWithPerms(orgID, map[int64]map[string][]string{}, nil) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				response := createService(ruleStore).RouteDeleteAlertRules(request, folder.Title, "") | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 				require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				require.Empty(t, getRecordedCommand(ruleStore)) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 			t.Run("delete only non-provisioned groups that user is authorized", func(t *testing.T) { | 
					
						
							|  |  |  | 				ruleStore := initFakeRuleStore(t) | 
					
						
							|  |  |  | 				provisioningStore := provisioning.NewFakeProvisioningStore() | 
					
						
							| 
									
										
										
										
											2022-05-07 02:55:27 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 				authorizedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("authz_"+util.GenerateShortUID()))) | 
					
						
							| 
									
										
										
										
											2022-05-07 02:55:27 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 				provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("provisioned_"+util.GenerateShortUID()))) | 
					
						
							|  |  |  | 				err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI) | 
					
						
							| 
									
										
										
										
											2022-05-07 02:55:27 +08:00
										 |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 				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())))...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				permissions := createPermissionsForRules(append(authorizedRulesInFolder, provisionedRulesInFolder...), orgID) | 
					
						
							|  |  |  | 				requestCtx := createRequestContextWithPerms(orgID, permissions, nil) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "") | 
					
						
							| 
									
										
										
										
											2022-05-07 02:55:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body())) | 
					
						
							| 
									
										
										
										
											2023-03-15 06:02:51 +08:00
										 |  |  | 				assertRulesDeleted(t, authorizedRulesInFolder, ruleStore) | 
					
						
							| 
									
										
										
										
											2022-05-07 02:55:27 +08:00
										 |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 			t.Run("return 400 if all rules user can access are provisioned", func(t *testing.T) { | 
					
						
							|  |  |  | 				ruleStore := initFakeRuleStore(t) | 
					
						
							|  |  |  | 				provisioningStore := provisioning.NewFakeProvisioningStore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(util.GenerateShortUID()))) | 
					
						
							|  |  |  | 				err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI) | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				ruleStore.PutRule(context.Background(), provisionedRulesInFolder...) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 				// more rules in the same namespace but user does not have access to them
 | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 				ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(util.GenerateShortUID())))...) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				permissions := createPermissionsForRules(provisionedRulesInFolder, orgID) | 
					
						
							|  |  |  | 				requestCtx := createRequestContextWithPerms(orgID, permissions, nil) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "") | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body())) | 
					
						
							|  |  |  | 				require.Empty(t, getRecordedCommand(ruleStore)) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 			t.Run("should return 202 if folder is empty", func(t *testing.T) { | 
					
						
							|  |  |  | 				ruleStore := initFakeRuleStore(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				requestCtx := createRequestContext(orgID, nil) | 
					
						
							|  |  |  | 				response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, "") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body())) | 
					
						
							|  |  |  | 				require.Empty(t, getRecordedCommand(ruleStore)) | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 		t.Run("and group argument is not empty", func(t *testing.T) { | 
					
						
							|  |  |  | 			groupName := util.GenerateShortUID() | 
					
						
							|  |  |  | 			t.Run("return 401 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))) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 				ruleStore.PutRule(context.Background(), authorizedRulesInGroup...) | 
					
						
							|  |  |  | 				// more rules in the same group but user is not authorized to access them
 | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 				ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))...) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				permissions := createPermissionsForRules(authorizedRulesInGroup, orgID) | 
					
						
							|  |  |  | 				requestCtx := createRequestContextWithPerms(orgID, permissions, nil) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body())) | 
					
						
							|  |  |  | 				deleteCommands := getRecordedCommand(ruleStore) | 
					
						
							|  |  |  | 				require.Empty(t, deleteCommands) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			t.Run("return 400 if group is provisioned", func(t *testing.T) { | 
					
						
							|  |  |  | 				ruleStore := initFakeRuleStore(t) | 
					
						
							|  |  |  | 				provisioningStore := provisioning.NewFakeProvisioningStore() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName))) | 
					
						
							|  |  |  | 				err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI) | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				ruleStore.PutRule(context.Background(), provisionedRulesInFolder...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				permissions := createPermissionsForRules(provisionedRulesInFolder, orgID) | 
					
						
							|  |  |  | 				requestCtx := createRequestContextWithPerms(orgID, permissions, nil) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body())) | 
					
						
							|  |  |  | 				deleteCommands := getRecordedCommand(ruleStore) | 
					
						
							|  |  |  | 				require.Empty(t, deleteCommands) | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | func TestRouteGetNamespaceRulesConfig(t *testing.T) { | 
					
						
							|  |  |  | 	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() | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 			ruleStore := fakes.NewRuleStore(t) | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 			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...) | 
					
						
							|  |  |  | 			ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 			permissions := createPermissionsForRules(expectedRules, orgID) | 
					
						
							|  |  |  | 			req := createRequestContextWithPerms(orgID, permissions, nil) | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 			response := createService(ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title) | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusAccepted, response.Status()) | 
					
						
							|  |  |  | 			result := &apimodels.NamespaceConfigResponse{} | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(response.Body(), result)) | 
					
						
							|  |  |  | 			require.NotNil(t, result) | 
					
						
							|  |  |  | 			for namespace, groups := range *result { | 
					
						
							|  |  |  | 				require.Equal(t, folder.Title, namespace) | 
					
						
							|  |  |  | 				for _, group := range groups { | 
					
						
							|  |  |  | 				grouploop: | 
					
						
							|  |  |  | 					for _, actualRule := range group.Rules { | 
					
						
							|  |  |  | 						for i, expected := range expectedRules { | 
					
						
							|  |  |  | 							if actualRule.GrafanaManagedAlert.UID == expected.UID { | 
					
						
							|  |  |  | 								expectedRules = append(expectedRules[:i], expectedRules[i+1:]...) | 
					
						
							|  |  |  | 								continue grouploop | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						assert.Failf(t, "rule in a group was not found in expected", "rule %s group %s", actualRule.GrafanaManagedAlert.Title, group.Name) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			assert.Emptyf(t, expectedRules, "not all expected rules were returned") | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-04-29 03:27:34 +08:00
										 |  |  | 	t.Run("should return the provenance of the alert rules", func(t *testing.T) { | 
					
						
							|  |  |  | 		orgID := rand.Int63() | 
					
						
							|  |  |  | 		folder := randFolder() | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 		ruleStore := fakes.NewRuleStore(t) | 
					
						
							| 
									
										
										
										
											2022-04-29 03:27:34 +08:00
										 |  |  | 		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...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 		svc := createService(ruleStore) | 
					
						
							| 
									
										
										
										
											2022-04-29 03:27:34 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// add provenance to the first generated rule
 | 
					
						
							|  |  |  | 		rule := &models.AlertRule{ | 
					
						
							|  |  |  | 			UID: expectedRules[0].UID, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err := svc.provenanceStore.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 		req := createRequestContext(orgID, nil) | 
					
						
							| 
									
										
										
										
											2022-06-24 04:13:39 +08:00
										 |  |  | 		response := svc.RouteGetNamespaceRulesConfig(req, folder.Title) | 
					
						
							| 
									
										
										
										
											2022-04-29 03:27:34 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		require.Equal(t, http.StatusAccepted, response.Status()) | 
					
						
							|  |  |  | 		result := &apimodels.NamespaceConfigResponse{} | 
					
						
							|  |  |  | 		require.NoError(t, json.Unmarshal(response.Body(), result)) | 
					
						
							|  |  |  | 		require.NotNil(t, result) | 
					
						
							|  |  |  | 		found := false | 
					
						
							|  |  |  | 		for namespace, groups := range *result { | 
					
						
							|  |  |  | 			require.Equal(t, folder.Title, namespace) | 
					
						
							|  |  |  | 			for _, group := range groups { | 
					
						
							|  |  |  | 				for _, actualRule := range group.Rules { | 
					
						
							|  |  |  | 					if actualRule.GrafanaManagedAlert.UID == expectedRules[0].UID { | 
					
						
							| 
									
										
										
										
											2023-02-28 06:57:15 +08:00
										 |  |  | 						require.Equal(t, apimodels.Provenance(models.ProvenanceAPI), actualRule.GrafanaManagedAlert.Provenance) | 
					
						
							| 
									
										
										
										
											2022-04-29 03:27:34 +08:00
										 |  |  | 						found = true | 
					
						
							|  |  |  | 					} else { | 
					
						
							| 
									
										
										
										
											2023-02-28 06:57:15 +08:00
										 |  |  | 						require.Equal(t, apimodels.Provenance(models.ProvenanceNone), actualRule.GrafanaManagedAlert.Provenance) | 
					
						
							| 
									
										
										
										
											2022-04-29 03:27:34 +08:00
										 |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		require.True(t, found) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 	t.Run("should enforce order of rules in the group", func(t *testing.T) { | 
					
						
							|  |  |  | 		orgID := rand.Int63() | 
					
						
							|  |  |  | 		folder := randFolder() | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 		ruleStore := fakes.NewRuleStore(t) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) | 
					
						
							|  |  |  | 		groupKey := models.GenerateGroupKey(orgID) | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | 		groupKey.NamespaceUID = folder.UID | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex())) | 
					
						
							|  |  |  | 		ruleStore.PutRule(context.Background(), expectedRules...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 		req := createRequestContext(orgID, nil) | 
					
						
							|  |  |  | 		response := createService(ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		require.Equal(t, http.StatusAccepted, response.Status()) | 
					
						
							|  |  |  | 		result := &apimodels.NamespaceConfigResponse{} | 
					
						
							|  |  |  | 		require.NoError(t, json.Unmarshal(response.Body(), result)) | 
					
						
							|  |  |  | 		require.NotNil(t, result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		models.RulesGroup(expectedRules).SortByGroupIndex() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.Contains(t, *result, folder.Title) | 
					
						
							|  |  |  | 		groups := (*result)[folder.Title] | 
					
						
							|  |  |  | 		require.Len(t, groups, 1) | 
					
						
							|  |  |  | 		group := groups[0] | 
					
						
							|  |  |  | 		require.Equal(t, groupKey.RuleGroup, group.Name) | 
					
						
							|  |  |  | 		for i, actual := range groups[0].Rules { | 
					
						
							|  |  |  | 			expected := expectedRules[i] | 
					
						
							|  |  |  | 			if actual.GrafanaManagedAlert.UID != expected.UID { | 
					
						
							|  |  |  | 				var actualUIDs []string | 
					
						
							|  |  |  | 				var expectedUIDs []string | 
					
						
							|  |  |  | 				for _, rule := range group.Rules { | 
					
						
							|  |  |  | 					actualUIDs = append(actualUIDs, rule.GrafanaManagedAlert.UID) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				for _, rule := range expectedRules { | 
					
						
							|  |  |  | 					expectedUIDs = append(expectedUIDs, rule.UID) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				require.Fail(t, fmt.Sprintf("rules are not sorted by group index. Expected: %v. Actual: %v", expectedUIDs, actualUIDs)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 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() | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 			ruleStore := fakes.NewRuleStore(t) | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 			folder1 := randFolder() | 
					
						
							|  |  |  | 			folder2 := randFolder() | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | 			ruleStore.Folders[orgID] = []*folder.Folder{folder1, folder2} | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			group1Key := models.GenerateGroupKey(orgID) | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | 			group1Key.NamespaceUID = folder1.UID | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 			group2Key := models.GenerateGroupKey(orgID) | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | 			group2Key.NamespaceUID = folder2.UID | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			group1 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group1Key))) | 
					
						
							|  |  |  | 			group2 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group2Key))) | 
					
						
							|  |  |  | 			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) { | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				permissions := createPermissionsForRules(append(group1, group2[1:]...), orgID) | 
					
						
							|  |  |  | 				request := createRequestContextWithPerms(orgID, permissions, nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				response := createService(ruleStore).RouteGetRulesConfig(request) | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 				require.Equal(t, http.StatusOK, response.Status()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				result := &apimodels.NamespaceConfigResponse{} | 
					
						
							|  |  |  | 				require.NoError(t, json.Unmarshal(response.Body(), result)) | 
					
						
							|  |  |  | 				require.NotNil(t, result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				require.Contains(t, *result, folder1.Title) | 
					
						
							|  |  |  | 				require.NotContains(t, *result, folder2.Title) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				groups := (*result)[folder1.Title] | 
					
						
							|  |  |  | 				require.Len(t, groups, 1) | 
					
						
							|  |  |  | 				require.Equal(t, group1Key.RuleGroup, groups[0].Name) | 
					
						
							|  |  |  | 				require.Len(t, groups[0].Rules, len(group1)) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should return rules in group sorted by group index", func(t *testing.T) { | 
					
						
							|  |  |  | 		orgID := rand.Int63() | 
					
						
							|  |  |  | 		folder := randFolder() | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 		ruleStore := fakes.NewRuleStore(t) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) | 
					
						
							|  |  |  | 		groupKey := models.GenerateGroupKey(orgID) | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | 		groupKey.NamespaceUID = folder.UID | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex())) | 
					
						
							|  |  |  | 		ruleStore.PutRule(context.Background(), expectedRules...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 		req := createRequestContext(orgID, nil) | 
					
						
							|  |  |  | 		response := createService(ruleStore).RouteGetRulesConfig(req) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		require.Equal(t, http.StatusOK, response.Status()) | 
					
						
							|  |  |  | 		result := &apimodels.NamespaceConfigResponse{} | 
					
						
							|  |  |  | 		require.NoError(t, json.Unmarshal(response.Body(), result)) | 
					
						
							|  |  |  | 		require.NotNil(t, result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		models.RulesGroup(expectedRules).SortByGroupIndex() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.Contains(t, *result, folder.Title) | 
					
						
							|  |  |  | 		groups := (*result)[folder.Title] | 
					
						
							|  |  |  | 		require.Len(t, groups, 1) | 
					
						
							|  |  |  | 		group := groups[0] | 
					
						
							|  |  |  | 		require.Equal(t, groupKey.RuleGroup, group.Name) | 
					
						
							|  |  |  | 		for i, actual := range groups[0].Rules { | 
					
						
							|  |  |  | 			expected := expectedRules[i] | 
					
						
							|  |  |  | 			if actual.GrafanaManagedAlert.UID != expected.UID { | 
					
						
							|  |  |  | 				var actualUIDs []string | 
					
						
							|  |  |  | 				var expectedUIDs []string | 
					
						
							|  |  |  | 				for _, rule := range group.Rules { | 
					
						
							|  |  |  | 					actualUIDs = append(actualUIDs, rule.GrafanaManagedAlert.UID) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				for _, rule := range expectedRules { | 
					
						
							|  |  |  | 					expectedUIDs = append(expectedUIDs, rule.UID) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				require.Fail(t, fmt.Sprintf("rules are not sorted by group index. Expected: %v. Actual: %v", expectedUIDs, actualUIDs)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRouteGetRulesGroupConfig(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() | 
					
						
							|  |  |  | 			folder := randFolder() | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 			ruleStore := fakes.NewRuleStore(t) | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 			ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) | 
					
						
							|  |  |  | 			groupKey := models.GenerateGroupKey(orgID) | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | 			groupKey.NamespaceUID = folder.UID | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey))) | 
					
						
							|  |  |  | 			ruleStore.PutRule(context.Background(), expectedRules...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			t.Run("and return 401 if user does not have access one of rules", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				permissions := createPermissionsForRules(expectedRules[1:], orgID) | 
					
						
							|  |  |  | 				request := createRequestContextWithPerms(orgID, permissions, map[string]string{ | 
					
						
							|  |  |  | 					":Namespace": folder.Title, | 
					
						
							|  |  |  | 					":Groupname": groupKey.RuleGroup, | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 				response := createService(ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup) | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 				require.Equal(t, http.StatusUnauthorized, response.Status()) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			t.Run("and return rules if user has access to all of them", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 				permissions := createPermissionsForRules(expectedRules, orgID) | 
					
						
							|  |  |  | 				request := createRequestContextWithPerms(orgID, permissions, map[string]string{ | 
					
						
							|  |  |  | 					":Namespace": folder.Title, | 
					
						
							|  |  |  | 					":Groupname": groupKey.RuleGroup, | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 				response := createService(ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup) | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				require.Equal(t, http.StatusAccepted, response.Status()) | 
					
						
							|  |  |  | 				result := &apimodels.RuleGroupConfigResponse{} | 
					
						
							|  |  |  | 				require.NoError(t, json.Unmarshal(response.Body(), result)) | 
					
						
							|  |  |  | 				require.NotNil(t, result) | 
					
						
							|  |  |  | 				require.Len(t, result.Rules, len(expectedRules)) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should return rules in group sorted by group index", func(t *testing.T) { | 
					
						
							|  |  |  | 		orgID := rand.Int63() | 
					
						
							|  |  |  | 		folder := randFolder() | 
					
						
							| 
									
										
										
										
											2022-10-01 03:36:51 +08:00
										 |  |  | 		ruleStore := fakes.NewRuleStore(t) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) | 
					
						
							|  |  |  | 		groupKey := models.GenerateGroupKey(orgID) | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | 		groupKey.NamespaceUID = folder.UID | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex())) | 
					
						
							|  |  |  | 		ruleStore.PutRule(context.Background(), expectedRules...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 		req := createRequestContext(orgID, nil) | 
					
						
							|  |  |  | 		response := createService(ruleStore).RouteGetRulesGroupConfig(req, folder.Title, groupKey.RuleGroup) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		require.Equal(t, http.StatusAccepted, response.Status()) | 
					
						
							|  |  |  | 		result := &apimodels.RuleGroupConfigResponse{} | 
					
						
							|  |  |  | 		require.NoError(t, json.Unmarshal(response.Body(), result)) | 
					
						
							|  |  |  | 		require.NotNil(t, result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		models.RulesGroup(expectedRules).SortByGroupIndex() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for i, actual := range result.Rules { | 
					
						
							|  |  |  | 			expected := expectedRules[i] | 
					
						
							|  |  |  | 			if actual.GrafanaManagedAlert.UID != expected.UID { | 
					
						
							|  |  |  | 				var actualUIDs []string | 
					
						
							|  |  |  | 				var expectedUIDs []string | 
					
						
							|  |  |  | 				for _, rule := range result.Rules { | 
					
						
							|  |  |  | 					actualUIDs = append(actualUIDs, rule.GrafanaManagedAlert.UID) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				for _, rule := range expectedRules { | 
					
						
							|  |  |  | 					expectedUIDs = append(expectedUIDs, rule.UID) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				require.Fail(t, fmt.Sprintf("rules are not sorted by group index. Expected: %v. Actual: %v", expectedUIDs, actualUIDs)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-16 04:01:14 +08:00
										 |  |  | func TestVerifyProvisionedRulesNotAffected(t *testing.T) { | 
					
						
							|  |  |  | 	orgID := rand.Int63() | 
					
						
							|  |  |  | 	group := models.GenerateGroupKey(orgID) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 	affectedGroups := make(map[models.AlertRuleGroupKey]models.RulesGroup) | 
					
						
							| 
									
										
										
										
											2022-06-16 04:01:14 +08:00
										 |  |  | 	var allRules []*models.AlertRule | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		rules := models.GenerateAlertRules(rand.Intn(3)+1, models.AlertRuleGen(withGroupKey(group))) | 
					
						
							|  |  |  | 		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))) | 
					
						
							|  |  |  | 			allRules = append(allRules, rules...) | 
					
						
							|  |  |  | 			affectedGroups[g] = rules | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-08-02 12:41:23 +08:00
										 |  |  | 	ch := &store.GroupDelta{ | 
					
						
							| 
									
										
										
										
											2022-06-16 04:01:14 +08:00
										 |  |  | 		GroupKey:       group, | 
					
						
							|  |  |  | 		AffectedGroups: affectedGroups, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should return error if at least one rule in affected groups is provisioned", func(t *testing.T) { | 
					
						
							|  |  |  | 		rand.Shuffle(len(allRules), func(i, j int) { | 
					
						
							|  |  |  | 			allRules[j], allRules[i] = allRules[i], allRules[j] | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		storeResult := make(map[string]models.Provenance, len(allRules)) | 
					
						
							|  |  |  | 		storeResult[allRules[0].UID] = models.ProvenanceAPI | 
					
						
							|  |  |  | 		storeResult[allRules[1].UID] = models.ProvenanceFile | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		provenanceStore := &provisioning.MockProvisioningStore{} | 
					
						
							|  |  |  | 		provenanceStore.EXPECT().GetProvenances(mock.Anything, orgID, "alertRule").Return(storeResult, nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		result := verifyProvisionedRulesNotAffected(context.Background(), provenanceStore, orgID, ch) | 
					
						
							|  |  |  | 		require.Error(t, result) | 
					
						
							|  |  |  | 		require.ErrorIs(t, result, errProvisionedResource) | 
					
						
							|  |  |  | 		assert.Contains(t, result.Error(), allRules[0].GetGroupKey().String()) | 
					
						
							|  |  |  | 		assert.Contains(t, result.Error(), allRules[1].GetGroupKey().String()) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should return nil if all have ProvenanceNone", func(t *testing.T) { | 
					
						
							|  |  |  | 		storeResult := make(map[string]models.Provenance, len(allRules)) | 
					
						
							|  |  |  | 		for _, rule := range allRules { | 
					
						
							|  |  |  | 			storeResult[rule.UID] = models.ProvenanceNone | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		provenanceStore := &provisioning.MockProvisioningStore{} | 
					
						
							|  |  |  | 		provenanceStore.EXPECT().GetProvenances(mock.Anything, orgID, "alertRule").Return(storeResult, nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		result := verifyProvisionedRulesNotAffected(context.Background(), provenanceStore, orgID, ch) | 
					
						
							|  |  |  | 		require.NoError(t, result) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should return nil if no alerts have provisioning status", func(t *testing.T) { | 
					
						
							|  |  |  | 		provenanceStore := &provisioning.MockProvisioningStore{} | 
					
						
							|  |  |  | 		provenanceStore.EXPECT().GetProvenances(mock.Anything, orgID, "alertRule").Return(make(map[string]models.Provenance, len(allRules)), nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		result := verifyProvisionedRulesNotAffected(context.Background(), provenanceStore, orgID, ch) | 
					
						
							|  |  |  | 		require.NoError(t, result) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | func TestValidateQueries(t *testing.T) { | 
					
						
							|  |  |  | 	delta := store.GroupDelta{ | 
					
						
							|  |  |  | 		New: []*models.AlertRule{ | 
					
						
							|  |  |  | 			models.AlertRuleGen(func(rule *models.AlertRule) { | 
					
						
							|  |  |  | 				rule.Condition = "New" | 
					
						
							|  |  |  | 			})(), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		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" | 
					
						
							|  |  |  | 				})(), | 
					
						
							|  |  |  | 				Diff: nil, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Delete: []*models.AlertRule{ | 
					
						
							|  |  |  | 			models.AlertRuleGen(func(rule *models.AlertRule) { | 
					
						
							|  |  |  | 				rule.Condition = "Deleted" | 
					
						
							|  |  |  | 			})(), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should validate New and Updated only", func(t *testing.T) { | 
					
						
							|  |  |  | 		validator := &recordingConditionValidator{} | 
					
						
							|  |  |  | 		err := validateQueries(context.Background(), &delta, validator, nil) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		for _, condition := range validator.recorded { | 
					
						
							|  |  |  | 			if condition.Condition == "New" || condition.Condition == "Update_New" { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			assert.Failf(t, "validated unexpected condition", "condition '%s' was validated but should not", condition.Condition) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	t.Run("should return rule validate error if fails on new rule", func(t *testing.T) { | 
					
						
							|  |  |  | 		validator := &recordingConditionValidator{ | 
					
						
							|  |  |  | 			hook: func(c models.Condition) error { | 
					
						
							|  |  |  | 				if c.Condition == "New" { | 
					
						
							|  |  |  | 					return errors.New("test") | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err := validateQueries(context.Background(), &delta, validator, nil) | 
					
						
							|  |  |  | 		require.Error(t, err) | 
					
						
							|  |  |  | 		require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	t.Run("should return rule validate error with UID if fails on updated rule", func(t *testing.T) { | 
					
						
							|  |  |  | 		validator := &recordingConditionValidator{ | 
					
						
							|  |  |  | 			hook: func(c models.Condition) error { | 
					
						
							|  |  |  | 				if c.Condition == "Update_New" { | 
					
						
							|  |  |  | 					return errors.New("test") | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err := validateQueries(context.Background(), &delta, validator, nil) | 
					
						
							|  |  |  | 		require.Error(t, err) | 
					
						
							|  |  |  | 		require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation) | 
					
						
							|  |  |  | 		require.ErrorContains(t, err, delta.Update[0].New.UID) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | func createServiceWithProvenanceStore(store *fakes.RuleStore, provenanceStore provisioning.ProvisioningStore) *RulerSrv { | 
					
						
							|  |  |  | 	svc := createService(store) | 
					
						
							| 
									
										
										
										
											2022-08-25 03:33:33 +08:00
										 |  |  | 	svc.provenanceStore = provenanceStore | 
					
						
							|  |  |  | 	return svc | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | func createService(store *fakes.RuleStore) *RulerSrv { | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 	return &RulerSrv{ | 
					
						
							|  |  |  | 		xactManager:     store, | 
					
						
							|  |  |  | 		store:           store, | 
					
						
							|  |  |  | 		QuotaService:    nil, | 
					
						
							| 
									
										
										
										
											2022-04-29 03:27:34 +08:00
										 |  |  | 		provenanceStore: provisioning.NewFakeProvisioningStore(), | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 		log:             log.New("test"), | 
					
						
							| 
									
										
										
										
											2023-10-02 23:47:59 +08:00
										 |  |  | 		cfg: &setting.UnifiedAlertingSettings{ | 
					
						
							|  |  |  | 			BaseInterval: 10 * time.Second, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2023-11-16 00:54:54 +08:00
										 |  |  | 		authz: accesscontrol.NewRuleService(acimpl.ProvideAccessControl(setting.NewCfg())), | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | func createRequestContext(orgID int64, params map[string]string) *contextmodel.ReqContext { | 
					
						
							|  |  |  | 	defaultPerms := map[int64]map[string][]string{orgID: {datasources.ActionQuery: []string{datasources.ScopeAll}}} | 
					
						
							|  |  |  | 	return createRequestContextWithPerms(orgID, defaultPerms, params) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func createRequestContextWithPerms(orgID int64, permissions map[int64]map[string][]string, params map[string]string) *contextmodel.ReqContext { | 
					
						
							| 
									
										
										
										
											2022-06-18 01:55:31 +08:00
										 |  |  | 	uri, _ := url.Parse("http://localhost") | 
					
						
							| 
									
										
										
										
											2023-10-02 23:47:59 +08:00
										 |  |  | 	ctx := web.Context{ | 
					
						
							|  |  |  | 		Req: &http.Request{ | 
					
						
							|  |  |  | 			URL:    uri, | 
					
						
							|  |  |  | 			Header: make(http.Header), | 
					
						
							|  |  |  | 			Form:   make(url.Values), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Resp: web.NewResponseWriter("GET", httptest.NewRecorder()), | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-06-24 04:13:39 +08:00
										 |  |  | 	if params != nil { | 
					
						
							|  |  |  | 		ctx.Req = web.SetURLParams(ctx.Req, params) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | 	return &contextmodel.ReqContext{ | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 		IsSignedIn: true, | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 		SignedInUser: &user.SignedInUser{ | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 			Permissions: permissions, | 
					
						
							|  |  |  | 			OrgID:       orgID, | 
					
						
							| 
									
										
										
										
											2022-03-26 00:39:24 +08:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		Context: &ctx, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | func createPermissionsForRules(rules []*models.AlertRule, orgID int64) map[int64]map[string][]string { | 
					
						
							|  |  |  | 	permissions := map[string][]string{} | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 	for _, rule := range rules { | 
					
						
							|  |  |  | 		for _, query := range rule.Data { | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 			permissions[datasources.ActionQuery] = append(permissions[datasources.ActionQuery], datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID)) | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 	return map[int64]map[string][]string{orgID: permissions} | 
					
						
							| 
									
										
										
										
											2022-04-12 05:37:44 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 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 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | func withNamespace(namespace *folder.Folder) func(rule *models.AlertRule) { | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 	return func(rule *models.AlertRule) { | 
					
						
							| 
									
										
										
										
											2022-11-11 21:28:24 +08:00
										 |  |  | 		rule.NamespaceUID = namespace.UID | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-17 03:45:45 +08:00
										 |  |  | 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 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |