| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | package alerting | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 	"embed" | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 	"math/rand" | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2024-02-15 22:45:10 +08:00
										 |  |  | 	"slices" | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 	"github.com/google/go-cmp/cmp" | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 	"github.com/google/uuid" | 
					
						
							| 
									
										
										
										
											2024-01-05 00:47:13 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/data" | 
					
						
							| 
									
										
										
										
											2024-02-15 22:45:10 +08:00
										 |  |  | 	"github.com/prometheus/alertmanager/pkg/labels" | 
					
						
							| 
									
										
										
										
											2022-02-24 00:30:04 +08:00
										 |  |  | 	"github.com/prometheus/common/model" | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/expr" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/accesscontrol" | 
					
						
							| 
									
										
										
										
											2022-08-18 15:43:45 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/datasources" | 
					
						
							|  |  |  | 	datasourceService "github.com/grafana/grafana/pkg/services/datasources/service" | 
					
						
							| 
									
										
										
										
											2023-07-21 22:23:01 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/featuremgmt" | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" | 
					
						
							|  |  |  | 	ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/org" | 
					
						
							| 
									
										
										
										
											2022-06-28 20:32:25 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/user" | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/tests/testinfra" | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util" | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | //go:embed test-data/*.*
 | 
					
						
							|  |  |  | var testData embed.FS | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 15:11:56 +08:00
										 |  |  | func TestIntegrationAlertRulePermissions(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	// Setup Grafana and its Database
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 	dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							| 
									
										
										
										
											2021-09-29 22:16:40 +08:00
										 |  |  | 		DisableLegacyAlerting: true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting: true, | 
					
						
							|  |  |  | 		DisableAnonymous:      true, | 
					
						
							| 
									
										
										
										
											2022-02-09 17:26:06 +08:00
										 |  |  | 		AppModeProduction:     true, | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-08-25 21:11:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) | 
					
						
							| 
									
										
										
										
											2024-05-28 23:32:23 +08:00
										 |  |  | 	permissionsStore := resourcepermissions.NewStore(env.Cfg, env.SQLStore, featuremgmt.WithFeatures()) | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-05 00:16:28 +08:00
										 |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleEditor), | 
					
						
							| 
									
										
										
										
											2021-08-12 21:04:09 +08:00
										 |  |  | 		Password:       "password", | 
					
						
							|  |  |  | 		Login:          "grafana", | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-05-05 00:16:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password") | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create the namespace we'll save our alerts to.
 | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	apiClient.CreateFolder(t, "folder1", "folder1") | 
					
						
							|  |  |  | 	// Create the namespace we'll save our alerts to.
 | 
					
						
							|  |  |  | 	apiClient.CreateFolder(t, "folder2", "folder2") | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 	postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json")) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	var group1 apimodels.PostableRuleGroupConfig | 
					
						
							|  |  |  | 	require.NoError(t, json.Unmarshal(postGroupRaw, &group1)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	// Create rule under folder1
 | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 	_, status, response := apiClient.PostRulesGroupWithStatus(t, "folder1", &group1) | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 	require.Equalf(t, http.StatusAccepted, status, response) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	postGroupRaw, err = testData.ReadFile(path.Join("test-data", "rulegroup-2-post.json")) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	var group2 apimodels.PostableRuleGroupConfig | 
					
						
							|  |  |  | 	require.NoError(t, json.Unmarshal(postGroupRaw, &group2)) | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create rule under folder2
 | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 	_, status, response = apiClient.PostRulesGroupWithStatus(t, "folder2", &group2) | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 	require.Equalf(t, http.StatusAccepted, status, response) | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// With the rules created, let's make sure that rule definitions are stored.
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 	allRules, status, _ := apiClient.GetAllRulesWithStatus(t) | 
					
						
							|  |  |  | 	require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 	status, allExportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 		ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 	var allExport apimodels.AlertingFileExport | 
					
						
							|  |  |  | 	require.NoError(t, json.Unmarshal([]byte(allExportRaw), &allExport)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("when user has all permissions", func(t *testing.T) { | 
					
						
							|  |  |  | 		t.Run("Get all returns all rules", func(t *testing.T) { | 
					
						
							|  |  |  | 			var group1, group2 apimodels.GettableRuleGroupConfig | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-get.json")) | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 			require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 			require.NoError(t, json.Unmarshal(getGroup1Raw, &group1)) | 
					
						
							|  |  |  | 			getGroup2Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-2-get.json")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(getGroup2Raw, &group2)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			expected := apimodels.NamespaceConfigResponse{ | 
					
						
							|  |  |  | 				"folder1": []apimodels.GettableRuleGroupConfig{ | 
					
						
							|  |  |  | 					group1, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				"folder2": []apimodels.GettableRuleGroupConfig{ | 
					
						
							|  |  |  | 					group2, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			pathsToIgnore := []string{ | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.Updated", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.UID", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.ID", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.Data.Model", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.NamespaceUID", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.NamespaceID", | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// compare expected and actual and ignore the dynamic fields
 | 
					
						
							|  |  |  | 			diff := cmp.Diff(expected, allRules, cmp.FilterPath(func(path cmp.Path) bool { | 
					
						
							|  |  |  | 				for _, s := range pathsToIgnore { | 
					
						
							|  |  |  | 					if strings.Contains(path.String(), s) { | 
					
						
							|  |  |  | 						return true | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			}, cmp.Ignore())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Empty(t, diff) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for _, rule := range allRules["folder1"][0].Rules { | 
					
						
							|  |  |  | 				assert.Equal(t, "folder1", rule.GrafanaManagedAlert.NamespaceUID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			for _, rule := range allRules["folder2"][0].Rules { | 
					
						
							|  |  |  | 				assert.Equal(t, "folder2", rule.GrafanaManagedAlert.NamespaceUID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Get by folder returns groups in folder", func(t *testing.T) { | 
					
						
							|  |  |  | 			rules, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "folder1") | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusAccepted, status) | 
					
						
							|  |  |  | 			require.Contains(t, rules, "folder1") | 
					
						
							|  |  |  | 			require.Len(t, rules["folder1"], 1) | 
					
						
							|  |  |  | 			require.Equal(t, allRules["folder1"], rules["folder1"]) | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 		t.Run("Get group returns a single group", func(t *testing.T) { | 
					
						
							|  |  |  | 			rules := apiClient.GetRulesGroup(t, "folder2", allRules["folder2"][0].Name) | 
					
						
							|  |  |  | 			cmp.Diff(allRules["folder2"][0], rules.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export returns all rules", func(t *testing.T) { | 
					
						
							|  |  |  | 			var group1File, group2File apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-export.json")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(getGroup1Raw, &group1File)) | 
					
						
							|  |  |  | 			getGroup2Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-2-export.json")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(getGroup2Raw, &group2File)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			group1File.Groups = append(group1File.Groups, group2File.Groups...) | 
					
						
							|  |  |  | 			expected := group1File | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			pathsToIgnore := []string{ | 
					
						
							|  |  |  | 				"Groups.Rules.UID", | 
					
						
							|  |  |  | 				"Groups.Folder", | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// compare expected and actual and ignore the dynamic fields
 | 
					
						
							|  |  |  | 			diff := cmp.Diff(expected, allExport, cmp.FilterPath(func(path cmp.Path) bool { | 
					
						
							|  |  |  | 				for _, s := range pathsToIgnore { | 
					
						
							|  |  |  | 					if strings.Contains(path.String(), s) { | 
					
						
							|  |  |  | 						return true | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			}, cmp.Ignore())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Empty(t, diff) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Equal(t, "folder1", allExport.Groups[0].Folder) | 
					
						
							|  |  |  | 			require.Equal(t, "folder2", allExport.Groups[1].Folder) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from one folder", func(t *testing.T) { | 
					
						
							|  |  |  | 			expected := allExport.Groups[0] | 
					
						
							|  |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"folder1"}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 1) | 
					
						
							|  |  |  | 			require.Equal(t, expected, export.Groups[0]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from one group", func(t *testing.T) { | 
					
						
							|  |  |  | 			expected := allExport.Groups[0] | 
					
						
							|  |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"folder1"}, | 
					
						
							|  |  |  | 				GroupName:         expected.Name, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 1) | 
					
						
							|  |  |  | 			require.Equal(t, expected, export.Groups[0]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export single rule", func(t *testing.T) { | 
					
						
							|  |  |  | 			expected := allExport.Groups[0] | 
					
						
							|  |  |  | 			expected.Rules = []apimodels.AlertRuleExport{ | 
					
						
							|  |  |  | 				expected.Rules[0], | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				RuleUID:           expected.Rules[0].UID, | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			t.Log(exportRaw) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 1) | 
					
						
							|  |  |  | 			require.Equal(t, expected, export.Groups[0]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("when permissions for folder2 removed", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 		// remove permissions from folder2
 | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 		removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder2") | 
					
						
							| 
									
										
										
										
											2022-06-27 21:31:49 +08:00
										 |  |  | 		apiClient.ReloadCachedPermissions(t) | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 		t.Run("Get all returns all rules", func(t *testing.T) { | 
					
						
							|  |  |  | 			newAll, status, _ := apiClient.GetAllRulesWithStatus(t) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			require.NotContains(t, newAll, "folder2") | 
					
						
							|  |  |  | 			require.Contains(t, newAll, "folder1") | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 		t.Run("Get by folder returns groups in folder", func(t *testing.T) { | 
					
						
							|  |  |  | 			_, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "folder2") | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusForbidden, status) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2021-11-08 21:26:08 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 		t.Run("Get group returns a single group", func(t *testing.T) { | 
					
						
							|  |  |  | 			u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/folder2/arulegroup", apiClient.url) | 
					
						
							|  |  |  | 			// nolint:gosec
 | 
					
						
							|  |  |  | 			resp, err := http.Get(u) | 
					
						
							| 
									
										
										
										
											2021-11-08 21:26:08 +08:00
										 |  |  | 			require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 			defer func() { | 
					
						
							|  |  |  | 				_ = resp.Body.Close() | 
					
						
							|  |  |  | 			}() | 
					
						
							|  |  |  | 			assert.Equal(t, http.StatusForbidden, resp.StatusCode) | 
					
						
							| 
									
										
										
										
											2021-11-08 21:26:08 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 		t.Run("Export returns all rules", func(t *testing.T) { | 
					
						
							|  |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 1) | 
					
						
							|  |  |  | 			require.Equal(t, "folder1", export.Groups[0].Folder) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from one folder", func(t *testing.T) { | 
					
						
							|  |  |  | 			status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"folder2"}, | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2023-12-08 02:43:58 +08:00
										 |  |  | 			assert.Equal(t, http.StatusForbidden, status) | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from one group", func(t *testing.T) { | 
					
						
							|  |  |  | 			status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"folder2"}, | 
					
						
							|  |  |  | 				GroupName:         "arulegroup", | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			assert.Equal(t, http.StatusForbidden, status) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export single rule", func(t *testing.T) { | 
					
						
							|  |  |  | 			uid := allRules["folder2"][0].Rules[0].GrafanaManagedAlert.UID | 
					
						
							|  |  |  | 			status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				RuleUID:           uid, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusForbidden, status) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("when all permissions are revoked", func(t *testing.T) { | 
					
						
							|  |  |  | 			removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1") | 
					
						
							|  |  |  | 			apiClient.ReloadCachedPermissions(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			rules, status, _ := apiClient.GetAllRulesWithStatus(t) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			require.Empty(t, rules) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			status, _ = apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusNotFound, status) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | func TestIntegrationAlertRuleNestedPermissions(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Setup Grafana and its Database
 | 
					
						
							|  |  |  | 	dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		EnableFeatureToggles:  []string{featuremgmt.FlagNestedFolders}, | 
					
						
							|  |  |  | 		DisableLegacyAlerting: true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting: true, | 
					
						
							|  |  |  | 		DisableAnonymous:      true, | 
					
						
							|  |  |  | 		AppModeProduction:     true, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) | 
					
						
							| 
									
										
										
										
											2024-05-28 23:32:23 +08:00
										 |  |  | 	permissionsStore := resourcepermissions.NewStore(env.Cfg, env.SQLStore, featuremgmt.WithFeatures()) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleEditor), | 
					
						
							|  |  |  | 		Password:       "password", | 
					
						
							|  |  |  | 		Login:          "grafana", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create the namespace we'll save our alerts to.
 | 
					
						
							|  |  |  | 	apiClient.CreateFolder(t, "folder1", "folder1") | 
					
						
							|  |  |  | 	// Create the namespace we'll save our alerts to.
 | 
					
						
							|  |  |  | 	apiClient.CreateFolder(t, "folder2", "folder2") | 
					
						
							|  |  |  | 	// Create a subfolder
 | 
					
						
							|  |  |  | 	apiClient.CreateFolder(t, "subfolder", "subfolder", "folder1") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json")) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	var group1 apimodels.PostableRuleGroupConfig | 
					
						
							|  |  |  | 	require.NoError(t, json.Unmarshal(postGroupRaw, &group1)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create rule under folder1
 | 
					
						
							|  |  |  | 	_, status, response := apiClient.PostRulesGroupWithStatus(t, "folder1", &group1) | 
					
						
							|  |  |  | 	require.Equalf(t, http.StatusAccepted, status, response) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	postGroupRaw, err = testData.ReadFile(path.Join("test-data", "rulegroup-2-post.json")) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	var group2 apimodels.PostableRuleGroupConfig | 
					
						
							|  |  |  | 	require.NoError(t, json.Unmarshal(postGroupRaw, &group2)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create rule under folder2
 | 
					
						
							|  |  |  | 	_, status, response = apiClient.PostRulesGroupWithStatus(t, "folder2", &group2) | 
					
						
							|  |  |  | 	require.Equalf(t, http.StatusAccepted, status, response) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	postGroupRaw, err = testData.ReadFile(path.Join("test-data", "rulegroup-3-post.json")) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	var group3 apimodels.PostableRuleGroupConfig | 
					
						
							|  |  |  | 	require.NoError(t, json.Unmarshal(postGroupRaw, &group3)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create rule under subfolder
 | 
					
						
							|  |  |  | 	_, status, response = apiClient.PostRulesGroupWithStatus(t, "subfolder", &group3) | 
					
						
							|  |  |  | 	require.Equalf(t, http.StatusAccepted, status, response) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// With the rules created, let's make sure that rule definitions are stored.
 | 
					
						
							|  |  |  | 	allRules, status, _ := apiClient.GetAllRulesWithStatus(t) | 
					
						
							|  |  |  | 	require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 	status, allExportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 		ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 	var allExport apimodels.AlertingFileExport | 
					
						
							|  |  |  | 	require.NoError(t, json.Unmarshal([]byte(allExportRaw), &allExport)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("when user has all permissions", func(t *testing.T) { | 
					
						
							|  |  |  | 		t.Run("Get all returns all rules", func(t *testing.T) { | 
					
						
							|  |  |  | 			var group1, group2, group3 apimodels.GettableRuleGroupConfig | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-get.json")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(getGroup1Raw, &group1)) | 
					
						
							|  |  |  | 			getGroup2Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-2-get.json")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(getGroup2Raw, &group2)) | 
					
						
							|  |  |  | 			getGroup3Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-3-get.json")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(getGroup3Raw, &group3)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			expected := apimodels.NamespaceConfigResponse{ | 
					
						
							|  |  |  | 				"folder1": []apimodels.GettableRuleGroupConfig{ | 
					
						
							|  |  |  | 					group1, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				"folder2": []apimodels.GettableRuleGroupConfig{ | 
					
						
							|  |  |  | 					group2, | 
					
						
							|  |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2024-02-07 06:12:13 +08:00
										 |  |  | 				"folder1/subfolder": []apimodels.GettableRuleGroupConfig{ | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 					group3, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			pathsToIgnore := []string{ | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.Updated", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.UID", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.ID", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.Data.Model", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.NamespaceUID", | 
					
						
							|  |  |  | 				"GrafanaManagedAlert.NamespaceID", | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// compare expected and actual and ignore the dynamic fields
 | 
					
						
							|  |  |  | 			diff := cmp.Diff(expected, allRules, cmp.FilterPath(func(path cmp.Path) bool { | 
					
						
							|  |  |  | 				for _, s := range pathsToIgnore { | 
					
						
							|  |  |  | 					if strings.Contains(path.String(), s) { | 
					
						
							|  |  |  | 						return true | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			}, cmp.Ignore())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Empty(t, diff) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for _, rule := range allRules["folder1"][0].Rules { | 
					
						
							|  |  |  | 				assert.Equal(t, "folder1", rule.GrafanaManagedAlert.NamespaceUID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for _, rule := range allRules["folder2"][0].Rules { | 
					
						
							|  |  |  | 				assert.Equal(t, "folder2", rule.GrafanaManagedAlert.NamespaceUID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-07 06:12:13 +08:00
										 |  |  | 			for _, rule := range allRules["folder1/subfolder"][0].Rules { | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 				assert.Equal(t, "subfolder", rule.GrafanaManagedAlert.NamespaceUID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Get by folder returns groups in folder", func(t *testing.T) { | 
					
						
							|  |  |  | 			rules, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "folder1") | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusAccepted, status) | 
					
						
							|  |  |  | 			require.Contains(t, rules, "folder1") | 
					
						
							|  |  |  | 			require.Len(t, rules["folder1"], 1) | 
					
						
							|  |  |  | 			require.Equal(t, allRules["folder1"], rules["folder1"]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Get group returns a single group", func(t *testing.T) { | 
					
						
							|  |  |  | 			rules := apiClient.GetRulesGroup(t, "folder2", allRules["folder2"][0].Name) | 
					
						
							|  |  |  | 			cmp.Diff(allRules["folder2"][0], rules.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Get by folder returns groups in folder with nested folder format", func(t *testing.T) { | 
					
						
							|  |  |  | 			rules, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "subfolder") | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusAccepted, status) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-07 06:12:13 +08:00
										 |  |  | 			nestedKey := "folder1/subfolder" | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			require.Contains(t, rules, nestedKey) | 
					
						
							|  |  |  | 			require.Len(t, rules[nestedKey], 1) | 
					
						
							|  |  |  | 			require.Equal(t, allRules[nestedKey], rules[nestedKey]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export returns all rules", func(t *testing.T) { | 
					
						
							|  |  |  | 			var group1File, group2File, group3File apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-export.json")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(getGroup1Raw, &group1File)) | 
					
						
							|  |  |  | 			getGroup2Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-2-export.json")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(getGroup2Raw, &group2File)) | 
					
						
							|  |  |  | 			getGroup3Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-3-export.json")) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal(getGroup3Raw, &group3File)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			group1File.Groups = append(group1File.Groups, group2File.Groups...) | 
					
						
							|  |  |  | 			group1File.Groups = append(group1File.Groups, group3File.Groups...) | 
					
						
							|  |  |  | 			expected := group1File | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			pathsToIgnore := []string{ | 
					
						
							|  |  |  | 				"Groups.Rules.UID", | 
					
						
							|  |  |  | 				"Groups.Folder", | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// compare expected and actual and ignore the dynamic fields
 | 
					
						
							|  |  |  | 			diff := cmp.Diff(expected, allExport, cmp.FilterPath(func(path cmp.Path) bool { | 
					
						
							|  |  |  | 				for _, s := range pathsToIgnore { | 
					
						
							|  |  |  | 					if strings.Contains(path.String(), s) { | 
					
						
							|  |  |  | 						return true | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			}, cmp.Ignore())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Empty(t, diff) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Equal(t, "folder1", allExport.Groups[0].Folder) | 
					
						
							|  |  |  | 			require.Equal(t, "folder2", allExport.Groups[1].Folder) | 
					
						
							| 
									
										
										
										
											2024-05-31 16:09:20 +08:00
										 |  |  | 			require.Equal(t, "folder1/subfolder", allExport.Groups[2].Folder) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from one folder", func(t *testing.T) { | 
					
						
							|  |  |  | 			expected := allExport.Groups[0] | 
					
						
							|  |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"folder1"}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 1) | 
					
						
							|  |  |  | 			require.Equal(t, expected, export.Groups[0]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from a subfolder", func(t *testing.T) { | 
					
						
							|  |  |  | 			expected := allExport.Groups[2] | 
					
						
							|  |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"subfolder"}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 1) | 
					
						
							|  |  |  | 			require.Equal(t, expected, export.Groups[0]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from one group", func(t *testing.T) { | 
					
						
							|  |  |  | 			expected := allExport.Groups[0] | 
					
						
							|  |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"folder1"}, | 
					
						
							|  |  |  | 				GroupName:         expected.Name, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 1) | 
					
						
							|  |  |  | 			require.Equal(t, expected, export.Groups[0]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from one group under subfolder", func(t *testing.T) { | 
					
						
							|  |  |  | 			expected := allExport.Groups[2] | 
					
						
							|  |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"subfolder"}, | 
					
						
							|  |  |  | 				GroupName:         expected.Name, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 1) | 
					
						
							|  |  |  | 			require.Equal(t, expected, export.Groups[0]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export single rule", func(t *testing.T) { | 
					
						
							|  |  |  | 			expected := allExport.Groups[0] | 
					
						
							|  |  |  | 			expected.Rules = []apimodels.AlertRuleExport{ | 
					
						
							|  |  |  | 				expected.Rules[0], | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				RuleUID:           expected.Rules[0].UID, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			t.Log(exportRaw) | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 1) | 
					
						
							|  |  |  | 			require.Equal(t, expected, export.Groups[0]) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("when permissions for folder2 removed", func(t *testing.T) { | 
					
						
							|  |  |  | 		// remove permissions for folder2
 | 
					
						
							|  |  |  | 		removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder2") | 
					
						
							|  |  |  | 		// remove permissions for subfolder (inherits from folder1)
 | 
					
						
							|  |  |  | 		removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "subfolder") | 
					
						
							|  |  |  | 		apiClient.ReloadCachedPermissions(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Get all returns all rules", func(t *testing.T) { | 
					
						
							|  |  |  | 			newAll, status, _ := apiClient.GetAllRulesWithStatus(t) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			require.Contains(t, newAll, "folder1") | 
					
						
							|  |  |  | 			require.NotContains(t, newAll, "folder2") | 
					
						
							| 
									
										
										
										
											2024-02-07 06:12:13 +08:00
										 |  |  | 			require.Contains(t, newAll, "folder1/subfolder") | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Get by folder returns groups in folder", func(t *testing.T) { | 
					
						
							|  |  |  | 			_, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "folder2") | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusForbidden, status) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Get group returns a single group", func(t *testing.T) { | 
					
						
							|  |  |  | 			u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/folder2/arulegroup", apiClient.url) | 
					
						
							|  |  |  | 			// nolint:gosec
 | 
					
						
							|  |  |  | 			resp, err := http.Get(u) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			defer func() { | 
					
						
							|  |  |  | 				_ = resp.Body.Close() | 
					
						
							|  |  |  | 			}() | 
					
						
							|  |  |  | 			assert.Equal(t, http.StatusForbidden, resp.StatusCode) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export returns all rules", func(t *testing.T) { | 
					
						
							|  |  |  | 			status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			var export apimodels.AlertingFileExport | 
					
						
							|  |  |  | 			require.NoError(t, json.Unmarshal([]byte(exportRaw), &export)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			require.Len(t, export.Groups, 2) | 
					
						
							|  |  |  | 			require.Equal(t, "folder1", export.Groups[0].Folder) | 
					
						
							| 
									
										
										
										
											2024-05-31 16:09:20 +08:00
										 |  |  | 			require.Equal(t, "folder1/subfolder", export.Groups[1].Folder) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from one folder", func(t *testing.T) { | 
					
						
							|  |  |  | 			status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"folder2"}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			assert.Equal(t, http.StatusForbidden, status) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export from one group", func(t *testing.T) { | 
					
						
							|  |  |  | 			status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				FolderUID:         []string{"folder2"}, | 
					
						
							|  |  |  | 				GroupName:         "arulegroup", | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			assert.Equal(t, http.StatusForbidden, status) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Export single rule", func(t *testing.T) { | 
					
						
							|  |  |  | 			uid := allRules["folder2"][0].Rules[0].GrafanaManagedAlert.UID | 
					
						
							|  |  |  | 			status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 				RuleUID:           uid, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusForbidden, status) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("when all permissions are revoked", func(t *testing.T) { | 
					
						
							|  |  |  | 			removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1") | 
					
						
							|  |  |  | 			apiClient.ReloadCachedPermissions(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			rules, status, _ := apiClient.GetAllRulesWithStatus(t) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 			require.Empty(t, rules) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			status, _ = apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{ | 
					
						
							|  |  |  | 				ExportQueryParams: apimodels.ExportQueryParams{Format: "json"}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusNotFound, status) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | func createRule(t *testing.T, client apiClient, folder string) (apimodels.PostableRuleGroupConfig, string) { | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	t.Helper() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	interval, err := model.ParseDuration("1m") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 	doubleInterval := 2 * interval | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 	rules := apimodels.PostableRuleGroupConfig{ | 
					
						
							|  |  |  | 		Name:     "arulegroup", | 
					
						
							|  |  |  | 		Interval: interval, | 
					
						
							|  |  |  | 		Rules: []apimodels.PostableExtendedRuleNode{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				ApiRuleNode: &apimodels.ApiRuleNode{ | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 					For:         &doubleInterval, | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 					Labels:      map[string]string{"label1": "val1"}, | 
					
						
							|  |  |  | 					Annotations: map[string]string{"annotation1": "val1"}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ | 
					
						
							|  |  |  | 					Title:     fmt.Sprintf("rule under folder %s", folder), | 
					
						
							|  |  |  | 					Condition: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 					Data: []apimodels.AlertQuery{ | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 						{ | 
					
						
							|  |  |  | 							RefID: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 							RelativeTimeRange: apimodels.RelativeTimeRange{ | 
					
						
							|  |  |  | 								From: apimodels.Duration(time.Duration(5) * time.Hour), | 
					
						
							|  |  |  | 								To:   apimodels.Duration(time.Duration(3) * time.Hour), | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 							}, | 
					
						
							| 
									
										
										
										
											2023-02-01 01:50:10 +08:00
										 |  |  | 							DatasourceUID: expr.DatasourceUID, | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | 							Model: json.RawMessage(`{ | 
					
						
							|  |  |  | 								"type": "math", | 
					
						
							|  |  |  | 								"expression": "2 + 3 > 1" | 
					
						
							|  |  |  | 								}`), | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 	resp, status, _ := client.PostRulesGroupWithStatus(t, folder, &rules) | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	assert.Equal(t, http.StatusAccepted, status) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 	require.Len(t, resp.Created, 1) | 
					
						
							|  |  |  | 	return rules, resp.Created[0] | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestAlertRulePostExport(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Setup Grafana and its Database
 | 
					
						
							|  |  |  | 	dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		DisableLegacyAlerting: true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting: true, | 
					
						
							|  |  |  | 		DisableAnonymous:      true, | 
					
						
							|  |  |  | 		AppModeProduction:     true, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) | 
					
						
							| 
									
										
										
										
											2024-05-28 23:32:23 +08:00
										 |  |  | 	permissionsStore := resourcepermissions.NewStore(env.Cfg, env.SQLStore, featuremgmt.WithFeatures()) | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2023-10-06 03:47:49 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleEditor), | 
					
						
							|  |  |  | 		Password:       "password", | 
					
						
							|  |  |  | 		Login:          "grafana", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create the namespace we'll save our alerts to.
 | 
					
						
							|  |  |  | 	apiClient.CreateFolder(t, "folder1", "folder1") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var group1 apimodels.PostableRuleGroupConfig | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	group1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json")) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	require.NoError(t, json.Unmarshal(group1Raw, &group1)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should return in export format", func(t *testing.T) { | 
					
						
							|  |  |  | 		var expected, actual apimodels.AlertingFileExport | 
					
						
							|  |  |  | 		getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-export.json")) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.NoError(t, json.Unmarshal(getGroup1Raw, &expected)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		status, actualRaw := apiClient.PostRulesExportWithStatus(t, "folder1", &group1, &apimodels.ExportQueryParams{ | 
					
						
							|  |  |  | 			Download: false, | 
					
						
							|  |  |  | 			Format:   "json", | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		require.Equal(t, http.StatusOK, status) | 
					
						
							|  |  |  | 		require.NoError(t, json.Unmarshal([]byte(actualRaw), &actual)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		pathsToIgnore := []string{ | 
					
						
							|  |  |  | 			"Groups.Rules.UID", | 
					
						
							|  |  |  | 			"Groups.Folder", | 
					
						
							|  |  |  | 			"Data.Model", // Model is not amended with default values
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// compare expected and actual and ignore the dynamic fields
 | 
					
						
							|  |  |  | 		diff := cmp.Diff(expected, actual, cmp.FilterPath(func(path cmp.Path) bool { | 
					
						
							|  |  |  | 			for _, s := range pathsToIgnore { | 
					
						
							|  |  |  | 				if strings.Contains(path.String(), s) { | 
					
						
							|  |  |  | 					return true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		}, cmp.Ignore())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.Empty(t, diff) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.Equal(t, actual.Groups[0].Folder, "folder1") | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should return 403 when no access to folder", func(t *testing.T) { | 
					
						
							|  |  |  | 		removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1") | 
					
						
							|  |  |  | 		apiClient.ReloadCachedPermissions(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		status, _ := apiClient.PostRulesExportWithStatus(t, "folder1", &group1, &apimodels.ExportQueryParams{ | 
					
						
							|  |  |  | 			Download: false, | 
					
						
							|  |  |  | 			Format:   "json", | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		require.Equal(t, http.StatusForbidden, status) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-04-30 00:15:15 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 15:11:56 +08:00
										 |  |  | func TestIntegrationAlertRuleConflictingTitle(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 	// Setup Grafana and its Database
 | 
					
						
							|  |  |  | 	dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							| 
									
										
										
										
											2021-09-29 22:16:40 +08:00
										 |  |  | 		DisableLegacyAlerting: true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting: true, | 
					
						
							|  |  |  | 		EnableQuota:           true, | 
					
						
							|  |  |  | 		DisableAnonymous:      true, | 
					
						
							|  |  |  | 		ViewersCanEdit:        true, | 
					
						
							| 
									
										
										
										
											2022-02-09 17:26:06 +08:00
										 |  |  | 		AppModeProduction:     true, | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create user
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleAdmin), | 
					
						
							| 
									
										
										
										
											2021-08-12 21:04:09 +08:00
										 |  |  | 		Password:       "admin", | 
					
						
							|  |  |  | 		Login:          "admin", | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-16 18:45:41 +08:00
										 |  |  | 	// Create the namespace we'll save our alerts to.
 | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	apiClient.CreateFolder(t, "folder1", "folder1") | 
					
						
							| 
									
										
										
										
											2022-05-16 18:45:41 +08:00
										 |  |  | 	// Create the namespace we'll save our alerts to.
 | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	apiClient.CreateFolder(t, "folder2", "folder2") | 
					
						
							| 
									
										
										
										
											2022-05-16 18:45:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 	rules := newTestingRuleConfig(t) | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 	respModel, status, _ := apiClient.PostRulesGroupWithStatus(t, "folder1", &rules) | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	assert.Equal(t, http.StatusAccepted, status) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 	require.Len(t, respModel.Created, len(rules.Rules)) | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 	// fetch the created rules, so we can get the uid's and trigger
 | 
					
						
							|  |  |  | 	// and update by reusing the uid's
 | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	createdRuleGroup := apiClient.GetRulesGroup(t, "folder1", rules.Name).GettableRuleGroupConfig | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 	require.Len(t, createdRuleGroup.Rules, 2) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 	t.Run("trying to create alert with same title under same folder should fail", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-06-09 06:51:50 +08:00
										 |  |  | 		rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup) | 
					
						
							|  |  |  | 		rulesWithUID.Rules = append(rulesWithUID.Rules, rules.Rules[0]) // Create new copy of first rule.
 | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		_, status, body := apiClient.PostRulesGroupWithStatus(t, "folder1", &rulesWithUID) | 
					
						
							| 
									
										
										
										
											2024-02-08 01:55:48 +08:00
										 |  |  | 		assert.Equal(t, http.StatusConflict, status) | 
					
						
							| 
									
										
										
										
											2022-04-14 23:54:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 		var res map[string]any | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 		require.NoError(t, json.Unmarshal([]byte(body), &res)) | 
					
						
							| 
									
										
										
										
											2024-02-08 01:55:48 +08:00
										 |  |  | 		require.Contains(t, res["message"], ngmodels.ErrAlertRuleUniqueConstraintViolation.Error()) | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("trying to update an alert to the title of an existing alert in the same folder should fail", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-06-09 06:51:50 +08:00
										 |  |  | 		rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup) | 
					
						
							|  |  |  | 		rulesWithUID.Rules[1].GrafanaManagedAlert.Title = "AlwaysFiring" | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		_, status, body := apiClient.PostRulesGroupWithStatus(t, "folder1", &rulesWithUID) | 
					
						
							| 
									
										
										
										
											2024-02-08 01:55:48 +08:00
										 |  |  | 		assert.Equal(t, http.StatusConflict, status) | 
					
						
							| 
									
										
										
										
											2022-04-14 23:54:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 		var res map[string]any | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 		require.NoError(t, json.Unmarshal([]byte(body), &res)) | 
					
						
							| 
									
										
										
										
											2024-02-08 01:55:48 +08:00
										 |  |  | 		require.Contains(t, res["message"], ngmodels.ErrAlertRuleUniqueConstraintViolation.Error()) | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("trying to create alert with same title under another folder should succeed", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 		rules := newTestingRuleConfig(t) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		resp, status, _ := apiClient.PostRulesGroupWithStatus(t, "folder2", &rules) | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 		assert.Equal(t, http.StatusAccepted, status) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		require.Len(t, resp.Created, len(rules.Rules)) | 
					
						
							| 
									
										
										
										
											2023-06-09 06:51:50 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("trying to swap titles of existing alerts in the same folder should work", func(t *testing.T) { | 
					
						
							|  |  |  | 		rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup) | 
					
						
							|  |  |  | 		title0 := rulesWithUID.Rules[0].GrafanaManagedAlert.Title | 
					
						
							|  |  |  | 		title1 := rulesWithUID.Rules[1].GrafanaManagedAlert.Title | 
					
						
							|  |  |  | 		rulesWithUID.Rules[0].GrafanaManagedAlert.Title = title1 | 
					
						
							|  |  |  | 		rulesWithUID.Rules[1].GrafanaManagedAlert.Title = title0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		resp, status, _ := apiClient.PostRulesGroupWithStatus(t, "folder1", &rulesWithUID) | 
					
						
							| 
									
										
										
										
											2023-06-09 06:51:50 +08:00
										 |  |  | 		assert.Equal(t, http.StatusAccepted, status) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		require.Len(t, resp.Updated, 2) | 
					
						
							| 
									
										
										
										
											2023-06-09 06:51:50 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("trying to update titles of existing alerts in a chain in the same folder should work", func(t *testing.T) { | 
					
						
							|  |  |  | 		rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup) | 
					
						
							|  |  |  | 		rulesWithUID.Rules[0].GrafanaManagedAlert.Title = rulesWithUID.Rules[1].GrafanaManagedAlert.Title | 
					
						
							|  |  |  | 		rulesWithUID.Rules[1].GrafanaManagedAlert.Title = "something new" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		resp, status, _ := apiClient.PostRulesGroupWithStatus(t, "folder1", &rulesWithUID) | 
					
						
							| 
									
										
										
										
											2023-06-09 06:51:50 +08:00
										 |  |  | 		assert.Equal(t, http.StatusAccepted, status) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		require.Len(t, resp.Updated, len(rulesWithUID.Rules)) | 
					
						
							| 
									
										
										
										
											2021-06-05 01:45:26 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 15:11:56 +08:00
										 |  |  | func TestIntegrationRulerRulesFilterByDashboard(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 	dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		EnableFeatureToggles: []string{"ngalert"}, | 
					
						
							|  |  |  | 		DisableAnonymous:     true, | 
					
						
							| 
									
										
										
										
											2022-02-09 17:26:06 +08:00
										 |  |  | 		AppModeProduction:    true, | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleEditor), | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 		Password:       "password", | 
					
						
							|  |  |  | 		Login:          "grafana", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-16 18:45:41 +08:00
										 |  |  | 	dashboardUID := "default" | 
					
						
							|  |  |  | 	// Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
 | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 	apiClient.CreateFolder(t, "default", "default") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 	interval, err := model.ParseDuration("10s") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Now, let's create some rules
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		rules := apimodels.PostableRuleGroupConfig{ | 
					
						
							|  |  |  | 			Name: "anotherrulegroup", | 
					
						
							|  |  |  | 			Rules: []apimodels.PostableExtendedRuleNode{ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					ApiRuleNode: &apimodels.ApiRuleNode{ | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 						For:    &interval, | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 						Labels: map[string]string{}, | 
					
						
							|  |  |  | 						Annotations: map[string]string{ | 
					
						
							|  |  |  | 							"__dashboardUid__": dashboardUID, | 
					
						
							|  |  |  | 							"__panelId__":      "1", | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ | 
					
						
							|  |  |  | 						Title:     "AlwaysFiring", | 
					
						
							|  |  |  | 						Condition: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 						Data: []apimodels.AlertQuery{ | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 							{ | 
					
						
							|  |  |  | 								RefID: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 								RelativeTimeRange: apimodels.RelativeTimeRange{ | 
					
						
							|  |  |  | 									From: apimodels.Duration(time.Duration(5) * time.Hour), | 
					
						
							|  |  |  | 									To:   apimodels.Duration(time.Duration(3) * time.Hour), | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 								}, | 
					
						
							| 
									
										
										
										
											2023-02-01 01:50:10 +08:00
										 |  |  | 								DatasourceUID: expr.DatasourceUID, | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 								Model: json.RawMessage(`{ | 
					
						
							|  |  |  | 									"type": "math", | 
					
						
							|  |  |  | 									"expression": "2 + 3 > 1" | 
					
						
							|  |  |  | 									}`), | 
					
						
							|  |  |  | 							}, | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ | 
					
						
							|  |  |  | 						Title:     "AlwaysFiringButSilenced", | 
					
						
							|  |  |  | 						Condition: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 						Data: []apimodels.AlertQuery{ | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 							{ | 
					
						
							|  |  |  | 								RefID: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 								RelativeTimeRange: apimodels.RelativeTimeRange{ | 
					
						
							|  |  |  | 									From: apimodels.Duration(time.Duration(5) * time.Hour), | 
					
						
							|  |  |  | 									To:   apimodels.Duration(time.Duration(3) * time.Hour), | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 								}, | 
					
						
							| 
									
										
										
										
											2023-02-01 01:50:10 +08:00
										 |  |  | 								DatasourceUID: expr.DatasourceUID, | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 								Model: json.RawMessage(`{ | 
					
						
							|  |  |  | 									"type": "math", | 
					
						
							|  |  |  | 									"expression": "2 + 3 > 1" | 
					
						
							|  |  |  | 									}`), | 
					
						
							|  |  |  | 							}, | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 						NoDataState:  apimodels.NoDataState(ngmodels.Alerting), | 
					
						
							|  |  |  | 						ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState), | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		resp, status, _ := apiClient.PostRulesGroupWithStatus(t, "default", &rules) | 
					
						
							| 
									
										
										
										
											2022-06-21 23:39:22 +08:00
										 |  |  | 		assert.Equal(t, http.StatusAccepted, status) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		require.Len(t, resp.Created, len(rules.Rules)) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	expectedAllJSON := fmt.Sprintf(` | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	"default": [{ | 
					
						
							|  |  |  | 		"name": "anotherrulegroup", | 
					
						
							|  |  |  | 		"interval": "1m", | 
					
						
							|  |  |  | 		"rules": [{ | 
					
						
							|  |  |  | 			"expr": "", | 
					
						
							|  |  |  | 			"for": "10s", | 
					
						
							|  |  |  | 			"annotations": { | 
					
						
							|  |  |  | 				"__dashboardUid__": "%s", | 
					
						
							|  |  |  | 				"__panelId__": "1" | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"grafana_alert": { | 
					
						
							|  |  |  | 				"id": 1, | 
					
						
							|  |  |  | 				"orgId": 1, | 
					
						
							|  |  |  | 				"title": "AlwaysFiring", | 
					
						
							|  |  |  | 				"condition": "A", | 
					
						
							|  |  |  | 				"data": [{ | 
					
						
							|  |  |  | 					"refId": "A", | 
					
						
							|  |  |  | 					"queryType": "", | 
					
						
							|  |  |  | 					"relativeTimeRange": { | 
					
						
							|  |  |  | 						"from": 18000, | 
					
						
							|  |  |  | 						"to": 10800 | 
					
						
							|  |  |  | 					}, | 
					
						
							| 
									
										
										
										
											2023-02-01 01:50:10 +08:00
										 |  |  | 					"datasourceUid": "__expr__", | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 					"model": { | 
					
						
							|  |  |  | 						"expression": "2 + 3 \u003e 1", | 
					
						
							|  |  |  | 						"intervalMs": 1000, | 
					
						
							|  |  |  | 						"maxDataPoints": 43200, | 
					
						
							|  |  |  | 						"type": "math" | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}], | 
					
						
							|  |  |  | 				"updated": "2021-02-21T01:10:30Z", | 
					
						
							|  |  |  | 				"intervalSeconds": 60, | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 				"is_paused": false, | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 				"version": 1, | 
					
						
							|  |  |  | 				"uid": "uid", | 
					
						
							|  |  |  | 				"namespace_uid": "nsuid", | 
					
						
							|  |  |  | 				"rule_group": "anotherrulegroup", | 
					
						
							|  |  |  | 				"no_data_state": "NoData", | 
					
						
							|  |  |  | 				"exec_err_state": "Alerting" | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, { | 
					
						
							|  |  |  | 			"expr": "", | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 			"for":"0s", | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 			"grafana_alert": { | 
					
						
							|  |  |  | 				"id": 2, | 
					
						
							|  |  |  | 				"orgId": 1, | 
					
						
							|  |  |  | 				"title": "AlwaysFiringButSilenced", | 
					
						
							|  |  |  | 				"condition": "A", | 
					
						
							|  |  |  | 				"data": [{ | 
					
						
							|  |  |  | 					"refId": "A", | 
					
						
							|  |  |  | 					"queryType": "", | 
					
						
							|  |  |  | 					"relativeTimeRange": { | 
					
						
							|  |  |  | 						"from": 18000, | 
					
						
							|  |  |  | 						"to": 10800 | 
					
						
							|  |  |  | 					}, | 
					
						
							| 
									
										
										
										
											2023-02-01 01:50:10 +08:00
										 |  |  | 					"datasourceUid": "__expr__", | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 					"model": { | 
					
						
							|  |  |  | 						"expression": "2 + 3 \u003e 1", | 
					
						
							|  |  |  | 						"intervalMs": 1000, | 
					
						
							|  |  |  | 						"maxDataPoints": 43200, | 
					
						
							|  |  |  | 						"type": "math" | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}], | 
					
						
							|  |  |  | 				"updated": "2021-02-21T01:10:30Z", | 
					
						
							|  |  |  | 				"intervalSeconds": 60, | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 				"is_paused": false, | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 				"version": 1, | 
					
						
							|  |  |  | 				"uid": "uid", | 
					
						
							|  |  |  | 				"namespace_uid": "nsuid", | 
					
						
							|  |  |  | 				"rule_group": "anotherrulegroup", | 
					
						
							|  |  |  | 				"no_data_state": "Alerting", | 
					
						
							|  |  |  | 				"exec_err_state": "Alerting" | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}] | 
					
						
							|  |  |  | 	}] | 
					
						
							|  |  |  | }`, dashboardUID) | 
					
						
							|  |  |  | 	expectedFilteredByJSON := fmt.Sprintf(` | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	"default": [{ | 
					
						
							|  |  |  | 		"name": "anotherrulegroup", | 
					
						
							|  |  |  | 		"interval": "1m", | 
					
						
							|  |  |  | 		"rules": [{ | 
					
						
							|  |  |  | 			"expr": "", | 
					
						
							|  |  |  | 			"for": "10s", | 
					
						
							|  |  |  | 			"annotations": { | 
					
						
							|  |  |  | 				"__dashboardUid__": "%s", | 
					
						
							|  |  |  | 				"__panelId__": "1" | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"grafana_alert": { | 
					
						
							|  |  |  | 				"id": 1, | 
					
						
							|  |  |  | 				"orgId": 1, | 
					
						
							|  |  |  | 				"title": "AlwaysFiring", | 
					
						
							|  |  |  | 				"condition": "A", | 
					
						
							|  |  |  | 				"data": [{ | 
					
						
							|  |  |  | 					"refId": "A", | 
					
						
							|  |  |  | 					"queryType": "", | 
					
						
							|  |  |  | 					"relativeTimeRange": { | 
					
						
							|  |  |  | 						"from": 18000, | 
					
						
							|  |  |  | 						"to": 10800 | 
					
						
							|  |  |  | 					}, | 
					
						
							| 
									
										
										
										
											2023-02-01 01:50:10 +08:00
										 |  |  | 					"datasourceUid": "__expr__", | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 					"model": { | 
					
						
							|  |  |  | 						"expression": "2 + 3 \u003e 1", | 
					
						
							|  |  |  | 						"intervalMs": 1000, | 
					
						
							|  |  |  | 						"maxDataPoints": 43200, | 
					
						
							|  |  |  | 						"type": "math" | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}], | 
					
						
							|  |  |  | 				"updated": "2021-02-21T01:10:30Z", | 
					
						
							|  |  |  | 				"intervalSeconds": 60, | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 				"is_paused": false, | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 				"version": 1, | 
					
						
							|  |  |  | 				"uid": "uid", | 
					
						
							|  |  |  | 				"namespace_uid": "nsuid", | 
					
						
							|  |  |  | 				"rule_group": "anotherrulegroup", | 
					
						
							|  |  |  | 				"no_data_state": "NoData", | 
					
						
							|  |  |  | 				"exec_err_state": "Alerting" | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}] | 
					
						
							|  |  |  | 	}] | 
					
						
							|  |  |  | }`, dashboardUID) | 
					
						
							|  |  |  | 	expectedNoneJSON := `{}` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Now, let's see how this looks like.
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr) | 
					
						
							|  |  |  | 		// nolint:gosec
 | 
					
						
							|  |  |  | 		resp, err := http.Get(promRulesURL) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		t.Cleanup(func() { | 
					
						
							|  |  |  | 			err := resp.Body.Close() | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 		b, err := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Equal(t, 200, resp.StatusCode) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		body, _ := rulesNamespaceWithoutVariableValues(t, b) | 
					
						
							|  |  |  | 		require.JSONEq(t, expectedAllJSON, body) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Now, let's check we get the same rule when filtering by dashboard_uid
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s", grafanaListedAddr, dashboardUID) | 
					
						
							|  |  |  | 		// nolint:gosec
 | 
					
						
							|  |  |  | 		resp, err := http.Get(promRulesURL) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		t.Cleanup(func() { | 
					
						
							|  |  |  | 			err := resp.Body.Close() | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 		b, err := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Equal(t, 200, resp.StatusCode) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		body, _ := rulesNamespaceWithoutVariableValues(t, b) | 
					
						
							|  |  |  | 		require.JSONEq(t, expectedFilteredByJSON, body) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Now, let's check we get no rules when filtering by an unknown dashboard_uid
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s", grafanaListedAddr, "abc") | 
					
						
							|  |  |  | 		// nolint:gosec
 | 
					
						
							|  |  |  | 		resp, err := http.Get(promRulesURL) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		t.Cleanup(func() { | 
					
						
							|  |  |  | 			err := resp.Body.Close() | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 		b, err := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Equal(t, 200, resp.StatusCode) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.JSONEq(t, expectedNoneJSON, string(b)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Now, let's check we get the same rule when filtering by dashboard_uid and panel_id
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=1", grafanaListedAddr, dashboardUID) | 
					
						
							|  |  |  | 		// nolint:gosec
 | 
					
						
							|  |  |  | 		resp, err := http.Get(promRulesURL) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		t.Cleanup(func() { | 
					
						
							|  |  |  | 			err := resp.Body.Close() | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 		b, err := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Equal(t, 200, resp.StatusCode) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		body, _ := rulesNamespaceWithoutVariableValues(t, b) | 
					
						
							|  |  |  | 		require.JSONEq(t, expectedFilteredByJSON, body) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Now, let's check we get no rules when filtering by dashboard_uid and unknown panel_id
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=2", grafanaListedAddr, dashboardUID) | 
					
						
							|  |  |  | 		// nolint:gosec
 | 
					
						
							|  |  |  | 		resp, err := http.Get(promRulesURL) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		t.Cleanup(func() { | 
					
						
							|  |  |  | 			err := resp.Body.Close() | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 		b, err := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Equal(t, 200, resp.StatusCode) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.JSONEq(t, expectedNoneJSON, string(b)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Now, let's check an invalid panel_id returns a 400 Bad Request response
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=invalid", grafanaListedAddr, dashboardUID) | 
					
						
							|  |  |  | 		// nolint:gosec
 | 
					
						
							|  |  |  | 		resp, err := http.Get(promRulesURL) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		t.Cleanup(func() { | 
					
						
							|  |  |  | 			err := resp.Body.Close() | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		require.Equal(t, http.StatusBadRequest, resp.StatusCode) | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 		b, err := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 		var res map[string]any | 
					
						
							| 
									
										
										
										
											2022-04-14 23:54:49 +08:00
										 |  |  | 		require.NoError(t, json.Unmarshal(b, &res)) | 
					
						
							|  |  |  | 		require.Equal(t, `invalid panel_id: strconv.ParseInt: parsing "invalid": invalid syntax`, res["message"]) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Now, let's check a panel_id without dashboard_uid returns a 400 Bad Request response
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?panel_id=1", grafanaListedAddr) | 
					
						
							|  |  |  | 		// nolint:gosec
 | 
					
						
							|  |  |  | 		resp, err := http.Get(promRulesURL) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		t.Cleanup(func() { | 
					
						
							|  |  |  | 			err := resp.Body.Close() | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		require.Equal(t, http.StatusBadRequest, resp.StatusCode) | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 		b, err := io.ReadAll(resp.Body) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 		var res map[string]any | 
					
						
							| 
									
										
										
										
											2022-04-14 23:54:49 +08:00
										 |  |  | 		require.NoError(t, json.Unmarshal(b, &res)) | 
					
						
							|  |  |  | 		require.Equal(t, "panel_id must be set with dashboard_uid", res["message"]) | 
					
						
							| 
									
										
										
										
											2021-10-04 23:33:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 15:11:56 +08:00
										 |  |  | func TestIntegrationRuleGroupSequence(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 	// Setup Grafana and its Database
 | 
					
						
							|  |  |  | 	dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		DisableLegacyAlerting: true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting: true, | 
					
						
							|  |  |  | 		DisableAnonymous:      true, | 
					
						
							|  |  |  | 		AppModeProduction:     true, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleEditor), | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 		Password:       "password", | 
					
						
							|  |  |  | 		Login:          "grafana", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client := newAlertingApiClient(grafanaListedAddr, "grafana", "password") | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 	parentFolderUID := util.GenerateShortUID() | 
					
						
							|  |  |  | 	client.CreateFolder(t, parentFolderUID, "parent") | 
					
						
							|  |  |  | 	folderUID := util.GenerateShortUID() | 
					
						
							|  |  |  | 	client.CreateFolder(t, folderUID, "folder1", parentFolderUID) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	group1 := generateAlertRuleGroup(5, alertRuleGen()) | 
					
						
							|  |  |  | 	group2 := generateAlertRuleGroup(5, alertRuleGen()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 	_, status, _ := client.PostRulesGroupWithStatus(t, folderUID, &group1) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 	require.Equal(t, http.StatusAccepted, status) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 	_, status, _ = client.PostRulesGroupWithStatus(t, folderUID, &group2) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 	require.Equal(t, http.StatusAccepted, status) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should persist order of the rules in a group", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		group1Get := client.GetRulesGroup(t, folderUID, group1.Name) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 		assert.Equal(t, group1.Name, group1Get.Name) | 
					
						
							|  |  |  | 		assert.Equal(t, group1.Interval, group1Get.Interval) | 
					
						
							|  |  |  | 		assert.Len(t, group1Get.Rules, len(group1.Rules)) | 
					
						
							|  |  |  | 		for i, getRule := range group1Get.Rules { | 
					
						
							|  |  |  | 			rule := group1.Rules[i] | 
					
						
							|  |  |  | 			assert.Equal(t, getRule.GrafanaManagedAlert.Title, rule.GrafanaManagedAlert.Title) | 
					
						
							|  |  |  | 			assert.NotEmpty(t, getRule.GrafanaManagedAlert.UID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// now shuffle the rules
 | 
					
						
							|  |  |  | 		postableGroup1 := convertGettableRuleGroupToPostable(group1Get.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 		rand.Shuffle(len(postableGroup1.Rules), func(i, j int) { | 
					
						
							|  |  |  | 			postableGroup1.Rules[i], postableGroup1.Rules[j] = postableGroup1.Rules[j], postableGroup1.Rules[i] | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		expectedUids := make([]string, 0, len(postableGroup1.Rules)) | 
					
						
							|  |  |  | 		for _, rule := range postableGroup1.Rules { | 
					
						
							|  |  |  | 			expectedUids = append(expectedUids, rule.GrafanaManagedAlert.UID) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		_, status, _ := client.PostRulesGroupWithStatus(t, folderUID, &postableGroup1) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 		require.Equal(t, http.StatusAccepted, status) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		group1Get = client.GetRulesGroup(t, folderUID, group1.Name) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		require.Len(t, group1Get.Rules, len(postableGroup1.Rules)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		actualUids := make([]string, 0, len(group1Get.Rules)) | 
					
						
							|  |  |  | 		for _, getRule := range group1Get.Rules { | 
					
						
							|  |  |  | 			actualUids = append(actualUids, getRule.GrafanaManagedAlert.UID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		assert.Equal(t, expectedUids, actualUids) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should be able to move a rule from another group in a specific position", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		group1Get := client.GetRulesGroup(t, folderUID, group1.Name) | 
					
						
							|  |  |  | 		group2Get := client.GetRulesGroup(t, folderUID, group2.Name) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		movedRule := convertGettableRuleToPostable(group2Get.Rules[3]) | 
					
						
							|  |  |  | 		// now shuffle the rules
 | 
					
						
							|  |  |  | 		postableGroup1 := convertGettableRuleGroupToPostable(group1Get.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 		postableGroup1.Rules = append(append(append([]apimodels.PostableExtendedRuleNode{}, postableGroup1.Rules[0:1]...), movedRule), postableGroup1.Rules[2:]...) | 
					
						
							|  |  |  | 		expectedUids := make([]string, 0, len(postableGroup1.Rules)) | 
					
						
							|  |  |  | 		for _, rule := range postableGroup1.Rules { | 
					
						
							|  |  |  | 			expectedUids = append(expectedUids, rule.GrafanaManagedAlert.UID) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		_, status, _ := client.PostRulesGroupWithStatus(t, folderUID, &postableGroup1) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 		require.Equal(t, http.StatusAccepted, status) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		group1Get = client.GetRulesGroup(t, folderUID, group1.Name) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		require.Len(t, group1Get.Rules, len(postableGroup1.Rules)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		actualUids := make([]string, 0, len(group1Get.Rules)) | 
					
						
							|  |  |  | 		for _, getRule := range group1Get.Rules { | 
					
						
							|  |  |  | 			actualUids = append(actualUids, getRule.GrafanaManagedAlert.UID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		assert.Equal(t, expectedUids, actualUids) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		group2Get = client.GetRulesGroup(t, folderUID, group2.Name) | 
					
						
							| 
									
										
										
										
											2022-06-22 22:52:46 +08:00
										 |  |  | 		assert.Len(t, group2Get.Rules, len(group2.Rules)-1) | 
					
						
							|  |  |  | 		for _, rule := range group2Get.Rules { | 
					
						
							|  |  |  | 			require.NotEqual(t, movedRule.GrafanaManagedAlert.UID, rule.GrafanaManagedAlert.UID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												Alerting: Support UTF-8 (#81512)
This pull request updates our fork of Alertmanager to commit 65bdab0, which is based on commit 5658f8c in Prometheus Alertmanager.
It applies the changes from grafana/alerting#155 which removes the overrides for validation of alerts, labels and silences that we had put in place to allow alerts and silences to work for non-Prometheus datasources. However, as this is now supported in Alertmanager with the UTF-8 work, we can use the new upstream functions and remove these overrides.
The compat package is a package in Alertmanager that takes care of backwards compatibility when parsing matchers, validating alerts, labels and silences. It has three modes: classic mode, UTF-8 strict mode, fallback mode. These modes are controlled via compat.InitFromFlags. Grafana initializes the compat package without any feature flags, which is the equivalent of fallback mode. Classic and UTF-8 strict mode are used in Mimir.
While Grafana Managed Alerts have no need for fallback mode, Grafana can still be used as an interface to manage the configurations of Mimir Alertmanagers and view configurations of Prometheus Alertmanager, and those installations might not have migrated or being running on older versions. Such installations behave as if in classic mode, and Grafana must be able to parse their configurations to interact with them for some period of time. As such, Grafana uses fallback mode until we are ready to drop support for outdated installations of Mimir and the Prometheus Alertmanager.
											
										 
											2024-02-06 16:33:47 +08:00
										 |  |  | func TestIntegrationRuleCreate(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 	dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		DisableLegacyAlerting: true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting: true, | 
					
						
							|  |  |  | 		AppModeProduction:     true, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) | 
					
						
							|  |  |  | 	createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
											  
											
												Alerting: Support UTF-8 (#81512)
This pull request updates our fork of Alertmanager to commit 65bdab0, which is based on commit 5658f8c in Prometheus Alertmanager.
It applies the changes from grafana/alerting#155 which removes the overrides for validation of alerts, labels and silences that we had put in place to allow alerts and silences to work for non-Prometheus datasources. However, as this is now supported in Alertmanager with the UTF-8 work, we can use the new upstream functions and remove these overrides.
The compat package is a package in Alertmanager that takes care of backwards compatibility when parsing matchers, validating alerts, labels and silences. It has three modes: classic mode, UTF-8 strict mode, fallback mode. These modes are controlled via compat.InitFromFlags. Grafana initializes the compat package without any feature flags, which is the equivalent of fallback mode. Classic and UTF-8 strict mode are used in Mimir.
While Grafana Managed Alerts have no need for fallback mode, Grafana can still be used as an interface to manage the configurations of Mimir Alertmanagers and view configurations of Prometheus Alertmanager, and those installations might not have migrated or being running on older versions. Such installations behave as if in classic mode, and Grafana must be able to parse their configurations to interact with them for some period of time. As such, Grafana uses fallback mode until we are ready to drop support for outdated installations of Mimir and the Prometheus Alertmanager.
											
										 
											2024-02-06 16:33:47 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleAdmin), | 
					
						
							|  |  |  | 		Password:       "admin", | 
					
						
							|  |  |  | 		Login:          "admin", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	client := newAlertingApiClient(grafanaListedAddr, "admin", "admin") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	namespaceUID := "default" | 
					
						
							|  |  |  | 	client.CreateFolder(t, namespaceUID, namespaceUID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cases := []struct { | 
					
						
							|  |  |  | 		name   string | 
					
						
							|  |  |  | 		config apimodels.PostableRuleGroupConfig | 
					
						
							|  |  |  | 	}{{ | 
					
						
							|  |  |  | 		name: "can create a rule with UTF-8", | 
					
						
							|  |  |  | 		config: apimodels.PostableRuleGroupConfig{ | 
					
						
							|  |  |  | 			Name:     "test1", | 
					
						
							|  |  |  | 			Interval: model.Duration(time.Minute), | 
					
						
							|  |  |  | 			Rules: []apimodels.PostableExtendedRuleNode{ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					ApiRuleNode: &apimodels.ApiRuleNode{ | 
					
						
							|  |  |  | 						For: util.Pointer(model.Duration(2 * time.Minute)), | 
					
						
							|  |  |  | 						Labels: map[string]string{ | 
					
						
							|  |  |  | 							"foo🙂":  "bar", | 
					
						
							|  |  |  | 							"_bar1": "baz🙂", | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 						Annotations: map[string]string{ | 
					
						
							|  |  |  | 							"Προμηθέας": "prom",      // Prometheus in Greek
 | 
					
						
							|  |  |  | 							"犬":         "Shiba Inu", // Dog in Japanese
 | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ | 
					
						
							|  |  |  | 						Title:     "test1 rule1", | 
					
						
							|  |  |  | 						Condition: "A", | 
					
						
							|  |  |  | 						Data: []apimodels.AlertQuery{ | 
					
						
							|  |  |  | 							{ | 
					
						
							|  |  |  | 								RefID: "A", | 
					
						
							|  |  |  | 								RelativeTimeRange: apimodels.RelativeTimeRange{ | 
					
						
							|  |  |  | 									From: apimodels.Duration(0), | 
					
						
							|  |  |  | 									To:   apimodels.Duration(15 * time.Minute), | 
					
						
							|  |  |  | 								}, | 
					
						
							|  |  |  | 								DatasourceUID: expr.DatasourceUID, | 
					
						
							|  |  |  | 								Model:         json.RawMessage(`{"type": "math","expression": "1"}`), | 
					
						
							|  |  |  | 							}, | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tc := range cases { | 
					
						
							|  |  |  | 		t.Run(tc.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			resp, status, _ := client.PostRulesGroupWithStatus(t, namespaceUID, &tc.config) | 
					
						
							|  |  |  | 			require.Equal(t, http.StatusAccepted, status) | 
					
						
							|  |  |  | 			require.Len(t, resp.Created, 1) | 
					
						
							|  |  |  | 			require.Len(t, resp.Updated, 0) | 
					
						
							|  |  |  | 			require.Len(t, resp.Deleted, 0) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 15:11:56 +08:00
										 |  |  | func TestIntegrationRuleUpdate(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 	// Setup Grafana and its Database
 | 
					
						
							|  |  |  | 	dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		DisableLegacyAlerting: true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting: true, | 
					
						
							|  |  |  | 		DisableAnonymous:      true, | 
					
						
							|  |  |  | 		AppModeProduction:     true, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) | 
					
						
							| 
									
										
										
										
											2024-05-28 23:32:23 +08:00
										 |  |  | 	permissionsStore := resourcepermissions.NewStore(env.Cfg, env.SQLStore, featuremgmt.WithFeatures()) | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleEditor), | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 		Password:       "password", | 
					
						
							|  |  |  | 		Login:          "grafana", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 	if setting.IsEnterprise { | 
					
						
							|  |  |  | 		// add blanket access to data sources.
 | 
					
						
							|  |  |  | 		_, err := permissionsStore.SetUserResourcePermission(context.Background(), | 
					
						
							|  |  |  | 			1, | 
					
						
							|  |  |  | 			accesscontrol.User{ID: userID}, | 
					
						
							|  |  |  | 			resourcepermissions.SetResourcePermissionCommand{ | 
					
						
							|  |  |  | 				Actions: []string{ | 
					
						
							|  |  |  | 					datasources.ActionQuery, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				Resource:          datasources.ScopeRoot, | 
					
						
							|  |  |  | 				ResourceID:        "*", | 
					
						
							|  |  |  | 				ResourceAttribute: "uid", | 
					
						
							|  |  |  | 			}, nil) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleAdmin), | 
					
						
							|  |  |  | 		Password:       "admin", | 
					
						
							|  |  |  | 		Login:          "admin", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 	client := newAlertingApiClient(grafanaListedAddr, "grafana", "password") | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 	folderUID := util.GenerateShortUID() | 
					
						
							|  |  |  | 	client.CreateFolder(t, folderUID, "folder1") | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should be able to reset 'for' to 0", func(t *testing.T) { | 
					
						
							|  |  |  | 		group := generateAlertRuleGroup(1, alertRuleGen()) | 
					
						
							|  |  |  | 		expected := model.Duration(10 * time.Second) | 
					
						
							|  |  |  | 		group.Rules[0].ApiRuleNode.For = &expected | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		getGroup := client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 		require.Equal(t, expected, *getGroup.Rules[0].ApiRuleNode.For) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 		expected = 0 | 
					
						
							|  |  |  | 		group.Rules[0].ApiRuleNode.For = &expected | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		_, status, body = client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		getGroup = client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 		require.Equal(t, expected, *getGroup.Rules[0].ApiRuleNode.For) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 	t.Run("when data source missing", func(t *testing.T) { | 
					
						
							|  |  |  | 		var groupName string | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			ds1 := adminClient.CreateTestDatasource(t) | 
					
						
							|  |  |  | 			group := generateAlertRuleGroup(3, alertRuleGen(withDatasourceQuery(ds1.Body.Datasource.UID))) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			getGroup := client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Len(t, group.Rules, 3) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			adminClient.DeleteDatasource(t, ds1.Body.Datasource.UID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// expire datasource caching
 | 
					
						
							|  |  |  | 			<-time.After(datasourceService.DefaultCacheTTL + 1*time.Second) // TODO delete when TTL could be configured
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			groupName = group.Name | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("noop should not fail", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			getGroup := client.GetRulesGroup(t, folderUID, groupName) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		t.Run("should not let update rule if it does not fix datasource", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			getGroup := client.GetRulesGroup(t, folderUID, groupName) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			group.Rules[0].GrafanaManagedAlert.Title = uuid.NewString() | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if status == http.StatusAccepted { | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 				assert.Len(t, resp.Deleted, 1) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 				getGroup = client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 				assert.NotEqualf(t, group.Rules[0].GrafanaManagedAlert.Title, getGroup.Rules[0].GrafanaManagedAlert.Title, "group was updated") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			require.Equalf(t, http.StatusBadRequest, status, "expected BadRequest. Response: %s", body) | 
					
						
							|  |  |  | 			assert.Contains(t, body, "data source not found") | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		t.Run("should let delete broken rule", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			getGroup := client.GetRulesGroup(t, folderUID, groupName) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// remove the last rule.
 | 
					
						
							|  |  |  | 			group.Rules = group.Rules[0 : len(group.Rules)-1] | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			require.Equalf(t, http.StatusAccepted, status, "failed to delete last rule from group. Response: %s", body) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 			assert.Len(t, resp.Deleted, 1) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			getGroup = client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 			require.Len(t, group.Rules, 2) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		t.Run("should let fix single rule", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			getGroup := client.GetRulesGroup(t, folderUID, groupName) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			ds2 := adminClient.CreateTestDatasource(t) | 
					
						
							|  |  |  | 			withDatasourceQuery(ds2.Body.Datasource.UID)(&group.Rules[0]) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 			assert.Len(t, resp.Deleted, 0) | 
					
						
							|  |  |  | 			assert.Len(t, resp.Updated, 2) | 
					
						
							|  |  |  | 			assert.Len(t, resp.Created, 0) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			getGroup = client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 			require.Equal(t, ds2.Body.Datasource.UID, group.Rules[0].GrafanaManagedAlert.Data[0].DatasourceUID) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		t.Run("should let delete group", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			status, body := client.DeleteRulesGroup(t, folderUID, groupName) | 
					
						
							| 
									
										
										
										
											2023-06-16 01:33:42 +08:00
										 |  |  | 			require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | func newTestingRuleConfig(t *testing.T) apimodels.PostableRuleGroupConfig { | 
					
						
							|  |  |  | 	interval, err := model.ParseDuration("1m") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	firstRule := apimodels.PostableExtendedRuleNode{ | 
					
						
							|  |  |  | 		ApiRuleNode: &apimodels.ApiRuleNode{ | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 			For:         &interval, | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 			Labels:      map[string]string{"label1": "val1"}, | 
					
						
							|  |  |  | 			Annotations: map[string]string{"annotation1": "val1"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		// this rule does not explicitly set no data and error states
 | 
					
						
							|  |  |  | 		// therefore it should get the default values
 | 
					
						
							|  |  |  | 		GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ | 
					
						
							|  |  |  | 			Title:     "AlwaysFiring", | 
					
						
							|  |  |  | 			Condition: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 			Data: []apimodels.AlertQuery{ | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 				{ | 
					
						
							|  |  |  | 					RefID: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 					RelativeTimeRange: apimodels.RelativeTimeRange{ | 
					
						
							|  |  |  | 						From: apimodels.Duration(time.Duration(5) * time.Hour), | 
					
						
							|  |  |  | 						To:   apimodels.Duration(time.Duration(3) * time.Hour), | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 					}, | 
					
						
							| 
									
										
										
										
											2023-02-01 01:50:10 +08:00
										 |  |  | 					DatasourceUID: expr.DatasourceUID, | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 					Model: json.RawMessage(`{ | 
					
						
							|  |  |  | 						"type": "math", | 
					
						
							|  |  |  | 						"expression": "2 + 3 > 1" | 
					
						
							|  |  |  | 						}`), | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	secondRule := apimodels.PostableExtendedRuleNode{ | 
					
						
							|  |  |  | 		ApiRuleNode: &apimodels.ApiRuleNode{ | 
					
						
							| 
									
										
										
										
											2022-06-30 23:46:26 +08:00
										 |  |  | 			For:         &interval, | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 			Labels:      map[string]string{"label1": "val1"}, | 
					
						
							|  |  |  | 			Annotations: map[string]string{"annotation1": "val1"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		// this rule does not explicitly set no data and error states
 | 
					
						
							|  |  |  | 		// therefore it should get the default values
 | 
					
						
							|  |  |  | 		GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ | 
					
						
							|  |  |  | 			Title:     "AlwaysFiring2", | 
					
						
							|  |  |  | 			Condition: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 			Data: []apimodels.AlertQuery{ | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 				{ | 
					
						
							|  |  |  | 					RefID: "A", | 
					
						
							| 
									
										
										
										
											2023-03-27 23:55:13 +08:00
										 |  |  | 					RelativeTimeRange: apimodels.RelativeTimeRange{ | 
					
						
							|  |  |  | 						From: apimodels.Duration(time.Duration(5) * time.Hour), | 
					
						
							|  |  |  | 						To:   apimodels.Duration(time.Duration(3) * time.Hour), | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 					}, | 
					
						
							| 
									
										
										
										
											2023-02-01 01:50:10 +08:00
										 |  |  | 					DatasourceUID: expr.DatasourceUID, | 
					
						
							| 
									
										
										
										
											2022-04-14 20:21:36 +08:00
										 |  |  | 					Model: json.RawMessage(`{ | 
					
						
							|  |  |  | 						"type": "math", | 
					
						
							|  |  |  | 						"expression": "2 + 3 > 1" | 
					
						
							|  |  |  | 						}`), | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return apimodels.PostableRuleGroupConfig{ | 
					
						
							|  |  |  | 		Name: "arulegroup", | 
					
						
							|  |  |  | 		Rules: []apimodels.PostableExtendedRuleNode{ | 
					
						
							|  |  |  | 			firstRule, | 
					
						
							|  |  |  | 			secondRule, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestIntegrationRulePause(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Setup Grafana and its Database
 | 
					
						
							|  |  |  | 	dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		DisableLegacyAlerting: true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting: true, | 
					
						
							|  |  |  | 		DisableAnonymous:      true, | 
					
						
							|  |  |  | 		AppModeProduction:     true, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleEditor), | 
					
						
							|  |  |  | 		Password:       "password", | 
					
						
							|  |  |  | 		Login:          "grafana", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client := newAlertingApiClient(grafanaListedAddr, "grafana", "password") | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 	folderUID := util.GenerateShortUID() | 
					
						
							|  |  |  | 	client.CreateFolder(t, folderUID, "folder1") | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should create a paused rule if isPaused is true", func(t *testing.T) { | 
					
						
							|  |  |  | 		group := generateAlertRuleGroup(1, alertRuleGen()) | 
					
						
							|  |  |  | 		expectedIsPaused := true | 
					
						
							|  |  |  | 		group.Rules[0].GrafanaManagedAlert.IsPaused = &expectedIsPaused | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		require.Len(t, resp.Created, 1) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		getGroup := client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body) | 
					
						
							|  |  |  | 		require.Equal(t, expectedIsPaused, getGroup.Rules[0].GrafanaManagedAlert.IsPaused) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should create a unpaused rule if isPaused is false", func(t *testing.T) { | 
					
						
							|  |  |  | 		group := generateAlertRuleGroup(1, alertRuleGen()) | 
					
						
							|  |  |  | 		expectedIsPaused := false | 
					
						
							|  |  |  | 		group.Rules[0].GrafanaManagedAlert.IsPaused = &expectedIsPaused | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		require.Len(t, resp.Created, 1) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		getGroup := client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body) | 
					
						
							|  |  |  | 		require.Equal(t, expectedIsPaused, getGroup.Rules[0].GrafanaManagedAlert.IsPaused) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should create a unpaused rule if isPaused is not present", func(t *testing.T) { | 
					
						
							|  |  |  | 		group := generateAlertRuleGroup(1, alertRuleGen()) | 
					
						
							|  |  |  | 		group.Rules[0].GrafanaManagedAlert.IsPaused = nil | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							| 
									
										
										
										
											2023-10-07 06:11:24 +08:00
										 |  |  | 		require.Len(t, resp.Created, 1) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 		getGroup := client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body) | 
					
						
							|  |  |  | 		require.False(t, getGroup.Rules[0].GrafanaManagedAlert.IsPaused) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	getBooleanPointer := func(b bool) *bool { return &b } | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		description          string | 
					
						
							|  |  |  | 		isPausedInDb         bool | 
					
						
							|  |  |  | 		isPausedInBody       *bool | 
					
						
							|  |  |  | 		expectedIsPausedInDb bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			description:          "should pause rule if there is a paused rule in DB and isPaused is true", | 
					
						
							|  |  |  | 			isPausedInDb:         true, | 
					
						
							|  |  |  | 			isPausedInBody:       getBooleanPointer(true), | 
					
						
							|  |  |  | 			expectedIsPausedInDb: true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			description:          "should unpause rule if there is a paused rule in DB and isPaused is false", | 
					
						
							|  |  |  | 			isPausedInDb:         true, | 
					
						
							|  |  |  | 			isPausedInBody:       getBooleanPointer(false), | 
					
						
							|  |  |  | 			expectedIsPausedInDb: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			description:          "should keep rule paused if there is a paused rule in DB and isPaused is not present", | 
					
						
							|  |  |  | 			isPausedInDb:         true, | 
					
						
							|  |  |  | 			isPausedInBody:       nil, | 
					
						
							|  |  |  | 			expectedIsPausedInDb: true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			description:          "should pause rule if there is an unpaused rule in DB and isPaused is true", | 
					
						
							|  |  |  | 			isPausedInDb:         false, | 
					
						
							|  |  |  | 			isPausedInBody:       getBooleanPointer(true), | 
					
						
							|  |  |  | 			expectedIsPausedInDb: true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			description:          "should unpause rule if there is an unpaused rule in DB and isPaused is false", | 
					
						
							|  |  |  | 			isPausedInDb:         false, | 
					
						
							|  |  |  | 			isPausedInBody:       getBooleanPointer(false), | 
					
						
							|  |  |  | 			expectedIsPausedInDb: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			description:          "should keep rule unpaused if there is an unpaused rule in DB and isPaused is not present", | 
					
						
							|  |  |  | 			isPausedInDb:         false, | 
					
						
							|  |  |  | 			isPausedInBody:       nil, | 
					
						
							|  |  |  | 			expectedIsPausedInDb: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tc := range testCases { | 
					
						
							|  |  |  | 		t.Run(tc.description, func(t *testing.T) { | 
					
						
							|  |  |  | 			group := generateAlertRuleGroup(1, alertRuleGen()) | 
					
						
							|  |  |  | 			group.Rules[0].GrafanaManagedAlert.IsPaused = &tc.isPausedInDb | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 			require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			getGroup := client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 			require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 			group.Rules[0].GrafanaManagedAlert.IsPaused = tc.isPausedInBody | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			_, status, body = client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 			require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 17:07:39 +08:00
										 |  |  | 			getGroup = client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							| 
									
										
										
										
											2023-02-01 20:15:03 +08:00
										 |  |  | 			require.Equal(t, tc.expectedIsPausedInDb, getGroup.Rules[0].GrafanaManagedAlert.IsPaused) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-01-05 00:47:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestIntegrationHysteresisRule(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Setup Grafana and its Database. Scheduler is set to evaluate every 1 second
 | 
					
						
							|  |  |  | 	dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		DisableLegacyAlerting:        true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting:        true, | 
					
						
							|  |  |  | 		DisableAnonymous:             true, | 
					
						
							|  |  |  | 		AppModeProduction:            true, | 
					
						
							|  |  |  | 		NGAlertSchedulerBaseInterval: 1 * time.Second, | 
					
						
							|  |  |  | 		EnableFeatureToggles:         []string{featuremgmt.FlagConfigurableSchedulerTick, featuremgmt.FlagRecoveryThreshold}, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) | 
					
						
							| 
									
										
										
										
											2024-01-05 00:47:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2024-01-05 00:47:13 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleAdmin), | 
					
						
							|  |  |  | 		Password:       "password", | 
					
						
							|  |  |  | 		Login:          "grafana", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	folder := "hysteresis" | 
					
						
							|  |  |  | 	testDs := apiClient.CreateTestDatasource(t) | 
					
						
							|  |  |  | 	apiClient.CreateFolder(t, folder, folder) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bodyRaw, err := testData.ReadFile("test-data/hysteresis_rule.json") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var postData apimodels.PostableRuleGroupConfig | 
					
						
							|  |  |  | 	require.NoError(t, json.Unmarshal(bodyRaw, &postData)) | 
					
						
							|  |  |  | 	for _, rule := range postData.Rules { | 
					
						
							|  |  |  | 		for i := range rule.GrafanaManagedAlert.Data { | 
					
						
							|  |  |  | 			rule.GrafanaManagedAlert.Data[i].DatasourceUID = strings.ReplaceAll(rule.GrafanaManagedAlert.Data[i].DatasourceUID, "REPLACE_ME", testDs.Body.Datasource.UID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	changes, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &postData) | 
					
						
							|  |  |  | 	require.Equalf(t, http.StatusAccepted, status, body) | 
					
						
							|  |  |  | 	require.Len(t, changes.Created, 1) | 
					
						
							|  |  |  | 	ruleUid := changes.Created[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var frame data.Frame | 
					
						
							|  |  |  | 	require.Eventuallyf(t, func() bool { | 
					
						
							|  |  |  | 		frame, status, body = apiClient.GetRuleHistoryWithStatus(t, ruleUid) | 
					
						
							|  |  |  | 		require.Equalf(t, http.StatusOK, status, body) | 
					
						
							|  |  |  | 		return frame.Rows() > 1 | 
					
						
							|  |  |  | 	}, 15*time.Second, 1*time.Second, "Alert state history expected to have more than one record but got %d. Body: %s", frame.Rows(), body) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, _ := frame.FieldByName("next") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	alertingIdx := 0 | 
					
						
							|  |  |  | 	normalIdx := 1 | 
					
						
							|  |  |  | 	if f.At(alertingIdx).(string) != "Alerting" { | 
					
						
							|  |  |  | 		alertingIdx = 1 | 
					
						
							|  |  |  | 		normalIdx = 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	assert.Equalf(t, "Alerting", f.At(alertingIdx).(string), body) | 
					
						
							|  |  |  | 	assert.Equalf(t, "Normal", f.At(normalIdx).(string), body) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	type HistoryData struct { | 
					
						
							|  |  |  | 		Values map[string]int64 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, _ = frame.FieldByName("data") | 
					
						
							|  |  |  | 	var d HistoryData | 
					
						
							|  |  |  | 	require.NoErrorf(t, json.Unmarshal([]byte(f.At(alertingIdx).(string)), &d), body) | 
					
						
							|  |  |  | 	assert.EqualValuesf(t, 5, d.Values["B"], body) | 
					
						
							|  |  |  | 	require.NoErrorf(t, json.Unmarshal([]byte(f.At(normalIdx).(string)), &d), body) | 
					
						
							|  |  |  | 	assert.EqualValuesf(t, 1, d.Values["B"], body) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-02-15 22:45:10 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestIntegrationRuleNotificationSettings(t *testing.T) { | 
					
						
							|  |  |  | 	testinfra.SQLiteIntegrationTest(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Setup Grafana and its Database. Scheduler is set to evaluate every 1 second
 | 
					
						
							|  |  |  | 	dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		DisableLegacyAlerting:        true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting:        true, | 
					
						
							|  |  |  | 		DisableAnonymous:             true, | 
					
						
							|  |  |  | 		AppModeProduction:            true, | 
					
						
							|  |  |  | 		NGAlertSchedulerBaseInterval: 1 * time.Second, | 
					
						
							|  |  |  | 		EnableFeatureToggles:         []string{featuremgmt.FlagConfigurableSchedulerTick, featuremgmt.FlagAlertingSimplifiedRouting}, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) | 
					
						
							| 
									
										
										
										
											2024-02-15 22:45:10 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							| 
									
										
										
										
											2024-04-04 21:04:47 +08:00
										 |  |  | 	createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							| 
									
										
										
										
											2024-02-15 22:45:10 +08:00
										 |  |  | 		DefaultOrgRole: string(org.RoleAdmin), | 
					
						
							|  |  |  | 		Password:       "password", | 
					
						
							|  |  |  | 		Login:          "grafana", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	folder := "Test-Alerting" | 
					
						
							|  |  |  | 	apiClient.CreateFolder(t, folder, folder) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testDataRaw, err := testData.ReadFile(path.Join("test-data", "rule-notification-settings-1-post.json")) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	type testData struct { | 
					
						
							|  |  |  | 		RuleGroup    apimodels.PostableRuleGroupConfig | 
					
						
							|  |  |  | 		Receiver     apimodels.EmbeddedContactPoint | 
					
						
							|  |  |  | 		TimeInterval apimodels.MuteTimeInterval | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var d testData | 
					
						
							|  |  |  | 	err = json.Unmarshal(testDataRaw, &d) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	apiClient.EnsureReceiver(t, d.Receiver) | 
					
						
							|  |  |  | 	apiClient.EnsureMuteTiming(t, d.TimeInterval) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("create should fail if receiver does not exist", func(t *testing.T) { | 
					
						
							|  |  |  | 		var copyD testData | 
					
						
							|  |  |  | 		err = json.Unmarshal(testDataRaw, ©D) | 
					
						
							|  |  |  | 		group := copyD.RuleGroup | 
					
						
							|  |  |  | 		ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings | 
					
						
							|  |  |  | 		ns.Receiver = "random-receiver" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group) | 
					
						
							|  |  |  | 		require.Equalf(t, http.StatusBadRequest, status, body) | 
					
						
							|  |  |  | 		t.Log(body) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("create should fail if mute timing does not exist", func(t *testing.T) { | 
					
						
							|  |  |  | 		var copyD testData | 
					
						
							|  |  |  | 		err = json.Unmarshal(testDataRaw, ©D) | 
					
						
							|  |  |  | 		group := copyD.RuleGroup | 
					
						
							|  |  |  | 		ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings | 
					
						
							|  |  |  | 		ns.MuteTimeIntervals = []string{"random-time-interval"} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group) | 
					
						
							|  |  |  | 		require.Equalf(t, http.StatusBadRequest, status, body) | 
					
						
							|  |  |  | 		t.Log(body) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-17 00:14:39 +08:00
										 |  |  | 	t.Run("create should not fail if group_by is missing required labels but they should still be used", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-02-15 22:45:10 +08:00
										 |  |  | 		var copyD testData | 
					
						
							|  |  |  | 		err = json.Unmarshal(testDataRaw, ©D) | 
					
						
							|  |  |  | 		group := copyD.RuleGroup | 
					
						
							|  |  |  | 		ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings | 
					
						
							|  |  |  | 		ns.GroupBy = []string{"label1"} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group) | 
					
						
							| 
									
										
										
										
											2024-04-17 00:14:39 +08:00
										 |  |  | 		require.Equalf(t, http.StatusAccepted, status, body) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		cfg, status, body := apiClient.GetAlertmanagerConfigWithStatus(t) | 
					
						
							|  |  |  | 		if !assert.Equalf(t, http.StatusOK, status, body) { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Ensure that the group by contains the default required labels.
 | 
					
						
							|  |  |  | 		autogenRoute := cfg.AlertmanagerConfig.Route.Routes[0] | 
					
						
							|  |  |  | 		receiverRoute := autogenRoute.Routes[0] | 
					
						
							|  |  |  | 		ruleRoute := receiverRoute.Routes[0] | 
					
						
							|  |  |  | 		assert.Equal(t, []model.LabelName{ngmodels.FolderTitleLabel, model.AlertNameLabel, "label1"}, ruleRoute.GroupBy) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-15 22:45:10 +08:00
										 |  |  | 		t.Log(body) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-17 00:14:39 +08:00
										 |  |  | 	t.Run("create with '...' groupBy followed by config post should succeed", func(t *testing.T) { | 
					
						
							|  |  |  | 		var copyD testData | 
					
						
							|  |  |  | 		err = json.Unmarshal(testDataRaw, ©D) | 
					
						
							|  |  |  | 		group := copyD.RuleGroup | 
					
						
							|  |  |  | 		ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings | 
					
						
							|  |  |  | 		ns.GroupBy = []string{ngmodels.FolderTitleLabel, model.AlertNameLabel, ngmodels.GroupByAll} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group) | 
					
						
							|  |  |  | 		require.Equalf(t, http.StatusAccepted, status, body) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Now update the config with no changes.
 | 
					
						
							|  |  |  | 		_, status, body = apiClient.GetAlertmanagerConfigWithStatus(t) | 
					
						
							|  |  |  | 		if !assert.Equalf(t, http.StatusOK, status, body) { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		cfg := apimodels.PostableUserConfig{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err = json.Unmarshal([]byte(body), &cfg) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ok, err := apiClient.PostConfiguration(t, cfg) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.True(t, ok) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-15 22:45:10 +08:00
										 |  |  | 	t.Run("should create rule and generate route", func(t *testing.T) { | 
					
						
							|  |  |  | 		_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &d.RuleGroup) | 
					
						
							|  |  |  | 		require.Equalf(t, http.StatusAccepted, status, body) | 
					
						
							|  |  |  | 		notificationSettings := d.RuleGroup.Rules[0].GrafanaManagedAlert.NotificationSettings | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var routeBody string | 
					
						
							|  |  |  | 		if !assert.EventuallyWithT(t, func(c *assert.CollectT) { | 
					
						
							|  |  |  | 			amConfig, status, body := apiClient.GetAlertmanagerConfigWithStatus(t) | 
					
						
							|  |  |  | 			routeBody = body | 
					
						
							|  |  |  | 			if !assert.Equalf(t, http.StatusOK, status, body) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			route := amConfig.AlertmanagerConfig.Route | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if !assert.Len(c, route.Routes, 1) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Check that we are in the auto-generated root
 | 
					
						
							|  |  |  | 			autogenRoute := route.Routes[0] | 
					
						
							|  |  |  | 			if !assert.Len(c, autogenRoute.ObjectMatchers, 1) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			canContinue := assert.Equal(c, ngmodels.AutogeneratedRouteLabel, autogenRoute.ObjectMatchers[0].Name) | 
					
						
							|  |  |  | 			assert.Equal(c, labels.MatchEqual, autogenRoute.ObjectMatchers[0].Type) | 
					
						
							|  |  |  | 			assert.Equal(c, "true", autogenRoute.ObjectMatchers[0].Value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert.Equalf(c, route.Receiver, autogenRoute.Receiver, "Autogenerated root receiver must be the default one") | 
					
						
							|  |  |  | 			assert.Nil(c, autogenRoute.GroupWait) | 
					
						
							|  |  |  | 			assert.Nil(c, autogenRoute.GroupInterval) | 
					
						
							|  |  |  | 			assert.Nil(c, autogenRoute.RepeatInterval) | 
					
						
							|  |  |  | 			assert.Empty(c, autogenRoute.MuteTimeIntervals) | 
					
						
							|  |  |  | 			assert.Empty(c, autogenRoute.GroupBy) | 
					
						
							|  |  |  | 			if !canContinue { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Now check that the second level is route for receivers
 | 
					
						
							|  |  |  | 			if !assert.NotEmpty(c, autogenRoute.Routes) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// There can be many routes, for all receivers
 | 
					
						
							|  |  |  | 			idx := slices.IndexFunc(autogenRoute.Routes, func(route *apimodels.Route) bool { | 
					
						
							|  |  |  | 				return route.Receiver == notificationSettings.Receiver | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			if !assert.GreaterOrEqual(t, idx, 0) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			receiverRoute := autogenRoute.Routes[idx] | 
					
						
							|  |  |  | 			if !assert.Len(c, receiverRoute.ObjectMatchers, 1) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			canContinue = assert.Equal(c, ngmodels.AutogeneratedRouteReceiverNameLabel, receiverRoute.ObjectMatchers[0].Name) | 
					
						
							|  |  |  | 			assert.Equal(c, labels.MatchEqual, receiverRoute.ObjectMatchers[0].Type) | 
					
						
							|  |  |  | 			assert.Equal(c, notificationSettings.Receiver, receiverRoute.ObjectMatchers[0].Value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert.Equal(c, notificationSettings.Receiver, receiverRoute.Receiver) | 
					
						
							|  |  |  | 			assert.Nil(c, receiverRoute.GroupWait) | 
					
						
							|  |  |  | 			assert.Nil(c, receiverRoute.GroupInterval) | 
					
						
							|  |  |  | 			assert.Nil(c, receiverRoute.RepeatInterval) | 
					
						
							|  |  |  | 			assert.Empty(c, receiverRoute.MuteTimeIntervals) | 
					
						
							|  |  |  | 			var groupBy []string | 
					
						
							|  |  |  | 			for _, name := range receiverRoute.GroupBy { | 
					
						
							|  |  |  | 				groupBy = append(groupBy, string(name)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			slices.Sort(groupBy) | 
					
						
							|  |  |  | 			assert.EqualValues(c, []string{"alertname", "grafana_folder"}, groupBy) | 
					
						
							|  |  |  | 			if !canContinue { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Now check that we created the 3rd level for specific combination of settings
 | 
					
						
							|  |  |  | 			if !assert.Lenf(c, receiverRoute.Routes, 1, "Receiver route should contain one options route") { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			optionsRoute := receiverRoute.Routes[0] | 
					
						
							|  |  |  | 			if !assert.Len(c, optionsRoute.ObjectMatchers, 1) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			assert.Equal(c, ngmodels.AutogeneratedRouteSettingsHashLabel, optionsRoute.ObjectMatchers[0].Name) | 
					
						
							|  |  |  | 			assert.Equal(c, labels.MatchEqual, optionsRoute.ObjectMatchers[0].Type) | 
					
						
							|  |  |  | 			assert.EqualValues(c, notificationSettings.GroupWait, optionsRoute.GroupWait) | 
					
						
							|  |  |  | 			assert.EqualValues(c, notificationSettings.GroupInterval, optionsRoute.GroupInterval) | 
					
						
							|  |  |  | 			assert.EqualValues(c, notificationSettings.RepeatInterval, optionsRoute.RepeatInterval) | 
					
						
							|  |  |  | 			assert.EqualValues(c, notificationSettings.MuteTimeIntervals, optionsRoute.MuteTimeIntervals) | 
					
						
							|  |  |  | 			groupBy = nil | 
					
						
							|  |  |  | 			for _, name := range optionsRoute.GroupBy { | 
					
						
							|  |  |  | 				groupBy = append(groupBy, string(name)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			assert.EqualValues(c, notificationSettings.GroupBy, groupBy) | 
					
						
							|  |  |  | 		}, 10*time.Second, 1*time.Second) { | 
					
						
							|  |  |  | 			t.Logf("config: %s", routeBody) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should correctly create alerts", func(t *testing.T) { | 
					
						
							|  |  |  | 		var response string | 
					
						
							|  |  |  | 		if !assert.EventuallyWithT(t, func(c *assert.CollectT) { | 
					
						
							|  |  |  | 			groups, status, body := apiClient.GetActiveAlertsWithStatus(t) | 
					
						
							|  |  |  | 			require.Equalf(t, http.StatusOK, status, body) | 
					
						
							|  |  |  | 			response = body | 
					
						
							|  |  |  | 			if len(groups) == 0 { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			g := groups[0] | 
					
						
							|  |  |  | 			alert := g.Alerts[0] | 
					
						
							|  |  |  | 			assert.Contains(c, alert.Labels, ngmodels.AutogeneratedRouteLabel) | 
					
						
							|  |  |  | 			assert.Equal(c, "true", alert.Labels[ngmodels.AutogeneratedRouteLabel]) | 
					
						
							|  |  |  | 			assert.Contains(c, alert.Labels, ngmodels.AutogeneratedRouteReceiverNameLabel) | 
					
						
							|  |  |  | 			assert.Equal(c, d.Receiver.Name, alert.Labels[ngmodels.AutogeneratedRouteReceiverNameLabel]) | 
					
						
							|  |  |  | 			assert.Contains(c, alert.Labels, ngmodels.AutogeneratedRouteSettingsHashLabel) | 
					
						
							|  |  |  | 			assert.NotEmpty(c, alert.Labels[ngmodels.AutogeneratedRouteSettingsHashLabel]) | 
					
						
							|  |  |  | 		}, 10*time.Second, 1*time.Second) { | 
					
						
							|  |  |  | 			t.Logf("response: %s", response) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should update rule with empty settings and delete route", func(t *testing.T) { | 
					
						
							|  |  |  | 		var copyD testData | 
					
						
							|  |  |  | 		err = json.Unmarshal(testDataRaw, ©D) | 
					
						
							|  |  |  | 		group := copyD.RuleGroup | 
					
						
							|  |  |  | 		notificationSettings := group.Rules[0].GrafanaManagedAlert.NotificationSettings | 
					
						
							|  |  |  | 		group.Rules[0].GrafanaManagedAlert.NotificationSettings = nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group) | 
					
						
							|  |  |  | 		require.Equalf(t, http.StatusAccepted, status, body) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var routeBody string | 
					
						
							|  |  |  | 		if !assert.EventuallyWithT(t, func(c *assert.CollectT) { | 
					
						
							|  |  |  | 			amConfig, status, body := apiClient.GetAlertmanagerConfigWithStatus(t) | 
					
						
							|  |  |  | 			routeBody = body | 
					
						
							|  |  |  | 			if !assert.Equalf(t, http.StatusOK, status, body) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			route := amConfig.AlertmanagerConfig.Route | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if !assert.Len(c, route.Routes, 1) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Check that we are in the auto-generated root
 | 
					
						
							|  |  |  | 			autogenRoute := route.Routes[0] | 
					
						
							|  |  |  | 			if !assert.Len(c, autogenRoute.ObjectMatchers, 1) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if !assert.Equal(c, ngmodels.AutogeneratedRouteLabel, autogenRoute.ObjectMatchers[0].Name) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Now check that the second level is route for receivers
 | 
					
						
							|  |  |  | 			if !assert.NotEmpty(c, autogenRoute.Routes) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// There can be many routes, for all receivers
 | 
					
						
							|  |  |  | 			idx := slices.IndexFunc(autogenRoute.Routes, func(route *apimodels.Route) bool { | 
					
						
							|  |  |  | 				return route.Receiver == notificationSettings.Receiver | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			if !assert.GreaterOrEqual(t, idx, 0) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			receiverRoute := autogenRoute.Routes[idx] | 
					
						
							|  |  |  | 			if !assert.Empty(c, receiverRoute.Routes) { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, 10*time.Second, 1*time.Second) { | 
					
						
							|  |  |  | 			t.Logf("config: %s", routeBody) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-06-11 07:05:47 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestIntegrationRuleUpdateAllDatabases(t *testing.T) { | 
					
						
							|  |  |  | 	// Setup Grafana and its Database
 | 
					
						
							|  |  |  | 	dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ | 
					
						
							|  |  |  | 		DisableLegacyAlerting: true, | 
					
						
							|  |  |  | 		EnableUnifiedAlerting: true, | 
					
						
							|  |  |  | 		DisableAnonymous:      true, | 
					
						
							|  |  |  | 		AppModeProduction:     true, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create a user to make authenticated requests
 | 
					
						
							|  |  |  | 	createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ | 
					
						
							|  |  |  | 		DefaultOrgRole: string(org.RoleAdmin), | 
					
						
							|  |  |  | 		Password:       "admin", | 
					
						
							|  |  |  | 		Login:          "admin", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client := newAlertingApiClient(grafanaListedAddr, "admin", "admin") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	folderUID := util.GenerateShortUID() | 
					
						
							|  |  |  | 	client.CreateFolder(t, folderUID, "folder1") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("group renamed followed by delete for case-only changes should not delete both groups", func(t *testing.T) { // Regression test.
 | 
					
						
							|  |  |  | 		group := generateAlertRuleGroup(3, alertRuleGen()) | 
					
						
							|  |  |  | 		groupName := group.Name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							|  |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							|  |  |  | 		getGroup := client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							|  |  |  | 		require.Lenf(t, getGroup.Rules, 3, "expected 3 rules in group") | 
					
						
							|  |  |  | 		require.Equal(t, groupName, getGroup.Rules[0].GrafanaManagedAlert.RuleGroup) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig) | 
					
						
							|  |  |  | 		newGroup := strings.ToUpper(group.Name) | 
					
						
							|  |  |  | 		group.Name = newGroup | 
					
						
							|  |  |  | 		_, status, body = client.PostRulesGroupWithStatus(t, folderUID, &group) | 
					
						
							|  |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		getGroup = client.GetRulesGroup(t, folderUID, group.Name) | 
					
						
							|  |  |  | 		require.Lenf(t, getGroup.Rules, 3, "expected 3 rules in group") | 
					
						
							|  |  |  | 		require.Equal(t, newGroup, getGroup.Rules[0].GrafanaManagedAlert.RuleGroup) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		status, body = client.DeleteRulesGroup(t, folderUID, groupName) | 
					
						
							|  |  |  | 		require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Old group is gone.
 | 
					
						
							|  |  |  | 		getGroup = client.GetRulesGroup(t, folderUID, groupName) | 
					
						
							|  |  |  | 		require.Lenf(t, getGroup.Rules, 0, "expected no rules") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// New group still exists.
 | 
					
						
							|  |  |  | 		getGroup = client.GetRulesGroup(t, folderUID, newGroup) | 
					
						
							|  |  |  | 		require.Lenf(t, getGroup.Rules, 3, "expected 3 rules in group") | 
					
						
							|  |  |  | 		require.Equal(t, newGroup, getGroup.Rules[0].GrafanaManagedAlert.RuleGroup) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } |