package alertrule import ( "context" "time" "encoding/json" "fmt" "testing" "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/tests/apis/alerting/rules/common" "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/testutil" prom_model "github.com/prometheus/common/model" ) func TestMain(m *testing.M) { testsuite.Run(m) } func TestIntegrationResourceIdentifier(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) ctx := context.Background() helper := common.GetTestHelper(t) client := common.NewAlertRuleClient(t, helper.Org1.Admin) // Create test folder first common.CreateTestFolder(t, helper, "test-folder") rule := ngmodels.RuleGen.With( ngmodels.RuleMuts.WithUniqueUID(), ngmodels.RuleMuts.WithUniqueTitle(), ngmodels.RuleMuts.WithNamespaceUID("test-folder"), ngmodels.RuleMuts.WithGroupName("test-group"), ngmodels.RuleMuts.WithIntervalMatching(time.Duration(10)*time.Second), ).Generate() newResource := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ "grafana.app/folder": "test-folder", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer("query"), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration(fmt.Sprintf("%ds", rule.IntervalSeconds)), }, NoDataState: string(rule.NoDataState), ExecErrState: string(rule.ExecErrState), }, } // Test 1: Create with explicit name namedResource := newResource.Copy().(*v0alpha1.AlertRule) namedResource.Name = "explicit-name-rule" namedRule, err := client.Create(ctx, namedResource, v1.CreateOptions{}) require.NoError(t, err) require.Equal(t, "explicit-name-rule", namedRule.Name) require.NotEmpty(t, namedRule.UID) // Test 2: Create without explicit name (auto-generated) autoGenRule, err := client.Create(ctx, newResource, v1.CreateOptions{}) require.NoError(t, err) require.NotEmpty(t, autoGenRule.Name) require.NotEmpty(t, autoGenRule.UID) // Test 3: Get by identifier retrievedRule, err := client.Get(ctx, autoGenRule.Name, v1.GetOptions{}) require.NoError(t, err) require.Equal(t, autoGenRule.Name, retrievedRule.Name) require.Equal(t, newResource.Spec.Title, retrievedRule.Spec.Title) // Test 4: Update (should preserve name) updatedRule := retrievedRule.Copy().(*v0alpha1.AlertRule) updatedRule.Spec.Title = "updated-title" finalRule, err := client.Update(ctx, updatedRule, v1.UpdateOptions{}) require.NoError(t, err) require.Equal(t, "updated-title", finalRule.Spec.Title) require.Equal(t, retrievedRule.Name, finalRule.Name, "Update should preserve the resource name") require.NotEqual(t, retrievedRule.ResourceVersion, finalRule.ResourceVersion, "Update should change the resource version") // Test 5: Verify the update persisted finalRetrieved, err := client.Get(ctx, finalRule.Name, v1.GetOptions{}) require.NoError(t, err) require.Equal(t, finalRule.Spec.Title, finalRetrieved.Spec.Title) require.Equal(t, finalRule.Name, finalRetrieved.Name) require.Equal(t, finalRule.ResourceVersion, finalRetrieved.ResourceVersion) // Cleanup require.NoError(t, client.Delete(ctx, namedRule.Name, v1.DeleteOptions{})) require.NoError(t, client.Delete(ctx, finalRule.Name, v1.DeleteOptions{})) } // TestIntegrationResourcePermissions is skipped for now as access control is handled in the service layer func TestIntegrationResourcePermissions(t *testing.T) { t.Skip("Access control tests skipped - handled in service layer") } // TestIntegrationAccessControl tests basic access control functionality // Access control is primarily handled in the service layer, so this test focuses on basic CRUD operations func TestIntegrationAccessControl(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) ctx := context.Background() helper := common.GetTestHelper(t) // Test with admin user for basic functionality adminClient := common.NewAlertRuleClient(t, helper.Org1.Admin) // Create test folder first common.CreateTestFolder(t, helper, "test-folder") rule := ngmodels.RuleGen.With( ngmodels.RuleMuts.WithUniqueUID(), ngmodels.RuleMuts.WithUniqueTitle(), ngmodels.RuleMuts.WithNamespaceUID("test-folder"), ngmodels.RuleMuts.WithGroupName("test-group"), ngmodels.RuleMuts.WithIntervalMatching(time.Duration(10)*time.Second), ).Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ "grafana.app/folder": "test-folder", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration(fmt.Sprintf("%ds", rule.IntervalSeconds)), }, NoDataState: string(rule.NoDataState), ExecErrState: string(rule.ExecErrState), }, } t.Run("admin should be able to create rule", func(t *testing.T) { created, err := adminClient.Create(ctx, alertRule, v1.CreateOptions{}) require.NoError(t, err) require.NotNil(t, created) require.Equal(t, alertRule.Spec.Title, created.Spec.Title) // Cleanup defer func() { _ = adminClient.Delete(ctx, created.Name, v1.DeleteOptions{}) }() t.Run("admin should be able to read rule", func(t *testing.T) { read, err := adminClient.Get(ctx, created.Name, v1.GetOptions{}) require.NoError(t, err) require.Equal(t, created.Spec.Title, read.Spec.Title) }) t.Run("admin should be able to update rule", func(t *testing.T) { updated := created.Copy().(*v0alpha1.AlertRule) updated.Spec.Title = "updated-title" result, err := adminClient.Update(ctx, updated, v1.UpdateOptions{}) require.NoError(t, err) require.Equal(t, "updated-title", result.Spec.Title) }) t.Run("admin should be able to delete rule", func(t *testing.T) { err := adminClient.Delete(ctx, created.Name, v1.DeleteOptions{}) require.NoError(t, err) }) }) } func TestIntegrationCRUD(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) ctx := context.Background() helper := common.GetTestHelper(t) adminClient := common.NewAlertRuleClient(t, helper.Org1.Admin) // Create test folder first common.CreateTestFolder(t, helper, "test-folder") baseGen := ngmodels.RuleGen.With( ngmodels.RuleMuts.WithUniqueUID(), ngmodels.RuleMuts.WithUniqueTitle(), ngmodels.RuleMuts.WithNamespaceUID("test-folder"), ngmodels.RuleMuts.WithGroupName("test-group"), ngmodels.RuleMuts.WithIntervalMatching(time.Duration(10)*time.Second), ) t.Run("should be able to create and read rule", func(t *testing.T) { rule := baseGen.Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ "grafana.app/folder": "test-folder", "grafana.com/provenance": "", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration(fmt.Sprintf("%ds", rule.IntervalSeconds)), }, NoDataState: string(rule.NoDataState), ExecErrState: string(rule.ExecErrState), }, } created, err := adminClient.Create(ctx, alertRule, v1.CreateOptions{}) require.NoError(t, err) require.NotNil(t, created) t.Run("should be able to read what it is created", func(t *testing.T) { get, err := adminClient.Get(ctx, created.Name, v1.GetOptions{}) require.NoError(t, err) require.Equal(t, alertRule.Spec.Title, get.Spec.Title) createdDuration, err := prom_model.ParseDuration(string(alertRule.Spec.Trigger.Interval)) require.NoError(t, err) require.Equal(t, createdDuration.String(), string(get.Spec.Trigger.Interval)) provenance := get.GetProvenanceStatus() require.Equal(t, v0alpha1.ProvenanceStatusNone, provenance) }) // Cleanup require.NoError(t, adminClient.Delete(ctx, created.Name, v1.DeleteOptions{})) }) t.Run("should fail to create rule with invalid provenance status", func(t *testing.T) { rule := baseGen.Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ "grafana.app/folder": "test-folder", "grafana.com/provenance": "invalid", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration(fmt.Sprintf("%ds", rule.IntervalSeconds)), }, NoDataState: string(rule.NoDataState), ExecErrState: string(rule.ExecErrState), }, } _, err := adminClient.Create(ctx, alertRule, v1.CreateOptions{}) require.Error(t, err, "Creating invalid rule should fail") }) t.Run("should fail to create rule with invalid config", func(t *testing.T) { invalidRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ "grafana.app/folder": "test-folder", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: "invalid-rule", Expressions: v0alpha1.AlertRuleExpressionMap{}, // Empty data should fail Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: "30", }, NoDataState: "NoData", ExecErrState: "Error", }, } _, err := adminClient.Create(ctx, invalidRule, v1.CreateOptions{}) require.Errorf(t, err, "Expected error but got successful result") // The validation happens at the service level, so we just need to verify it fails require.Error(t, err, "Creating invalid rule should fail") }) t.Run("should not be able to add rule to group", func(t *testing.T) { rule := baseGen.Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ "grafana.app/folder": "test-folder", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration(fmt.Sprintf("%ds", rule.IntervalSeconds)), }, NoDataState: string(rule.NoDataState), ExecErrState: string(rule.ExecErrState), }, } created, err := adminClient.Create(ctx, alertRule, v1.CreateOptions{}) require.NoError(t, err) require.NotNil(t, created) get, err := adminClient.Get(ctx, created.Name, v1.GetOptions{}) require.NoError(t, err) require.Equal(t, created.Spec.Title, get.Spec.Title) // Attempt to update the group name via a patch (should fail) update := get if update.Labels == nil { update.Labels = map[string]string{} } update.Labels[v0alpha1.GroupLabelKey] = "new-group-name" _, err = adminClient.Update(ctx, update, v1.UpdateOptions{}) require.Error(t, err, "Updating the group name should fail") // Cleanup require.NoError(t, adminClient.Delete(ctx, created.Name, v1.DeleteOptions{})) }) t.Run("should not be able to create rule without any source query", func(t *testing.T) { rule := baseGen.Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ "grafana.app/folder": "test-folder", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration(fmt.Sprintf("%ds", rule.IntervalSeconds)), }, NoDataState: string(rule.NoDataState), ExecErrState: string(rule.ExecErrState), }, } created, err := adminClient.Create(ctx, alertRule, v1.CreateOptions{}) require.ErrorContains(t, err, "no query marked as source") require.Nil(t, created) }) t.Run("should not be able to create rule with interval less than base", func(t *testing.T) { rule := baseGen.With( ngmodels.RuleMuts.WithInterval(time.Duration(1) * time.Second), ).Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ "grafana.app/folder": "test-folder", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration(fmt.Sprintf("%ds", rule.IntervalSeconds)), }, NoDataState: string(rule.NoDataState), ExecErrState: string(rule.ExecErrState), }, } created, err := adminClient.Create(ctx, alertRule, v1.CreateOptions{}) require.ErrorContains(t, err, "trigger interval must be a multiple of base evaluation interval") require.Nil(t, created) }) } func TestIntegrationPatch(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) ctx := context.Background() helper := common.GetTestHelper(t) adminClient := common.NewAlertRuleClient(t, helper.Org1.Admin) // Create test folder first common.CreateTestFolder(t, helper, "test-folder") rule := ngmodels.RuleGen.With( ngmodels.RuleMuts.WithUniqueUID(), ngmodels.RuleMuts.WithUniqueTitle(), ngmodels.RuleMuts.WithNamespaceUID("test-folder"), ngmodels.RuleMuts.WithGroupName("test-group"), ngmodels.RuleMuts.WithIntervalMatching(time.Duration(10)*time.Second), ).Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ "grafana.app/folder": "test-folder", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration(fmt.Sprintf("%ds", rule.IntervalSeconds)), }, NoDataState: string(rule.NoDataState), ExecErrState: string(rule.ExecErrState), }, } current, err := adminClient.Create(ctx, alertRule, v1.CreateOptions{}) require.NoError(t, err) require.NotNil(t, current) t.Run("should patch with json patch", func(t *testing.T) { current, err := adminClient.Get(ctx, current.Name, v1.GetOptions{}) require.NoError(t, err) patch := []map[string]any{ { "op": "replace", "path": "/spec/title", "value": "patched-title", }, } patchData, err := json.Marshal(patch) require.NoError(t, err) result, err := adminClient.Patch(ctx, current.Name, types.JSONPatchType, patchData, v1.PatchOptions{}) require.NoError(t, err) require.Equal(t, "patched-title", result.Spec.Title) }) // Cleanup require.NoError(t, adminClient.Delete(ctx, current.Name, v1.DeleteOptions{})) } func TestIntegrationBasicAPI(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) ctx := context.Background() helper := common.GetTestHelper(t) client := common.NewAlertRuleClient(t, helper.Org1.Admin) t.Run("should be able to list rules", func(t *testing.T) { list, err := client.List(ctx, v1.ListOptions{}) require.NoError(t, err) // Should at least be able to list, even if empty require.NotNil(t, list) }) t.Run("should handle get of non-existent rule", func(t *testing.T) { _, err := client.Get(ctx, "non-existent", v1.GetOptions{}) // The API might return different error types, so just check that it's an error require.Error(t, err) t.Logf("Got error: %s", err) }) } func TestIntegrationFolderLabelSyncAndValidation(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) ctx := context.Background() helper := common.GetTestHelper(t) client := common.NewAlertRuleClient(t, helper.Org1.Admin) // Prepare two folders for label sync update scenario common.CreateTestFolder(t, helper, "test-folder-a") common.CreateTestFolder(t, helper, "test-folder-b") baseGen := ngmodels.RuleGen.With( ngmodels.RuleMuts.WithUniqueUID(), ngmodels.RuleMuts.WithUniqueTitle(), ngmodels.RuleMuts.WithNamespaceUID("test-folder-a"), ngmodels.RuleMuts.WithGroupName("test-group"), ngmodels.RuleMuts.WithIntervalMatching(time.Duration(10)*time.Second), ) t.Run("should keep folder label in sync with folder annotation on create and update", func(t *testing.T) { rule := baseGen.Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ v0alpha1.FolderAnnotationKey: "test-folder-a", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration(fmt.Sprintf("%ds", rule.IntervalSeconds)), }, NoDataState: string(rule.NoDataState), ExecErrState: string(rule.ExecErrState), }, } created, err := client.Create(ctx, alertRule, v1.CreateOptions{}) require.NoError(t, err) defer func() { _ = client.Delete(ctx, created.Name, v1.DeleteOptions{}) }() // On create, metadata.labels[v0alpha1.FolderLabelKey] should mirror annotation require.Equal(t, "test-folder-a", created.Labels[v0alpha1.FolderLabelKey]) // Update annotation to point to a different folder and ensure label follows updated := created.Copy().(*v0alpha1.AlertRule) if updated.Annotations == nil { updated.Annotations = map[string]string{} } updated.Annotations[v0alpha1.FolderAnnotationKey] = "test-folder-b" after, err := client.Update(ctx, updated, v1.UpdateOptions{}) require.NoError(t, err) require.Equal(t, "test-folder-b", after.Annotations[v0alpha1.FolderAnnotationKey]) require.Equal(t, "test-folder-b", after.Labels[v0alpha1.FolderLabelKey]) }) t.Run("should fail to create rule without folder annotation", func(t *testing.T) { rule := baseGen.Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{}, // missing grafana.app/folder }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: v0alpha1.AlertRulePromDuration("10s"), }, NoDataState: "NoData", ExecErrState: "Error", }, } created, err := client.Create(ctx, alertRule, v1.CreateOptions{}) require.Error(t, err) require.Nil(t, created) }) t.Run("should fail to create rule with group labels preset", func(t *testing.T) { rule := baseGen.Generate() alertRule := &v0alpha1.AlertRule{ ObjectMeta: v1.ObjectMeta{ Namespace: "default", Annotations: map[string]string{ v0alpha1.FolderAnnotationKey: "test-folder-a", }, Labels: map[string]string{ v0alpha1.GroupLabelKey: "some-group", v0alpha1.GroupIndexLabelKey: "0", }, }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { QueryType: util.Pointer(rule.Data[0].QueryType), DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), To: v0alpha1.AlertRulePromDurationWMillis("0s"), }, }, }, Trigger: v0alpha1.AlertRuleIntervalTrigger{Interval: v0alpha1.AlertRulePromDuration("10s")}, NoDataState: "NoData", ExecErrState: "Error", }, } created, err := client.Create(ctx, alertRule, v1.CreateOptions{}) require.Error(t, err) require.Nil(t, created) }) }