The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/ngalert/models/receivers_test.go

410 lines
14 KiB

package models
import (
"reflect"
"testing"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
)
func TestReceiver_Clone(t *testing.T) {
testCases := []struct {
name string
receiver Receiver
}{
{name: "empty receiver", receiver: Receiver{}},
{name: "empty integration", receiver: Receiver{Integrations: []*Integration{{Config: IntegrationConfig{}}}}},
{name: "random receiver", receiver: ReceiverGen()()},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
receiverClone := tc.receiver.Clone()
assert.Equal(t, tc.receiver, receiverClone)
for _, integration := range tc.receiver.Integrations {
integrationClone := integration.Clone()
assert.Equal(t, *integration, integrationClone)
}
})
}
}
func TestReceiver_EncryptDecrypt(t *testing.T) {
encryptFn := Base64Enrypt
decryptnFn := Base64Decrypt
// Test that all known integration types encrypt and decrypt their secrets.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
decrypedIntegration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
encrypted := decrypedIntegration.Clone()
secrets, err := channels_config.GetSecretKeysForContactPointType(integrationType)
assert.NoError(t, err)
for _, key := range secrets {
val, ok, err := extractField(encrypted.Settings, NewIntegrationFieldPath(key))
assert.NoError(t, err)
if ok {
encryptedVal, err := encryptFn(val)
assert.NoError(t, err)
encrypted.SecureSettings[key] = encryptedVal
}
}
testIntegration := decrypedIntegration.Clone()
err = testIntegration.Encrypt(encryptFn)
assert.NoError(t, err)
require.Equal(t, encrypted, testIntegration)
err = testIntegration.Decrypt(decryptnFn)
assert.NoError(t, err)
assert.Equal(t, decrypedIntegration, testIntegration)
})
}
}
func TestIntegration_Redact(t *testing.T) {
redactFn := func(key string) string {
return "TESTREDACTED"
}
// Test that all known integration types redact their secrets.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
validIntegration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
expected := validIntegration.Clone()
secrets, err := channels_config.GetSecretKeysForContactPointType(integrationType)
assert.NoError(t, err)
for _, key := range secrets {
err := setField(expected.Settings, NewIntegrationFieldPath(key), func(current any) any {
if s, isString := current.(string); isString && s != "" {
delete(expected.SecureSettings, key)
return redactFn(s)
}
return current
}, true)
require.NoError(t, err)
}
validIntegration.Redact(redactFn)
assert.Equal(t, expected, validIntegration)
})
}
}
func TestIntegration_Validate(t *testing.T) {
// Test that all known integration types are valid.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
validIntegration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
assert.NoError(t, validIntegration.Encrypt(Base64Enrypt))
assert.NoErrorf(t, validIntegration.Validate(Base64Decrypt), "integration should be valid")
invalidIntegration := IntegrationGen(IntegrationMuts.WithInvalidConfig(integrationType))()
assert.NoError(t, invalidIntegration.Encrypt(Base64Enrypt))
assert.Errorf(t, invalidIntegration.Validate(Base64Decrypt), "integration should be invalid")
})
}
}
func TestIntegration_WithExistingSecureFields(t *testing.T) {
// Test that WithExistingSecureFields will copy over the secure fields from the existing integration.
testCases := []struct {
name string
integration Integration
secureFields []string
existing Integration
expected Integration
}{
{
name: "test receiver",
integration: Integration{
SecureSettings: map[string]string{
"f1": "newVal1",
"f2": "newVal2",
"f3": "newVal3",
"f5": "newVal5",
},
},
secureFields: []string{"f2", "f4", "f5"},
existing: Integration{
SecureSettings: map[string]string{
"f1": "oldVal1",
"f2": "oldVal2",
"f3": "oldVal3",
"f4": "oldVal4",
},
},
expected: Integration{
SecureSettings: map[string]string{
"f1": "newVal1",
"f2": "oldVal2",
"f3": "newVal3",
"f4": "oldVal4",
},
},
},
{
name: "Integration[exists], SecureFields[true], Existing[exists]: old value",
integration: Integration{
SecureSettings: map[string]string{"f1": "newVal1"},
},
secureFields: []string{"f1"},
existing: Integration{SecureSettings: map[string]string{"f1": "oldVal1"}},
expected: Integration{SecureSettings: map[string]string{"f1": "oldVal1"}},
},
{
name: "Integration[exists], SecureFields[true], Existing[missing]: no value",
integration: Integration{
SecureSettings: map[string]string{"f1": "newVal1"},
},
secureFields: []string{"f1"},
existing: Integration{SecureSettings: map[string]string{}},
expected: Integration{SecureSettings: map[string]string{}},
},
{
name: "Integration[exists], SecureFields[false], Existing[exists]: new value",
integration: Integration{
SecureSettings: map[string]string{"f1": "newVal1"},
},
existing: Integration{SecureSettings: map[string]string{"f1": "oldVal1"}},
expected: Integration{SecureSettings: map[string]string{"f1": "newVal1"}},
},
{
name: "Integration[exists], SecureFields[false], Existing[missing]: new value",
integration: Integration{
SecureSettings: map[string]string{"f1": "newVal1"},
},
existing: Integration{SecureSettings: map[string]string{}},
expected: Integration{SecureSettings: map[string]string{"f1": "newVal1"}},
},
{
name: "Integration[missing], SecureFields[true], Existing[exists]: old value",
integration: Integration{
SecureSettings: map[string]string{},
},
secureFields: []string{"f1"},
existing: Integration{SecureSettings: map[string]string{"f1": "oldVal1"}},
expected: Integration{SecureSettings: map[string]string{"f1": "oldVal1"}},
},
{
name: "Integration[missing], SecureFields[true], Existing[missing]: no value",
integration: Integration{
SecureSettings: map[string]string{},
},
secureFields: []string{"f1"},
existing: Integration{SecureSettings: map[string]string{}},
expected: Integration{SecureSettings: map[string]string{}},
},
{
name: "Integration[missing], SecureFields[false], Existing[exists]: no value",
integration: Integration{
SecureSettings: map[string]string{},
},
existing: Integration{SecureSettings: map[string]string{"f1": "oldVal1"}},
expected: Integration{SecureSettings: map[string]string{}},
},
{
name: "Integration[missing], SecureFields[false], Existing[missing]: no value",
integration: Integration{
SecureSettings: map[string]string{},
},
existing: Integration{SecureSettings: map[string]string{}},
expected: Integration{SecureSettings: map[string]string{}},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.integration.WithExistingSecureFields(&tc.existing, tc.secureFields)
assert.Equal(t, tc.expected, tc.integration)
})
}
}
func TestIntegrationConfig(t *testing.T) {
// Test that all known integration types have a config and correctly mark their secrets as secure.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
config, err := IntegrationConfigFromType(integrationType)
assert.NoError(t, err)
secrets, err := channels_config.GetSecretKeysForContactPointType(integrationType)
assert.NoError(t, err)
allSecrets := make(map[string]struct{}, len(secrets))
for _, key := range secrets {
allSecrets[key] = struct{}{}
}
for field := range config.Fields {
_, isSecret := allSecrets[field]
assert.Equalf(t, isSecret, config.IsSecureField(NewIntegrationFieldPath(field)), "field '%s' is expected to be secret", field)
}
assert.False(t, config.IsSecureField(IntegrationFieldPath{"__--**unknown_field**--__"}))
})
}
t.Run("Unknown type returns error", func(t *testing.T) {
_, err := IntegrationConfigFromType("__--**unknown_type**--__")
assert.Error(t, err)
})
}
func TestIntegration_SecureFields(t *testing.T) {
// Test that all known integration types have a config and correctly mark their secrets as secure.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
t.Run("contains SecureSettings", func(t *testing.T) {
validIntegration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
expected := make(map[string]bool, len(validIntegration.SecureSettings))
for _, path := range validIntegration.Config.GetSecretFields() {
if validIntegration.Config.IsSecureField(path) {
expected[path.String()] = true
validIntegration.SecureSettings[path.String()] = "test"
_, _, err := extractField(validIntegration.Settings, path)
require.NoError(t, err)
continue
}
}
assert.Equal(t, expected, validIntegration.SecureFields())
})
t.Run("contains secret Settings not in SecureSettings", func(t *testing.T) {
validIntegration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
expected := make(map[string]bool, len(validIntegration.SecureSettings))
for _, path := range validIntegration.Config.GetSecretFields() {
if validIntegration.Config.IsSecureField(path) {
expected[path.String()] = true
assert.NoError(t, setField(validIntegration.Settings, path, func(current any) any {
return "test"
}, false))
delete(validIntegration.SecureSettings, path.String())
}
}
assert.Equal(t, expected, validIntegration.SecureFields())
})
})
}
}
// This is a broken type that will error if marshalled.
type broken struct {
f1 string
}
func (b broken) MarshalJSON() ([]byte, error) {
return nil, assert.AnError
}
func TestReceiver_Fingerprint(t *testing.T) {
// Test that the fingerprint is stable.
im := IntegrationMuts
baseReceiver := ReceiverGen(ReceiverMuts.WithName("test receiver"), ReceiverMuts.WithIntegrations(
IntegrationGen(im.WithName("test receiver"), im.WithValidConfig("slack"))(),
))()
baseReceiver.Integrations[0].UID = "stable UID"
baseReceiver.Integrations[0].DisableResolveMessage = true
baseReceiver.Integrations[0].SecureSettings = map[string]string{"test2": "test2", "test3": "test223", "test1": "rest22"}
baseReceiver.Integrations[0].Settings["broken"] = broken{f1: "this"} // Add a broken type to ensure it is stable in the fingerprint.
baseReceiver.Integrations[0].Settings["sub-map"] = map[string]any{
"setting": "value",
"something": 123,
"data": []string{"test"},
} // Add a broken type to ensure it is stable in the fingerprint.
baseReceiver.Integrations[0].Config = IntegrationConfig{Type: baseReceiver.Integrations[0].Config.Type} // Remove all fields except Type.
completelyDifferentReceiver := ReceiverGen(ReceiverMuts.WithName("test receiver2"), ReceiverMuts.WithIntegrations(
IntegrationGen(im.WithName("test receiver2"), im.WithValidConfig("discord"))(),
))()
completelyDifferentReceiver.Integrations[0].UID = "stable UID2"
completelyDifferentReceiver.Integrations[0].DisableResolveMessage = false
completelyDifferentReceiver.Integrations[0].SecureSettings = map[string]string{"test": "test"}
completelyDifferentReceiver.Provenance = ProvenanceAPI
completelyDifferentReceiver.Integrations[0].Config = IntegrationConfig{Type: completelyDifferentReceiver.Integrations[0].Config.Type} // Remove all fields except Type.
t.Run("stable across code changes", func(t *testing.T) {
expectedFingerprint := "c0c82936be34b183" // If this is a valid fingerprint generation change, update the expected value.
assert.Equal(t, expectedFingerprint, baseReceiver.Fingerprint())
})
t.Run("stable across clones", func(t *testing.T) {
fingerprint := baseReceiver.Fingerprint()
receiverClone := baseReceiver.Clone()
assert.Equal(t, fingerprint, receiverClone.Fingerprint())
})
t.Run("stable across Version field modification", func(t *testing.T) {
fingerprint := baseReceiver.Fingerprint()
receiverClone := baseReceiver.Clone()
receiverClone.Version = "new version"
assert.Equal(t, fingerprint, receiverClone.Fingerprint())
})
t.Run("unstable across field modification", func(t *testing.T) {
fingerprint := baseReceiver.Fingerprint()
excludedFields := map[string]struct{}{
"Version": {},
}
reflectVal := reflect.ValueOf(&completelyDifferentReceiver).Elem()
receiverType := reflect.TypeOf((*Receiver)(nil)).Elem()
for i := 0; i < receiverType.NumField(); i++ {
field := receiverType.Field(i).Name
if _, ok := excludedFields[field]; ok {
continue
}
cp := baseReceiver.Clone()
// Get the current field being modified.
v := reflect.ValueOf(&cp).Elem()
vf := v.Field(i)
otherField := reflectVal.Field(i)
if reflect.DeepEqual(otherField.Interface(), vf.Interface()) {
assert.Failf(t, "filds are identical", "Receiver field %s is the same as the original, test does not ensure instability across the field", field)
continue
}
// Set the field to the value of the completelyDifferentReceiver.
vf.Set(otherField)
f2 := cp.Fingerprint()
assert.NotEqualf(t, fingerprint, f2, "Receiver field %s does not seem to be used in fingerprint", field)
}
excludedFields = map[string]struct{}{}
reflectVal = reflect.ValueOf(completelyDifferentReceiver.Integrations[0]).Elem()
integrationType := reflect.TypeOf((*Integration)(nil)).Elem()
for i := 0; i < integrationType.NumField(); i++ {
field := integrationType.Field(i).Name
if _, ok := excludedFields[field]; ok {
continue
}
cp := baseReceiver.Clone()
integrationCp := cp.Integrations[0]
// Get the current field being modified.
v := reflect.ValueOf(integrationCp).Elem()
vf := v.Field(i)
otherField := reflectVal.Field(i)
if reflect.DeepEqual(otherField.Interface(), vf.Interface()) {
assert.Failf(t, "filds are identical", "Integration field %s is the same as the original, test does not ensure instability across the field", field)
continue
}
// Set the field to the value of the completelyDifferentReceiver.
vf.Set(otherField)
f2 := cp.Fingerprint()
assert.NotEqualf(t, fingerprint, f2, "Integration field %s does not seem to be used in fingerprint", field)
}
})
}