@ -1,12 +1,12 @@
package api
package api
import (
import (
"errors"
"fmt"
"fmt"
"strconv"
"strconv"
"testing"
"testing"
"time"
"time"
"github.com/google/uuid"
"github.com/prometheus/common/model"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
"golang.org/x/exp/rand"
"golang.org/x/exp/rand"
@ -98,6 +98,90 @@ func randFolder() *folder.Folder {
}
}
}
}
func TestValidateCondition ( t * testing . T ) {
testcases := [ ] struct {
name string
condition string
data [ ] apimodels . AlertQuery
errorMsg string
} {
{
name : "error when condition is empty" ,
condition : "" ,
data : [ ] apimodels . AlertQuery { } ,
errorMsg : "condition cannot be empty" ,
} ,
{
name : "error when data is empty" ,
condition : "A" ,
data : [ ] apimodels . AlertQuery { } ,
errorMsg : "no query/expressions specified" ,
} ,
{
name : "error when condition does not exist" ,
condition : "A" ,
data : [ ] apimodels . AlertQuery {
{
RefID : "B" ,
} ,
{
RefID : "C" ,
} ,
} ,
errorMsg : "condition A does not exist, must be one of [B,C]" ,
} ,
{
name : "error when duplicated refId" ,
condition : "A" ,
data : [ ] apimodels . AlertQuery {
{
RefID : "A" ,
} ,
{
RefID : "A" ,
} ,
} ,
errorMsg : "refID 'A' is already used by query/expression at index 0" ,
} ,
{
name : "error when refId is empty" ,
condition : "A" ,
data : [ ] apimodels . AlertQuery {
{
RefID : "" ,
} ,
{
RefID : "A" ,
} ,
} ,
errorMsg : "refID is not specified for data query/expression at index 0" ,
} ,
{
name : "valid case" ,
condition : "B" ,
data : [ ] apimodels . AlertQuery {
{
RefID : "A" ,
} ,
{
RefID : "B" ,
} ,
} ,
} ,
}
for _ , tc := range testcases {
t . Run ( tc . name , func ( t * testing . T ) {
err := validateCondition ( tc . condition , tc . data )
if tc . errorMsg == "" {
require . NoError ( t , err )
} else {
require . ErrorContains ( t , err , tc . errorMsg )
}
} )
}
}
func TestValidateRuleGroup ( t * testing . T ) {
func TestValidateRuleGroup ( t * testing . T ) {
orgId := rand . Int63 ( )
orgId := rand . Int63 ( )
folder := randFolder ( )
folder := randFolder ( )
@ -110,22 +194,15 @@ func TestValidateRuleGroup(t *testing.T) {
t . Run ( "should validate struct and rules" , func ( t * testing . T ) {
t . Run ( "should validate struct and rules" , func ( t * testing . T ) {
g := validGroup ( cfg , rules ... )
g := validGroup ( cfg , rules ... )
conditionValidations := 0
alerts , err := validateRuleGroup ( & g , orgId , folder , cfg )
alerts , err := validateRuleGroup ( & g , orgId , folder , func ( condition models . Condition ) error {
conditionValidations ++
return nil
} , cfg )
require . NoError ( t , err )
require . NoError ( t , err )
require . Len ( t , alerts , len ( rules ) )
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 ) {
t . Run ( "should default to default interval from config if group interval is 0" , func ( t * testing . T ) {
g := validGroup ( cfg , rules ... )
g := validGroup ( cfg , rules ... )
g . Interval = 0
g . Interval = 0
alerts , err := validateRuleGroup ( & g , orgId , folder , func ( condition models . Condition ) error {
alerts , err := validateRuleGroup ( & g , orgId , folder , cfg )
return nil
} , cfg )
require . NoError ( t , err )
require . NoError ( t , err )
for _ , alert := range alerts {
for _ , alert := range alerts {
require . Equal ( t , int64 ( cfg . DefaultRuleEvaluationInterval . Seconds ( ) ) , alert . IntervalSeconds )
require . Equal ( t , int64 ( cfg . DefaultRuleEvaluationInterval . Seconds ( ) ) , alert . IntervalSeconds )
@ -140,9 +217,7 @@ func TestValidateRuleGroup(t *testing.T) {
isPaused = ! ( isPaused )
isPaused = ! ( isPaused )
}
}
g := validGroup ( cfg , rules ... )
g := validGroup ( cfg , rules ... )
alerts , err := validateRuleGroup ( & g , orgId , folder , func ( condition models . Condition ) error {
alerts , err := validateRuleGroup ( & g , orgId , folder , cfg )
return nil
} , cfg )
require . NoError ( t , err )
require . NoError ( t , err )
for _ , alert := range alerts {
for _ , alert := range alerts {
require . True ( t , alert . HasPause )
require . True ( t , alert . HasPause )
@ -214,9 +289,7 @@ func TestValidateRuleGroupFailures(t *testing.T) {
for _ , testCase := range testCases {
for _ , testCase := range testCases {
t . Run ( testCase . name , func ( t * testing . T ) {
t . Run ( testCase . name , func ( t * testing . T ) {
g := testCase . group ( )
g := testCase . group ( )
_ , err := validateRuleGroup ( g , orgId , folder , func ( condition models . Condition ) error {
_ , err := validateRuleGroup ( g , orgId , folder , cfg )
return nil
} , cfg )
require . Error ( t , err )
require . Error ( t , err )
if testCase . assert != nil {
if testCase . assert != nil {
testCase . assert ( t , g , err )
testCase . assert ( t , g , err )
@ -323,9 +396,7 @@ func TestValidateRuleNode_NoUID(t *testing.T) {
r := testCase . rule ( )
r := testCase . rule ( )
r . GrafanaManagedAlert . UID = ""
r . GrafanaManagedAlert . UID = ""
alert , err := validateRuleNode ( r , name , interval , orgId , folder , func ( condition models . Condition ) error {
alert , err := validateRuleNode ( r , name , interval , orgId , folder , cfg )
return nil
} , cfg )
require . NoError ( t , err )
require . NoError ( t , err )
testCase . assert ( t , r , alert )
testCase . assert ( t , r , alert )
} )
} )
@ -333,9 +404,7 @@ func TestValidateRuleNode_NoUID(t *testing.T) {
t . Run ( "accepts empty group name" , func ( t * testing . T ) {
t . Run ( "accepts empty group name" , func ( t * testing . T ) {
r := validRule ( )
r := validRule ( )
alert , err := validateRuleNode ( & r , "" , interval , orgId , folder , func ( condition models . Condition ) error {
alert , err := validateRuleNode ( & r , "" , interval , orgId , folder , cfg )
return nil
} , cfg )
require . NoError ( t , err )
require . NoError ( t , err )
require . Equal ( t , "" , alert . RuleGroup )
require . Equal ( t , "" , alert . RuleGroup )
} )
} )
@ -345,17 +414,13 @@ func TestValidateRuleNodeFailures_NoUID(t *testing.T) {
orgId := rand . Int63 ( )
orgId := rand . Int63 ( )
folder := randFolder ( )
folder := randFolder ( )
cfg := config ( t )
cfg := config ( t )
successValidation := func ( condition models . Condition ) error {
return nil
}
testCases := [ ] struct {
testCases := [ ] struct {
name string
name string
interval * time . Duration
interval * time . Duration
rule func ( ) * apimodels . PostableExtendedRuleNode
rule func ( ) * apimodels . PostableExtendedRuleNode
conditionValidation func ( condition models . Condition ) error
assert func ( t * testing . T , model * apimodels . PostableExtendedRuleNode , err error )
assert func ( t * testing . T , model * apimodels . PostableExtendedRuleNode , err error )
allowedIfNoUId bool
allowedIfNoUId bool
} {
} {
{
{
name : "fail if GrafanaManagedAlert is not specified" ,
name : "fail if GrafanaManagedAlert is not specified" ,
@ -415,16 +480,6 @@ func TestValidateRuleNodeFailures_NoUID(t *testing.T) {
return & r
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" ,
name : "fail if Dashboard UID is specified but not Panel ID" ,
rule : func ( ) * apimodels . PostableExtendedRuleNode {
rule : func ( ) * apimodels . PostableExtendedRuleNode {
@ -456,6 +511,38 @@ func TestValidateRuleNodeFailures_NoUID(t *testing.T) {
return & r
return & r
} ,
} ,
} ,
} ,
{
name : "fail if Condition is empty" ,
rule : func ( ) * apimodels . PostableExtendedRuleNode {
r := validRule ( )
r . GrafanaManagedAlert . Condition = ""
return & r
} ,
} ,
{
name : "fail if Data is empty" ,
rule : func ( ) * apimodels . PostableExtendedRuleNode {
r := validRule ( )
r . GrafanaManagedAlert . Data = nil
return & r
} ,
} ,
{
name : "fail if Condition does not exist" ,
rule : func ( ) * apimodels . PostableExtendedRuleNode {
r := validRule ( )
r . GrafanaManagedAlert . Condition = uuid . NewString ( )
return & r
} ,
} ,
{
name : "fail if Data has duplicate ref ID" ,
rule : func ( ) * apimodels . PostableExtendedRuleNode {
r := validRule ( )
r . GrafanaManagedAlert . Data = append ( r . GrafanaManagedAlert . Data , r . GrafanaManagedAlert . Data ... )
return & r
} ,
} ,
}
}
for _ , testCase := range testCases {
for _ , testCase := range testCases {
@ -464,17 +551,13 @@ func TestValidateRuleNodeFailures_NoUID(t *testing.T) {
if r . GrafanaManagedAlert != nil {
if r . GrafanaManagedAlert != nil {
r . GrafanaManagedAlert . UID = ""
r . GrafanaManagedAlert . UID = ""
}
}
f := successValidation
if testCase . conditionValidation != nil {
f = testCase . conditionValidation
}
interval := cfg . BaseInterval
interval := cfg . BaseInterval
if testCase . interval != nil {
if testCase . interval != nil {
interval = * testCase . interval
interval = * testCase . interval
}
}
_ , err := validateRuleNode ( r , "" , interval , orgId , folder , f , cfg )
_ , err := validateRuleNode ( r , "" , interval , orgId , folder , cfg )
require . Error ( t , err )
require . Error ( t , err )
if testCase . assert != nil {
if testCase . assert != nil {
testCase . assert ( t , r , err )
testCase . assert ( t , r , err )
@ -566,9 +649,7 @@ func TestValidateRuleNode_UID(t *testing.T) {
for _ , testCase := range testCases {
for _ , testCase := range testCases {
t . Run ( testCase . name , func ( t * testing . T ) {
t . Run ( testCase . name , func ( t * testing . T ) {
r := testCase . rule ( )
r := testCase . rule ( )
alert , err := validateRuleNode ( r , name , interval , orgId , folder , func ( condition models . Condition ) error {
alert , err := validateRuleNode ( r , name , interval , orgId , folder , cfg )
return nil
} , cfg )
require . NoError ( t , err )
require . NoError ( t , err )
testCase . assert ( t , r , alert )
testCase . assert ( t , r , alert )
} )
} )
@ -576,9 +657,7 @@ func TestValidateRuleNode_UID(t *testing.T) {
t . Run ( "accepts empty group name" , func ( t * testing . T ) {
t . Run ( "accepts empty group name" , func ( t * testing . T ) {
r := validRule ( )
r := validRule ( )
alert , err := validateRuleNode ( & r , "" , interval , orgId , folder , func ( condition models . Condition ) error {
alert , err := validateRuleNode ( & r , "" , interval , orgId , folder , cfg )
return nil
} , cfg )
require . NoError ( t , err )
require . NoError ( t , err )
require . Equal ( t , "" , alert . RuleGroup )
require . Equal ( t , "" , alert . RuleGroup )
} )
} )
@ -588,16 +667,12 @@ func TestValidateRuleNodeFailures_UID(t *testing.T) {
orgId := rand . Int63 ( )
orgId := rand . Int63 ( )
folder := randFolder ( )
folder := randFolder ( )
cfg := config ( t )
cfg := config ( t )
successValidation := func ( condition models . Condition ) error {
return nil
}
testCases := [ ] struct {
testCases := [ ] struct {
name string
name string
interval * time . Duration
interval * time . Duration
rule func ( ) * apimodels . PostableExtendedRuleNode
rule func ( ) * apimodels . PostableExtendedRuleNode
conditionValidation func ( condition models . Condition ) error
assert func ( t * testing . T , model * apimodels . PostableExtendedRuleNode , err error )
assert func ( t * testing . T , model * apimodels . PostableExtendedRuleNode , err error )
} {
} {
{
{
name : "fail if GrafanaManagedAlert is not specified" ,
name : "fail if GrafanaManagedAlert is not specified" ,
@ -635,16 +710,6 @@ func TestValidateRuleNodeFailures_UID(t *testing.T) {
return & r
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" ,
name : "fail if Dashboard UID is specified but not Panel ID" ,
rule : func ( ) * apimodels . PostableExtendedRuleNode {
rule : func ( ) * apimodels . PostableExtendedRuleNode {
@ -681,17 +746,13 @@ func TestValidateRuleNodeFailures_UID(t *testing.T) {
for _ , testCase := range testCases {
for _ , testCase := range testCases {
t . Run ( testCase . name , func ( t * testing . T ) {
t . Run ( testCase . name , func ( t * testing . T ) {
r := testCase . rule ( )
r := testCase . rule ( )
f := successValidation
if testCase . conditionValidation != nil {
f = testCase . conditionValidation
}
interval := cfg . BaseInterval
interval := cfg . BaseInterval
if testCase . interval != nil {
if testCase . interval != nil {
interval = * testCase . interval
interval = * testCase . interval
}
}
_ , err := validateRuleNode ( r , "" , interval , orgId , folder , f , cfg )
_ , err := validateRuleNode ( r , "" , interval , orgId , folder , cfg )
require . Error ( t , err )
require . Error ( t , err )
if testCase . assert != nil {
if testCase . assert != nil {
testCase . assert ( t , r , err )
testCase . assert ( t , r , err )
@ -724,11 +785,7 @@ func TestValidateRuleNodeIntervalFailures(t *testing.T) {
for _ , testCase := range testCases {
for _ , testCase := range testCases {
t . Run ( testCase . name , func ( t * testing . T ) {
t . Run ( testCase . name , func ( t * testing . T ) {
r := validRule ( )
r := validRule ( )
f := func ( condition models . Condition ) error {
_ , err := validateRuleNode ( & r , util . GenerateShortUID ( ) , testCase . interval , rand . Int63 ( ) , randFolder ( ) , cfg )
return nil
}
_ , err := validateRuleNode ( & r , util . GenerateShortUID ( ) , testCase . interval , rand . Int63 ( ) , randFolder ( ) , f , cfg )
require . Error ( t , err )
require . Error ( t , err )
} )
} )
}
}