2022-07-14 14:59:59 -05:00
package state
import (
"context"
2023-03-08 06:25:02 -06:00
"errors"
2022-07-14 14:59:59 -05:00
"fmt"
"net/url"
"testing"
2023-03-08 06:25:02 -06:00
"time"
2022-07-14 14:59:59 -05:00
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/models"
2023-03-08 06:25:02 -06:00
"github.com/grafana/grafana/pkg/services/ngalert/state/template"
2022-07-14 14:59:59 -05:00
"github.com/grafana/grafana/pkg/util"
)
2023-03-08 06:25:02 -06:00
func Test_expand ( t * testing . T ) {
ctx := context . Background ( )
logger := log . NewNopLogger ( )
// This test asserts that multierror returns a nil error if there are no errors.
// If the expand function forgets to use ErrorOrNil() then the error returned will
// be non-nil even if no errors have been added to the multierror.
t . Run ( "err is nil if there are no errors" , func ( t * testing . T ) {
result , err := expand ( ctx , logger , "test" , map [ string ] string { } , template . Data { } , nil , time . Now ( ) )
require . NoError ( t , err )
require . Len ( t , result , 0 )
} )
t . Run ( "original is expanded with template data" , func ( t * testing . T ) {
original := map [ string ] string { "Summary" : ` Instance {{ $labels .instance }} has been down for more than 5 minutes ` }
expected := map [ string ] string { "Summary" : "Instance host1 has been down for more than 5 minutes" }
data := template . Data { Labels : map [ string ] string { "instance" : "host1" } }
results , err := expand ( ctx , logger , "test" , original , data , nil , time . Now ( ) )
require . NoError ( t , err )
require . Equal ( t , expected , results )
} )
t . Run ( "original is returned with an error" , func ( t * testing . T ) {
original := map [ string ] string {
"Summary" : ` Instance {{ $labels . }} has been down for more than 5 minutes ` ,
}
data := template . Data { Labels : map [ string ] string { "instance" : "host1" } }
results , err := expand ( ctx , logger , "test" , original , data , nil , time . Now ( ) )
require . NotNil ( t , err )
require . Equal ( t , original , results )
// err should be an ExpandError that contains the template for the Summary and an error
var expandErr template . ExpandError
require . True ( t , errors . As ( err , & expandErr ) )
require . EqualError ( t , expandErr , "failed to expand template '{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}Instance {{ $labels. }} has been down for more than 5 minutes': error parsing template __alert_test: template: __alert_test:1: unexpected <.> in operand" )
} )
t . Run ( "originals are returned with two errors" , func ( t * testing . T ) {
original := map [ string ] string {
"Summary" : ` Instance {{ $labels . }} has been down for more than 5 minutes ` ,
"Description" : "The instance has been down for {{ $value minutes, please check the instance is online" ,
}
data := template . Data { Labels : map [ string ] string { "instance" : "host1" } }
results , err := expand ( ctx , logger , "test" , original , data , nil , time . Now ( ) )
require . NotNil ( t , err )
require . Equal ( t , original , results )
2023-06-19 04:29:45 -05:00
//nolint:errorlint
multierr , is := err . ( interface { Unwrap ( ) [ ] error } )
require . True ( t , is )
unwrappedErrors := multierr . Unwrap ( )
require . Equal ( t , len ( unwrappedErrors ) , 2 )
2023-03-08 06:25:02 -06:00
2023-03-09 05:08:05 -06:00
errsStr := [ ] string {
2023-06-19 04:29:45 -05:00
unwrappedErrors [ 0 ] . Error ( ) ,
unwrappedErrors [ 1 ] . Error ( ) ,
2023-03-09 05:08:05 -06:00
}
firstErrStr := "failed to expand template '{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}Instance {{ $labels. }} has been down for more than 5 minutes': error parsing template __alert_test: template: __alert_test:1: unexpected <.> in operand"
secondErrStr := "failed to expand template '{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}The instance has been down for {{ $value minutes, please check the instance is online': error parsing template __alert_test: template: __alert_test:1: function \"minutes\" not defined"
2023-03-08 06:25:02 -06:00
2023-03-09 05:08:05 -06:00
require . Contains ( t , errsStr , firstErrStr )
require . Contains ( t , errsStr , secondErrStr )
2023-06-19 04:29:45 -05:00
for _ , err := range unwrappedErrors {
2023-03-09 05:08:05 -06:00
var expandErr template . ExpandError
require . True ( t , errors . As ( err , & expandErr ) )
}
2023-03-08 06:25:02 -06:00
} )
t . Run ( "expanded and original is returned when there is one error" , func ( t * testing . T ) {
original := map [ string ] string {
"Summary" : ` Instance {{ $labels .instance }} has been down for more than 5 minutes ` ,
"Description" : "The instance has been down for {{ $value minutes, please check the instance is online" ,
}
expected := map [ string ] string {
"Summary" : "Instance host1 has been down for more than 5 minutes" ,
"Description" : "The instance has been down for {{ $value minutes, please check the instance is online" ,
}
data := template . Data { Labels : map [ string ] string { "instance" : "host1" } }
results , err := expand ( ctx , logger , "test" , original , data , nil , time . Now ( ) )
require . NotNil ( t , err )
require . Equal ( t , expected , results )
2023-06-19 04:29:45 -05:00
//nolint:errorlint
multierr , is := err . ( interface { Unwrap ( ) [ ] error } )
require . True ( t , is )
unwrappedErrors := multierr . Unwrap ( )
require . Equal ( t , len ( unwrappedErrors ) , 1 )
2023-03-08 06:25:02 -06:00
// assert each error matches the expected error
var expandErr template . ExpandError
require . True ( t , errors . As ( err , & expandErr ) )
require . EqualError ( t , expandErr , "failed to expand template '{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}The instance has been down for {{ $value minutes, please check the instance is online': error parsing template __alert_test: template: __alert_test:1: function \"minutes\" not defined" )
} )
}
2022-07-14 14:59:59 -05:00
func Test_getOrCreate ( t * testing . T ) {
2022-10-06 14:30:12 -05:00
url := & url . URL {
2022-07-14 14:59:59 -05:00
Scheme : "http" ,
Host : "localhost:3000" ,
Path : "/test" ,
2022-10-06 14:30:12 -05:00
}
l := log . New ( "test" )
c := newCache ( )
2022-07-14 14:59:59 -05:00
generateRule := models . AlertRuleGen ( models . WithNotEmptyLabels ( 5 , "rule-" ) )
t . Run ( "should combine all labels" , func ( t * testing . T ) {
rule := generateRule ( )
extraLabels := models . GenerateAlertLabels ( 5 , "extra-" )
result := eval . Result {
Instance : models . GenerateAlertLabels ( 5 , "result-" ) ,
}
2022-10-06 14:30:12 -05:00
state := c . getOrCreate ( context . Background ( ) , l , rule , result , extraLabels , url )
2022-07-14 14:59:59 -05:00
for key , expected := range extraLabels {
require . Equal ( t , expected , state . Labels [ key ] )
}
assert . Len ( t , state . Labels , len ( extraLabels ) + len ( rule . Labels ) + len ( result . Instance ) )
for key , expected := range extraLabels {
assert . Equal ( t , expected , state . Labels [ key ] )
}
for key , expected := range rule . Labels {
assert . Equal ( t , expected , state . Labels [ key ] )
}
for key , expected := range result . Instance {
assert . Equal ( t , expected , state . Labels [ key ] )
}
} )
t . Run ( "extra labels should take precedence over rule and result labels" , func ( t * testing . T ) {
rule := generateRule ( )
extraLabels := models . GenerateAlertLabels ( 2 , "extra-" )
result := eval . Result {
Instance : models . GenerateAlertLabels ( 5 , "result-" ) ,
}
for key := range extraLabels {
rule . Labels [ key ] = "rule-" + util . GenerateShortUID ( )
result . Instance [ key ] = "result-" + util . GenerateShortUID ( )
}
2022-10-06 14:30:12 -05:00
state := c . getOrCreate ( context . Background ( ) , l , rule , result , extraLabels , url )
2022-07-14 14:59:59 -05:00
for key , expected := range extraLabels {
require . Equal ( t , expected , state . Labels [ key ] )
}
} )
t . Run ( "rule labels should take precedence over result labels" , func ( t * testing . T ) {
rule := generateRule ( )
extraLabels := models . GenerateAlertLabels ( 2 , "extra-" )
result := eval . Result {
Instance : models . GenerateAlertLabels ( 5 , "result-" ) ,
}
for key := range rule . Labels {
result . Instance [ key ] = "result-" + util . GenerateShortUID ( )
}
2022-10-06 14:30:12 -05:00
state := c . getOrCreate ( context . Background ( ) , l , rule , result , extraLabels , url )
2022-07-14 14:59:59 -05:00
for key , expected := range rule . Labels {
require . Equal ( t , expected , state . Labels [ key ] )
}
} )
t . Run ( "rule labels should be able to be expanded with result and extra labels" , func ( t * testing . T ) {
result := eval . Result {
Instance : models . GenerateAlertLabels ( 5 , "result-" ) ,
}
rule := generateRule ( )
extraLabels := models . GenerateAlertLabels ( 2 , "extra-" )
labelTemplates := make ( data . Labels )
for key := range extraLabels {
labelTemplates [ "rule-" + key ] = fmt . Sprintf ( "{{ with (index .Labels \"%s\") }}{{.}}{{end}}" , key )
}
for key := range result . Instance {
labelTemplates [ "rule-" + key ] = fmt . Sprintf ( "{{ with (index .Labels \"%s\") }}{{.}}{{end}}" , key )
}
rule . Labels = labelTemplates
2022-10-06 14:30:12 -05:00
state := c . getOrCreate ( context . Background ( ) , l , rule , result , extraLabels , url )
2022-07-14 14:59:59 -05:00
for key , expected := range extraLabels {
assert . Equal ( t , expected , state . Labels [ "rule-" + key ] )
}
for key , expected := range result . Instance {
assert . Equal ( t , expected , state . Labels [ "rule-" + key ] )
}
} )
2023-03-06 11:08:00 -06:00
2022-07-14 14:59:59 -05:00
t . Run ( "rule annotations should be able to be expanded with result and extra labels" , func ( t * testing . T ) {
result := eval . Result {
Instance : models . GenerateAlertLabels ( 5 , "result-" ) ,
}
rule := generateRule ( )
extraLabels := models . GenerateAlertLabels ( 2 , "extra-" )
annotationTemplates := make ( data . Labels )
for key := range extraLabels {
annotationTemplates [ "rule-" + key ] = fmt . Sprintf ( "{{ with (index .Labels \"%s\") }}{{.}}{{end}}" , key )
}
for key := range result . Instance {
annotationTemplates [ "rule-" + key ] = fmt . Sprintf ( "{{ with (index .Labels \"%s\") }}{{.}}{{end}}" , key )
}
rule . Annotations = annotationTemplates
2022-10-06 14:30:12 -05:00
state := c . getOrCreate ( context . Background ( ) , l , rule , result , extraLabels , url )
2022-07-14 14:59:59 -05:00
for key , expected := range extraLabels {
assert . Equal ( t , expected , state . Annotations [ "rule-" + key ] )
}
for key , expected := range result . Instance {
assert . Equal ( t , expected , state . Annotations [ "rule-" + key ] )
}
} )
2023-03-06 11:08:00 -06:00
t . Run ( "expected Reduce and Math expression values" , func ( t * testing . T ) {
result := eval . Result {
Instance : models . GenerateAlertLabels ( 5 , "result-" ) ,
Values : map [ string ] eval . NumberValueCapture {
2023-03-13 05:06:04 -05:00
"A" : { Var : "A" , Value : util . Pointer ( 1.0 ) } ,
"B" : { Var : "B" , Value : util . Pointer ( 2.0 ) } ,
2023-03-06 11:08:00 -06:00
} ,
}
rule := generateRule ( )
state := c . getOrCreate ( context . Background ( ) , l , rule , result , nil , url )
assert . Equal ( t , map [ string ] float64 { "A" : 1 , "B" : 2 } , state . Values )
} )
t . Run ( "expected Classic Condition values" , func ( t * testing . T ) {
result := eval . Result {
Instance : models . GenerateAlertLabels ( 5 , "result-" ) ,
Values : map [ string ] eval . NumberValueCapture {
2023-03-13 05:06:04 -05:00
"B0" : { Var : "B" , Value : util . Pointer ( 1.0 ) } ,
"B1" : { Var : "B" , Value : util . Pointer ( 2.0 ) } ,
2023-03-06 11:08:00 -06:00
} ,
}
rule := generateRule ( )
state := c . getOrCreate ( context . Background ( ) , l , rule , result , nil , url )
assert . Equal ( t , map [ string ] float64 { "B0" : 1 , "B1" : 2 } , state . Values )
} )
2022-07-14 14:59:59 -05:00
}
func Test_mergeLabels ( t * testing . T ) {
t . Run ( "merges two maps" , func ( t * testing . T ) {
a := models . GenerateAlertLabels ( 5 , "set1-" )
b := models . GenerateAlertLabels ( 5 , "set2-" )
result := mergeLabels ( a , b )
require . Len ( t , result , len ( a ) + len ( b ) )
for key , val := range a {
require . Equal ( t , val , result [ key ] )
}
for key , val := range b {
require . Equal ( t , val , result [ key ] )
}
} )
t . Run ( "first set take precedence if conflict" , func ( t * testing . T ) {
a := models . GenerateAlertLabels ( 5 , "set1-" )
b := models . GenerateAlertLabels ( 5 , "set2-" )
c := b . Copy ( )
for key , val := range a {
c [ key ] = "set2-" + val
}
result := mergeLabels ( a , c )
require . Len ( t , result , len ( a ) + len ( b ) )
for key , val := range a {
require . Equal ( t , val , result [ key ] )
}
for key , val := range b {
require . Equal ( t , val , result [ key ] )
}
} )
}