// Copyright 2020-2026 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 package v1beta2_test import ( "reflect" "testing" rbacv1 "k8s.io/api/rbac/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" capsulerbac "github.com/projectcapsule/capsule/pkg/api/rbac" ) func testTenant() *capsulev1beta2.Tenant { return &capsulev1beta2.Tenant{ Spec: capsulev1beta2.TenantSpec{ AdditionalRoleBindings: []capsulerbac.AdditionalRoleBindingsSpec{ { ClusterRoleName: "developer", Subjects: []rbacv1.Subject{ {Kind: "User", Name: "user2"}, {Kind: "Group", Name: "group1"}, }, }, { ClusterRoleName: "cluster-admin", Subjects: []rbacv1.Subject{ {Kind: "User", Name: "user3"}, {Kind: "Group", Name: "group1"}, }, }, { ClusterRoleName: "deployer", Subjects: []rbacv1.Subject{ {Kind: "ServiceAccount", Name: "system:serviceaccount:argocd:argo-operator"}, }, }, }, }, Status: capsulev1beta2.TenantStatus{ Owners: capsulerbac.OwnerStatusListSpec{ { UserSpec: capsulerbac.UserSpec{ Kind: capsulerbac.UserOwner, Name: "user1", }, ClusterRoles: []string{"cluster-admin", "read-only"}, }, { UserSpec: capsulerbac.UserSpec{ Kind: capsulerbac.GroupOwner, Name: "group1", }, ClusterRoles: []string{"edit"}, }, { UserSpec: capsulerbac.UserSpec{ Kind: capsulerbac.ServiceAccountOwner, Name: "service", }, ClusterRoles: []string{"read-only"}, }, }, }, } } func TestGetSubjectsByClusterRoles(t *testing.T) { t.Parallel() tenant := testTenant() expected := map[string][]rbacv1.Subject{ "cluster-admin": { {Kind: "User", Name: "user1"}, {Kind: "User", Name: "user3"}, {Kind: "Group", Name: "group1"}, }, "read-only": { {Kind: "User", Name: "user1"}, {Kind: "ServiceAccount", Name: "service"}, }, "edit": { {Kind: "Group", Name: "group1"}, }, "developer": { {Kind: "User", Name: "user2"}, {Kind: "Group", Name: "group1"}, }, "deployer": { {Kind: "ServiceAccount", Name: "system:serviceaccount:argocd:argo-operator"}, }, } got := tenant.GetSubjectsByClusterRoles(nil) if !reflect.DeepEqual(got, expected) { t.Fatalf("expected %#v\n got %#v", expected, got) } expectedIgnored := map[string][]rbacv1.Subject{ "cluster-admin": { {Kind: "User", Name: "user1"}, {Kind: "User", Name: "user3"}, {Kind: "Group", Name: "group1"}, }, "read-only": { {Kind: "User", Name: "user1"}, }, "edit": { {Kind: "Group", Name: "group1"}, }, "developer": { {Kind: "User", Name: "user2"}, {Kind: "Group", Name: "group1"}, }, } gotIgnored := tenant.GetSubjectsByClusterRoles([]capsulerbac.OwnerKind{capsulerbac.ServiceAccountOwner}) if !reflect.DeepEqual(gotIgnored, expectedIgnored) { t.Fatalf("expected %#v\n got %#v", expectedIgnored, gotIgnored) } } func TestGetClusterRolesBySubjectSorted(t *testing.T) { t.Parallel() tenant := &capsulev1beta2.Tenant{ Spec: capsulev1beta2.TenantSpec{ AdditionalRoleBindings: []capsulerbac.AdditionalRoleBindingsSpec{ { ClusterRoleName: "deployer", Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", Name: "system:serviceaccount:argocd:argo-operator", }, }, }, { ClusterRoleName: "developer", Subjects: []rbacv1.Subject{ { Kind: "Group", Name: "group1", }, }, }, }, }, Status: capsulev1beta2.TenantStatus{ Owners: capsulerbac.OwnerStatusListSpec{ { UserSpec: capsulerbac.UserSpec{ Kind: capsulerbac.UserOwner, Name: "user1", }, ClusterRoles: []string{"cluster-admin", "read-only"}, }, { UserSpec: capsulerbac.UserSpec{ Kind: capsulerbac.UserOwner, Name: "user2", }, ClusterRoles: []string{"developer"}, }, { UserSpec: capsulerbac.UserSpec{ Kind: capsulerbac.UserOwner, Name: "user3", }, ClusterRoles: []string{"cluster-admin"}, }, { UserSpec: capsulerbac.UserSpec{ Kind: capsulerbac.GroupOwner, Name: "group1", }, ClusterRoles: []string{"edit", "developer", "cluster-admin"}, }, { UserSpec: capsulerbac.UserSpec{ Kind: capsulerbac.ServiceAccountOwner, Name: "service", }, ClusterRoles: []string{"read-only"}, }, }, }, } expected := []capsulerbac.SubjectRoles{ {Kind: "Group", Name: "group1", Roles: []string{"cluster-admin", "developer", "edit"}}, {Kind: "ServiceAccount", Name: "service", Roles: []string{"read-only"}}, {Kind: "ServiceAccount", Name: "system:serviceaccount:argocd:argo-operator", Roles: []string{"deployer"}}, {Kind: "User", Name: "user1", Roles: []string{"cluster-admin", "read-only"}}, {Kind: "User", Name: "user2", Roles: []string{"developer"}}, {Kind: "User", Name: "user3", Roles: []string{"cluster-admin"}}, } t.Run("includes all kinds and is deterministic, deduped and sorted", func(t *testing.T) { t.Parallel() got := tenant.GetClusterRolesBySubject(nil) if !reflect.DeepEqual(got, expected) { t.Fatalf("expected %#v\n got %#v", expected, got) } }) t.Run("ignores ServiceAccount kind", func(t *testing.T) { t.Parallel() got := tenant.GetClusterRolesBySubject([]capsulerbac.OwnerKind{capsulerbac.ServiceAccountOwner}) expectedIgnored := []capsulerbac.SubjectRoles{ {Kind: "Group", Name: "group1", Roles: []string{"cluster-admin", "developer", "edit"}}, {Kind: "User", Name: "user1", Roles: []string{"cluster-admin", "read-only"}}, {Kind: "User", Name: "user2", Roles: []string{"developer"}}, {Kind: "User", Name: "user3", Roles: []string{"cluster-admin"}}, } if !reflect.DeepEqual(got, expectedIgnored) { t.Fatalf("expected %#v\n got %#v", expectedIgnored, got) } }) t.Run("empty tenant yields empty slice", func(t *testing.T) { t.Parallel() empty := &capsulev1beta2.Tenant{} got := empty.GetClusterRolesBySubject(nil) if len(got) != 0 { t.Fatalf("expected empty, got %#v", got) } }) }