// Copyright 2020-2026 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 package admission_test import ( "bytes" "testing" "github.com/projectcapsule/capsule/pkg/runtime/admission" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" ) func TestValidateWebhookClientConfig(t *testing.T) { t.Parallel() webhookURL := "https://capsule.example.com" service := &admissionregistrationv1.ServiceReference{ Name: "capsule-webhook-service", Namespace: "capsule-system", } tests := []struct { name string client *admissionregistrationv1.WebhookClientConfig wantErr bool }{ {name: "nil", wantErr: true}, {name: "neither", client: &admissionregistrationv1.WebhookClientConfig{}, wantErr: true}, { name: "url", client: &admissionregistrationv1.WebhookClientConfig{URL: &webhookURL}, }, { name: "service", client: &admissionregistrationv1.WebhookClientConfig{Service: service}, }, { name: "both", client: &admissionregistrationv1.WebhookClientConfig{ URL: &webhookURL, Service: service, }, wantErr: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() err := admission.ValidateWebhookClientConfig(test.client) if (err != nil) != test.wantErr { t.Fatalf("ValidateWebhookClientConfig() error = %v, wantErr %t", err, test.wantErr) } }) } } func TestDynamicClientWithPath_EmptyPath_NoChange(t *testing.T) { t.Parallel() origURL := "https://example.com" in := admissionregistrationv1.WebhookClientConfig{ URL: &origURL, CABundle: []byte("ca"), } out := admission.DynamicClientWithPath(in, "") if out.URL == nil || *out.URL != origURL { t.Fatalf("URL changed unexpectedly: got=%v want=%q", ptrStr(out.URL), origURL) } if out.Service != nil { t.Fatalf("Service changed unexpectedly: got=%#v want=nil", out.Service) } if !bytes.Equal(out.CABundle, in.CABundle) { t.Fatalf("CABundle changed unexpectedly: got=%q want=%q", out.CABundle, in.CABundle) } } func TestDynamicClientWithPath_URLMode_AppendsNormalizedPath(t *testing.T) { t.Parallel() tests := []struct { name string base string path string want string }{ {"no_trailing_slash_path_with_leading", "https://example.com", "/validate", "https://example.com/validate"}, {"no_trailing_slash_path_without_leading", "https://example.com", "validate", "https://example.com/validate"}, {"trailing_slash_base", "https://example.com/", "/validate", "https://example.com/validate"}, {"multiple_slashes_path", "https://example.com/", "///a//b/", "https://example.com/a/b"}, {"root_path", "https://example.com/", "/", "https://example.com/"}, {"only_slashes_path", "https://example.com", "////", "https://example.com/"}, {"dot_segments", "https://example.com", "/a/./b/../c", "https://example.com/a/c"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() inURL := tt.base in := admissionregistrationv1.WebhookClientConfig{ URL: &inURL, } out := admission.DynamicClientWithPath(in, tt.path) if out.URL == nil { t.Fatalf("URL is nil, want %q", tt.want) } if *out.URL != tt.want { t.Fatalf("URL = %q, want %q", *out.URL, tt.want) } }) } } func TestDynamicClientWithPath_URLMode_DoesNotMutateInput(t *testing.T) { t.Parallel() orig := "https://example.com/" in := admissionregistrationv1.WebhookClientConfig{URL: &orig} out := admission.DynamicClientWithPath(in, "/validate") // output should differ if out.URL == nil || *out.URL != "https://example.com/validate" { t.Fatalf("unexpected output URL: got=%v", ptrStr(out.URL)) } // input must remain unchanged if in.URL == nil || *in.URL != "https://example.com/" { t.Fatalf("input was mutated: in.URL=%v", ptrStr(in.URL)) } } func TestDynamicClientWithPath_ServiceMode_SetsServicePath(t *testing.T) { t.Parallel() in := admissionregistrationv1.WebhookClientConfig{ Service: &admissionregistrationv1.ServiceReference{ Namespace: "ns", Name: "svc", }, } out := admission.DynamicClientWithPath(in, "validate") if out.Service == nil || out.Service.Path == nil { t.Fatalf("Service/Path not set: %#v", out.Service) } if *out.Service.Path != "/validate" { t.Fatalf("Service.Path = %q, want %q", *out.Service.Path, "/validate") } // other fields preserved if out.Service.Namespace != "ns" || out.Service.Name != "svc" { t.Fatalf("Service fields changed unexpectedly: %#v", out.Service) } } func TestDynamicClientWithPath_ServiceMode_DoesNotMutateInputService(t *testing.T) { t.Parallel() in := admissionregistrationv1.WebhookClientConfig{ Service: &admissionregistrationv1.ServiceReference{ Namespace: "ns", Name: "svc", }, } // capture pointer identity origSvcPtr := in.Service out := admission.DynamicClientWithPath(in, "/validate") if out.Service == nil || out.Service.Path == nil || *out.Service.Path != "/validate" { t.Fatalf("unexpected out.Service: %#v", out.Service) } // Must not mutate input's service struct if in.Service == nil { t.Fatalf("input service became nil") } if in.Service.Path != nil { t.Fatalf("input service was mutated; expected Path=nil, got=%q", *in.Service.Path) } // Additionally, output must not reuse the same *ServiceReference pointer // (since we copy it to avoid aliasing). if out.Service == origSvcPtr { t.Fatalf("output service pointer aliases input; expected copy") } } func TestDynamicClientWithPath_URLTakesPrecedenceOverService(t *testing.T) { t.Parallel() origURL := "https://example.com/" in := admissionregistrationv1.WebhookClientConfig{ URL: &origURL, Service: &admissionregistrationv1.ServiceReference{ Namespace: "ns", Name: "svc", }, } out := admission.DynamicClientWithPath(in, "/validate") // URL mode should win if out.URL == nil || *out.URL != "https://example.com/validate" { t.Fatalf("URL = %v, want %q", ptrStr(out.URL), "https://example.com/validate") } // Ensure we did not set Service.Path in output when URL used (function returns early) // Service itself is shallow-copied, so it will be the same pointer as input here. // Importantly: it must not have been mutated. if out.Service == nil { t.Fatalf("Service unexpectedly nil; function should keep it as-is in URL mode") } if out.Service.Path != nil { t.Fatalf("Service.Path was unexpectedly set in URL mode: %q", *out.Service.Path) } } func TestDynamicClientWithPath_NoURLNoService_NoPanicNoChange(t *testing.T) { t.Parallel() in := admissionregistrationv1.WebhookClientConfig{} out := admission.DynamicClientWithPath(in, "/validate") // No URL/Service to apply to => should be unchanged other than shallow copy. if out.URL != nil { t.Fatalf("expected URL nil, got %v", ptrStr(out.URL)) } if out.Service != nil { t.Fatalf("expected Service nil, got %#v", out.Service) } } func TestDynamicClientWithPath_PreservesCABundle(t *testing.T) { t.Parallel() origURL := "https://example.com" in := admissionregistrationv1.WebhookClientConfig{ URL: &origURL, CABundle: []byte("my-ca"), } out := admission.DynamicClientWithPath(in, "/validate") if !bytes.Equal(out.CABundle, []byte("my-ca")) { t.Fatalf("CABundle not preserved: got=%q want=%q", out.CABundle, "my-ca") } } func TestDynamicClientWithPath_Idempotent(t *testing.T) { t.Parallel() origURL := "https://example.com/" in := admissionregistrationv1.WebhookClientConfig{URL: &origURL} first := admission.DynamicClientWithPath(in, "/a//b/") second := admission.DynamicClientWithPath(first, "/a//b/") if first.URL == nil || second.URL == nil { t.Fatalf("unexpected nil URL: first=%v second=%v", ptrStr(first.URL), ptrStr(second.URL)) } if *first.URL != *second.URL { t.Fatalf("not idempotent: first=%q second=%q", *first.URL, *second.URL) } } func ptrStr(p *string) string { if p == nil { return "" } return *p }