@@ -28,6 +28,8 @@ import (
28
28
29
29
"github.com/go-kit/log"
30
30
"github.com/gogo/status"
31
+ "github.com/google/go-cmp/cmp"
32
+ "github.com/google/uuid"
31
33
"github.com/grafana/alerting/definition"
32
34
alertingReceivers "github.com/grafana/alerting/receivers"
33
35
"github.com/grafana/dskit/clusterutil"
@@ -3632,8 +3634,8 @@ func TestComputeConfig(t *testing.T) {
3632
3634
}
3633
3635
3634
3636
func Test_amConfigFingerprint (t * testing.T ) {
3637
+ const expectedTotalFields = 23 // Total fields: 3 (PostableApiTemplate) + 14 (EmailSenderConfig) + 6 (amConfig)
3635
3638
t .Run ("ensure all fields in the fingerprint" , func (t * testing.T ) {
3636
- const expectedTotalFields = 23 // Total fields: 3 (PostableApiTemplate) + 14 (EmailSenderConfig) + 6 (amConfig)
3637
3639
// Helper function to get field count of a struct
3638
3640
getFieldCount := func (v interface {}) int {
3639
3641
t := reflect .TypeOf (v )
@@ -3651,6 +3653,131 @@ func Test_amConfigFingerprint(t *testing.T) {
3651
3653
3652
3654
require .Equalf (t , expectedTotalFields , totalFields , "Total fields across structs is %d, expected %d; new fields may require updating fingerprint method" , totalFields , expectedTotalFields )
3653
3655
})
3656
+
3657
+ url , err := url .Parse ("http://localhost" )
3658
+ require .NoError (t , err )
3659
+
3660
+ fullConfig := amConfig {
3661
+ User : "user-grafana" ,
3662
+ RawConfig : simpleConfigOne ,
3663
+ Templates : []definition.PostableApiTemplate {
3664
+ {
3665
+ Name : "test" ,
3666
+ Content : "test" ,
3667
+ Kind : definition .MimirTemplateKind ,
3668
+ },
3669
+ {
3670
+ Name : "test2" ,
3671
+ Content : "test2" ,
3672
+ Kind : definition .GrafanaTemplateKind ,
3673
+ },
3674
+ {
3675
+ Name : "test3" ,
3676
+ Content : "test3" ,
3677
+ Kind : definition .GrafanaTemplateKind ,
3678
+ },
3679
+ },
3680
+ TmplExternalURL : url ,
3681
+ EmailConfig : alertingReceivers.EmailSenderConfig {
3682
+ AuthPassword : "custom-password" ,
3683
+ AuthUser : "custom-user" ,
3684
+ ContentTypes : []string {"text/html" , "text/plain" },
3685
+ EhloIdentity : "custom-identity" ,
3686
+ ExternalURL : "http://custom-url" ,
3687
+ FromAddress : "custom@address.com" ,
3688
+ FromName : "Custom From Name" ,
3689
+ Host : "custom-host" ,
3690
+ SentBy : "Mimir vunknown" ,
3691
+ SkipVerify : true ,
3692
+ StartTLSPolicy : "custom-policy" ,
3693
+ StaticHeaders : map [string ]string {"test" : "test" , "test2" : "test2" , "test3" : "test3" },
3694
+ },
3695
+ }
3696
+
3697
+ jsonCfg , err := json .Marshal (fullConfig )
3698
+ require .NoError (t , err )
3699
+
3700
+ t .Run ("fingerprint should be stable" , func (t * testing.T ) {
3701
+ expected := fullConfig .fingerprint ()
3702
+
3703
+ // do it many times to make sure order of elements in the map does not affect fingerprint
3704
+ for i := 0 ; i < 100 ; i ++ {
3705
+ cfg2 := amConfig {}
3706
+ require .NoError (t , json .Unmarshal (jsonCfg , & cfg2 )) // copy structure
3707
+ assert .Empty (t , cmp .Diff (fullConfig , cfg2 , cmp .AllowUnexported (amConfig {})))
3708
+ rand .Shuffle (len (cfg2 .Templates ), func (i , j int ) {
3709
+ cfg2 .Templates [i ], cfg2 .Templates [j ] = cfg2 .Templates [j ], cfg2 .Templates [i ]
3710
+ })
3711
+ require .Equal (t , expected , cfg2 .fingerprint ())
3712
+ }
3713
+ })
3714
+
3715
+ t .Run ("fingerprint should change" , func (t * testing.T ) {
3716
+ cfg := amConfig {}
3717
+ require .NoError (t , json .Unmarshal (jsonCfg , & cfg )) // copy structure
3718
+ notChecked := expectedTotalFields
3719
+ setStringFieldsWithRandomValue := func (val reflect.Value , callback func (fieldName string )) {
3720
+ t := val .Type ()
3721
+ for i := 0 ; i < t .NumField (); i ++ {
3722
+ field := val .Field (i )
3723
+ // Skip unexported fields (cannot be set via reflection)
3724
+ if ! field .CanSet () {
3725
+ continue
3726
+ }
3727
+ switch field .Kind () {
3728
+ case reflect .String :
3729
+ field .SetString (uuid .NewString ())
3730
+ case reflect .Bool :
3731
+ field .SetBool (! field .Bool ())
3732
+ default :
3733
+ continue
3734
+ }
3735
+ callback (t .Field (i ).Name )
3736
+ notChecked --
3737
+ }
3738
+ }
3739
+
3740
+ lastFingerprint := cfg .fingerprint ()
3741
+ assertField := func (prefix string ) func (fieldName string ) {
3742
+ return func (fieldName string ) {
3743
+ newFP := cfg .fingerprint ()
3744
+ assert .NotEqualf (t , lastFingerprint , newFP , "Changes in fields [%s%s] did not cause fingerprint to change" , prefix , fieldName )
3745
+ lastFingerprint = newFP
3746
+ }
3747
+ }
3748
+
3749
+ setStringFieldsWithRandomValue (reflect .ValueOf (& cfg ).Elem (), assertField ("" ))
3750
+ setStringFieldsWithRandomValue (reflect .ValueOf (& cfg .EmailConfig ).Elem (), assertField ("EmailConfig." ))
3751
+ setStringFieldsWithRandomValue (reflect .ValueOf (& cfg .Templates [1 ]).Elem (), assertField ("Templates[1]." ))
3752
+ cfg .Templates = append (cfg .Templates , definition.PostableApiTemplate {
3753
+ Name : "test3" ,
3754
+ Content : "test3" ,
3755
+ Kind : definition .GrafanaTemplateKind ,
3756
+ })
3757
+ assertField ("" )("Templates" )
3758
+ notChecked --
3759
+
3760
+ cfg .TmplExternalURL = nil
3761
+ assertField ("" )("TmplExternalURL" )
3762
+ cfg .TmplExternalURL , err = url .Parse ("http://new-url" )
3763
+ require .NoError (t , err )
3764
+ assertField ("" )("TmplExternalURL" )
3765
+ notChecked --
3766
+
3767
+ cfg .EmailConfig .ContentTypes = []string {"text/plain" , "text/html" }
3768
+ assertField ("EmailConfig." )("ContentTypes" )
3769
+ notChecked --
3770
+
3771
+ cfg .EmailConfig .StaticHeaders = map [string ]string {"test2" : "test" , "test" : "test2" , "test3" : "test3" }
3772
+ assertField ("EmailConfig." )("StaticHeaders" )
3773
+ notChecked --
3774
+
3775
+ cfg .EmailConfig = alertingReceivers.EmailSenderConfig {}
3776
+ assertField ("" )("EmailConfig" )
3777
+ notChecked --
3778
+
3779
+ require .Equal (t , 0 , notChecked )
3780
+ })
3654
3781
}
3655
3782
3656
3783
func TestSyncStates (t * testing.T ) {
0 commit comments