Files
capsule/pkg/runtime/client/patch_test.go
Oliver Bähler a6b830b1af feat: add ruleset api(#1844)
* 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>
2026-01-27 14:28:48 +01:00

425 lines
11 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package client_test
import (
"fmt"
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/projectcapsule/capsule/pkg/runtime/client"
)
func TestAddLabelsPatch_MapInput(t *testing.T) {
t.Run("nil labels => add op", func(t *testing.T) {
var labels map[string]string // nil
patches := client.AddLabelsPatch(labels, map[string]string{
"a": "1",
})
want := []client.JSONPatch{
{Operation: "add", Path: "/metadata/labels", Value: map[string]string{}},
{Operation: "add", Path: "/metadata/labels/a", Value: "1"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
t.Run("existing key same value => no patch", func(t *testing.T) {
labels := map[string]string{"a": "1"}
patches := client.AddLabelsPatch(labels, map[string]string{
"a": "1",
})
if len(patches) != 0 {
t.Fatalf("expected no patches, got %v", patches)
}
})
t.Run("existing key different value => replace op", func(t *testing.T) {
labels := map[string]string{"a": "1"}
patches := client.AddLabelsPatch(labels, map[string]string{
"a": "2",
})
want := []client.JSONPatch{
{Operation: "replace", Path: "/metadata/labels/a", Value: "2"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
t.Run("missing key => add op", func(t *testing.T) {
labels := map[string]string{"a": "1"}
patches := client.AddLabelsPatch(labels, map[string]string{
"b": "2",
})
want := []client.JSONPatch{
{Operation: "add", Path: "/metadata/labels/b", Value: "2"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
t.Run("key contains slash => path escaped with ~1", func(t *testing.T) {
labels := map[string]string{}
patches := client.AddLabelsPatch(labels, map[string]string{
"projectcapsule.dev/tenant": "wind",
})
want := []client.JSONPatch{
{Operation: "add", Path: "/metadata/labels/projectcapsule.dev~1tenant", Value: "wind"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
}
func TestAddAnnotationsPatch_MapInput(t *testing.T) {
t.Run("nil annotations => add op", func(t *testing.T) {
var annotations map[string]string // nil
patches := client.AddAnnotationsPatch(annotations, map[string]string{
"a": "1",
})
want := []client.JSONPatch{
{Operation: "add", Path: "/metadata/annotations", Value: map[string]string{}},
{Operation: "add", Path: "/metadata/annotations/a", Value: "1"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
t.Run("existing key same value => no patch", func(t *testing.T) {
annotations := map[string]string{"a": "1"}
patches := client.AddAnnotationsPatch(annotations, map[string]string{
"a": "1",
})
if len(patches) != 0 {
t.Fatalf("expected no patches, got %v", patches)
}
})
t.Run("existing key different value => replace op", func(t *testing.T) {
annotations := map[string]string{"a": "1"}
patches := client.AddAnnotationsPatch(annotations, map[string]string{
"a": "2",
})
want := []client.JSONPatch{
{Operation: "replace", Path: "/metadata/annotations/a", Value: "2"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
t.Run("missing key => add op", func(t *testing.T) {
annotations := map[string]string{"a": "1"}
patches := client.AddAnnotationsPatch(annotations, map[string]string{
"b": "2",
})
want := []client.JSONPatch{
{Operation: "add", Path: "/metadata/annotations/b", Value: "2"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
t.Run("key contains slash => path escaped with ~1", func(t *testing.T) {
annotations := map[string]string{}
patches := client.AddAnnotationsPatch(annotations, map[string]string{
"example.com/foo": "bar",
})
want := []client.JSONPatch{
{Operation: "add", Path: "/metadata/annotations/example.com~1foo", Value: "bar"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
}
func TestPatchRemoveLabels_MapInput(t *testing.T) {
t.Run("nil labels => no patch", func(t *testing.T) {
var labels map[string]string // nil
patches := client.PatchRemoveLabels(labels, []string{"a"})
if len(patches) != 0 {
t.Fatalf("expected no patches, got %v", patches)
}
})
t.Run("existing key => remove patch", func(t *testing.T) {
labels := map[string]string{"a": "1"}
patches := client.PatchRemoveLabels(labels, []string{"a"})
want := []client.JSONPatch{
{Operation: "remove", Path: "/metadata/labels/a"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
t.Run("missing key => no patch", func(t *testing.T) {
labels := map[string]string{"a": "1"}
patches := client.PatchRemoveLabels(labels, []string{"nope"})
if len(patches) != 0 {
t.Fatalf("expected no patches, got %v", patches)
}
})
t.Run("key contains slash => path escaped with ~1", func(t *testing.T) {
labels := map[string]string{"projectcapsule.dev/tenant": "wind"}
patches := client.PatchRemoveLabels(labels, []string{"projectcapsule.dev/tenant"})
want := []client.JSONPatch{
{Operation: "remove", Path: "/metadata/labels/projectcapsule.dev~1tenant"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
}
func TestPatchRemoveAnnotations_MapInput(t *testing.T) {
t.Run("nil annotations => no patch", func(t *testing.T) {
var annotations map[string]string // nil
patches := client.PatchRemoveAnnotations(annotations, []string{"a"})
if len(patches) != 0 {
t.Fatalf("expected no patches, got %v", patches)
}
})
t.Run("existing key => remove patch", func(t *testing.T) {
annotations := map[string]string{"a": "1"}
patches := client.PatchRemoveAnnotations(annotations, []string{"a"})
want := []client.JSONPatch{
{Operation: "remove", Path: "/metadata/annotations/a"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
t.Run("missing key => no patch", func(t *testing.T) {
annotations := map[string]string{"a": "1"}
patches := client.PatchRemoveAnnotations(annotations, []string{"nope"})
if len(patches) != 0 {
t.Fatalf("expected no patches, got %v", patches)
}
})
t.Run("key contains slash => path escaped with ~1", func(t *testing.T) {
annotations := map[string]string{"example.com/foo": "bar"}
patches := client.PatchRemoveAnnotations(annotations, []string{"example.com/foo"})
want := []client.JSONPatch{
{Operation: "remove", Path: "/metadata/annotations/example.com~1foo"},
}
if !reflect.DeepEqual(patches, want) {
t.Fatalf("unexpected patches\nwant=%v\ngot =%v", want, patches)
}
})
}
func TestRemoveOwnerReferencePatch(t *testing.T) {
t.Parallel()
mkRef := func(name, uid string, controller, block bool) metav1.OwnerReference {
c := controller
b := block
return metav1.OwnerReference{
APIVersion: "v1",
Kind: "ConfigMap",
Name: name,
UID: types.UID(uid),
Controller: &c,
BlockOwnerDeletion: &b,
}
}
t.Run("nil toRemove returns nil", func(t *testing.T) {
t.Parallel()
refs := []metav1.OwnerReference{mkRef("a", "uid-a", true, true)}
got := client.RemoveOwnerReferencePatch(refs, nil)
if got != nil {
t.Fatalf("expected nil, got %#v", got)
}
})
t.Run("empty ownerRefs returns nil", func(t *testing.T) {
t.Parallel()
toRemove := mkRef("a", "uid-a", true, true)
got := client.RemoveOwnerReferencePatch(nil, &toRemove)
if got != nil {
t.Fatalf("expected nil, got %#v", got)
}
got = client.RemoveOwnerReferencePatch([]metav1.OwnerReference{}, &toRemove)
if got != nil {
t.Fatalf("expected nil, got %#v", got)
}
})
t.Run("no matching ownerReference returns nil", func(t *testing.T) {
t.Parallel()
refs := []metav1.OwnerReference{
mkRef("a", "uid-a", true, true),
mkRef("b", "uid-b", false, false),
}
// Different UID and name/kind => should not match
toRemove := mkRef("c", "uid-c", true, true)
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
if got != nil {
t.Fatalf("expected nil, got %#v", got)
}
})
t.Run("match in middle returns single remove patch with correct index", func(t *testing.T) {
t.Parallel()
refs := []metav1.OwnerReference{
mkRef("a", "uid-a", true, true),
mkRef("b", "uid-b", false, false),
mkRef("c", "uid-c", true, false),
}
// Make toRemove identical to refs[1] so LooseOwnerReferenceEqual is true.
toRemove := refs[1]
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
if got == nil {
t.Fatalf("expected patches, got nil")
}
if len(got) != 1 {
t.Fatalf("expected 1 patch, got %d: %#v", len(got), got)
}
if got[0].Operation != "remove" {
t.Fatalf("expected op=remove, got %q", got[0].Operation)
}
wantPath := "/metadata/ownerReferences/1"
if got[0].Path != wantPath {
t.Fatalf("expected path=%q, got %q", wantPath, got[0].Path)
}
})
t.Run("match first occurrence only", func(t *testing.T) {
t.Parallel()
// Duplicate entries (shouldn't happen, but function breaks on first match).
ref := mkRef("dup", "uid-dup", true, true)
refs := []metav1.OwnerReference{ref, ref}
toRemove := ref
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
if got == nil || len(got) != 1 {
t.Fatalf("expected 1 patch, got %#v", got)
}
wantPath := "/metadata/ownerReferences/0"
if got[0].Path != wantPath {
t.Fatalf("expected path=%q, got %q", wantPath, got[0].Path)
}
})
t.Run("single ownerRef match returns remove element patch AND remove field patch", func(t *testing.T) {
t.Parallel()
only := mkRef("only", "uid-only", true, true)
refs := []metav1.OwnerReference{only}
toRemove := only
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
if got == nil {
t.Fatalf("expected patches, got nil")
}
if len(got) != 2 {
t.Fatalf("expected 2 patches, got %d: %#v", len(got), got)
}
if got[0].Operation != "remove" || got[0].Path != "/metadata/ownerReferences/0" {
t.Fatalf("unexpected first patch: %#v", got[0])
}
if got[1].Operation != "remove" || got[1].Path != "/metadata/ownerReferences" {
t.Fatalf("unexpected second patch: %#v", got[1])
}
})
t.Run("index in path is correct for each position", func(t *testing.T) {
t.Parallel()
refs := []metav1.OwnerReference{
mkRef("a", "uid-a", true, true),
mkRef("b", "uid-b", true, true),
mkRef("c", "uid-c", true, true),
}
for i := range refs {
i := i
t.Run(fmt.Sprintf("match index %d", i), func(t *testing.T) {
t.Parallel()
toRemove := refs[i]
got := client.RemoveOwnerReferencePatch(refs, &toRemove)
if got == nil || len(got) != 1 {
t.Fatalf("expected 1 patch, got %#v", got)
}
wantPath := fmt.Sprintf("/metadata/ownerReferences/%d", i)
if got[0].Path != wantPath {
t.Fatalf("expected path=%q, got %q", wantPath, got[0].Path)
}
})
}
})
}