package prom import ( "encoding/json" "fmt" "maps" "testing" "time" "github.com/google/uuid" prommodel "github.com/prometheus/common/model" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/expr/mathexp" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/util" ) func TestPrometheusRulesToGrafana(t *testing.T) { defaultInterval := 2 * time.Minute testCases := []struct { name string orgID int64 namespace string promGroup PrometheusRuleGroup config Config expectError bool errorMsg string }{ { name: "valid rule group", orgID: 1, namespace: "some-namespace-uid", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), QueryOffset: util.Pointer(prommodel.Duration(1 * time.Minute)), Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "cpu_usage > 80", For: util.Pointer(prommodel.Duration(5 * time.Minute)), KeepFiringFor: util.Pointer(prommodel.Duration(60 * time.Second)), Labels: map[string]string{ "severity": "critical", }, Annotations: map[string]string{ "summary": "CPU usage is critical", }, }, }, }, expectError: false, }, { // If the rule group has no recording rules, the target datasource // can be anything and should not be validated. name: "alert rules with non-prometheus target datasource", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "up == 0", }, }, }, config: Config{ TargetDatasourceUID: "target-datasource-uid", TargetDatasourceType: "non-prometheus-datasource", }, expectError: false, }, { // If the rule group has recording rules and a non-prometheus target datasource, // we should return an error name: "recording rules with non-prometheus target datasource", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Record: "some_metric", Expr: "sum(rate(http_requests_total[5m]))", }, }, }, config: Config{ TargetDatasourceUID: "target-datasource-uid", TargetDatasourceType: "non-prometheus-datasource", }, expectError: true, errorMsg: "invalid target datasource type: non-prometheus-datasource, must be prometheus", }, { // If the rule group has recording rules and a non-prometheus target datasource, // we should return an error name: "mixed group with both alert and recording rules requires prometheus target datasource", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ Name: "mixed-rules-group", Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "up == 0", }, { Record: "some_metric", Expr: "sum(rate(http_requests_total[5m]))", }, }, }, config: Config{ TargetDatasourceUID: "target-datasource-uid", TargetDatasourceType: "non-prometheus-datasource", }, expectError: true, errorMsg: "invalid target datasource type: non-prometheus-datasource, must be prometheus", }, { name: "rule group with empty interval", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "up == 0", }, }, }, expectError: false, }, { name: "recording rule", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Record: "some_metric", Expr: "sum(rate(http_requests_total[5m]))", }, }, }, expectError: false, }, { name: "recording rule with target datasource", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Record: "some_metric", Expr: "sum(rate(http_requests_total[5m]))", }, }, }, config: Config{ TargetDatasourceUID: "target-datasource-uid", TargetDatasourceType: datasources.DS_PROMETHEUS, }, expectError: false, }, { name: "query_offset must be >= 0", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), QueryOffset: util.Pointer(prommodel.Duration(-1)), Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "up == 0", }, }, }, expectError: true, errorMsg: "query_offset must be >= 0", }, { name: "rule group with limit is not supported", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Limit: 5, Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "up == 0", }, }, }, expectError: true, errorMsg: "limit is not supported", }, { name: "rule group with labels", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Labels: map[string]string{"team": "devops"}, Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "up == 0", }, }, }, expectError: false, }, { name: "when global query offset is set, it should be used", orgID: 1, namespace: "some-namespace-uid", promGroup: PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "cpu_usage > 80", For: util.Pointer(prommodel.Duration(5 * time.Minute)), Labels: map[string]string{ "severity": "critical", }, Annotations: map[string]string{ "summary": "CPU usage is critical", }, }, }, }, config: Config{ EvaluationOffset: util.Pointer(5 * time.Minute), }, expectError: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { tc.config.DatasourceUID = "datasource-uid" tc.config.DatasourceType = datasources.DS_PROMETHEUS tc.config.DefaultInterval = defaultInterval converter, err := NewConverter(tc.config) require.NoError(t, err) grafanaGroup, err := converter.PrometheusRulesToGrafana(tc.orgID, tc.namespace, tc.promGroup) if tc.expectError { require.Error(t, err, tc.name) if tc.errorMsg != "" { require.Contains(t, err.Error(), tc.errorMsg, tc.name) } return } require.NoError(t, err, tc.name) require.Equal(t, tc.promGroup.Name, grafanaGroup.Title, tc.name) expectedInterval := int64(time.Duration(tc.promGroup.Interval).Seconds()) if expectedInterval == 0 { expectedInterval = int64(defaultInterval.Seconds()) } require.Equal(t, expectedInterval, grafanaGroup.Interval, tc.name) require.Equal(t, len(tc.promGroup.Rules), len(grafanaGroup.Rules), tc.name) for j, promRule := range tc.promGroup.Rules { grafanaRule := grafanaGroup.Rules[j] if promRule.Record != "" { require.Equal(t, promRule.Record, grafanaRule.Title) require.NotNil(t, grafanaRule.Record) require.Equal(t, grafanaRule.Record.From, queryRefID) require.Equal(t, promRule.Record, grafanaRule.Record.Metric) targetDatasourceUID := tc.config.TargetDatasourceUID if targetDatasourceUID == "" { targetDatasourceUID = tc.config.DatasourceUID } require.Equal(t, targetDatasourceUID, grafanaRule.Record.TargetDatasourceUID) } else { require.Equal(t, promRule.Alert, grafanaRule.Title) } var expectedFor time.Duration if promRule.For != nil { expectedFor = time.Duration(*promRule.For) } require.Equal(t, expectedFor, grafanaRule.For, tc.name) var expectedKeepFiringFor time.Duration if promRule.KeepFiringFor != nil { expectedKeepFiringFor = time.Duration(*promRule.KeepFiringFor) } require.Equal(t, expectedKeepFiringFor, grafanaRule.KeepFiringFor, tc.name) expectedLabels := make(map[string]string, len(promRule.Labels)+len(tc.promGroup.Labels)) maps.Copy(expectedLabels, tc.promGroup.Labels) maps.Copy(expectedLabels, promRule.Labels) expectedLabels = withInternalLabel(expectedLabels) uidData := fmt.Sprintf("%d|%s|%s|%d", tc.orgID, tc.namespace, tc.promGroup.Name, j) u := uuid.NewSHA1(uuid.NameSpaceOID, []byte(uidData)) require.Equal(t, u.String(), grafanaRule.UID, tc.name) require.Equal(t, expectedLabels, grafanaRule.Labels, tc.name) require.Equal(t, promRule.Annotations, grafanaRule.Annotations, tc.name) evalOffset := time.Duration(0) if tc.config.EvaluationOffset != nil { evalOffset = *tc.config.EvaluationOffset } if tc.promGroup.QueryOffset != nil { // group-level offset takes precedence evalOffset = time.Duration(*tc.promGroup.QueryOffset) } require.Equal(t, models.Duration(evalOffset), grafanaRule.Data[0].RelativeTimeRange.To) require.Equal(t, models.Duration(10*time.Minute+evalOffset), grafanaRule.Data[0].RelativeTimeRange.From) require.Equal(t, util.Pointer(1), grafanaRule.MissingSeriesEvalsToResolve) require.Equal(t, models.OkErrState, grafanaRule.ExecErrState) require.Equal(t, models.OK, grafanaRule.NoDataState) originalRuleDefinition, err := yaml.Marshal(promRule) require.NoError(t, err) require.Equal(t, string(originalRuleDefinition), grafanaRule.Metadata.PrometheusStyleRule.OriginalRuleDefinition) } }) } } func TestPrometheusRulesToGrafanaWithDuplicateRuleNames(t *testing.T) { cfg := Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 2 * time.Minute, } converter, err := NewConverter(cfg) require.NoError(t, err) promGroup := PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Alert: "alert", Expr: "up", }, { Alert: "alert", Expr: "up", }, { Alert: "another alert", Expr: "up", }, { Alert: "alert", Expr: "up", }, }, } group, err := converter.PrometheusRulesToGrafana(1, "namespaceUID", promGroup) require.NoError(t, err) require.Equal(t, "test-group-1", group.Title) require.Len(t, group.Rules, 4) require.Equal(t, "alert", group.Rules[0].Title) require.Equal(t, "alert", group.Rules[1].Title) require.Equal(t, "another alert", group.Rules[2].Title) require.Equal(t, "alert", group.Rules[3].Title) } func TestCreateMathNode(t *testing.T) { node, err := createMathNode() require.NoError(t, err) require.Equal(t, expr.DatasourceUID, node.DatasourceUID) require.Equal(t, string(expr.QueryTypeMath), node.QueryType) require.Equal(t, "prometheus_math", node.RefID) var model map[string]interface{} err = json.Unmarshal(node.Model, &model) require.NoError(t, err) require.Equal(t, "prometheus_math", model["refId"]) require.Equal(t, string(expr.QueryTypeMath), model["type"]) require.Equal(t, "is_number($query) || is_nan($query) || is_inf($query)", model["expression"]) ds := model["datasource"].(map[string]interface{}) require.Equal(t, expr.DatasourceUID, ds["name"]) require.Equal(t, expr.DatasourceType, ds["type"]) require.Equal(t, expr.DatasourceUID, ds["uid"]) } func TestCreateThresholdNode(t *testing.T) { node, err := createThresholdNode() require.NoError(t, err) require.Equal(t, expr.DatasourceUID, node.DatasourceUID) require.Equal(t, string(expr.QueryTypeThreshold), node.QueryType) require.Equal(t, "threshold", node.RefID) var model map[string]interface{} err = json.Unmarshal(node.Model, &model) require.NoError(t, err) require.Equal(t, "threshold", model["refId"]) require.Equal(t, string(expr.QueryTypeThreshold), model["type"]) ds := model["datasource"].(map[string]interface{}) require.Equal(t, expr.DatasourceUID, ds["name"]) require.Equal(t, expr.DatasourceType, ds["type"]) require.Equal(t, expr.DatasourceUID, ds["uid"]) conditions := model["conditions"].([]interface{}) require.Len(t, conditions, 1) condition := conditions[0].(map[string]interface{}) evaluator := condition["evaluator"].(map[string]interface{}) require.Equal(t, string(expr.ThresholdIsAbove), evaluator["type"]) require.Equal(t, []interface{}{float64(0)}, evaluator["params"]) } func TestPrometheusRulesToGrafana_NodesInRules(t *testing.T) { cfg := Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 2 * time.Minute, } converter, err := NewConverter(cfg) require.NoError(t, err) t.Run("alert rule should have math and threshold nodes", func(t *testing.T) { group := PrometheusRuleGroup{ Name: "test", Rules: []PrometheusRule{ { Alert: "alert1", Expr: "up == 0", }, }, } result, err := converter.PrometheusRulesToGrafana(1, "namespace", group) require.NoError(t, err) require.Len(t, result.Rules, 1) require.Len(t, result.Rules[0].Data, 3) // First node should be query require.Equal(t, "query", result.Rules[0].Data[0].RefID) // Second node should be math require.Equal(t, "prometheus_math", result.Rules[0].Data[1].RefID) require.Equal(t, string(expr.QueryTypeMath), result.Rules[0].Data[1].QueryType) // Check that the math expression is valid var model map[string]interface{} err = json.Unmarshal(result.Rules[0].Data[1].Model, &model) require.NoError(t, err) require.Equal(t, "is_number($query) || is_nan($query) || is_inf($query)", model["expression"]) // The math expression should be parsed successfully _, err = mathexp.New(model["expression"].(string)) require.NoError(t, err) // Third node should be threshold require.Equal(t, "threshold", result.Rules[0].Data[2].RefID) require.Equal(t, string(expr.QueryTypeThreshold), result.Rules[0].Data[2].QueryType) }) t.Run("recording rule should only have query node", func(t *testing.T) { group := PrometheusRuleGroup{ Name: "test", Rules: []PrometheusRule{ { Record: "metric", Expr: "sum(rate(http_requests_total[5m]))", }, }, } result, err := converter.PrometheusRulesToGrafana(1, "namespace", group) require.NoError(t, err) require.Len(t, result.Rules, 1) require.Len(t, result.Rules[0].Data, 1) // Should only have query node require.Equal(t, "query", result.Rules[0].Data[0].RefID) }) } func TestPrometheusRulesToGrafana_GroupLabels(t *testing.T) { cfg := Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 2 * time.Minute, } converter, err := NewConverter(cfg) require.NoError(t, err) t.Run("group labels are merged with alert rule labels", func(t *testing.T) { promGroup := PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Labels: map[string]string{ "group_label": "group_value", "common_label": "group_value", }, Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "cpu_usage > 80", Labels: map[string]string{ "rule_label": "rule_value", "common_label": "rule_value", // rule-level label should take precedence }, }, }, } grafanaGroup, err := converter.PrometheusRulesToGrafana(1, "namespace", promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) // Check that the labels are merged and the rule label takes precedence require.Equal( t, withInternalLabel(map[string]string{ "group_label": "group_value", "rule_label": "rule_value", "common_label": "rule_value", }), grafanaGroup.Rules[0].Labels, ) }) t.Run("group labels are merged with recording rule labels", func(t *testing.T) { promGroup := PrometheusRuleGroup{ Name: "recording-group", Interval: prommodel.Duration(10 * time.Second), Labels: map[string]string{ "group_label": "group_value", "common_label": "group_value", }, Rules: []PrometheusRule{ { Record: "recording_metric", Expr: "sum(rate(http_requests_total[5m]))", Labels: map[string]string{ "rule_label": "rule_value", "common_label": "rule_value", }, }, }, } grafanaGroup, err := converter.PrometheusRulesToGrafana(1, "namespace", promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) // Check that the labels are merged and the rule label takes precedence require.Equal( t, withInternalLabel(map[string]string{ "group_label": "group_value", "rule_label": "rule_value", "common_label": "rule_value", }), grafanaGroup.Rules[0].Labels, ) }) t.Run("rule with no labels gets group labels", func(t *testing.T) { promGroup := PrometheusRuleGroup{ Name: "group-with-labels", Interval: prommodel.Duration(10 * time.Second), Labels: map[string]string{ "group_label1": "group_value1", "group_label2": "group_value2", }, Rules: []PrometheusRule{ { Alert: "alert-no-labels", Expr: "up == 0", }, }, } grafanaGroup, err := converter.PrometheusRulesToGrafana(1, "namespace", promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) require.Equal(t, withInternalLabel(promGroup.Labels), grafanaGroup.Rules[0].Labels) }) t.Run("rule and group with nil labels", func(t *testing.T) { promGroup := PrometheusRuleGroup{ Name: "group-no-labels", Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Alert: "alert-no-labels", Expr: "up == 0", }, }, } grafanaGroup, err := converter.PrometheusRulesToGrafana(1, "namespace", promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) require.Equal(t, withInternalLabel(map[string]string{}), grafanaGroup.Rules[0].Labels) }) } func TestPrometheusRulesToGrafana_UID(t *testing.T) { orgID := int64(1) namespace := "some-namespace" promGroup := PrometheusRuleGroup{ Name: "test-group-1", Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Alert: "alert-1", Expr: "cpu_usage > 80", For: util.Pointer(prommodel.Duration(5 * time.Minute)), Labels: map[string]string{ "severity": "critical", ruleUIDLabel: "rule-uid-1", }, Annotations: map[string]string{ "summary": "CPU usage is critical", }, }, }, } converter, err := NewConverter(Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 2 * time.Minute, }) require.NoError(t, err) t.Run("if not specified, UID is generated based on the rule index", func(t *testing.T) { grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup) require.NoError(t, err) firstUID := grafanaGroup.Rules[0].UID // Convert again grafanaGroup, err = converter.PrometheusRulesToGrafana(orgID, namespace, promGroup) require.NoError(t, err) secondUID := grafanaGroup.Rules[0].UID // They must be equal require.NotEmpty(t, firstUID) require.Equal(t, firstUID, secondUID) }) t.Run("if the special label is specified", func(t *testing.T) { t.Run("and the label is valid it should be used", func(t *testing.T) { orgID := int64(1) namespace := "some-namespace" converter, err := NewConverter(Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 2 * time.Minute, }) require.NoError(t, err) promGroup.Rules[0].Labels[ruleUIDLabel] = "rule-uid-1" grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup) require.NoError(t, err) require.Equal(t, "rule-uid-1", grafanaGroup.Rules[0].UID) }) t.Run("and the label is invalid", func(t *testing.T) { orgID := int64(1) namespace := "some-namespace" converter, err := NewConverter(Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 2 * time.Minute, }) require.NoError(t, err) // create a string of 50 characters promGroup.Rules[0].Labels[ruleUIDLabel] = "aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmm" // too long grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup) require.Errorf(t, err, "invalid UID label value") require.Nil(t, grafanaGroup) }) t.Run("and the label is empty", func(t *testing.T) { orgID := int64(1) namespace := "some-namespace" converter, err := NewConverter(Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 2 * time.Minute, }) require.NoError(t, err) promGroup.Rules[0].Labels[ruleUIDLabel] = "" grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup) require.Errorf(t, err, "invalid UID label value") require.Nil(t, grafanaGroup) }) }) } func TestPrometheusRulesToGrafana_KeepOriginalRuleDefinition(t *testing.T) { orgID := int64(1) namespace := "namespace" promGroup := PrometheusRuleGroup{ Name: "test-group", Rules: []PrometheusRule{ { Alert: "test-alert", Expr: "up == 0", }, }, } testCases := []struct { name string keepOriginalRuleDefinition *bool expectDefinition bool }{ { name: "keep original rule definition is true", keepOriginalRuleDefinition: util.Pointer(true), expectDefinition: true, }, { name: "keep original rule definition is false", keepOriginalRuleDefinition: util.Pointer(false), expectDefinition: false, }, { name: "keep original rule definition is nil (should use default)", keepOriginalRuleDefinition: nil, expectDefinition: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cfg := Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 1 * time.Minute, KeepOriginalRuleDefinition: tc.keepOriginalRuleDefinition, } converter, err := NewConverter(cfg) require.NoError(t, err) // Convert the Prometheus rule to Grafana grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) if tc.expectDefinition { originalRuleDefinition, err := yaml.Marshal(promGroup.Rules[0]) require.NoError(t, err) require.Equal( t, string(originalRuleDefinition), grafanaGroup.Rules[0].Metadata.PrometheusStyleRule.OriginalRuleDefinition, ) } else { require.Nil(t, grafanaGroup.Rules[0].Metadata.PrometheusStyleRule) } }) } } func TestPrometheusRulesToGrafana_NotificationSettings(t *testing.T) { orgID := int64(1) namespace := "namespace" promGroup := PrometheusRuleGroup{ Name: "test-group", Rules: []PrometheusRule{ { Alert: "test-alert", Expr: "up == 0", }, }, } testCases := []struct { name string notificationSettings []models.NotificationSettings }{ { name: "with notification settings specified", notificationSettings: []models.NotificationSettings{ { Receiver: "test-receiver", GroupBy: []string{"alertname", "instance"}, }, }, }, { name: "without notification settings", notificationSettings: nil, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cfg := Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 1 * time.Minute, NotificationSettings: tc.notificationSettings, } converter, err := NewConverter(cfg) require.NoError(t, err) grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) if tc.notificationSettings != nil { require.NotNil(t, grafanaGroup.Rules[0].NotificationSettings) require.Len(t, grafanaGroup.Rules[0].NotificationSettings, len(tc.notificationSettings)) require.Equal(t, tc.notificationSettings, grafanaGroup.Rules[0].NotificationSettings) } else { require.Nil(t, grafanaGroup.Rules[0].NotificationSettings) } }) } } func TestQueryModelContainsRequiredParameters(t *testing.T) { cfg := Config{ DatasourceUID: "datasource-uid", DatasourceType: datasources.DS_PROMETHEUS, DefaultInterval: 1 * time.Minute, } converter, err := NewConverter(cfg) require.NoError(t, err) promRule := PrometheusRule{ Alert: "test-alert", Expr: "up == 0", } queries, err := converter.createQuery(promRule.Expr, false, PrometheusRuleGroup{}) require.NoError(t, err) require.Len(t, queries, 3) for _, query := range queries { var model map[string]any err = json.Unmarshal(query.Model, &model) require.NoError(t, err) // Check intervalMs intervalMs, exists := model["intervalMs"] require.True(t, exists) _, isNumber := intervalMs.(float64) require.True(t, isNumber, "intervalMs should be a number") // Check maxDataPoints maxDataPoints, exists := model["maxDataPoints"] require.True(t, exists) _, isNumber = maxDataPoints.(float64) require.True(t, isNumber, "maxDataPoints should be a number") } } func withInternalLabel(l map[string]string) map[string]string { result := map[string]string{ models.ConvertedPrometheusRuleLabel: "true", } maps.Copy(result, l) return result }