diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index b4a4a94eaba..017641765f8 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -803,8 +803,9 @@ func (alertRule *AlertRule) Copy() *AlertRule { if alertRule.Record != nil { result.Record = &Record{ - From: alertRule.Record.From, - Metric: alertRule.Record.Metric, + From: alertRule.Record.From, + Metric: alertRule.Record.Metric, + TargetDatasourceUID: alertRule.Record.TargetDatasourceUID, } } diff --git a/pkg/services/ngalert/models/alert_rule_test.go b/pkg/services/ngalert/models/alert_rule_test.go index 329e979ea03..25d49826828 100644 --- a/pkg/services/ngalert/models/alert_rule_test.go +++ b/pkg/services/ngalert/models/alert_rule_test.go @@ -1028,6 +1028,13 @@ func TestAlertRuleCopy(t *testing.T) { copied := rule.Copy() require.NotSame(t, rule.Metadata.PrometheusStyleRule, copied.Metadata.PrometheusStyleRule) }) + t.Run("should return an exact copy of recording rule", func(t *testing.T) { + for i := 0; i < 100; i++ { + rule := RuleGen.With(RuleGen.WithAllRecordingRules()).GenerateRef() + copied := rule.Copy() + require.Empty(t, rule.Diff(copied)) + } + }) } // This test makes sure the default generator @@ -1067,6 +1074,48 @@ func TestGeneratorFillsAllFields(t *testing.T) { require.FailNow(t, "AlertRule generator does not populate fields", "skipped fields: %v", maps.Keys(fields)) } +func TestGeneratorFillsAllRecordingRuleFields(t *testing.T) { + ignoredFields := map[string]struct{}{ + "ID": {}, + "IsPaused": {}, + "NoDataState": {}, + "ExecErrState": {}, + "Condition": {}, + "KeepFiringFor": {}, + "MissingSeriesEvalsToResolve": {}, + "For": {}, + "NotificationSettings": {}, + } + + tpe := reflect.TypeOf(AlertRule{}) + fields := make(map[string]struct{}, tpe.NumField()) + for i := 0; i < tpe.NumField(); i++ { + if _, ok := ignoredFields[tpe.Field(i).Name]; ok { + continue + } + fields[tpe.Field(i).Name] = struct{}{} + } + + for i := 0; i < 1000; i++ { + rule := RuleGen.With(RuleGen.WithAllRecordingRules()).Generate() + v := reflect.ValueOf(rule) + + for j := 0; j < tpe.NumField(); j++ { + field := tpe.Field(j) + value := v.Field(j) + if !value.IsValid() || value.Kind() == reflect.Ptr && value.IsNil() || value.IsZero() { + continue + } + delete(fields, field.Name) + if len(fields) == 0 { + return + } + } + } + + require.FailNow(t, "AlertRule generator does not populate fields", "skipped fields: %v", maps.Keys(fields)) +} + func TestValidateAlertRule(t *testing.T) { t.Run("keepFiringFor", func(t *testing.T) { testCases := []struct { diff --git a/pkg/services/ngalert/models/testing.go b/pkg/services/ngalert/models/testing.go index aa8e7c8f32d..a33440e5f40 100644 --- a/pkg/services/ngalert/models/testing.go +++ b/pkg/services/ngalert/models/testing.go @@ -561,6 +561,14 @@ func (a *AlertRuleMutators) WithAllRecordingRules() AlertRuleMutator { } } +func (a *AlertRuleMutators) WithoutTargetDataSource() AlertRuleMutator { + return func(rule *AlertRule) { + if rule.Record != nil { + rule.Record.TargetDatasourceUID = "" + } + } +} + func (a *AlertRuleMutators) WithMetric(metric string) AlertRuleMutator { return func(rule *AlertRule) { if rule.Record == nil { @@ -1374,10 +1382,14 @@ func ConvertToRecordingRule(rule *AlertRule) { if rule.Record.Metric == "" { rule.Record.Metric = fmt.Sprintf("some_metric_%s", util.GenerateShortUID()) } + if rule.Record.TargetDatasourceUID == "" { + rule.Record.TargetDatasourceUID = util.GenerateShortUID() + } rule.Condition = "" rule.NoDataState = "" rule.ExecErrState = "" rule.For = 0 + rule.KeepFiringFor = 0 rule.NotificationSettings = nil rule.MissingSeriesEvalsToResolve = nil } diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index 046522f3214..d2196562b6d 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -231,8 +231,8 @@ func (f *RuleStore) ListAlertRules(_ context.Context, q *models.ListAlertRulesQu if q.ReceiverName != "" && (len(r.NotificationSettings) < 1 || r.NotificationSettings[0].Receiver != q.ReceiverName) { continue } - - ruleList = append(ruleList, r) + copyR := models.CopyRule(r) + ruleList = append(ruleList, copyR) } return ruleList, nil