mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			717 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			717 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
| package api
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/prometheus/common/model"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 	"golang.org/x/exp/rand"
 | |
| 
 | |
| 	models2 "github.com/grafana/grafana/pkg/models"
 | |
| 	apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
 | |
| 	"github.com/grafana/grafana/pkg/services/ngalert/models"
 | |
| 	"github.com/grafana/grafana/pkg/services/ngalert/store"
 | |
| 	"github.com/grafana/grafana/pkg/setting"
 | |
| 	"github.com/grafana/grafana/pkg/util"
 | |
| )
 | |
| 
 | |
| var allNoData = []apimodels.NoDataState{
 | |
| 	apimodels.OK,
 | |
| 	apimodels.NoData,
 | |
| 	apimodels.Alerting,
 | |
| }
 | |
| 
 | |
| var allExecError = []apimodels.ExecutionErrorState{
 | |
| 	apimodels.ErrorErrState,
 | |
| 	apimodels.AlertingErrState,
 | |
| }
 | |
| 
 | |
| func config(t *testing.T) *setting.UnifiedAlertingSettings {
 | |
| 	t.Helper()
 | |
| 	baseInterval := time.Duration(rand.Intn(99)+1) * time.Second
 | |
| 	result := &setting.UnifiedAlertingSettings{
 | |
| 		BaseInterval:                  baseInterval,
 | |
| 		DefaultRuleEvaluationInterval: baseInterval * time.Duration(rand.Intn(9)+1),
 | |
| 	}
 | |
| 	t.Logf("Config Base interval is [%v]", result.BaseInterval)
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| func validRule() apimodels.PostableExtendedRuleNode {
 | |
| 	return apimodels.PostableExtendedRuleNode{
 | |
| 		ApiRuleNode: &apimodels.ApiRuleNode{
 | |
| 			For: model.Duration(rand.Int63n(1000)),
 | |
| 			Labels: map[string]string{
 | |
| 				"test-label": "data",
 | |
| 			},
 | |
| 			Annotations: map[string]string{
 | |
| 				"test-annotation": "data",
 | |
| 			},
 | |
| 		},
 | |
| 		GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
 | |
| 			Title:     fmt.Sprintf("TEST-ALERT-%d", rand.Int63()),
 | |
| 			Condition: "A",
 | |
| 			Data: []models.AlertQuery{
 | |
| 				{
 | |
| 					RefID:     "A",
 | |
| 					QueryType: "TEST",
 | |
| 					RelativeTimeRange: models.RelativeTimeRange{
 | |
| 						From: 10,
 | |
| 						To:   0,
 | |
| 					},
 | |
| 					DatasourceUID: "DATASOURCE_TEST",
 | |
| 					Model:         nil,
 | |
| 				},
 | |
| 			},
 | |
| 			UID:          util.GenerateShortUID(),
 | |
| 			NoDataState:  allNoData[rand.Intn(len(allNoData)-1)],
 | |
| 			ExecErrState: allExecError[rand.Intn(len(allExecError)-1)],
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func validGroup(cfg *setting.UnifiedAlertingSettings, rules ...apimodels.PostableExtendedRuleNode) apimodels.PostableRuleGroupConfig {
 | |
| 	return apimodels.PostableRuleGroupConfig{
 | |
| 		Name:     "TEST-ALERTS-" + util.GenerateShortUID(),
 | |
| 		Interval: model.Duration(cfg.BaseInterval * time.Duration(rand.Int63n(10))),
 | |
| 		Rules:    rules,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func randFolder() *models2.Folder {
 | |
| 	return &models2.Folder{
 | |
| 		Id:        rand.Int63(),
 | |
| 		Uid:       util.GenerateShortUID(),
 | |
| 		Title:     "TEST-FOLDER-" + util.GenerateShortUID(),
 | |
| 		Url:       "",
 | |
| 		Version:   0,
 | |
| 		Created:   time.Time{},
 | |
| 		Updated:   time.Time{},
 | |
| 		UpdatedBy: 0,
 | |
| 		CreatedBy: 0,
 | |
| 		HasAcl:    false,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestValidateRuleGroup(t *testing.T) {
 | |
| 	orgId := rand.Int63()
 | |
| 	folder := randFolder()
 | |
| 
 | |
| 	rules := make([]apimodels.PostableExtendedRuleNode, 0, rand.Intn(4)+1)
 | |
| 	for i := 0; i < cap(rules); i++ {
 | |
| 		rules = append(rules, validRule())
 | |
| 	}
 | |
| 	cfg := config(t)
 | |
| 
 | |
| 	t.Run("should validate struct and rules", func(t *testing.T) {
 | |
| 		g := validGroup(cfg, rules...)
 | |
| 		conditionValidations := 0
 | |
| 		alerts, err := validateRuleGroup(&g, orgId, folder, func(condition models.Condition) error {
 | |
| 			conditionValidations++
 | |
| 			return nil
 | |
| 		}, cfg)
 | |
| 		require.NoError(t, err)
 | |
| 		require.Len(t, alerts, len(rules))
 | |
| 		require.Equal(t, len(rules), conditionValidations)
 | |
| 	})
 | |
| 	t.Run("should default to default interval from config if group interval is 0", func(t *testing.T) {
 | |
| 		g := validGroup(cfg, rules...)
 | |
| 		g.Interval = 0
 | |
| 		alerts, err := validateRuleGroup(&g, orgId, folder, func(condition models.Condition) error {
 | |
| 			return nil
 | |
| 		}, cfg)
 | |
| 		require.NoError(t, err)
 | |
| 		for _, alert := range alerts {
 | |
| 			require.Equal(t, int64(cfg.DefaultRuleEvaluationInterval.Seconds()), alert.IntervalSeconds)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestValidateRuleGroupFailures(t *testing.T) {
 | |
| 	orgId := rand.Int63()
 | |
| 	folder := randFolder()
 | |
| 	cfg := config(t)
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name   string
 | |
| 		group  func() *apimodels.PostableRuleGroupConfig
 | |
| 		assert func(t *testing.T, apiModel *apimodels.PostableRuleGroupConfig, err error)
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "fail if title is empty",
 | |
| 			group: func() *apimodels.PostableRuleGroupConfig {
 | |
| 				g := validGroup(cfg)
 | |
| 				g.Name = ""
 | |
| 				return &g
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if title is too long",
 | |
| 			group: func() *apimodels.PostableRuleGroupConfig {
 | |
| 				g := validGroup(cfg)
 | |
| 				for len(g.Name) < store.AlertRuleMaxRuleGroupNameLength {
 | |
| 					g.Name += g.Name
 | |
| 				}
 | |
| 				return &g
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if interval is negative",
 | |
| 			group: func() *apimodels.PostableRuleGroupConfig {
 | |
| 				g := validGroup(cfg)
 | |
| 				g.Interval = model.Duration(-(rand.Int63n(1000) + 1))
 | |
| 				return &g
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if interval is not aligned with base interval",
 | |
| 			group: func() *apimodels.PostableRuleGroupConfig {
 | |
| 				g := validGroup(cfg)
 | |
| 				g.Interval = model.Duration(cfg.BaseInterval + time.Duration(rand.Intn(10)+1)*time.Second)
 | |
| 				return &g
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if two rules have same UID",
 | |
| 			group: func() *apimodels.PostableRuleGroupConfig {
 | |
| 				r1 := validRule()
 | |
| 				r2 := validRule()
 | |
| 				uid := util.GenerateShortUID()
 | |
| 				r1.GrafanaManagedAlert.UID = uid
 | |
| 				r2.GrafanaManagedAlert.UID = uid
 | |
| 				g := validGroup(cfg, r1, r2)
 | |
| 				return &g
 | |
| 			},
 | |
| 			assert: func(t *testing.T, apiModel *apimodels.PostableRuleGroupConfig, err error) {
 | |
| 				require.Contains(t, err.Error(), apiModel.Rules[0].GrafanaManagedAlert.UID)
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, testCase := range testCases {
 | |
| 		t.Run(testCase.name, func(t *testing.T) {
 | |
| 			g := testCase.group()
 | |
| 			_, err := validateRuleGroup(g, orgId, folder, func(condition models.Condition) error {
 | |
| 				return nil
 | |
| 			}, cfg)
 | |
| 			require.Error(t, err)
 | |
| 			if testCase.assert != nil {
 | |
| 				testCase.assert(t, g, err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestValidateRuleNode_NoUID(t *testing.T) {
 | |
| 	orgId := rand.Int63()
 | |
| 	folder := randFolder()
 | |
| 	name := util.GenerateShortUID()
 | |
| 	var cfg = config(t)
 | |
| 	interval := cfg.BaseInterval * time.Duration(rand.Int63n(10)+1)
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name   string
 | |
| 		rule   func() *apimodels.PostableExtendedRuleNode
 | |
| 		assert func(t *testing.T, model *apimodels.PostableExtendedRuleNode, rule *models.AlertRule)
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "coverts api model to AlertRule",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, int64(0), alert.ID)
 | |
| 				require.Equal(t, orgId, alert.OrgID)
 | |
| 				require.Equal(t, api.GrafanaManagedAlert.Title, alert.Title)
 | |
| 				require.Equal(t, api.GrafanaManagedAlert.Condition, alert.Condition)
 | |
| 				require.Equal(t, api.GrafanaManagedAlert.Data, alert.Data)
 | |
| 				require.Equal(t, time.Time{}, alert.Updated)
 | |
| 				require.Equal(t, int64(interval.Seconds()), alert.IntervalSeconds)
 | |
| 				require.Equal(t, int64(0), alert.Version)
 | |
| 				require.Equal(t, api.GrafanaManagedAlert.UID, alert.UID)
 | |
| 				require.Equal(t, folder.Uid, alert.NamespaceUID)
 | |
| 				require.Nil(t, alert.DashboardUID)
 | |
| 				require.Nil(t, alert.PanelID)
 | |
| 				require.Equal(t, name, alert.RuleGroup)
 | |
| 				require.Equal(t, models.NoDataState(api.GrafanaManagedAlert.NoDataState), alert.NoDataState)
 | |
| 				require.Equal(t, models.ExecutionErrorState(api.GrafanaManagedAlert.ExecErrState), alert.ExecErrState)
 | |
| 				require.Equal(t, time.Duration(api.ApiRuleNode.For), alert.For)
 | |
| 				require.Equal(t, api.ApiRuleNode.Annotations, alert.Annotations)
 | |
| 				require.Equal(t, api.ApiRuleNode.Labels, alert.Labels)
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "coverts api without ApiRuleNode",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.ApiRuleNode = nil
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, time.Duration(0), alert.For)
 | |
| 				require.Nil(t, alert.Annotations)
 | |
| 				require.Nil(t, alert.Labels)
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "defaults to NoData if NoDataState is empty",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.NoDataState = ""
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, models.NoData, alert.NoDataState)
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "defaults to Alerting if ExecErrState is empty",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.ExecErrState = ""
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, models.AlertingErrState, alert.ExecErrState)
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "extracts Dashboard UID and Panel Id from annotations",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.ApiRuleNode.Annotations = map[string]string{
 | |
| 					models.DashboardUIDAnnotation: util.GenerateShortUID(),
 | |
| 					models.PanelIDAnnotation:      strconv.Itoa(rand.Int()),
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, api.ApiRuleNode.Annotations[models.DashboardUIDAnnotation], *alert.DashboardUID)
 | |
| 				panelId, err := strconv.Atoi(api.ApiRuleNode.Annotations[models.PanelIDAnnotation])
 | |
| 				require.NoError(t, err)
 | |
| 				require.Equal(t, int64(panelId), *alert.PanelID)
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, testCase := range testCases {
 | |
| 		t.Run(testCase.name, func(t *testing.T) {
 | |
| 			r := testCase.rule()
 | |
| 			r.GrafanaManagedAlert.UID = ""
 | |
| 
 | |
| 			alert, err := validateRuleNode(r, name, interval, orgId, folder, func(condition models.Condition) error {
 | |
| 				return nil
 | |
| 			}, cfg)
 | |
| 			require.NoError(t, err)
 | |
| 			testCase.assert(t, r, alert)
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	t.Run("accepts empty group name", func(t *testing.T) {
 | |
| 		r := validRule()
 | |
| 		alert, err := validateRuleNode(&r, "", interval, orgId, folder, func(condition models.Condition) error {
 | |
| 			return nil
 | |
| 		}, cfg)
 | |
| 		require.NoError(t, err)
 | |
| 		require.Equal(t, "", alert.RuleGroup)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestValidateRuleNodeFailures_NoUID(t *testing.T) {
 | |
| 	orgId := rand.Int63()
 | |
| 	folder := randFolder()
 | |
| 	cfg := config(t)
 | |
| 	successValidation := func(condition models.Condition) error {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name                string
 | |
| 		interval            *time.Duration
 | |
| 		rule                func() *apimodels.PostableExtendedRuleNode
 | |
| 		conditionValidation func(condition models.Condition) error
 | |
| 		assert              func(t *testing.T, model *apimodels.PostableExtendedRuleNode, err error)
 | |
| 		allowedIfNoUId      bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "fail if GrafanaManagedAlert is not specified",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert = nil
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if title is empty",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.Title = ""
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if title is too long",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				for len(r.GrafanaManagedAlert.Title) < store.AlertRuleMaxTitleLength {
 | |
| 					r.GrafanaManagedAlert.Title += r.GrafanaManagedAlert.Title
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if NoDataState is not known",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.NoDataState = apimodels.NoDataState(util.GenerateShortUID())
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if ExecErrState is not known",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.ExecErrState = apimodels.ExecutionErrorState(util.GenerateShortUID())
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if there are not data (nil)",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.Data = nil
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if there are not data (empty)",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.Data = make([]models.AlertQuery, 0, 1)
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if validator function returns error",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				return &r
 | |
| 			},
 | |
| 			conditionValidation: func(condition models.Condition) error {
 | |
| 				return errors.New("BAD alert condition")
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if Dashboard UID is specified but not Panel ID",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.ApiRuleNode.Annotations = map[string]string{
 | |
| 					models.DashboardUIDAnnotation: util.GenerateShortUID(),
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if Dashboard UID is specified and Panel ID is NaN",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.ApiRuleNode.Annotations = map[string]string{
 | |
| 					models.DashboardUIDAnnotation: util.GenerateShortUID(),
 | |
| 					models.PanelIDAnnotation:      util.GenerateShortUID(),
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if PanelID is specified but not Dashboard UID ",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.ApiRuleNode.Annotations = map[string]string{
 | |
| 					models.PanelIDAnnotation: "0",
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, testCase := range testCases {
 | |
| 		t.Run(testCase.name, func(t *testing.T) {
 | |
| 			r := testCase.rule()
 | |
| 			if r.GrafanaManagedAlert != nil {
 | |
| 				r.GrafanaManagedAlert.UID = ""
 | |
| 			}
 | |
| 			f := successValidation
 | |
| 			if testCase.conditionValidation != nil {
 | |
| 				f = testCase.conditionValidation
 | |
| 			}
 | |
| 
 | |
| 			interval := cfg.BaseInterval
 | |
| 			if testCase.interval != nil {
 | |
| 				interval = *testCase.interval
 | |
| 			}
 | |
| 
 | |
| 			_, err := validateRuleNode(r, "", interval, orgId, folder, f, cfg)
 | |
| 			require.Error(t, err)
 | |
| 			if testCase.assert != nil {
 | |
| 				testCase.assert(t, r, err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestValidateRuleNode_UID(t *testing.T) {
 | |
| 	orgId := rand.Int63()
 | |
| 	folder := randFolder()
 | |
| 	name := util.GenerateShortUID()
 | |
| 	var cfg = config(t)
 | |
| 	interval := cfg.BaseInterval * time.Duration(rand.Int63n(10)+1)
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name   string
 | |
| 		rule   func() *apimodels.PostableExtendedRuleNode
 | |
| 		assert func(t *testing.T, model *apimodels.PostableExtendedRuleNode, rule *models.AlertRule)
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "use empty Title",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.Title = ""
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, "", alert.Title)
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "use empty NoData if NoDataState is empty",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.NoDataState = ""
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, models.NoDataState(""), alert.NoDataState)
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "use empty Alerting if ExecErrState is empty",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.ExecErrState = ""
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, models.ExecutionErrorState(""), alert.ExecErrState)
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "use empty Condition and Data if they are empty",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.Condition = ""
 | |
| 				r.GrafanaManagedAlert.Data = nil
 | |
| 				if rand.Int63()%2 == 0 {
 | |
| 					r.GrafanaManagedAlert.Data = make([]models.AlertQuery, 0)
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, "", alert.Condition)
 | |
| 				require.Len(t, alert.Data, 0)
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "extracts Dashboard UID and Panel Id from annotations",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.ApiRuleNode.Annotations = map[string]string{
 | |
| 					models.DashboardUIDAnnotation: util.GenerateShortUID(),
 | |
| 					models.PanelIDAnnotation:      strconv.Itoa(rand.Int()),
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 			assert: func(t *testing.T, api *apimodels.PostableExtendedRuleNode, alert *models.AlertRule) {
 | |
| 				require.Equal(t, api.ApiRuleNode.Annotations[models.DashboardUIDAnnotation], *alert.DashboardUID)
 | |
| 				panelId, err := strconv.Atoi(api.ApiRuleNode.Annotations[models.PanelIDAnnotation])
 | |
| 				require.NoError(t, err)
 | |
| 				require.Equal(t, int64(panelId), *alert.PanelID)
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, testCase := range testCases {
 | |
| 		t.Run(testCase.name, func(t *testing.T) {
 | |
| 			r := testCase.rule()
 | |
| 			alert, err := validateRuleNode(r, name, interval, orgId, folder, func(condition models.Condition) error {
 | |
| 				return nil
 | |
| 			}, cfg)
 | |
| 			require.NoError(t, err)
 | |
| 			testCase.assert(t, r, alert)
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	t.Run("accepts empty group name", func(t *testing.T) {
 | |
| 		r := validRule()
 | |
| 		alert, err := validateRuleNode(&r, "", interval, orgId, folder, func(condition models.Condition) error {
 | |
| 			return nil
 | |
| 		}, cfg)
 | |
| 		require.NoError(t, err)
 | |
| 		require.Equal(t, "", alert.RuleGroup)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestValidateRuleNodeFailures_UID(t *testing.T) {
 | |
| 	orgId := rand.Int63()
 | |
| 	folder := randFolder()
 | |
| 	cfg := config(t)
 | |
| 	successValidation := func(condition models.Condition) error {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name                string
 | |
| 		interval            *time.Duration
 | |
| 		rule                func() *apimodels.PostableExtendedRuleNode
 | |
| 		conditionValidation func(condition models.Condition) error
 | |
| 		assert              func(t *testing.T, model *apimodels.PostableExtendedRuleNode, err error)
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "fail if GrafanaManagedAlert is not specified",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert = nil
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if title is too long",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				for len(r.GrafanaManagedAlert.Title) < store.AlertRuleMaxTitleLength {
 | |
| 					r.GrafanaManagedAlert.Title += r.GrafanaManagedAlert.Title
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if there are not data (nil) but condition is set",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.Data = nil
 | |
| 				r.GrafanaManagedAlert.Condition = "A"
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if there are not data (empty) but condition is set",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.GrafanaManagedAlert.Data = make([]models.AlertQuery, 0, 1)
 | |
| 				r.GrafanaManagedAlert.Condition = "A"
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if validator function returns error",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				return &r
 | |
| 			},
 | |
| 			conditionValidation: func(condition models.Condition) error {
 | |
| 				return errors.New("BAD alert condition")
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if Dashboard UID is specified but not Panel ID",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.ApiRuleNode.Annotations = map[string]string{
 | |
| 					models.DashboardUIDAnnotation: util.GenerateShortUID(),
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if Dashboard UID is specified and Panel ID is NaN",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.ApiRuleNode.Annotations = map[string]string{
 | |
| 					models.DashboardUIDAnnotation: util.GenerateShortUID(),
 | |
| 					models.PanelIDAnnotation:      util.GenerateShortUID(),
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "fail if PanelID is specified but not Dashboard UID ",
 | |
| 			rule: func() *apimodels.PostableExtendedRuleNode {
 | |
| 				r := validRule()
 | |
| 				r.ApiRuleNode.Annotations = map[string]string{
 | |
| 					models.PanelIDAnnotation: "0",
 | |
| 				}
 | |
| 				return &r
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, testCase := range testCases {
 | |
| 		t.Run(testCase.name, func(t *testing.T) {
 | |
| 			r := testCase.rule()
 | |
| 			f := successValidation
 | |
| 			if testCase.conditionValidation != nil {
 | |
| 				f = testCase.conditionValidation
 | |
| 			}
 | |
| 
 | |
| 			interval := cfg.BaseInterval
 | |
| 			if testCase.interval != nil {
 | |
| 				interval = *testCase.interval
 | |
| 			}
 | |
| 
 | |
| 			_, err := validateRuleNode(r, "", interval, orgId, folder, f, cfg)
 | |
| 			require.Error(t, err)
 | |
| 			if testCase.assert != nil {
 | |
| 				testCase.assert(t, r, err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestValidateRuleNodeIntervalFailures(t *testing.T) {
 | |
| 	cfg := config(t)
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name     string
 | |
| 		interval time.Duration
 | |
| 	}{
 | |
| 		{
 | |
| 			name:     "fail if interval is negative",
 | |
| 			interval: -time.Duration(rand.Int63n(10)+1) * time.Second,
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "fail if interval is 0",
 | |
| 			interval: 0,
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "fail if interval is not multiple of base interval",
 | |
| 			interval: cfg.BaseInterval + time.Duration(rand.Int63n(int64(cfg.BaseInterval.Seconds())-2)+1)*time.Second,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, testCase := range testCases {
 | |
| 		t.Run(testCase.name, func(t *testing.T) {
 | |
| 			r := validRule()
 | |
| 			f := func(condition models.Condition) error {
 | |
| 				return nil
 | |
| 			}
 | |
| 
 | |
| 			_, err := validateRuleNode(&r, util.GenerateShortUID(), testCase.interval, rand.Int63(), randFolder(), f, cfg)
 | |
| 			require.Error(t, err)
 | |
| 		})
 | |
| 	}
 | |
| }
 |