@ -3,14 +3,17 @@ package state
import (
"context"
"errors"
"fmt"
"math"
"math/rand"
"net/url"
"testing"
"time"
"github.com/benbjohnson/clock"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -801,3 +804,331 @@ func TestGetRuleExtraLabels(t *testing.T) {
} )
}
}
func TestNewState ( t * testing . T ) {
url := & url . URL {
Scheme : "http" ,
Host : "localhost:3000" ,
Path : "/test" ,
}
l := log . New ( "test" )
gen := ngmodels . RuleGen
generateRule := gen . With ( gen . WithNotEmptyLabels ( 5 , "rule-" ) ) . GenerateRef
t . Run ( "should combine all labels" , func ( t * testing . T ) {
rule := generateRule ( )
extraLabels := ngmodels . GenerateAlertLabels ( 5 , "extra-" )
result := eval . Result {
Instance : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
}
state := newState ( context . Background ( ) , l , rule , result , extraLabels , url )
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 := ngmodels . GenerateAlertLabels ( 2 , "extra-" )
result := eval . Result {
Instance : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
}
for key := range extraLabels {
rule . Labels [ key ] = "rule-" + util . GenerateShortUID ( )
result . Instance [ key ] = "result-" + util . GenerateShortUID ( )
}
state := newState ( context . Background ( ) , l , rule , result , extraLabels , url )
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 := ngmodels . GenerateAlertLabels ( 2 , "extra-" )
result := eval . Result {
Instance : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
}
for key := range rule . Labels {
result . Instance [ key ] = "result-" + util . GenerateShortUID ( )
}
state := newState ( context . Background ( ) , l , rule , result , extraLabels , url )
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 : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
}
rule := generateRule ( )
extraLabels := ngmodels . 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
state := newState ( context . Background ( ) , l , rule , result , extraLabels , url )
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 ] )
}
} )
t . Run ( "rule annotations should be able to be expanded with result and extra labels" , func ( t * testing . T ) {
result := eval . Result {
Instance : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
}
rule := generateRule ( )
extraLabels := ngmodels . 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
state := newState ( context . Background ( ) , l , rule , result , extraLabels , url )
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 ] )
}
} )
t . Run ( "when result labels collide with system labels from LabelsUserCannotSpecify" , func ( t * testing . T ) {
result := eval . Result {
Instance : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
}
m := ngmodels . LabelsUserCannotSpecify
t . Cleanup ( func ( ) {
ngmodels . LabelsUserCannotSpecify = m
} )
ngmodels . LabelsUserCannotSpecify = map [ string ] struct { } {
"__label1__" : { } ,
"label2__" : { } ,
"__label3" : { } ,
"label4" : { } ,
}
result . Instance [ "__label1__" ] = uuid . NewString ( )
result . Instance [ "label2__" ] = uuid . NewString ( )
result . Instance [ "__label3" ] = uuid . NewString ( )
result . Instance [ "label4" ] = uuid . NewString ( )
rule := generateRule ( )
state := newState ( context . Background ( ) , l , rule , result , nil , url )
for key := range ngmodels . LabelsUserCannotSpecify {
assert . NotContains ( t , state . Labels , key )
}
assert . Contains ( t , state . Labels , "label1" )
assert . Equal ( t , state . Labels [ "label1" ] , result . Instance [ "__label1__" ] )
assert . Contains ( t , state . Labels , "label2" )
assert . Equal ( t , state . Labels [ "label2" ] , result . Instance [ "label2__" ] )
assert . Contains ( t , state . Labels , "label3" )
assert . Equal ( t , state . Labels [ "label3" ] , result . Instance [ "__label3" ] )
assert . Contains ( t , state . Labels , "label4_user" )
assert . Equal ( t , state . Labels [ "label4_user" ] , result . Instance [ "label4" ] )
t . Run ( "should drop label if renamed collides with existing" , func ( t * testing . T ) {
result . Instance [ "label1" ] = uuid . NewString ( )
result . Instance [ "label1_user" ] = uuid . NewString ( )
result . Instance [ "label4_user" ] = uuid . NewString ( )
state = newState ( context . Background ( ) , l , rule , result , nil , url )
assert . NotContains ( t , state . Labels , "__label1__" )
assert . Contains ( t , state . Labels , "label1" )
assert . Equal ( t , state . Labels [ "label1" ] , result . Instance [ "label1" ] )
assert . Equal ( t , state . Labels [ "label1_user" ] , result . Instance [ "label1_user" ] )
assert . NotContains ( t , state . Labels , "label4" )
assert . Equal ( t , state . Labels [ "label4_user" ] , result . Instance [ "label4_user" ] )
} )
} )
t . Run ( "creates a state with preset fields if there is no current state" , func ( t * testing . T ) {
rule := generateRule ( )
extraLabels := ngmodels . GenerateAlertLabels ( 2 , "extra-" )
result := eval . Result {
Instance : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
}
expectedLbl , expectedAnn := expandAnnotationsAndLabels ( context . Background ( ) , l , rule , result , extraLabels , url )
state := newState ( context . Background ( ) , l , rule , result , extraLabels , url )
assert . Equal ( t , rule . OrgID , state . OrgID )
assert . Equal ( t , rule . UID , state . AlertRuleUID )
assert . Equal ( t , state . Labels . Fingerprint ( ) , state . CacheID )
assert . Equal ( t , result . State , state . State )
assert . Equal ( t , "" , state . StateReason )
assert . Equal ( t , result . Instance . Fingerprint ( ) , state . ResultFingerprint )
assert . Nil ( t , state . LatestResult )
assert . Nil ( t , state . Error )
assert . Nil ( t , state . Image )
assert . EqualValues ( t , expectedAnn , state . Annotations )
assert . EqualValues ( t , expectedLbl , state . Labels )
assert . Nil ( t , state . Values )
assert . Equal ( t , result . EvaluatedAt , state . StartsAt )
assert . Equal ( t , result . EvaluatedAt , state . EndsAt )
assert . Nil ( t , state . ResolvedAt )
assert . Nil ( t , state . LastSentAt )
assert . Equal ( t , "" , state . LastEvaluationString )
assert . Equal ( t , result . EvaluatedAt , state . LastEvaluationTime )
assert . Equal ( t , result . EvaluationDuration , state . EvaluationDuration )
} )
}
func TestPatch ( t * testing . T ) {
key := ngmodels . GenerateRuleKey ( 1 )
t . Run ( "it populates some fields from the current state if it exists" , func ( t * testing . T ) {
result := eval . Result {
Instance : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
}
state := randomSate ( key )
orig := state . Copy ( )
current := randomSate ( key )
patch ( & state , & current , result )
// Fields that should not change
assert . Equal ( t , orig . OrgID , state . OrgID )
assert . Equal ( t , orig . AlertRuleUID , state . AlertRuleUID )
assert . Equal ( t , orig . CacheID , state . CacheID )
assert . Equal ( t , orig . ResultFingerprint , state . ResultFingerprint )
assert . EqualValues ( t , orig . Annotations , state . Annotations )
assert . EqualValues ( t , orig . Labels , state . Labels )
assert . Equal ( t , orig . LastEvaluationTime , state . LastEvaluationTime )
assert . Equal ( t , orig . EvaluationDuration , state . EvaluationDuration )
assert . Equal ( t , current . State , state . State )
assert . Equal ( t , current . StateReason , state . StateReason )
assert . Equal ( t , current . Image , state . Image )
assert . Equal ( t , current . LatestResult , state . LatestResult )
assert . Equal ( t , current . Error , state . Error )
assert . Equal ( t , current . Values , state . Values )
assert . Equal ( t , current . StartsAt , state . StartsAt )
assert . Equal ( t , current . EndsAt , state . EndsAt )
assert . Equal ( t , current . ResolvedAt , state . ResolvedAt )
assert . Equal ( t , current . LastSentAt , state . LastSentAt )
assert . Equal ( t , current . LastEvaluationString , state . LastEvaluationString )
} )
t . Run ( "copies system-owned annotations from current state" , func ( t * testing . T ) {
state := randomSate ( key )
orig := state . Copy ( )
expectedAnnotations := data . Labels ( state . Annotations ) . Copy ( )
current := randomSate ( key )
for key := range ngmodels . InternalAnnotationNameSet {
val := util . GenerateShortUID ( )
current . Annotations [ key ] = val
expectedAnnotations [ key ] = val
}
result := eval . Result {
Instance : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
}
patch ( & state , & current , result )
assert . EqualValues ( t , expectedAnnotations , state . Annotations )
assert . Equal ( t , current . State , state . State )
assert . Equal ( t , current . StateReason , state . StateReason )
assert . Equal ( t , current . Image , state . Image )
assert . Equal ( t , current . LatestResult , state . LatestResult )
assert . Equal ( t , current . Error , state . Error )
assert . Equal ( t , current . Values , state . Values )
assert . Equal ( t , current . StartsAt , state . StartsAt )
assert . Equal ( t , current . EndsAt , state . EndsAt )
assert . Equal ( t , current . ResolvedAt , state . ResolvedAt )
assert . Equal ( t , current . LastSentAt , state . LastSentAt )
assert . Equal ( t , current . LastEvaluationString , state . LastEvaluationString )
// Fields that should not change
assert . Equal ( t , orig . OrgID , state . OrgID )
assert . Equal ( t , orig . AlertRuleUID , state . AlertRuleUID )
assert . Equal ( t , orig . CacheID , state . CacheID )
assert . Equal ( t , orig . ResultFingerprint , state . ResultFingerprint )
assert . EqualValues ( t , orig . Labels , state . Labels )
assert . Equal ( t , orig . LastEvaluationTime , state . LastEvaluationTime )
assert . Equal ( t , orig . EvaluationDuration , state . EvaluationDuration )
} )
t . Run ( "if result Error and current state is Error it should copy datasource_uid and ref_id labels" , func ( t * testing . T ) {
state := randomSate ( key )
orig := state . Copy ( )
current := randomSate ( key )
current . State = eval . Error
current . Labels [ "datasource_uid" ] = util . GenerateShortUID ( )
current . Labels [ "ref_id" ] = util . GenerateShortUID ( )
result := eval . Result {
Instance : ngmodels . GenerateAlertLabels ( 5 , "result-" ) ,
State : eval . Error ,
}
expectedLabels := orig . Labels . Copy ( )
expectedLabels [ "datasource_uid" ] = current . Labels [ "datasource_uid" ]
expectedLabels [ "ref_id" ] = current . Labels [ "ref_id" ]
patch ( & state , & current , result )
assert . Equal ( t , expectedLabels , state . Labels )
assert . Equal ( t , current . State , state . State )
assert . Equal ( t , current . StateReason , state . StateReason )
assert . Equal ( t , current . Image , state . Image )
assert . Equal ( t , current . LatestResult , state . LatestResult )
assert . Equal ( t , current . Error , state . Error )
assert . Equal ( t , current . Values , state . Values )
assert . Equal ( t , current . StartsAt , state . StartsAt )
assert . Equal ( t , current . EndsAt , state . EndsAt )
assert . Equal ( t , current . ResolvedAt , state . ResolvedAt )
assert . Equal ( t , current . LastSentAt , state . LastSentAt )
assert . Equal ( t , current . LastEvaluationString , state . LastEvaluationString )
// Fields that should not change
assert . Equal ( t , orig . OrgID , state . OrgID )
assert . Equal ( t , orig . AlertRuleUID , state . AlertRuleUID )
assert . Equal ( t , orig . CacheID , state . CacheID )
assert . Equal ( t , orig . ResultFingerprint , state . ResultFingerprint )
assert . Equal ( t , orig . LastEvaluationTime , state . LastEvaluationTime )
assert . Equal ( t , orig . EvaluationDuration , state . EvaluationDuration )
assert . EqualValues ( t , orig . Annotations , state . Annotations )
} )
}