// Copyright 2020-2026 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 package utils_test import ( "testing" rbacv1 "k8s.io/api/rbac/v1" "github.com/projectcapsule/capsule/pkg/api/rbac" "github.com/projectcapsule/capsule/pkg/utils" ) func TestRoleBindingHashFunc_Deterministic(t *testing.T) { b := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{ {Kind: "User", Name: "alice"}, {Kind: "Group", Name: "devops"}, }, } h1 := utils.RoleBindingHashFunc(b) h2 := utils.RoleBindingHashFunc(b) if h1 != h2 { t.Fatalf("expected deterministic hash, got %q and %q", h1, h2) } if h1 == "" { t.Fatalf("expected non-empty hash") } } func TestRoleBindingHashFunc_ChangesWhenClusterRoleChanges(t *testing.T) { b1 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{{Kind: "User", Name: "alice"}}, } b2 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "view", Subjects: []rbacv1.Subject{{Kind: "User", Name: "alice"}}, } h1 := utils.RoleBindingHashFunc(b1) h2 := utils.RoleBindingHashFunc(b2) if h1 == h2 { t.Fatalf("expected different hashes when ClusterRoleName changes, got %q", h1) } } func TestRoleBindingHashFunc_ChangesWhenSubjectKindChanges(t *testing.T) { b1 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{{Kind: "User", Name: "alice"}}, } b2 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{{Kind: "Group", Name: "alice"}}, } h1 := utils.RoleBindingHashFunc(b1) h2 := utils.RoleBindingHashFunc(b2) if h1 == h2 { t.Fatalf("expected different hashes when subject Kind changes, got %q", h1) } } func TestRoleBindingHashFunc_ChangesWhenSubjectNameChanges(t *testing.T) { b1 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{{Kind: "User", Name: "alice"}}, } b2 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{{Kind: "User", Name: "bob"}}, } h1 := utils.RoleBindingHashFunc(b1) h2 := utils.RoleBindingHashFunc(b2) if h1 == h2 { t.Fatalf("expected different hashes when subject Name changes, got %q", h1) } } func TestRoleBindingHashFunc_ChangesWhenSubjectNamespaceChanges(t *testing.T) { b1 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{{ Kind: rbacv1.ServiceAccountKind, Namespace: "team-a", Name: "deployer", }}, } b2 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{{ Kind: rbacv1.ServiceAccountKind, Namespace: "team-b", Name: "deployer", }}, } h1 := utils.RoleBindingHashFunc(b1) h2 := utils.RoleBindingHashFunc(b2) if h1 == h2 { t.Fatalf("expected different hashes when subject Namespace changes, got %q", h1) } } func TestRoleBindingHashFunc_ChangesWhenSubjectAPIGroupChanges(t *testing.T) { b1 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{{ APIGroup: rbacv1.GroupName, Kind: rbacv1.UserKind, Name: "alice", }}, } b2 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{{ APIGroup: "example.com", Kind: rbacv1.UserKind, Name: "alice", }}, } h1 := utils.RoleBindingHashFunc(b1) h2 := utils.RoleBindingHashFunc(b2) if h1 == h2 { t.Fatalf("expected different hashes when subject APIGroup changes, got %q", h1) } } func TestRoleBindingHashFunc_UsesUnambiguousFieldBoundaries(t *testing.T) { b1 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "ab", Subjects: []rbacv1.Subject{{Kind: "c", Name: "d"}}, } b2 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "a", Subjects: []rbacv1.Subject{{Kind: "bc", Name: "d"}}, } h1 := utils.RoleBindingHashFunc(b1) h2 := utils.RoleBindingHashFunc(b2) if h1 == h2 { t.Fatalf("expected different hashes for differently bounded fields, got %q", h1) } } func TestRoleBindingHashFunc_EmptyInputsStillProduceHash(t *testing.T) { b := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "", Subjects: nil, } h := utils.RoleBindingHashFunc(b) if h == "" { t.Fatalf("expected non-empty hash even for empty input") } } func TestRoleBindingHashFunc_SubjectOrderMatters(t *testing.T) { b1 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{ {Kind: "User", Name: "alice"}, {Kind: "Group", Name: "devops"}, }, } b2 := rbac.AdditionalRoleBindingsSpec{ ClusterRoleName: "admin", Subjects: []rbacv1.Subject{ {Kind: "Group", Name: "devops"}, {Kind: "User", Name: "alice"}, }, } h1 := utils.RoleBindingHashFunc(b1) h2 := utils.RoleBindingHashFunc(b2) if h1 == h2 { t.Fatalf("expected different hashes when subject order changes, got %q", h1) } }