// 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) } }) } }) }