2024-08-30 10:27:26 +08:00
package receivers
import (
"context"
"embed"
"encoding/json"
"fmt"
"maps"
"net/http"
"path"
"slices"
"strings"
"testing"
2025-10-03 22:37:49 +08:00
"github.com/grafana/alerting/notify/notifytest"
"github.com/grafana/alerting/receivers/line"
2025-09-26 21:31:50 +08:00
"github.com/grafana/alerting/receivers/schema"
2024-08-30 10:27:26 +08:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
2025-09-18 01:19:50 +08:00
"github.com/grafana/alerting/notify"
2025-09-23 21:42:40 +08:00
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1"
2024-08-30 10:27:26 +08:00
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
2024-10-12 00:19:52 +08:00
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/tracing"
2025-09-18 01:19:50 +08:00
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/routingtree"
2024-08-30 10:27:26 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
2024-09-21 06:31:42 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
2024-08-30 10:27:26 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
2024-09-24 02:12:25 +08:00
alertingac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
2024-08-30 10:27:26 +08:00
"github.com/grafana/grafana/pkg/services/ngalert/api"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
2024-09-18 00:07:31 +08:00
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
2024-08-30 10:27:26 +08:00
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/tests/api/alerting"
"github.com/grafana/grafana/pkg/tests/apis"
2025-09-18 01:19:50 +08:00
test_common "github.com/grafana/grafana/pkg/tests/apis/alerting/notifications/common"
2024-08-30 10:27:26 +08:00
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/grafana/grafana/pkg/util"
2025-09-08 21:49:49 +08:00
"github.com/grafana/grafana/pkg/util/testutil"
2024-08-30 10:27:26 +08:00
)
//go:embed test-data/*.*
var testData embed . FS
func TestMain ( m * testing . M ) {
2025-05-23 17:35:59 +08:00
testsuite . Run ( m )
2024-08-30 10:27:26 +08:00
}
func getTestHelper ( t * testing . T ) * apis . K8sTestHelper {
2025-01-09 03:31:35 +08:00
return apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts { } )
2024-08-30 10:27:26 +08:00
}
func TestIntegrationResourceIdentifier ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-08-30 10:27:26 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
2025-04-12 05:38:53 +08:00
client := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-08-30 10:27:26 +08:00
newResource := & v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-08-30 10:27:26 +08:00
Title : "Test-Receiver" ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration { } ,
2024-08-30 10:27:26 +08:00
} ,
}
t . Run ( "create should fail if object name is specified" , func ( t * testing . T ) {
2024-12-09 23:29:05 +08:00
resource := newResource . Copy ( ) . ( * v0alpha1 . Receiver )
2024-08-30 10:27:26 +08:00
resource . Name = "new-receiver"
_ , err := client . Create ( ctx , resource , v1 . CreateOptions { } )
require . Truef ( t , errors . IsBadRequest ( err ) , "Expected BadRequest but got %s" , err )
} )
var resourceID string
t . Run ( "create should succeed and provide resource name" , func ( t * testing . T ) {
actual , err := client . Create ( ctx , newResource , v1 . CreateOptions { } )
require . NoError ( t , err )
require . NotEmptyf ( t , actual . Name , "Resource name should not be empty" )
require . NotEmptyf ( t , actual . UID , "Resource UID should not be empty" )
resourceID = actual . Name
} )
t . Run ( "resource should be available by the identifier" , func ( t * testing . T ) {
actual , err := client . Get ( ctx , resourceID , v1 . GetOptions { } )
require . NoError ( t , err )
require . NotEmptyf ( t , actual . Name , "Resource name should not be empty" )
require . Equal ( t , newResource . Spec , actual . Spec )
} )
2024-09-18 00:07:31 +08:00
t . Run ( "update should rename receiver if name in the specification changes" , func ( t * testing . T ) {
existing , err := client . Get ( ctx , resourceID , v1 . GetOptions { } )
require . NoError ( t , err )
2024-12-09 23:29:05 +08:00
updated := existing . Copy ( ) . ( * v0alpha1 . Receiver )
2024-09-18 00:07:31 +08:00
updated . Spec . Title = "another-newReceiver"
actual , err := client . Update ( ctx , updated , v1 . UpdateOptions { } )
require . NoError ( t , err )
require . Equal ( t , updated . Spec , actual . Spec )
require . NotEqualf ( t , updated . Name , actual . Name , "Update should change the resource name but it didn't" )
require . NotEqualf ( t , updated . ResourceVersion , actual . ResourceVersion , "Update should change the resource version but it didn't" )
resource , err := client . Get ( ctx , actual . Name , v1 . GetOptions { } )
require . NoError ( t , err )
require . Equal ( t , actual . Spec , resource . Spec )
require . Equal ( t , actual . Name , resource . Name )
require . Equal ( t , actual . ResourceVersion , resource . ResourceVersion )
} )
2024-08-30 10:27:26 +08:00
}
2024-09-24 02:12:25 +08:00
// TestIntegrationResourcePermissions focuses on testing resource permissions for the alerting receiver resource. It
// verifies that access is correctly set when creating resources and assigning permissions to users, teams, and roles.
func TestIntegrationResourcePermissions ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-09-24 02:12:25 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
org1 := helper . Org1
2025-09-18 01:19:50 +08:00
noneUser := org1 . None
2024-09-24 02:12:25 +08:00
creator := helper . CreateUser ( "creator" , apis . Org1 , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
createWildcardPermission (
accesscontrol . ActionAlertingReceiversCreate ,
) ,
} )
admin := org1 . Admin
viewer := org1 . Viewer
editor := org1 . Editor
2025-04-12 05:38:53 +08:00
adminClient := test_common . NewReceiverClient ( t , admin )
2024-09-24 02:12:25 +08:00
writeACMetadata := [ ] string { "canWrite" , "canDelete" }
allACMetadata := [ ] string { "canWrite" , "canDelete" , "canReadSecrets" , "canAdmin" }
mustID := func ( user apis . User ) int64 {
id , err := user . Identity . GetInternalID ( )
require . NoError ( t , err )
return id
}
for _ , tc := range [ ] struct {
name string
creatingUser apis . User
testUser apis . User
assignments [ ] accesscontrol . SetResourcePermissionCommand
expACMetadata [ ] string
expRead bool
} {
// Basic access.
{
name : "Admin creates and has all metadata and access" ,
creatingUser : admin ,
testUser : admin ,
assignments : nil ,
expACMetadata : allACMetadata ,
expRead : true ,
} ,
{
name : "Creator creates and has all metadata and access" ,
creatingUser : creator ,
testUser : creator ,
assignments : nil ,
expACMetadata : allACMetadata ,
expRead : true ,
} ,
{
name : "Admin creates, noneUser has no metadata and no access" ,
creatingUser : admin ,
testUser : noneUser ,
assignments : nil ,
expACMetadata : nil ,
expRead : false ,
} ,
{
name : "Admin creates, viewer has no metadata but has access" ,
creatingUser : admin ,
testUser : viewer ,
expACMetadata : nil ,
expRead : true ,
} ,
{
name : "Admin creates, editor has write metadata and access" ,
creatingUser : admin ,
testUser : editor ,
expACMetadata : writeACMetadata ,
expRead : true ,
} ,
// User-based assignments.
{
name : "Admin creates, assigns read, noneUser has no metadata but has access" ,
creatingUser : admin ,
testUser : noneUser ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { UserID : mustID ( noneUser ) , Permission : string ( alertingac . ReceiverPermissionView ) } } ,
expACMetadata : nil ,
expRead : true ,
} ,
{
name : "Admin creates, assigns write, noneUser has write metadata and access" ,
creatingUser : admin ,
testUser : noneUser ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { UserID : mustID ( noneUser ) , Permission : string ( alertingac . ReceiverPermissionEdit ) } } ,
expACMetadata : writeACMetadata ,
expRead : true ,
} ,
{
name : "Admin creates, assigns admin, noneUser has all metadata and access" ,
creatingUser : admin ,
testUser : noneUser ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { UserID : mustID ( noneUser ) , Permission : string ( alertingac . ReceiverPermissionAdmin ) } } ,
expACMetadata : allACMetadata ,
expRead : true ,
} ,
// Other users don't get assignments.
{
name : "Admin creates, assigns read to noneUser, creator has no metadata and no access" ,
creatingUser : admin ,
testUser : creator ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { UserID : mustID ( noneUser ) , Permission : string ( alertingac . ReceiverPermissionView ) } } ,
expACMetadata : nil ,
expRead : false ,
} ,
{
name : "Admin creates, assigns write to noneUser, creator has no metadata and no access" ,
creatingUser : admin ,
testUser : creator ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { UserID : mustID ( noneUser ) , Permission : string ( alertingac . ReceiverPermissionEdit ) } } ,
expACMetadata : nil ,
expRead : false ,
} ,
{
name : "Admin creates, assigns admin to noneUser, creator has no metadata and no access" ,
creatingUser : admin ,
testUser : creator ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { UserID : mustID ( noneUser ) , Permission : string ( alertingac . ReceiverPermissionAdmin ) } } ,
expACMetadata : nil ,
expRead : false ,
} ,
// Role-based access.
{
name : "Admin creates, assigns editor, viewer has write metadata and access" ,
creatingUser : admin ,
testUser : viewer ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { UserID : mustID ( viewer ) , Permission : string ( alertingac . ReceiverPermissionEdit ) } } ,
expACMetadata : writeACMetadata ,
expRead : true ,
} ,
{
name : "Admin creates, assigns admin, viewer has all metadata and access" ,
creatingUser : admin ,
testUser : viewer ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { UserID : mustID ( viewer ) , Permission : string ( alertingac . ReceiverPermissionAdmin ) } } ,
expACMetadata : allACMetadata ,
expRead : true ,
} ,
{
name : "Admin creates, assigns admin, editor has all metadata and access" ,
creatingUser : admin ,
testUser : editor ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { UserID : mustID ( editor ) , Permission : string ( alertingac . ReceiverPermissionAdmin ) } } ,
expACMetadata : allACMetadata ,
expRead : true ,
} ,
// Team-based access. Staff team has editor+admin but not viewer in it.
{
name : "Admin creates, assigns admin to staff, viewer has no metadata and access" ,
creatingUser : admin ,
testUser : viewer ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { TeamID : org1 . Staff . ID , Permission : string ( alertingac . ReceiverPermissionAdmin ) } } ,
expACMetadata : nil ,
expRead : true ,
} ,
{
name : "Admin creates, assigns admin to staff, editor has all metadata and access" ,
creatingUser : admin ,
testUser : editor ,
assignments : [ ] accesscontrol . SetResourcePermissionCommand { { TeamID : org1 . Staff . ID , Permission : string ( alertingac . ReceiverPermissionAdmin ) } } ,
expACMetadata : allACMetadata ,
expRead : true ,
} ,
} {
t . Run ( tc . name , func ( t * testing . T ) {
2025-04-12 05:38:53 +08:00
createClient := test_common . NewReceiverClient ( t , tc . creatingUser )
client := test_common . NewReceiverClient ( t , tc . testUser )
2024-09-24 02:12:25 +08:00
var created = & v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-09-24 02:12:25 +08:00
Title : "receiver-1" ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration { } ,
2024-09-24 02:12:25 +08:00
} ,
}
d , err := json . Marshal ( created )
require . NoError ( t , err )
// Create receiver with creatingUser
created , err = createClient . Create ( ctx , created , v1 . CreateOptions { } )
require . NoErrorf ( t , err , "Payload %s" , string ( d ) )
require . NotNil ( t , created )
defer func ( ) {
_ = adminClient . Delete ( ctx , created . Name , v1 . DeleteOptions { } )
} ( )
// Assign resource permissions
cliCfg := helper . Org1 . Admin . NewRestConfig ( )
alertingApi := alerting . NewAlertingLegacyAPIClient ( helper . GetEnv ( ) . Server . HTTPServer . Listener . Addr ( ) . String ( ) , cliCfg . Username , cliCfg . Password )
for _ , permission := range tc . assignments {
status , body := alertingApi . AssignReceiverPermission ( t , created . Name , permission )
require . Equalf ( t , http . StatusOK , status , "Expected status 200 but got %d: %s" , status , body )
}
// Test read
if tc . expRead {
// Helper methods.
extractReceiverFromList := func ( list * v0alpha1 . ReceiverList , name string ) * v0alpha1 . Receiver {
for i := range list . Items {
if list . Items [ i ] . Name == name {
2024-12-09 23:29:05 +08:00
return list . Items [ i ] . Copy ( ) . ( * v0alpha1 . Receiver )
2024-09-24 02:12:25 +08:00
}
}
return nil
}
// Obtain expected responses using admin client as source of truth.
expectedGetWithMetadata , expectedListWithMetadata := func ( ) ( * v0alpha1 . Receiver , * v0alpha1 . Receiver ) {
expectedGet , err := adminClient . Get ( ctx , created . Name , v1 . GetOptions { } )
require . NoError ( t , err )
require . NotNil ( t , expectedGet )
// Set expected metadata.
2024-12-09 23:29:05 +08:00
expectedGetWithMetadata := expectedGet . Copy ( ) . ( * v0alpha1 . Receiver )
2024-09-24 02:12:25 +08:00
// Clear any existing access control metadata.
for _ , k := range allACMetadata {
delete ( expectedGetWithMetadata . Annotations , v0alpha1 . AccessControlAnnotation ( k ) )
}
for _ , ac := range tc . expACMetadata {
expectedGetWithMetadata . SetAccessControl ( ac )
}
expectedList , err := adminClient . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
expectedListWithMetadata := extractReceiverFromList ( expectedList , created . Name )
require . NotNil ( t , expectedListWithMetadata )
2024-12-09 23:29:05 +08:00
expectedListWithMetadata = expectedListWithMetadata . Copy ( ) . ( * v0alpha1 . Receiver )
2024-09-24 02:12:25 +08:00
// Clear any existing access control metadata.
for _ , k := range allACMetadata {
delete ( expectedListWithMetadata . Annotations , v0alpha1 . AccessControlAnnotation ( k ) )
}
for _ , ac := range tc . expACMetadata {
expectedListWithMetadata . SetAccessControl ( ac )
}
return expectedGetWithMetadata , expectedListWithMetadata
} ( )
t . Run ( "should be able to list receivers" , func ( t * testing . T ) {
list , err := client . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
listedReceiver := extractReceiverFromList ( list , created . Name )
assert . Equalf ( t , expectedListWithMetadata , listedReceiver , "Expected %v but got %v" , expectedListWithMetadata , listedReceiver )
} )
t . Run ( "should be able to read receiver by resource identifier" , func ( t * testing . T ) {
got , err := client . Get ( ctx , expectedGetWithMetadata . Name , v1 . GetOptions { } )
require . NoError ( t , err )
assert . Equalf ( t , expectedGetWithMetadata , got , "Expected %v but got %v" , expectedGetWithMetadata , got )
} )
} else {
2024-09-28 02:56:32 +08:00
t . Run ( "list receivers should be empty" , func ( t * testing . T ) {
list , err := client . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
require . Emptyf ( t , list . Items , "Expected no receivers but got %v" , list . Items )
2024-09-24 02:12:25 +08:00
} )
t . Run ( "should be forbidden to read receiver by name" , func ( t * testing . T ) {
_ , err := client . Get ( ctx , created . Name , v1 . GetOptions { } )
require . Truef ( t , errors . IsForbidden ( err ) , "should get Forbidden error but got %s" , err )
} )
}
} )
}
}
2024-08-30 10:27:26 +08:00
func TestIntegrationAccessControl ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-08-30 10:27:26 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
org1 := helper . Org1
type testCase struct {
2024-09-13 01:57:53 +08:00
user apis . User
canRead bool
canUpdate bool
canCreate bool
canDelete bool
canReadSecrets bool
2024-09-21 06:31:42 +08:00
canAdmin bool
2024-08-30 10:27:26 +08:00
}
// region users
unauthorized := helper . CreateUser ( "unauthorized" , "Org1" , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand { } )
reader := helper . CreateUser ( "reader" , apis . Org1 , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
createWildcardPermission ( accesscontrol . ActionAlertingReceiversRead ) ,
} )
secretsReader := helper . CreateUser ( "secretsReader" , apis . Org1 , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
createWildcardPermission ( accesscontrol . ActionAlertingReceiversReadSecrets ) ,
} )
creator := helper . CreateUser ( "creator" , apis . Org1 , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
createWildcardPermission (
accesscontrol . ActionAlertingReceiversRead ,
accesscontrol . ActionAlertingReceiversCreate ,
) ,
} )
updater := helper . CreateUser ( "updater" , apis . Org1 , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
createWildcardPermission (
accesscontrol . ActionAlertingReceiversRead ,
accesscontrol . ActionAlertingReceiversUpdate ,
) ,
} )
deleter := helper . CreateUser ( "deleter" , apis . Org1 , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
createWildcardPermission (
accesscontrol . ActionAlertingReceiversRead ,
accesscontrol . ActionAlertingReceiversDelete ,
) ,
} )
legacyReader := helper . CreateUser ( "legacyReader" , apis . Org1 , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
{
Actions : [ ] string {
accesscontrol . ActionAlertingNotificationsRead ,
} ,
} ,
} )
legacyWriter := helper . CreateUser ( "legacyWriter" , "Org1" , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
{
Actions : [ ] string {
accesscontrol . ActionAlertingNotificationsRead ,
accesscontrol . ActionAlertingNotificationsWrite ,
} ,
} ,
} )
2024-09-21 06:31:42 +08:00
adminLikeUser := helper . CreateUser ( "adminLikeUser" , apis . Org1 , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
createWildcardPermission ( append (
[ ] string { accesscontrol . ActionAlertingReceiversCreate } ,
ossaccesscontrol . ReceiversAdminActions ... ,
) ... ) ,
} )
2024-08-30 10:27:26 +08:00
2024-10-22 22:04:13 +08:00
// Test receivers with uids longer than 40 characters. User name is used in receiver name.
adminLikeUserLongName := helper . CreateUser ( "adminLikeUserCreatingAReallyLongReceiverName" , apis . Org1 , org . RoleNone , [ ] resourcepermissions . SetResourcePermissionCommand {
createWildcardPermission ( append (
[ ] string { accesscontrol . ActionAlertingReceiversCreate } ,
ossaccesscontrol . ReceiversAdminActions ... ,
) ... ) ,
} )
2024-08-30 10:27:26 +08:00
// endregion
testCases := [ ] testCase {
{
user : unauthorized ,
canRead : false ,
canUpdate : false ,
canCreate : false ,
canDelete : false ,
} ,
{
2024-09-21 06:31:42 +08:00
user : org1 . Admin ,
canRead : true ,
canCreate : true ,
canUpdate : true ,
canDelete : true ,
canAdmin : true ,
canReadSecrets : true ,
2024-08-30 10:27:26 +08:00
} ,
{
user : org1 . Editor ,
canRead : true ,
canUpdate : true ,
canCreate : true ,
canDelete : true ,
} ,
{
user : org1 . Viewer ,
canRead : true ,
} ,
{
user : reader ,
canRead : true ,
} ,
{
2024-09-13 01:57:53 +08:00
user : secretsReader ,
canRead : true ,
canReadSecrets : true ,
2024-08-30 10:27:26 +08:00
} ,
{
user : creator ,
canRead : true ,
canCreate : true ,
} ,
{
user : updater ,
canRead : true ,
canUpdate : true ,
} ,
{
user : deleter ,
canRead : true ,
canDelete : true ,
} ,
{
user : legacyReader ,
canRead : true ,
} ,
{
user : legacyWriter ,
canRead : true ,
canCreate : true ,
canUpdate : true ,
canDelete : true ,
} ,
2024-09-21 06:31:42 +08:00
{
user : adminLikeUser ,
canRead : true ,
canCreate : true ,
canUpdate : true ,
canDelete : true ,
canAdmin : true ,
canReadSecrets : true ,
} ,
2024-10-22 22:04:13 +08:00
{
user : adminLikeUserLongName ,
canRead : true ,
canCreate : true ,
canUpdate : true ,
canDelete : true ,
canAdmin : true ,
canReadSecrets : true ,
} ,
2024-08-30 10:27:26 +08:00
}
2025-04-12 05:38:53 +08:00
adminClient := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-08-30 10:27:26 +08:00
for _ , tc := range testCases {
t . Run ( fmt . Sprintf ( "user '%s'" , tc . user . Identity . GetLogin ( ) ) , func ( t * testing . T ) {
2025-04-12 05:38:53 +08:00
client := test_common . NewReceiverClient ( t , tc . user )
2024-08-30 10:27:26 +08:00
var expected = & v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-08-30 10:27:26 +08:00
Title : fmt . Sprintf ( "receiver-1-%s" , tc . user . Identity . GetLogin ( ) ) ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration { } ,
2024-08-30 10:27:26 +08:00
} ,
}
d , err := json . Marshal ( expected )
require . NoError ( t , err )
2024-12-09 23:29:05 +08:00
newReceiver := expected . Copy ( ) . ( * v0alpha1 . Receiver )
2024-09-24 02:12:25 +08:00
newReceiver . Spec . Title = fmt . Sprintf ( "receiver-2-%s" , tc . user . Identity . GetLogin ( ) )
2024-08-30 10:27:26 +08:00
if tc . canCreate {
t . Run ( "should be able to create receiver" , func ( t * testing . T ) {
actual , err := client . Create ( ctx , newReceiver , v1 . CreateOptions { } )
require . NoErrorf ( t , err , "Payload %s" , string ( d ) )
2024-09-24 02:12:25 +08:00
require . Equal ( t , newReceiver . Spec , actual . Spec )
2024-08-30 10:27:26 +08:00
t . Run ( "should fail if already exists" , func ( t * testing . T ) {
_ , err := client . Create ( ctx , newReceiver , v1 . CreateOptions { } )
require . Truef ( t , errors . IsConflict ( err ) , "expected bad request but got %s" , err )
} )
2024-09-24 02:12:25 +08:00
// Cleanup.
require . NoError ( t , adminClient . Delete ( ctx , actual . Name , v1 . DeleteOptions { } ) )
2024-08-30 10:27:26 +08:00
} )
} else {
t . Run ( "should be forbidden to create" , func ( t * testing . T ) {
2024-09-24 02:12:25 +08:00
_ , err := client . Create ( ctx , newReceiver , v1 . CreateOptions { } )
2024-08-30 10:27:26 +08:00
require . Truef ( t , errors . IsForbidden ( err ) , "Payload %s" , string ( d ) )
} )
}
2024-09-24 02:12:25 +08:00
// create resource to proceed with other tests. We don't use the one created above because the user will always
// have admin permissions on it.
expected , err = adminClient . Create ( ctx , expected , v1 . CreateOptions { } )
require . NoErrorf ( t , err , "Payload %s" , string ( d ) )
require . NotNil ( t , expected )
2024-08-30 10:27:26 +08:00
if tc . canRead {
2024-09-14 01:20:09 +08:00
// Set expected metadata.
2024-12-09 23:29:05 +08:00
expectedWithMetadata := expected . Copy ( ) . ( * v0alpha1 . Receiver )
2024-09-14 01:20:09 +08:00
expectedWithMetadata . SetInUse ( 0 , nil )
2025-09-16 21:32:04 +08:00
expectedWithMetadata . SetCanUse ( true )
2024-09-13 01:57:53 +08:00
if tc . canUpdate {
expectedWithMetadata . SetAccessControl ( "canWrite" )
}
if tc . canDelete {
expectedWithMetadata . SetAccessControl ( "canDelete" )
}
if tc . canReadSecrets {
expectedWithMetadata . SetAccessControl ( "canReadSecrets" )
}
2024-09-21 06:31:42 +08:00
if tc . canAdmin {
expectedWithMetadata . SetAccessControl ( "canAdmin" )
}
2024-08-30 10:27:26 +08:00
t . Run ( "should be able to list receivers" , func ( t * testing . T ) {
list , err := client . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
require . Len ( t , list . Items , 2 ) // default + created
} )
t . Run ( "should be able to read receiver by resource identifier" , func ( t * testing . T ) {
got , err := client . Get ( ctx , expected . Name , v1 . GetOptions { } )
require . NoError ( t , err )
2024-09-13 01:57:53 +08:00
require . Equal ( t , expectedWithMetadata , got )
2024-08-30 10:27:26 +08:00
t . Run ( "should get NotFound if resource does not exist" , func ( t * testing . T ) {
_ , err := client . Get ( ctx , "Notfound" , v1 . GetOptions { } )
require . Truef ( t , errors . IsNotFound ( err ) , "Should get NotFound error but got: %s" , err )
} )
} )
} else {
2024-09-28 02:56:32 +08:00
t . Run ( "list receivers should be empty" , func ( t * testing . T ) {
list , err := client . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
require . Emptyf ( t , list . Items , "Expected no receivers but got %v" , list . Items )
2024-08-30 10:27:26 +08:00
} )
t . Run ( "should be forbidden to read receiver by name" , func ( t * testing . T ) {
_ , err := client . Get ( ctx , expected . Name , v1 . GetOptions { } )
require . Truef ( t , errors . IsForbidden ( err ) , "should get Forbidden error but got %s" , err )
t . Run ( "should get forbidden even if name does not exist" , func ( t * testing . T ) {
_ , err := client . Get ( ctx , "Notfound" , v1 . GetOptions { } )
require . Truef ( t , errors . IsForbidden ( err ) , "should get Forbidden error but got %s" , err )
} )
} )
}
2024-12-09 23:29:05 +08:00
updatedExpected := expected . Copy ( ) . ( * v0alpha1 . Receiver )
2024-08-30 10:27:26 +08:00
updatedExpected . Spec . Integrations = append ( updatedExpected . Spec . Integrations , createIntegration ( t , "email" ) )
d , err = json . Marshal ( updatedExpected )
require . NoError ( t , err )
if tc . canUpdate {
t . Run ( "should be able to update receiver" , func ( t * testing . T ) {
updated , err := client . Update ( ctx , updatedExpected , v1 . UpdateOptions { } )
require . NoErrorf ( t , err , "Payload %s" , string ( d ) )
expected = updated
t . Run ( "should get NotFound if name does not exist" , func ( t * testing . T ) {
2024-12-09 23:29:05 +08:00
up := updatedExpected . Copy ( ) . ( * v0alpha1 . Receiver )
2024-08-30 10:27:26 +08:00
up . Name = "notFound"
_ , err := client . Update ( ctx , up , v1 . UpdateOptions { } )
require . Truef ( t , errors . IsNotFound ( err ) , "Should get NotFound error but got: %s" , err )
} )
} )
} else {
t . Run ( "should be forbidden to update receiver" , func ( t * testing . T ) {
_ , err := client . Update ( ctx , updatedExpected , v1 . UpdateOptions { } )
require . Truef ( t , errors . IsForbidden ( err ) , "should get Forbidden error but got %s" , err )
t . Run ( "should get forbidden even if resource does not exist" , func ( t * testing . T ) {
2024-12-09 23:29:05 +08:00
up := updatedExpected . Copy ( ) . ( * v0alpha1 . Receiver )
2024-08-30 10:27:26 +08:00
up . Name = "notFound"
_ , err := client . Update ( ctx , up , v1 . UpdateOptions { } )
require . Truef ( t , errors . IsForbidden ( err ) , "should get Forbidden error but got %s" , err )
} )
} )
}
deleteOptions := v1 . DeleteOptions { Preconditions : & v1 . Preconditions { ResourceVersion : util . Pointer ( expected . ResourceVersion ) } }
if tc . canDelete {
t . Run ( "should be able to delete receiver" , func ( t * testing . T ) {
err := client . Delete ( ctx , expected . Name , deleteOptions )
require . NoError ( t , err )
t . Run ( "should get NotFound if name does not exist" , func ( t * testing . T ) {
err := client . Delete ( ctx , "notfound" , v1 . DeleteOptions { } )
require . Truef ( t , errors . IsNotFound ( err ) , "Should get NotFound error but got: %s" , err )
} )
} )
} else {
t . Run ( "should be forbidden to delete receiver" , func ( t * testing . T ) {
err := client . Delete ( ctx , expected . Name , deleteOptions )
require . Truef ( t , errors . IsForbidden ( err ) , "should get Forbidden error but got %s" , err )
t . Run ( "should be forbidden even if resource does not exist" , func ( t * testing . T ) {
err := client . Delete ( ctx , "notfound" , v1 . DeleteOptions { } )
require . Truef ( t , errors . IsForbidden ( err ) , "should get Forbidden error but got %s" , err )
} )
} )
require . NoError ( t , adminClient . Delete ( ctx , expected . Name , v1 . DeleteOptions { } ) )
}
if tc . canRead {
t . Run ( "should get empty list if no receivers" , func ( t * testing . T ) {
list , err := client . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
require . Len ( t , list . Items , 1 )
} )
}
} )
}
}
2024-09-14 01:20:09 +08:00
func TestIntegrationInUseMetadata ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-09-14 01:20:09 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
cliCfg := helper . Org1 . Admin . NewRestConfig ( )
legacyCli := alerting . NewAlertingLegacyAPIClient ( helper . GetEnv ( ) . Server . HTTPServer . Listener . Addr ( ) . String ( ) , cliCfg . Username , cliCfg . Password )
2025-04-12 05:38:53 +08:00
adminClient := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-09-14 01:20:09 +08:00
// Prepare environment and create notification policy and rule that use receiver
alertmanagerRaw , err := testData . ReadFile ( path . Join ( "test-data" , "notification-settings.json" ) )
require . NoError ( t , err )
var amConfig definitions . PostableUserConfig
require . NoError ( t , json . Unmarshal ( alertmanagerRaw , & amConfig ) )
// Add more references to the receiver in other routes.
route1 := * amConfig . AlertmanagerConfig . Route . Routes [ 0 ]
route1 . Routes = nil
route2 := route1
parentRoute := * amConfig . AlertmanagerConfig . Route . Routes [ 0 ]
parentRoute . Routes = [ ] * definitions . Route { & route1 , & route2 }
amConfig . AlertmanagerConfig . Route . Routes = append ( amConfig . AlertmanagerConfig . Route . Routes , & parentRoute )
2025-04-12 05:38:53 +08:00
persistInitialConfig ( t , amConfig )
2024-09-14 01:20:09 +08:00
postGroupRaw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-1.json" ) )
require . NoError ( t , err )
var ruleGroup definitions . PostableRuleGroupConfig
require . NoError ( t , json . Unmarshal ( postGroupRaw , & ruleGroup ) )
// Add more references to the receiver by creating adding same rule with a different title.
ruleGen := func ( ) definitions . PostableGrafanaRule { return * ruleGroup . Rules [ 0 ] . GrafanaManagedAlert }
rule2 := ruleGen ( )
rule2 . Title = "Rule2"
rule2 . NotificationSettings = & definitions . AlertRuleNotificationSettings { Receiver : "grafana-default-email" }
rule3 := ruleGen ( )
rule3 . Title = "Rule3"
ruleGroup . Rules = append ( ruleGroup . Rules ,
definitions . PostableExtendedRuleNode {
ApiRuleNode : ruleGroup . Rules [ 0 ] . ApiRuleNode ,
GrafanaManagedAlert : & rule2 ,
} ,
definitions . PostableExtendedRuleNode {
ApiRuleNode : ruleGroup . Rules [ 0 ] . ApiRuleNode ,
GrafanaManagedAlert : & rule3 ,
} ,
)
folderUID := "test-folder"
legacyCli . CreateFolder ( t , folderUID , "TEST" )
2025-03-15 04:14:06 +08:00
_ , status , data := legacyCli . PostRulesGroupWithStatus ( t , folderUID , & ruleGroup , false )
2024-09-14 01:20:09 +08:00
require . Equalf ( t , http . StatusAccepted , status , "Failed to post Rule: %s" , data )
requestReceivers := func ( t * testing . T , title string ) ( v0alpha1 . Receiver , v0alpha1 . Receiver ) {
t . Helper ( )
receivers , err := adminClient . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
require . Len ( t , receivers . Items , 2 )
idx := slices . IndexFunc ( receivers . Items , func ( interval v0alpha1 . Receiver ) bool {
return interval . Spec . Title == title
} )
receiverListed := receivers . Items [ idx ]
receiverGet , err := adminClient . Get ( ctx , receiverListed . Name , v1 . GetOptions { } )
require . NoError ( t , err )
return receiverListed , * receiverGet
}
checkInUse := func ( t * testing . T , receiverList , receiverGet v0alpha1 . Receiver , routes , rules int ) {
t . Helper ( )
assert . Equalf ( t , fmt . Sprintf ( "%d" , routes ) , receiverList . Annotations [ v0alpha1 . InUseAnnotation ( "routes" ) ] , "LIST: Expected %s used by %d routes" , receiverList . Spec . Title , routes )
assert . Equalf ( t , fmt . Sprintf ( "%d" , rules ) , receiverList . Annotations [ v0alpha1 . InUseAnnotation ( "rules" ) ] , "LIST: Expected %s used by %d rules" , receiverList . Spec . Title , rules )
assert . Equalf ( t , fmt . Sprintf ( "%d" , routes ) , receiverGet . Annotations [ v0alpha1 . InUseAnnotation ( "routes" ) ] , "GET: Expected %s used by %d routes" , receiverGet . Spec . Title , routes )
assert . Equalf ( t , fmt . Sprintf ( "%d" , rules ) , receiverGet . Annotations [ v0alpha1 . InUseAnnotation ( "rules" ) ] , "GET: Expected %s used by %d rules" , receiverGet . Spec . Title , rules )
}
receiverListed , receiverGet := requestReceivers ( t , "user-defined" )
checkInUse ( t , receiverListed , receiverGet , 4 , 2 )
// Verify the default.
receiverListed , receiverGet = requestReceivers ( t , "grafana-default-email" )
checkInUse ( t , receiverListed , receiverGet , 1 , 1 )
// Removing the new extra route should leave only 1.
amConfig . AlertmanagerConfig . Route . Routes = amConfig . AlertmanagerConfig . Route . Routes [ : 1 ]
2025-04-12 05:38:53 +08:00
v1Route , err := routingtree . ConvertToK8sResource ( helper . Org1 . AdminServiceAccount . OrgId , * amConfig . AlertmanagerConfig . Route , "" , func ( int64 ) string { return "default" } )
require . NoError ( t , err )
routeAdminClient := test_common . NewRoutingTreeClient ( t , helper . Org1 . Admin )
_ , err = routeAdminClient . Update ( ctx , v1Route , v1 . UpdateOptions { } )
require . NoError ( t , err )
2024-09-14 01:20:09 +08:00
receiverListed , receiverGet = requestReceivers ( t , "user-defined" )
checkInUse ( t , receiverListed , receiverGet , 1 , 2 )
// Remove the extra rules.
ruleGroup . Rules = ruleGroup . Rules [ : 1 ]
2025-03-15 04:14:06 +08:00
_ , status , data = legacyCli . PostRulesGroupWithStatus ( t , folderUID , & ruleGroup , false )
2024-09-14 01:20:09 +08:00
require . Equalf ( t , http . StatusAccepted , status , "Failed to post Rule: %s" , data )
receiverListed , receiverGet = requestReceivers ( t , "user-defined" )
checkInUse ( t , receiverListed , receiverGet , 1 , 1 )
receiverListed , receiverGet = requestReceivers ( t , "grafana-default-email" )
checkInUse ( t , receiverListed , receiverGet , 1 , 0 )
2025-04-12 05:38:53 +08:00
// Remove the remaining routes.
2024-09-14 01:20:09 +08:00
amConfig . AlertmanagerConfig . Route . Routes = nil
2025-04-12 05:38:53 +08:00
v1route , err := routingtree . ConvertToK8sResource ( 1 , * amConfig . AlertmanagerConfig . Route , "" , func ( int64 ) string { return "default" } )
require . NoError ( t , err )
_ , err = routeAdminClient . Update ( ctx , v1route , v1 . UpdateOptions { } )
require . NoError ( t , err )
2024-09-14 01:20:09 +08:00
2025-04-12 05:38:53 +08:00
// Remove the remaining rules.
2024-09-14 01:20:09 +08:00
ruleGroup . Rules = nil
2025-03-15 04:14:06 +08:00
_ , status , data = legacyCli . PostRulesGroupWithStatus ( t , folderUID , & ruleGroup , false )
2024-09-14 01:20:09 +08:00
require . Equalf ( t , http . StatusAccepted , status , "Failed to post Rule: %s" , data )
receiverListed , receiverGet = requestReceivers ( t , "user-defined" )
checkInUse ( t , receiverListed , receiverGet , 0 , 0 )
receiverListed , receiverGet = requestReceivers ( t , "grafana-default-email" )
checkInUse ( t , receiverListed , receiverGet , 1 , 0 )
}
2024-08-30 10:27:26 +08:00
func TestIntegrationProvisioning ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-08-30 10:27:26 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
org := helper . Org1
admin := org . Admin
2025-04-12 05:38:53 +08:00
adminClient := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-08-30 10:27:26 +08:00
env := helper . GetEnv ( )
2025-01-14 17:26:15 +08:00
ac := acimpl . ProvideAccessControl ( env . FeatureToggles )
2024-10-12 00:19:52 +08:00
db , err := store . ProvideDBStore ( env . Cfg , env . FeatureToggles , env . SQLStore , & foldertest . FakeService { } , & dashboards . FakeDashboardService { } , ac , bus . ProvideBus ( tracing . InitializeTracerForTest ( ) ) )
2024-08-30 10:27:26 +08:00
require . NoError ( t , err )
created , err := adminClient . Create ( ctx , & v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-08-30 10:27:26 +08:00
Title : "test-receiver-1" ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration {
2024-08-30 10:27:26 +08:00
createIntegration ( t , "email" ) ,
} ,
} ,
} , v1 . CreateOptions { } )
require . NoError ( t , err )
require . Equal ( t , "none" , created . GetProvenanceStatus ( ) )
t . Run ( "should provide provenance status" , func ( t * testing . T ) {
require . NoError ( t , db . SetProvenance ( ctx , & definitions . EmbeddedContactPoint {
UID : * created . Spec . Integrations [ 0 ] . Uid ,
} , admin . Identity . GetOrgID ( ) , "API" ) )
got , err := adminClient . Get ( ctx , created . Name , v1 . GetOptions { } )
require . NoError ( t , err )
require . Equal ( t , "API" , got . GetProvenanceStatus ( ) )
} )
t . Run ( "should not let update if provisioned" , func ( t * testing . T ) {
got , err := adminClient . Get ( ctx , created . Name , v1 . GetOptions { } )
require . NoError ( t , err )
2024-12-09 23:29:05 +08:00
updated := got . Copy ( ) . ( * v0alpha1 . Receiver )
2024-08-30 10:27:26 +08:00
updated . Spec . Integrations = append ( updated . Spec . Integrations , createIntegration ( t , "email" ) )
_ , err = adminClient . Update ( ctx , updated , v1 . UpdateOptions { } )
require . Truef ( t , errors . IsForbidden ( err ) , "should get Forbidden error but got %s" , err )
} )
t . Run ( "should not let delete if provisioned" , func ( t * testing . T ) {
err := adminClient . Delete ( ctx , created . Name , v1 . DeleteOptions { } )
require . Truef ( t , errors . IsForbidden ( err ) , "should get Forbidden error but got %s" , err )
} )
}
func TestIntegrationOptimisticConcurrency ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-08-30 10:27:26 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
2025-04-12 05:38:53 +08:00
adminClient := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-08-30 10:27:26 +08:00
receiver := v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-08-30 10:27:26 +08:00
Title : "receiver-1" ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration { } ,
2024-08-30 10:27:26 +08:00
} ,
}
created , err := adminClient . Create ( ctx , & receiver , v1 . CreateOptions { } )
require . NoError ( t , err )
require . NotNil ( t , created )
require . NotEmpty ( t , created . ResourceVersion )
t . Run ( "should forbid if version does not match" , func ( t * testing . T ) {
2024-12-09 23:29:05 +08:00
updated := created . Copy ( ) . ( * v0alpha1 . Receiver )
2024-08-30 10:27:26 +08:00
updated . ResourceVersion = "test"
_ , err := adminClient . Update ( ctx , updated , v1 . UpdateOptions { } )
require . Truef ( t , errors . IsConflict ( err ) , "should get Forbidden error but got %s" , err )
} )
t . Run ( "should update if version matches" , func ( t * testing . T ) {
2024-12-09 23:29:05 +08:00
updated := created . Copy ( ) . ( * v0alpha1 . Receiver )
2024-08-30 10:27:26 +08:00
updated . Spec . Integrations = append ( updated . Spec . Integrations , createIntegration ( t , "email" ) )
actualUpdated , err := adminClient . Update ( ctx , updated , v1 . UpdateOptions { } )
require . NoError ( t , err )
for i , integration := range actualUpdated . Spec . Integrations {
updated . Spec . Integrations [ i ] . Uid = integration . Uid
}
require . EqualValues ( t , updated . Spec , actualUpdated . Spec )
require . NotEqual ( t , updated . ResourceVersion , actualUpdated . ResourceVersion )
} )
t . Run ( "should fail to update if version is empty" , func ( t * testing . T ) {
2024-12-09 23:29:05 +08:00
updated := created . Copy ( ) . ( * v0alpha1 . Receiver )
2024-08-30 10:27:26 +08:00
updated . ResourceVersion = ""
updated . Spec . Integrations = append ( updated . Spec . Integrations , createIntegration ( t , "webhook" ) )
_ , err := adminClient . Update ( ctx , updated , v1 . UpdateOptions { } )
require . Truef ( t , errors . IsConflict ( err ) , "should get Forbidden error but got %s" , err ) // TODO Change that? K8s returns 400 instead.
} )
t . Run ( "should fail to delete if version does not match" , func ( t * testing . T ) {
actual , err := adminClient . Get ( ctx , created . Name , v1 . GetOptions { } )
require . NoError ( t , err )
err = adminClient . Delete ( ctx , actual . Name , v1 . DeleteOptions {
Preconditions : & v1 . Preconditions {
ResourceVersion : util . Pointer ( "something" ) ,
} ,
} )
require . Truef ( t , errors . IsConflict ( err ) , "should get Forbidden error but got %s" , err )
} )
t . Run ( "should succeed if version matches" , func ( t * testing . T ) {
actual , err := adminClient . Get ( ctx , created . Name , v1 . GetOptions { } )
require . NoError ( t , err )
err = adminClient . Delete ( ctx , actual . Name , v1 . DeleteOptions {
Preconditions : & v1 . Preconditions {
ResourceVersion : util . Pointer ( actual . ResourceVersion ) ,
} ,
} )
require . NoError ( t , err )
} )
t . Run ( "should succeed if version is empty" , func ( t * testing . T ) {
actual , err := adminClient . Create ( ctx , & receiver , v1 . CreateOptions { } )
require . NoError ( t , err )
err = adminClient . Delete ( ctx , actual . Name , v1 . DeleteOptions {
Preconditions : & v1 . Preconditions {
ResourceVersion : util . Pointer ( actual . ResourceVersion ) ,
} ,
} )
require . NoError ( t , err )
} )
}
func TestIntegrationPatch ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-08-30 10:27:26 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
2025-04-12 05:38:53 +08:00
adminClient := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-08-30 10:27:26 +08:00
receiver := v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-08-30 10:27:26 +08:00
Title : "receiver" ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration {
2024-08-30 10:27:26 +08:00
createIntegration ( t , "email" ) ,
createIntegration ( t , "webhook" ) ,
createIntegration ( t , "sns" ) ,
} ,
} ,
}
current , err := adminClient . Create ( ctx , & receiver , v1 . CreateOptions { } )
require . NoError ( t , err )
require . NotNil ( t , current )
t . Run ( "should patch with json patch" , func ( t * testing . T ) {
current , err := adminClient . Get ( ctx , current . Name , v1 . GetOptions { } )
require . NoError ( t , err )
2025-05-08 20:59:33 +08:00
index := slices . IndexFunc ( current . Spec . Integrations , func ( t v0alpha1 . ReceiverIntegration ) bool {
2024-08-30 10:27:26 +08:00
return t . Type == "webhook"
} )
patch := [ ] map [ string ] any {
{
"op" : "remove" ,
"path" : fmt . Sprintf ( "/spec/integrations/%d/settings/username" , index ) ,
} ,
{
"op" : "remove" ,
"path" : fmt . Sprintf ( "/spec/integrations/%d/secureFields/password" , index ) ,
} ,
{
"op" : "replace" ,
"path" : fmt . Sprintf ( "/spec/integrations/%d/settings/authorization_scheme" , index ) ,
"value" : "bearer" ,
} ,
{
"op" : "add" ,
"path" : fmt . Sprintf ( "/spec/integrations/%d/settings/authorization_credentials" , index ) ,
"value" : "authz-token" ,
} ,
{
"op" : "remove" ,
"path" : fmt . Sprintf ( "/spec/integrations/%d/secureFields/authorization_credentials" , index ) ,
} ,
}
expected := current . Spec . Integrations [ index ]
2024-12-09 23:29:05 +08:00
delete ( expected . Settings , "username" )
delete ( expected . Settings , "password" )
expected . Settings [ "authorization_scheme" ] = "bearer"
2024-08-30 10:27:26 +08:00
delete ( expected . SecureFields , "password" )
expected . SecureFields [ "authorization_credentials" ] = true
patchData , err := json . Marshal ( patch )
require . NoError ( t , err )
result , err := adminClient . Patch ( ctx , current . Name , types . JSONPatchType , patchData , v1 . PatchOptions { } )
require . NoError ( t , err )
require . EqualValues ( t , expected , result . Spec . Integrations [ index ] )
// Use export endpoint because it's the only way to get decrypted secrets fast.
cliCfg := helper . Org1 . Admin . NewRestConfig ( )
legacyCli := alerting . NewAlertingLegacyAPIClient ( helper . GetEnv ( ) . Server . HTTPServer . Listener . Addr ( ) . String ( ) , cliCfg . Username , cliCfg . Password )
export := legacyCli . ExportReceiverTyped ( t , current . Spec . Title , true )
// we need special unmarshaler to parse settings
cp , err := api . ContactPointFromContactPointExport ( export )
require . NoError ( t , err )
assert . Len ( t , cp . Sns , 1 )
assert . Len ( t , cp . Email , 1 )
require . Len ( t , cp . Webhook , 1 )
settings := cp . Webhook [ 0 ]
assert . EqualValues ( t , "authz-token" , * settings . AuthorizationCredentials )
assert . EqualValues ( t , "bearer" , * settings . AuthorizationScheme )
assert . Nil ( t , settings . Password )
assert . Nil ( t , settings . User )
} )
}
func TestIntegrationReferentialIntegrity ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-08-30 10:27:26 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
2024-09-18 00:07:31 +08:00
env := helper . GetEnv ( )
2025-01-14 17:26:15 +08:00
ac := acimpl . ProvideAccessControl ( env . FeatureToggles )
2024-10-12 00:19:52 +08:00
db , err := store . ProvideDBStore ( env . Cfg , env . FeatureToggles , env . SQLStore , & foldertest . FakeService { } , & dashboards . FakeDashboardService { } , ac , bus . ProvideBus ( tracing . InitializeTracerForTest ( ) ) )
2024-09-18 00:07:31 +08:00
require . NoError ( t , err )
orgID := helper . Org1 . Admin . Identity . GetOrgID ( )
2024-08-30 10:27:26 +08:00
cliCfg := helper . Org1 . Admin . NewRestConfig ( )
legacyCli := alerting . NewAlertingLegacyAPIClient ( helper . GetEnv ( ) . Server . HTTPServer . Listener . Addr ( ) . String ( ) , cliCfg . Username , cliCfg . Password )
2025-04-12 05:38:53 +08:00
adminClient := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-08-30 10:27:26 +08:00
// Prepare environment and create notification policy and rule that use time receiver
alertmanagerRaw , err := testData . ReadFile ( path . Join ( "test-data" , "notification-settings.json" ) )
require . NoError ( t , err )
var amConfig definitions . PostableUserConfig
require . NoError ( t , json . Unmarshal ( alertmanagerRaw , & amConfig ) )
2025-04-12 05:38:53 +08:00
persistInitialConfig ( t , amConfig )
2024-08-30 10:27:26 +08:00
postGroupRaw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-1.json" ) )
require . NoError ( t , err )
var ruleGroup definitions . PostableRuleGroupConfig
require . NoError ( t , json . Unmarshal ( postGroupRaw , & ruleGroup ) )
folderUID := "test-folder"
legacyCli . CreateFolder ( t , folderUID , "TEST" )
2025-03-15 04:14:06 +08:00
_ , status , data := legacyCli . PostRulesGroupWithStatus ( t , folderUID , & ruleGroup , false )
2024-08-30 10:27:26 +08:00
require . Equalf ( t , http . StatusAccepted , status , "Failed to post Rule: %s" , data )
receivers , err := adminClient . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
require . Len ( t , receivers . Items , 2 )
idx := slices . IndexFunc ( receivers . Items , func ( interval v0alpha1 . Receiver ) bool {
return interval . Spec . Title == "user-defined"
} )
receiver := receivers . Items [ idx ]
2024-09-18 00:07:31 +08:00
currentRoute := legacyCli . GetRoute ( t )
2025-02-08 00:24:28 +08:00
currentRuleGroup , status := legacyCli . GetRulesGroup ( t , folderUID , ruleGroup . Name )
require . Equal ( t , http . StatusAccepted , status )
2024-09-18 00:07:31 +08:00
t . Run ( "Update" , func ( t * testing . T ) {
t . Run ( "should rename all references if name changes" , func ( t * testing . T ) {
2024-12-09 23:29:05 +08:00
renamed := receiver . Copy ( ) . ( * v0alpha1 . Receiver )
2024-09-18 00:07:31 +08:00
expectedTitle := renamed . Spec . Title + "-new"
renamed . Spec . Title = expectedTitle
actual , err := adminClient . Update ( ctx , renamed , v1 . UpdateOptions { } )
require . NoError ( t , err )
2025-02-08 00:24:28 +08:00
updatedRuleGroup , status := legacyCli . GetRulesGroup ( t , folderUID , ruleGroup . Name )
require . Equal ( t , http . StatusAccepted , status )
2024-09-18 00:07:31 +08:00
for idx , rule := range updatedRuleGroup . Rules {
assert . Equalf ( t , expectedTitle , rule . GrafanaManagedAlert . NotificationSettings . Receiver , "receiver in rule %d should have been renamed but it did not" , idx )
}
updatedRoute := legacyCli . GetRoute ( t )
for _ , route := range updatedRoute . Routes {
assert . Equalf ( t , expectedTitle , route . Receiver , "time receiver in routes should have been renamed but it did not" )
}
actual , err = adminClient . Get ( ctx , actual . Name , v1 . GetOptions { } )
require . NoError ( t , err )
receiver = * actual
} )
t . Run ( "should fail if at least one resource is provisioned" , func ( t * testing . T ) {
require . NoError ( t , err )
2024-12-09 23:29:05 +08:00
renamed := receiver . Copy ( ) . ( * v0alpha1 . Receiver )
2024-09-18 00:07:31 +08:00
renamed . Spec . Title += util . GenerateShortUID ( )
t . Run ( "provisioned route" , func ( t * testing . T ) {
require . NoError ( t , db . SetProvenance ( ctx , & currentRoute , orgID , "API" ) )
t . Cleanup ( func ( ) {
require . NoError ( t , db . DeleteProvenance ( ctx , & currentRoute , orgID ) )
} )
actual , err := adminClient . Update ( ctx , renamed , v1 . UpdateOptions { } )
require . Errorf ( t , err , "Expected error but got successful result: %v" , actual )
require . Truef ( t , errors . IsConflict ( err ) , "Expected Conflict, got: %s" , err )
} )
t . Run ( "provisioned rules" , func ( t * testing . T ) {
ruleUid := currentRuleGroup . Rules [ 0 ] . GrafanaManagedAlert . UID
resource := & ngmodels . AlertRule { UID : ruleUid }
require . NoError ( t , db . SetProvenance ( ctx , resource , orgID , "API" ) )
t . Cleanup ( func ( ) {
require . NoError ( t , db . DeleteProvenance ( ctx , resource , orgID ) )
} )
actual , err := adminClient . Update ( ctx , renamed , v1 . UpdateOptions { } )
require . Errorf ( t , err , "Expected error but got successful result: %v" , actual )
require . Truef ( t , errors . IsConflict ( err ) , "Expected Conflict, got: %s" , err )
} )
} )
} )
2024-08-30 10:27:26 +08:00
t . Run ( "Delete" , func ( t * testing . T ) {
t . Run ( "should fail to delete if receiver is used in rule and routes" , func ( t * testing . T ) {
err := adminClient . Delete ( ctx , receiver . Name , v1 . DeleteOptions { } )
require . Truef ( t , errors . IsConflict ( err ) , "Expected Conflict, got: %s" , err )
} )
t . Run ( "should fail to delete if receiver is used in only rule" , func ( t * testing . T ) {
route := legacyCli . GetRoute ( t )
route . Routes [ 0 ] . Receiver = ""
legacyCli . UpdateRoute ( t , route , true )
err = adminClient . Delete ( ctx , receiver . Name , v1 . DeleteOptions { } )
require . Truef ( t , errors . IsConflict ( err ) , "Expected Conflict, got: %s" , err )
} )
} )
}
func TestIntegrationCRUD ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-08-30 10:27:26 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
2025-04-12 05:38:53 +08:00
adminClient := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-08-30 10:27:26 +08:00
var defaultReceiver * v0alpha1 . Receiver
t . Run ( "should list the default receiver" , func ( t * testing . T ) {
items , err := adminClient . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
assert . Len ( t , items . Items , 1 )
defaultReceiver = & items . Items [ 0 ]
assert . Equal ( t , "grafana-default-email" , defaultReceiver . Spec . Title )
assert . NotEmpty ( t , defaultReceiver . UID )
assert . NotEmpty ( t , defaultReceiver . Name )
assert . NotEmpty ( t , defaultReceiver . ResourceVersion )
defaultReceiver , err = adminClient . Get ( ctx , defaultReceiver . Name , v1 . GetOptions { } )
require . NoError ( t , err )
assert . NotEmpty ( t , defaultReceiver . UID )
assert . NotEmpty ( t , defaultReceiver . Name )
assert . NotEmpty ( t , defaultReceiver . ResourceVersion )
assert . Len ( t , defaultReceiver . Spec . Integrations , 1 )
} )
t . Run ( "should be able to update default receiver" , func ( t * testing . T ) {
require . NotNil ( t , defaultReceiver )
2024-12-09 23:29:05 +08:00
newDefault := defaultReceiver . Copy ( ) . ( * v0alpha1 . Receiver )
2025-10-03 22:37:49 +08:00
newDefault . Spec . Integrations = append ( newDefault . Spec . Integrations , createIntegration ( t , line . Type ) )
2024-08-30 10:27:26 +08:00
updatedReceiver , err := adminClient . Update ( ctx , newDefault , v1 . UpdateOptions { } )
require . NoError ( t , err )
2024-12-09 23:29:05 +08:00
expected := newDefault . Copy ( ) . ( * v0alpha1 . Receiver )
2024-08-30 10:27:26 +08:00
expected . Spec . Integrations [ 0 ] . Uid = updatedReceiver . Spec . Integrations [ 0 ] . Uid // default integration does not have UID before first update
lineIntegration := expected . Spec . Integrations [ 1 ]
lineIntegration . SecureFields = map [ string ] bool {
"token" : true ,
}
2024-12-09 23:29:05 +08:00
delete ( lineIntegration . Settings , "token" )
2024-08-30 10:27:26 +08:00
assert . Equal ( t , "LINE" , updatedReceiver . Spec . Integrations [ 1 ] . Type ) // this type is in the schema but not in backend
lineIntegration . Type = "LINE"
lineIntegration . Uid = updatedReceiver . Spec . Integrations [ 1 ] . Uid
expected . Spec . Integrations [ 1 ] = lineIntegration
assert . Equal ( t , expected . Spec , updatedReceiver . Spec )
} )
t . Run ( "should fail to create receiver with the existing name" , func ( t * testing . T ) {
newReceiver := & v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-08-30 10:27:26 +08:00
Title : defaultReceiver . Spec . Title ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration { } ,
2024-08-30 10:27:26 +08:00
} ,
}
_ , err := adminClient . Create ( ctx , newReceiver , v1 . CreateOptions { } )
require . Truef ( t , errors . IsConflict ( err ) , "Expected Conflict, got: %s" , err )
} )
t . Run ( "should not let delete default receiver" , func ( t * testing . T ) {
err := adminClient . Delete ( ctx , defaultReceiver . Name , v1 . DeleteOptions { } )
require . Truef ( t , errors . IsConflict ( err ) , "Expected Conflict, got: %s" , err )
} )
var receiver * v0alpha1 . Receiver
t . Run ( "should correctly persist all known integrations" , func ( t * testing . T ) {
2025-10-03 22:37:49 +08:00
integrations := make ( [ ] v0alpha1 . ReceiverIntegration , 0 , len ( notifytest . AllKnownV1ConfigsForTesting ) )
keysIter := maps . Keys ( notifytest . AllKnownV1ConfigsForTesting )
2024-08-30 10:27:26 +08:00
keys := slices . Collect ( keysIter )
2025-10-03 22:37:49 +08:00
slices . Sort ( keys )
2024-08-30 10:27:26 +08:00
for _ , key := range keys {
integrations = append ( integrations , createIntegration ( t , key ) )
}
2024-12-09 23:29:05 +08:00
var err error
2024-08-30 10:27:26 +08:00
receiver , err = adminClient . Create ( ctx , & v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-08-30 10:27:26 +08:00
Title : "all-receivers" ,
Integrations : integrations ,
} ,
} , v1 . CreateOptions { } )
require . NoError ( t , err )
require . Len ( t , receiver . Spec . Integrations , len ( integrations ) )
2024-09-14 01:20:09 +08:00
// Set expected metadata
2024-09-13 01:57:53 +08:00
receiver . SetAccessControl ( "canWrite" )
receiver . SetAccessControl ( "canDelete" )
2024-09-21 06:31:42 +08:00
receiver . SetAccessControl ( "canReadSecrets" )
receiver . SetAccessControl ( "canAdmin" )
2024-09-14 01:20:09 +08:00
receiver . SetInUse ( 0 , nil )
2025-09-16 21:32:04 +08:00
receiver . SetCanUse ( true )
2024-09-13 01:57:53 +08:00
2024-08-30 10:27:26 +08:00
// Use export endpoint because it's the only way to get decrypted secrets fast.
cliCfg := helper . Org1 . Admin . NewRestConfig ( )
legacyCli := alerting . NewAlertingLegacyAPIClient ( helper . GetEnv ( ) . Server . HTTPServer . Listener . Addr ( ) . String ( ) , cliCfg . Username , cliCfg . Password )
export := legacyCli . ExportReceiverTyped ( t , receiver . Spec . Title , true )
for _ , integration := range export . Receivers {
2025-10-03 22:37:49 +08:00
expected := notifytest . AllKnownV1ConfigsForTesting [ schema . IntegrationType ( integration . Type ) ]
2024-08-30 10:27:26 +08:00
assert . JSONEqf ( t , expected . Config , string ( integration . Settings ) , "integration %s" , integration . Type )
}
} )
t . Run ( "should be able read what it is created" , func ( t * testing . T ) {
get , err := adminClient . Get ( ctx , receiver . Name , v1 . GetOptions { } )
require . NoError ( t , err )
require . Equal ( t , receiver , get )
t . Run ( "should return secrets in secureFields but not settings" , func ( t * testing . T ) {
for _ , integration := range get . Spec . Integrations {
2025-09-26 21:31:50 +08:00
integrationType := schema . IntegrationType ( integration . Type )
2024-08-30 10:27:26 +08:00
t . Run ( integration . Type , func ( t * testing . T ) {
2025-10-03 22:37:49 +08:00
expected := notifytest . AllKnownV1ConfigsForTesting [ schema . IntegrationType ( integration . Type ) ]
2025-02-22 01:51:38 +08:00
var fields map [ string ] any
require . NoError ( t , json . Unmarshal ( [ ] byte ( expected . Config ) , & fields ) )
2025-09-26 21:31:50 +08:00
typeSchema , ok := notify . GetSchemaVersionForIntegration ( integrationType , schema . V1 )
require . True ( t , ok )
secretFields := typeSchema . GetSecretFieldsPaths ( )
2025-10-07 23:08:16 +08:00
for _ , fieldPath := range secretFields {
field := fieldPath . String ( )
2025-02-22 01:51:38 +08:00
if _ , ok := fields [ field ] ; ! ok { // skip field that is not in the original setting
continue
}
2024-08-30 10:27:26 +08:00
assert . Contains ( t , integration . SecureFields , field )
assert . Truef ( t , integration . SecureFields [ field ] , "secure field should be always true" )
2024-12-09 23:29:05 +08:00
value , ok , err := unstructured . NestedString ( integration . Settings , strings . Split ( field , "." ) ... )
2024-08-30 10:27:26 +08:00
assert . NoErrorf ( t , err , "failed to read field %s from settings" , field )
assert . Falsef ( t , ok , "secret field %s should not be in settings, value [%s]" , field , value )
}
} )
}
} )
} )
t . Run ( "should fail to persist receiver with invalid config" , func ( t * testing . T ) {
2025-10-03 22:37:49 +08:00
keysIter := maps . Keys ( notifytest . AllKnownV1ConfigsForTesting )
2024-08-30 10:27:26 +08:00
keys := slices . Collect ( keysIter )
2025-10-03 22:37:49 +08:00
slices . Sort ( keys )
2024-08-30 10:27:26 +08:00
for _ , key := range keys {
2025-10-03 22:37:49 +08:00
t . Run ( string ( key ) , func ( t * testing . T ) {
2024-08-30 10:27:26 +08:00
integration := createIntegration ( t , key )
// Make the integration invalid, so it fails to create. This is usually done by sending empty settings.
2024-12-09 23:29:05 +08:00
clear ( integration . Settings )
2024-08-30 10:27:26 +08:00
if key == "webex" {
// Webex integration is special case and passes validation without any settings so we instead set an invalid URL.
2024-12-09 23:29:05 +08:00
integration . Settings [ "api_url" ] = "(*^$*^%!@#$*()"
2024-08-30 10:27:26 +08:00
}
2024-12-09 23:29:05 +08:00
receiver , err := adminClient . Create ( ctx , & v0alpha1 . Receiver {
2024-08-30 10:27:26 +08:00
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-08-30 10:27:26 +08:00
Title : fmt . Sprintf ( "invalid-%s" , key ) ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration { integration } ,
2024-08-30 10:27:26 +08:00
} ,
} , v1 . CreateOptions { } )
require . Errorf ( t , err , "Expected error but got successful result: %v" , receiver )
require . Truef ( t , errors . IsBadRequest ( err ) , "Expected BadRequest, got: %s" , err )
} )
}
} )
}
2024-09-10 22:58:14 +08:00
func TestIntegrationReceiverListSelector ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-09-10 22:58:14 +08:00
ctx := context . Background ( )
helper := getTestHelper ( t )
2025-04-12 05:38:53 +08:00
adminClient := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-09-10 22:58:14 +08:00
recv1 := & v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-09-10 22:58:14 +08:00
Title : "test-receiver-1" ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration {
2024-09-10 22:58:14 +08:00
createIntegration ( t , "email" ) ,
} ,
} ,
}
2024-12-09 23:29:05 +08:00
recv1 , err := adminClient . Create ( ctx , recv1 , v1 . CreateOptions { } )
2024-09-10 22:58:14 +08:00
require . NoError ( t , err )
recv2 := & v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-09-10 22:58:14 +08:00
Title : "test-receiver-2" ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration {
2024-09-10 22:58:14 +08:00
createIntegration ( t , "email" ) ,
} ,
} ,
}
recv2 , err = adminClient . Create ( ctx , recv2 , v1 . CreateOptions { } )
require . NoError ( t , err )
env := helper . GetEnv ( )
2025-01-14 17:26:15 +08:00
ac := acimpl . ProvideAccessControl ( env . FeatureToggles )
2024-10-12 00:19:52 +08:00
db , err := store . ProvideDBStore ( env . Cfg , env . FeatureToggles , env . SQLStore , & foldertest . FakeService { } , & dashboards . FakeDashboardService { } , ac , bus . ProvideBus ( tracing . InitializeTracerForTest ( ) ) )
2024-09-10 22:58:14 +08:00
require . NoError ( t , err )
require . NoError ( t , db . SetProvenance ( ctx , & definitions . EmbeddedContactPoint {
UID : * recv2 . Spec . Integrations [ 0 ] . Uid ,
} , helper . Org1 . Admin . Identity . GetOrgID ( ) , "API" ) )
recv2 , err = adminClient . Get ( ctx , recv2 . Name , v1 . GetOptions { } )
require . NoError ( t , err )
receivers , err := adminClient . List ( ctx , v1 . ListOptions { } )
require . NoError ( t , err )
require . Len ( t , receivers . Items , 3 ) // Includes default.
t . Run ( "should filter by receiver name" , func ( t * testing . T ) {
2025-09-23 21:42:40 +08:00
t . Skip ( "disabled until app installer supports it" ) // TODO revisit when custom field selectors are supported
2024-09-10 22:58:14 +08:00
list , err := adminClient . List ( ctx , v1 . ListOptions {
FieldSelector : "spec.title=" + recv1 . Spec . Title ,
} )
require . NoError ( t , err )
require . Len ( t , list . Items , 1 )
require . Equal ( t , recv1 . Name , list . Items [ 0 ] . Name )
} )
t . Run ( "should filter by metadata name" , func ( t * testing . T ) {
list , err := adminClient . List ( ctx , v1 . ListOptions {
FieldSelector : "metadata.name=" + recv2 . Name ,
} )
require . NoError ( t , err )
require . Len ( t , list . Items , 1 )
require . Equal ( t , recv2 . Name , list . Items [ 0 ] . Name )
} )
t . Run ( "should filter by multiple filters" , func ( t * testing . T ) {
2025-09-23 21:42:40 +08:00
t . Skip ( "disabled until app installer supports it" ) // TODO revisit when custom field selectors are supported
2024-09-10 22:58:14 +08:00
list , err := adminClient . List ( ctx , v1 . ListOptions {
2024-12-09 23:29:05 +08:00
FieldSelector : fmt . Sprintf ( "metadata.name=%s,spec.title=%s" , recv2 . Name , recv2 . Spec . Title ) ,
2024-09-10 22:58:14 +08:00
} )
require . NoError ( t , err )
require . Len ( t , list . Items , 1 )
require . Equal ( t , recv2 . Name , list . Items [ 0 ] . Name )
} )
t . Run ( "should be empty when filter does not match" , func ( t * testing . T ) {
list , err := adminClient . List ( ctx , v1 . ListOptions {
2024-12-09 23:29:05 +08:00
FieldSelector : fmt . Sprintf ( "metadata.name=%s" , "unknown" ) ,
2024-09-10 22:58:14 +08:00
} )
require . NoError ( t , err )
require . Empty ( t , list . Items )
} )
}
2024-09-17 21:49:17 +08:00
// persistInitialConfig helps create an initial config with new receivers using legacy json. Config API blocks receiver
// modifications, so we need to use k8s API to create new receivers before posting the config.
2025-04-12 05:38:53 +08:00
func persistInitialConfig ( t * testing . T , amConfig definitions . PostableUserConfig ) {
2024-09-17 21:49:17 +08:00
ctx := context . Background ( )
2025-04-12 05:38:53 +08:00
helper := getTestHelper ( t )
receiverClient := test_common . NewReceiverClient ( t , helper . Org1 . Admin )
2024-09-17 21:49:17 +08:00
for _ , receiver := range amConfig . AlertmanagerConfig . Receivers {
if receiver . Name == "grafana-default-email" {
continue
}
toCreate := v0alpha1 . Receiver {
ObjectMeta : v1 . ObjectMeta {
Namespace : "default" ,
} ,
2025-05-08 20:59:33 +08:00
Spec : v0alpha1 . ReceiverSpec {
2024-09-17 21:49:17 +08:00
Title : receiver . Name ,
2025-05-08 20:59:33 +08:00
Integrations : [ ] v0alpha1 . ReceiverIntegration { } ,
2024-09-17 21:49:17 +08:00
} ,
}
for _ , integration := range receiver . GrafanaManagedReceivers {
settings := common . Unstructured { }
require . NoError ( t , settings . UnmarshalJSON ( integration . Settings ) )
2025-05-08 20:59:33 +08:00
toCreate . Spec . Integrations = append ( toCreate . Spec . Integrations , v0alpha1 . ReceiverIntegration {
2024-12-09 23:29:05 +08:00
Settings : settings . Object ,
2024-09-17 21:49:17 +08:00
Type : integration . Type ,
DisableResolveMessage : util . Pointer ( false ) ,
} )
}
2025-04-12 05:38:53 +08:00
created , err := receiverClient . Create ( ctx , & toCreate , v1 . CreateOptions { } )
2024-09-17 21:49:17 +08:00
require . NoError ( t , err )
for i , integration := range created . Spec . Integrations {
receiver . GrafanaManagedReceivers [ i ] . UID = * integration . Uid
}
}
2025-04-12 05:38:53 +08:00
nsMapper := func ( _ int64 ) string { return "default" }
2024-09-17 21:49:17 +08:00
2025-04-12 05:38:53 +08:00
routeClient := test_common . NewRoutingTreeClient ( t , helper . Org1 . Admin )
v1route , err := routingtree . ConvertToK8sResource ( helper . Org1 . AdminServiceAccount . OrgId , * amConfig . AlertmanagerConfig . Route , "" , nsMapper )
require . NoError ( t , err )
_ , err = routeClient . Update ( ctx , v1route , v1 . UpdateOptions { } )
require . NoError ( t , err )
2024-09-17 21:49:17 +08:00
}
2025-10-03 22:37:49 +08:00
func createIntegration ( t * testing . T , integrationType schema . IntegrationType ) v0alpha1 . ReceiverIntegration {
cfg , ok := notifytest . AllKnownV1ConfigsForTesting [ integrationType ]
2024-08-30 10:27:26 +08:00
require . Truef ( t , ok , "no known config for integration type %s" , integrationType )
2025-10-03 22:37:49 +08:00
return createIntegrationWithSettings ( t , integrationType , schema . V1 , cfg . Config )
2024-09-17 21:49:17 +08:00
}
2025-10-03 22:37:49 +08:00
func createIntegrationWithSettings ( t * testing . T , integrationType schema . IntegrationType , integrationVersion schema . Version , settingsJson string ) v0alpha1 . ReceiverIntegration {
2024-08-30 10:27:26 +08:00
settings := common . Unstructured { }
2024-09-17 21:49:17 +08:00
require . NoError ( t , settings . UnmarshalJSON ( [ ] byte ( settingsJson ) ) )
2025-05-08 20:59:33 +08:00
return v0alpha1 . ReceiverIntegration {
2024-12-09 23:29:05 +08:00
Settings : settings . Object ,
2025-10-03 22:37:49 +08:00
Type : string ( integrationType ) ,
Version : string ( integrationVersion ) ,
2024-08-30 10:27:26 +08:00
DisableResolveMessage : util . Pointer ( false ) ,
}
}
func createWildcardPermission ( actions ... string ) resourcepermissions . SetResourcePermissionCommand {
return resourcepermissions . SetResourcePermissionCommand {
Actions : actions ,
Resource : "receivers" ,
ResourceAttribute : "uid" ,
ResourceID : "*" ,
}
}