mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 18:09:58 +00:00
* fix(controller): decode old object for delete requests Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * fix(config): remove usergroups default Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * fix(config): remove usergroups default Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * sec(ghsa-2ww6-hf35-mfjm): intercept namespace subresource Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> --------- Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
326 lines
8.4 KiB
Go
326 lines
8.4 KiB
Go
// Copyright 2020-2026 Project Capsule Authors
|
||
// SPDX-License-Identifier: Apache-2.0
|
||
|
||
package api_test
|
||
|
||
import (
|
||
"math/rand"
|
||
"reflect"
|
||
"sort"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/projectcapsule/capsule/pkg/api"
|
||
)
|
||
|
||
func linearFindUser(list api.UserListSpec, name string, kind api.OwnerKind) (api.UserSpec, bool) {
|
||
for _, u := range list {
|
||
if u.Kind == kind && u.Name == name {
|
||
return u, true
|
||
}
|
||
}
|
||
return api.UserSpec{}, false
|
||
}
|
||
|
||
func slowIsPresent(u api.UserListSpec, name string, groups []string) bool {
|
||
for _, user := range u {
|
||
switch user.Kind {
|
||
case api.UserOwner, api.ServiceAccountOwner:
|
||
if name == user.Name {
|
||
return true
|
||
}
|
||
case api.GroupOwner:
|
||
for _, group := range groups {
|
||
if group == user.Name {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func TestByKindNameOrdering_UserListSpec(t *testing.T) {
|
||
u := api.UserListSpec{
|
||
api.UserSpec{Name: "b", Kind: api.ServiceAccountOwner},
|
||
api.UserSpec{Name: "z", Kind: api.UserOwner},
|
||
api.UserSpec{Name: "a", Kind: api.GroupOwner},
|
||
api.UserSpec{Name: "a", Kind: api.UserOwner},
|
||
}
|
||
|
||
// Sort using production ordering
|
||
got := append(api.UserListSpec(nil), u...)
|
||
sort.Sort(api.ByKindName(got))
|
||
|
||
// Manually sorted expectation using the same logic.
|
||
want := append(api.UserListSpec(nil), u...)
|
||
sort.Slice(want, func(i, j int) bool {
|
||
if want[i].Kind.String() != want[j].Kind.String() {
|
||
return want[i].Kind.String() < want[j].Kind.String()
|
||
}
|
||
return want[i].Name < want[j].Name
|
||
})
|
||
|
||
if len(got) != len(want) {
|
||
t.Fatalf("length mismatch: got %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range got {
|
||
if !reflect.DeepEqual(got[i], want[i]) {
|
||
t.Fatalf("ordering mismatch at %d: got %+v, want %+v", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestFindUser_Randomized(t *testing.T) {
|
||
rnd := rand.New(rand.NewSource(42))
|
||
|
||
ownerKinds := []api.OwnerKind{
|
||
api.GroupOwner,
|
||
api.UserOwner,
|
||
api.ServiceAccountOwner,
|
||
}
|
||
|
||
const (
|
||
numLists = 200
|
||
maxLength = 40
|
||
numLookupsPerList = 80
|
||
)
|
||
|
||
for listIdx := 0; listIdx < numLists; listIdx++ {
|
||
var list api.UserListSpec
|
||
n := rnd.Intn(maxLength)
|
||
for i := 0; i < n; i++ {
|
||
k := ownerKinds[rnd.Intn(len(ownerKinds))]
|
||
list = append(list, api.UserSpec{
|
||
Name: randomName(rnd, 3+rnd.Intn(4)), // length 3–6
|
||
Kind: k,
|
||
})
|
||
}
|
||
|
||
for lookupIdx := 0; lookupIdx < numLookupsPerList; lookupIdx++ {
|
||
var qName string
|
||
var qKind api.OwnerKind
|
||
|
||
if len(list) > 0 && rnd.Float64() < 0.6 {
|
||
// 60% of lookups: pick a real element, must be found
|
||
pick := list[rnd.Intn(len(list))]
|
||
qName = pick.Name
|
||
qKind = pick.Kind
|
||
} else {
|
||
// 40%: random query, may or may not exist
|
||
qName = randomName(rnd, 3+rnd.Intn(4))
|
||
qKind = ownerKinds[rnd.Intn(len(ownerKinds))]
|
||
}
|
||
|
||
listCopy := append(api.UserListSpec(nil), list...) // FindUser sorts in-place
|
||
gotUser, gotFound := listCopy.FindUser(qName, qKind)
|
||
wantUser, wantFound := linearFindUser(list, qName, qKind)
|
||
|
||
if gotFound != wantFound {
|
||
t.Fatalf("list=%d lookup=%d: found mismatch for (%q,%v): got=%v, want=%v",
|
||
listIdx, lookupIdx, qName, qKind, gotFound, wantFound)
|
||
}
|
||
if gotFound && !reflect.DeepEqual(gotUser, wantUser) {
|
||
t.Fatalf("list=%d lookup=%d: user mismatch for (%q,%v):\n got= %+v\nwant= %+v",
|
||
listIdx, lookupIdx, qName, qKind, gotUser, wantUser)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestIsPresent_RandomizedMatchesSlowImplementation(t *testing.T) {
|
||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||
|
||
ownerKinds := []api.OwnerKind{
|
||
api.UserOwner,
|
||
api.GroupOwner,
|
||
api.ServiceAccountOwner,
|
||
}
|
||
|
||
const (
|
||
numLists = 200
|
||
maxOwnersPerList = 30
|
||
numLookupsPerList = 80
|
||
maxGroupsPerUser = 10
|
||
)
|
||
|
||
for listIdx := 0; listIdx < numLists; listIdx++ {
|
||
// Generate a random user list (possibly with duplicates).
|
||
var users api.UserListSpec
|
||
nOwners := rnd.Intn(maxOwnersPerList)
|
||
for i := 0; i < nOwners; i++ {
|
||
kind := ownerKinds[rnd.Intn(len(ownerKinds))]
|
||
users = append(users, api.UserSpec{
|
||
Name: randomName(rnd, 3+rnd.Intn(4)), // length 3–6
|
||
Kind: kind,
|
||
})
|
||
}
|
||
|
||
for lookupIdx := 0; lookupIdx < numLookupsPerList; lookupIdx++ {
|
||
// Generate a random userName and groups,
|
||
// sometimes biased to hit existing owners/groups.
|
||
var userName string
|
||
var groups []string
|
||
|
||
// 50% of the time: pick an existing owner name as userName
|
||
if len(users) > 0 && rnd.Float64() < 0.5 {
|
||
pick := users[rnd.Intn(len(users))]
|
||
userName = pick.Name
|
||
} else {
|
||
userName = randomName(rnd, 3+rnd.Intn(4))
|
||
}
|
||
|
||
// Random groups, sometimes including owner names
|
||
nGroups := rnd.Intn(maxGroupsPerUser)
|
||
for i := 0; i < nGroups; i++ {
|
||
if len(users) > 0 && rnd.Float64() < 0.5 {
|
||
pick := users[rnd.Intn(len(users))]
|
||
groups = append(groups, pick.Name)
|
||
} else {
|
||
groups = append(groups, randomName(rnd, 3+rnd.Intn(4)))
|
||
}
|
||
}
|
||
|
||
got := users.IsPresent(userName, groups)
|
||
want := slowIsPresent(users, userName, groups)
|
||
|
||
if got != want {
|
||
t.Fatalf("list=%d lookup=%d: mismatch\n users=%v\n user=%q\n groups=%v\n optimized=%v\n slow=%v",
|
||
listIdx, lookupIdx, users, userName, groups, got, want)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestGetByKinds_Basic(t *testing.T) {
|
||
users := api.UserListSpec{
|
||
api.UserSpec{Name: "alice", Kind: api.UserOwner},
|
||
api.UserSpec{Name: "svc-1", Kind: api.ServiceAccountOwner},
|
||
api.UserSpec{Name: "team-a", Kind: api.GroupOwner},
|
||
api.UserSpec{Name: "bob", Kind: api.UserOwner},
|
||
api.UserSpec{Name: "team-b", Kind: api.GroupOwner},
|
||
}
|
||
|
||
eqStrings := func(got, want []string) bool {
|
||
if got == nil && want == nil {
|
||
return true
|
||
}
|
||
if len(got) != len(want) {
|
||
return false
|
||
}
|
||
sort.Strings(got)
|
||
sort.Strings(want)
|
||
for i := range got {
|
||
if got[i] != want[i] {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
// Single kind: UserOwner
|
||
gotUsers := users.GetByKinds([]api.OwnerKind{api.UserOwner})
|
||
wantUsers := []string{"alice", "bob"}
|
||
if !eqStrings(gotUsers, wantUsers) {
|
||
t.Fatalf("GetByKinds([UserOwner]) = %v, want %v", gotUsers, wantUsers)
|
||
}
|
||
|
||
// Single kind: GroupOwner
|
||
gotGroups := users.GetByKinds([]api.OwnerKind{api.GroupOwner})
|
||
wantGroups := []string{"team-a", "team-b"}
|
||
if !eqStrings(gotGroups, wantGroups) {
|
||
t.Fatalf("GetByKinds([GroupOwner]) = %v, want %v", gotGroups, wantGroups)
|
||
}
|
||
|
||
// Multiple kinds: UserOwner + ServiceAccountOwner
|
||
gotUsersAndSAs := users.GetByKinds([]api.OwnerKind{api.UserOwner, api.ServiceAccountOwner})
|
||
wantUsersAndSAs := []string{"alice", "bob", "svc-1"}
|
||
if !eqStrings(gotUsersAndSAs, wantUsersAndSAs) {
|
||
t.Fatalf("GetByKinds([UserOwner,ServiceAccountOwner]) = %v, want %v",
|
||
gotUsersAndSAs, wantUsersAndSAs)
|
||
}
|
||
|
||
// No kinds → nil
|
||
gotNone := users.GetByKinds(nil)
|
||
if gotNone != nil {
|
||
t.Fatalf("GetByKinds(nil) = %v, want nil", gotNone)
|
||
}
|
||
|
||
// Kind not present at all
|
||
gotUnknown := users.GetByKinds([]api.OwnerKind{api.OwnerKind("does-not-exist")})
|
||
if gotUnknown != nil {
|
||
t.Fatalf("GetByKinds([unknown]) = %v, want nil", gotUnknown)
|
||
}
|
||
}
|
||
|
||
func TestGetByKinds_Randomized(t *testing.T) {
|
||
rnd := rand.New(rand.NewSource(123))
|
||
|
||
ownerKinds := []api.OwnerKind{
|
||
api.UserOwner,
|
||
api.GroupOwner,
|
||
api.ServiceAccountOwner,
|
||
}
|
||
|
||
const (
|
||
numLists = 200
|
||
maxOwnersPerList = 50
|
||
)
|
||
|
||
for listIdx := 0; listIdx < numLists; listIdx++ {
|
||
var users api.UserListSpec
|
||
n := rnd.Intn(maxOwnersPerList)
|
||
for i := 0; i < n; i++ {
|
||
k := ownerKinds[rnd.Intn(len(ownerKinds))]
|
||
users = append(users, api.UserSpec{
|
||
Name: randomName(rnd, 3+rnd.Intn(4)), // reuse your helper
|
||
Kind: k,
|
||
})
|
||
}
|
||
|
||
// Try several random kind-subsets per list
|
||
for subsetIdx := 0; subsetIdx < 10; subsetIdx++ {
|
||
// Build a random subset of kinds
|
||
var kinds []api.OwnerKind
|
||
for _, k := range ownerKinds {
|
||
if rnd.Float64() < 0.5 {
|
||
kinds = append(kinds, k)
|
||
}
|
||
}
|
||
|
||
got := users.GetByKinds(kinds)
|
||
|
||
// Reference implementation: filter + sort
|
||
kindSet := make(map[api.OwnerKind]struct{}, len(kinds))
|
||
for _, k := range kinds {
|
||
kindSet[k] = struct{}{}
|
||
}
|
||
|
||
var want []string
|
||
if len(kinds) > 0 {
|
||
for _, u := range users {
|
||
if _, ok := kindSet[u.Kind]; ok {
|
||
want = append(want, u.Name)
|
||
}
|
||
}
|
||
}
|
||
|
||
if len(want) == 0 {
|
||
if got != nil {
|
||
t.Fatalf("list=%d subset=%d: expected nil, got %v (kinds=%v, users=%v)",
|
||
listIdx, subsetIdx, got, kinds, users)
|
||
}
|
||
continue
|
||
}
|
||
|
||
sort.Strings(want)
|
||
sort.Strings(got)
|
||
|
||
if !reflect.DeepEqual(got, want) {
|
||
t.Fatalf("list=%d subset=%d: GetByKinds mismatch\n kinds=%v\n got= %v\n want= %v",
|
||
listIdx, subsetIdx, kinds, got, want)
|
||
}
|
||
}
|
||
}
|
||
}
|