package iam import ( "context" "slices" "sync" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/stretchr/testify/require" iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/infra/log" v1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" ) func TestAfterTeamBindingCreate(t *testing.T) { var wg sync.WaitGroup b := &IdentityAccessManagementAPIBuilder{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), } t.Run("should create zanzana entry for team binding with member permission", func(t *testing.T) { wg.Add(1) teamBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "org-1", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, External: false, }, } testMemberBinding := func(ctx context.Context, req *v1.MutateRequest) error { defer wg.Done() require.NotNil(t, req) require.NotNil(t, req.Operations) require.Len(t, req.Operations, 1) require.Equal(t, "org-1", req.Namespace) expectedOperation := &v1.MutateOperation{ Operation: &v1.MutateOperation_CreateTeamBinding{ CreateTeamBinding: &v1.CreateTeamBindingOperation{ SubjectName: "user-1", TeamName: "team-1", Permission: "member", }, }, } require.True(t, containsTeamBindingOperation(req.Operations, expectedOperation)) return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testMemberBinding} b.AfterTeamBindingCreate(&teamBinding, nil) wg.Wait() }) t.Run("should create zanzana entry for team binding with admin permission", func(t *testing.T) { wg.Add(1) teamBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-2", Namespace: "org-2", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-2", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-2", }, Permission: iamv0.TeamBindingTeamPermissionAdmin, External: true, }, } testAdminBinding := func(ctx context.Context, req *v1.MutateRequest) error { defer wg.Done() require.NotNil(t, req) require.NotNil(t, req.Operations) require.Len(t, req.Operations, 1) require.Equal(t, "org-2", req.Namespace) expectedOperation := &v1.MutateOperation{ Operation: &v1.MutateOperation_CreateTeamBinding{ CreateTeamBinding: &v1.CreateTeamBindingOperation{ SubjectName: "user-2", TeamName: "team-2", Permission: "admin", }, }, } require.True(t, containsTeamBindingOperation(req.Operations, expectedOperation)) return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testAdminBinding} b.AfterTeamBindingCreate(&teamBinding, nil) wg.Wait() }) t.Run("should not write to zanzana when zClient is nil", func(t *testing.T) { builder := &IdentityAccessManagementAPIBuilder{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), zClient: nil, } teamBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-3", Namespace: "org-3", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-3", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-3", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } // Should not panic or error when zClient is nil builder.AfterTeamBindingCreate(&teamBinding, nil) }) } func TestBeginTeamBindingUpdate(t *testing.T) { var wg sync.WaitGroup b := &IdentityAccessManagementAPIBuilder{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), } t.Run("should update zanzana entry when permission changes from member to admin", func(t *testing.T) { wg.Add(1) oldBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "org-1", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } newBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "org-1", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionAdmin, }, } testPermissionUpdate := func(ctx context.Context, req *v1.MutateRequest) error { defer wg.Done() require.NotNil(t, req) require.Equal(t, "org-1", req.Namespace) require.NotNil(t, req.Operations) require.Len(t, req.Operations, 2) require.True(t, containsTeamBindingOperation(req.Operations, &v1.MutateOperation{ Operation: &v1.MutateOperation_DeleteTeamBinding{ DeleteTeamBinding: &v1.DeleteTeamBindingOperation{ SubjectName: "user-1", TeamName: "team-1", Permission: "member", }, }, })) require.True(t, containsTeamBindingOperation(req.Operations, &v1.MutateOperation{ Operation: &v1.MutateOperation_CreateTeamBinding{ CreateTeamBinding: &v1.CreateTeamBindingOperation{ SubjectName: "user-1", TeamName: "team-1", Permission: "admin", }, }, })) return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testPermissionUpdate} finishFunc, err := b.BeginTeamBindingUpdate(context.Background(), &newBinding, &oldBinding, nil) require.NoError(t, err) require.NotNil(t, finishFunc) finishFunc(context.Background(), true) wg.Wait() }) t.Run("should update zanzana entry when user changes", func(t *testing.T) { wg.Add(1) oldBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-2", Namespace: "org-2", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } newBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-2", Namespace: "org-2", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-2", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } testUserUpdate := func(ctx context.Context, req *v1.MutateRequest) error { defer wg.Done() require.NotNil(t, req) require.Equal(t, "org-2", req.Namespace) require.NotNil(t, req.Operations) require.Len(t, req.Operations, 2) require.True(t, containsTeamBindingOperation(req.Operations, &v1.MutateOperation{ Operation: &v1.MutateOperation_DeleteTeamBinding{ DeleteTeamBinding: &v1.DeleteTeamBindingOperation{ SubjectName: "user-1", TeamName: "team-1", Permission: "member", }, }, })) require.True(t, containsTeamBindingOperation(req.Operations, &v1.MutateOperation{ Operation: &v1.MutateOperation_CreateTeamBinding{ CreateTeamBinding: &v1.CreateTeamBindingOperation{ SubjectName: "user-2", TeamName: "team-1", Permission: "member", }, }, })) return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testUserUpdate} finishFunc, err := b.BeginTeamBindingUpdate(context.Background(), &newBinding, &oldBinding, nil) require.NoError(t, err) require.NotNil(t, finishFunc) finishFunc(context.Background(), true) wg.Wait() }) t.Run("should update zanzana entry when team changes", func(t *testing.T) { wg.Add(1) oldBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-3", Namespace: "org-3", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionAdmin, }, } newBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-3", Namespace: "org-3", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-2", }, Permission: iamv0.TeamBindingTeamPermissionAdmin, }, } testTeamUpdate := func(ctx context.Context, req *v1.MutateRequest) error { defer wg.Done() require.NotNil(t, req) require.Equal(t, "org-3", req.Namespace) require.NotNil(t, req.Operations) require.Len(t, req.Operations, 2) require.True(t, containsTeamBindingOperation(req.Operations, &v1.MutateOperation{ Operation: &v1.MutateOperation_DeleteTeamBinding{ DeleteTeamBinding: &v1.DeleteTeamBindingOperation{ SubjectName: "user-1", TeamName: "team-1", Permission: "admin", }, }, })) require.True(t, containsTeamBindingOperation(req.Operations, &v1.MutateOperation{ Operation: &v1.MutateOperation_CreateTeamBinding{ CreateTeamBinding: &v1.CreateTeamBindingOperation{ SubjectName: "user-1", TeamName: "team-2", Permission: "admin", }, }, })) return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testTeamUpdate} finishFunc, err := b.BeginTeamBindingUpdate(context.Background(), &newBinding, &oldBinding, nil) require.NoError(t, err) require.NotNil(t, finishFunc) finishFunc(context.Background(), true) wg.Wait() }) t.Run("should not write to zanzana when update fails", func(t *testing.T) { oldBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-4", Namespace: "org-4", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } newBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-4", Namespace: "org-4", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-2", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } testNoMutateOnFailure := func(ctx context.Context, req *v1.MutateRequest) error { // Should not be called when success=false require.Fail(t, "Mutate should not be called when update fails") return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testNoMutateOnFailure} finishFunc, err := b.BeginTeamBindingUpdate(context.Background(), &newBinding, &oldBinding, nil) require.NoError(t, err) require.NotNil(t, finishFunc) // Call finish function with success=false finishFunc(context.Background(), false) // No wait needed since mutate should not be called }) t.Run("should not write to zanzana when zClient is nil", func(t *testing.T) { builder := &IdentityAccessManagementAPIBuilder{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), zClient: nil, } oldBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-5", Namespace: "org-5", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } newBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-5", Namespace: "org-5", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-2", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } finishFunc, err := builder.BeginTeamBindingUpdate(context.Background(), &newBinding, &oldBinding, nil) require.NoError(t, err) require.Nil(t, finishFunc) // Should return nil when zClient is nil }) t.Run("should handle empty old binding subject name gracefully", func(t *testing.T) { wg.Add(1) oldBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-6", Namespace: "org-6", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "", // Empty name will cause server-side error on delete }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } newBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-6", Namespace: "org-6", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-2", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } testEmptyOldBinding := func(ctx context.Context, req *v1.MutateRequest) error { defer wg.Done() require.NotNil(t, req) require.Equal(t, "org-6", req.Namespace) require.NotNil(t, req.Operations) // Should have both delete and create operations // The delete will have empty subject and fail server-side validation require.Len(t, req.Operations, 2) // First operation is delete with empty subject require.True(t, containsTeamBindingOperation(req.Operations, &v1.MutateOperation{ Operation: &v1.MutateOperation_DeleteTeamBinding{ DeleteTeamBinding: &v1.DeleteTeamBindingOperation{ SubjectName: "", TeamName: "team-1", Permission: "member", }, }, })) // Second operation is create with valid data require.True(t, containsTeamBindingOperation(req.Operations, &v1.MutateOperation{ Operation: &v1.MutateOperation_CreateTeamBinding{ CreateTeamBinding: &v1.CreateTeamBindingOperation{ SubjectName: "user-2", TeamName: "team-1", Permission: "member", }, }, })) return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testEmptyOldBinding} finishFunc, err := b.BeginTeamBindingUpdate(context.Background(), &newBinding, &oldBinding, nil) require.NoError(t, err) require.NotNil(t, finishFunc) // Should still return finish function finishFunc(context.Background(), true) wg.Wait() }) t.Run("should return nil finish func when bindings are identical", func(t *testing.T) { oldBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-7", Namespace: "org-7", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } newBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-7", Namespace: "org-7", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } mutateCalled := false testNoMutateOnNoChange := func(ctx context.Context, req *v1.MutateRequest) error { mutateCalled = true require.Fail(t, "Mutate should not be called when bindings are identical") return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testNoMutateOnNoChange} finishFunc, err := b.BeginTeamBindingUpdate(context.Background(), &newBinding, &oldBinding, nil) require.NoError(t, err) require.Nil(t, finishFunc) // Should return nil when bindings are identical // Verify mutate was never called time.Sleep(100 * time.Millisecond) require.False(t, mutateCalled, "Mutate callback should not be called when bindings are identical") }) t.Run("should return nil finish func when new binding has empty subject name", func(t *testing.T) { oldBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-8", Namespace: "org-8", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } newBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-8", Namespace: "org-8", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "", // Empty name - should cause early return }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } mutateCalled := false testNoMutateOnInvalidBinding := func(ctx context.Context, req *v1.MutateRequest) error { mutateCalled = true require.Fail(t, "Mutate should not be called when new binding has empty subject name") return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testNoMutateOnInvalidBinding} finishFunc, err := b.BeginTeamBindingUpdate(context.Background(), &newBinding, &oldBinding, nil) require.NoError(t, err) require.Nil(t, finishFunc) // Should return nil when new binding has empty subject name // Verify mutate was never called time.Sleep(100 * time.Millisecond) require.False(t, mutateCalled, "Mutate callback should not be called when new binding has empty subject name") }) t.Run("should return nil finish func when new binding has empty team ref name", func(t *testing.T) { oldBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-9", Namespace: "org-9", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } newBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-9", Namespace: "org-9", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-2", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "", // Empty name - should cause early return }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } mutateCalled := false testNoMutateOnInvalidBinding := func(ctx context.Context, req *v1.MutateRequest) error { mutateCalled = true require.Fail(t, "Mutate should not be called when new binding has empty team ref name") return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testNoMutateOnInvalidBinding} finishFunc, err := b.BeginTeamBindingUpdate(context.Background(), &newBinding, &oldBinding, nil) require.NoError(t, err) require.Nil(t, finishFunc) // Should return nil when new binding has empty team ref name // Verify mutate was never called time.Sleep(100 * time.Millisecond) require.False(t, mutateCalled, "Mutate callback should not be called when new binding has empty team ref name") }) } func TestAfterTeamBindingDelete(t *testing.T) { var wg sync.WaitGroup b := &IdentityAccessManagementAPIBuilder{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), } t.Run("should delete zanzana entry for team binding with member permission", func(t *testing.T) { wg.Add(1) teamBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-1", Namespace: "org-1", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-1", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-1", }, Permission: iamv0.TeamBindingTeamPermissionMember, External: false, }, } testMemberDelete := func(ctx context.Context, req *v1.MutateRequest) error { defer wg.Done() require.NotNil(t, req) require.Equal(t, "org-1", req.Namespace) require.NotNil(t, req.Operations) require.Len(t, req.Operations, 1) expectedOperation := &v1.MutateOperation{ Operation: &v1.MutateOperation_DeleteTeamBinding{ DeleteTeamBinding: &v1.DeleteTeamBindingOperation{ SubjectName: "user-1", TeamName: "team-1", Permission: "member", }, }, } require.True(t, containsTeamBindingOperation(req.Operations, expectedOperation)) return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testMemberDelete} b.AfterTeamBindingDelete(&teamBinding, nil) wg.Wait() }) t.Run("should delete zanzana entry for team binding with admin permission", func(t *testing.T) { wg.Add(1) teamBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-2", Namespace: "org-2", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-2", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-2", }, Permission: iamv0.TeamBindingTeamPermissionAdmin, External: true, }, } testAdminDelete := func(ctx context.Context, req *v1.MutateRequest) error { defer wg.Done() require.NotNil(t, req) require.Equal(t, "org-2", req.Namespace) require.NotNil(t, req.Operations) require.Len(t, req.Operations, 1) expectedOperation := &v1.MutateOperation{ Operation: &v1.MutateOperation_DeleteTeamBinding{ DeleteTeamBinding: &v1.DeleteTeamBindingOperation{ SubjectName: "user-2", TeamName: "team-2", Permission: "admin", }, }, } require.True(t, containsTeamBindingOperation(req.Operations, expectedOperation)) return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testAdminDelete} b.AfterTeamBindingDelete(&teamBinding, nil) wg.Wait() }) t.Run("should not delete from zanzana when zClient is nil", func(t *testing.T) { builder := &IdentityAccessManagementAPIBuilder{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), zClient: nil, } teamBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-3", Namespace: "org-3", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-3", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "team-3", }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } // Should not panic or error when zClient is nil builder.AfterTeamBindingDelete(&teamBinding, nil) }) t.Run("should handle empty team name gracefully", func(t *testing.T) { wg.Add(1) // TeamBinding with empty team ref name will be sent to server which will return error teamBinding := iamv0.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding-4", Namespace: "org-4", }, Spec: iamv0.TeamBindingSpec{ Subject: iamv0.TeamBindingspecSubject{ Name: "user-4", }, TeamRef: iamv0.TeamBindingTeamRef{ Name: "", // Empty name will cause server-side error }, Permission: iamv0.TeamBindingTeamPermissionMember, }, } testErrorHandling := func(ctx context.Context, req *v1.MutateRequest) error { defer wg.Done() require.NotNil(t, req) require.NotNil(t, req.Operations) require.Len(t, req.Operations, 1) require.Equal(t, "org-4", req.Namespace) // Operation will have empty team name, which would fail server-side validation require.True(t, containsTeamBindingOperation(req.Operations, &v1.MutateOperation{ Operation: &v1.MutateOperation_DeleteTeamBinding{ DeleteTeamBinding: &v1.DeleteTeamBindingOperation{ SubjectName: "user-4", TeamName: "", Permission: "member", }, }, })) return nil } b.zClient = &FakeZanzanaClient{mutateCallback: testErrorHandling} b.AfterTeamBindingDelete(&teamBinding, nil) wg.Wait() }) } func containsTeamBindingOperation(operations []*v1.MutateOperation, operation *v1.MutateOperation) bool { return slices.ContainsFunc(operations, func(o *v1.MutateOperation) bool { switch operation.Operation.(type) { case *v1.MutateOperation_DeleteTeamBinding: deleteOperation := operation.Operation.(*v1.MutateOperation_DeleteTeamBinding) deleteO, ok := o.Operation.(*v1.MutateOperation_DeleteTeamBinding) if !ok { return false } return deleteO.DeleteTeamBinding.SubjectName == deleteOperation.DeleteTeamBinding.SubjectName && deleteO.DeleteTeamBinding.TeamName == deleteOperation.DeleteTeamBinding.TeamName && deleteO.DeleteTeamBinding.Permission == deleteOperation.DeleteTeamBinding.Permission case *v1.MutateOperation_CreateTeamBinding: createOperation := operation.Operation.(*v1.MutateOperation_CreateTeamBinding) createO, ok := o.Operation.(*v1.MutateOperation_CreateTeamBinding) if !ok { return false } return createO.CreateTeamBinding.SubjectName == createOperation.CreateTeamBinding.SubjectName && createO.CreateTeamBinding.TeamName == createOperation.CreateTeamBinding.TeamName && createO.CreateTeamBinding.Permission == createOperation.CreateTeamBinding.Permission } return false }) }