knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/testing/testing.go (about)

     1  /*
     2  Copyright 2019 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package testing
    18  
    19  import (
    20  	"encoding/json"
    21  	"sort"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/google/go-cmp/cmp/cmpopts"
    27  	jsonpatch "gomodules.xyz/jsonpatch/v2"
    28  	admissionv1 "k8s.io/api/admission/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"knative.dev/pkg/system"
    31  	pkgtest "knative.dev/pkg/testing"
    32  
    33  	// Makes system.Namespace work in tests.
    34  	_ "knative.dev/pkg/system/testing"
    35  )
    36  
    37  // CreateResource creates a testing.Resource with the given name in the system namespace.
    38  func CreateResource(name string) *pkgtest.Resource {
    39  	return &pkgtest.Resource{
    40  		TypeMeta: metav1.TypeMeta{
    41  			Kind:       "Resource",
    42  			APIVersion: "v1",
    43  		},
    44  		ObjectMeta: metav1.ObjectMeta{
    45  			Namespace: system.Namespace(),
    46  			Name:      name,
    47  		},
    48  		Spec: pkgtest.ResourceSpec{
    49  			FieldWithValidation: "magic value",
    50  		},
    51  	}
    52  }
    53  
    54  // ExpectAllowed checks that a given admission response allows the initiating request through.
    55  func ExpectAllowed(t *testing.T, resp *admissionv1.AdmissionResponse) {
    56  	t.Helper()
    57  	if !resp.Allowed {
    58  		t.Errorf("Expected allowed, but failed with %+v", resp.Result)
    59  	}
    60  }
    61  
    62  // ExpectFailsWith checks that a given admission response disallows the initiating request
    63  // through and contains the provided string in its error message.
    64  func ExpectFailsWith(t *testing.T, resp *admissionv1.AdmissionResponse, contains string) {
    65  	t.Helper()
    66  	if resp.Allowed {
    67  		t.Error("Expected denial, got allowed")
    68  		return
    69  	}
    70  	if !strings.Contains(resp.Result.Message, contains) {
    71  		t.Errorf("Expected failure containing %q got %q", contains, resp.Result.Message)
    72  	}
    73  }
    74  
    75  // ExpectWarnsWith checks that a given admission response warns on the initiating request
    76  // containing the provided string in its warning message.
    77  func ExpectWarnsWith(t *testing.T, resp *admissionv1.AdmissionResponse, contains string) {
    78  	t.Helper()
    79  	found := false
    80  	for _, warning := range resp.Warnings {
    81  		if strings.Contains(warning, contains) {
    82  			found = true
    83  		}
    84  	}
    85  	if !found {
    86  		t.Errorf("Expected warning containing %q got %v", contains, resp.Warnings)
    87  	}
    88  }
    89  
    90  // ExpectPatches checks that the provided serialized bytes consist of an expected
    91  // collection of patches.  This is used to verify the mutations made in a mutating
    92  // admission webhook's response.
    93  func ExpectPatches(t *testing.T, a []byte, e []jsonpatch.JsonPatchOperation) {
    94  	t.Helper()
    95  	var got []jsonpatch.JsonPatchOperation
    96  
    97  	err := json.Unmarshal(a, &got)
    98  	if err != nil {
    99  		t.Error("Failed to unmarshal patches:", err)
   100  		return
   101  	}
   102  
   103  	// Give the patch a deterministic ordering.
   104  	// Technically this can change the meaning, but the ordering is otherwise unstable
   105  	// and difficult to test.
   106  	sort.Slice(e, func(i, j int) bool {
   107  		lhs, rhs := e[i], e[j]
   108  		if lhs.Operation != rhs.Operation {
   109  			return lhs.Operation < rhs.Operation
   110  		}
   111  		return lhs.Path < rhs.Path
   112  	})
   113  	sort.Slice(got, func(i, j int) bool {
   114  		lhs, rhs := got[i], got[j]
   115  		if lhs.Operation != rhs.Operation {
   116  			return lhs.Operation < rhs.Operation
   117  		}
   118  		return lhs.Path < rhs.Path
   119  	})
   120  
   121  	// Even though diff is useful, seeing the whole objects
   122  	// one under another helps a lot.
   123  	t.Logf("Got Patches:  %#v", got)
   124  	t.Logf("Want Patches: %#v", e)
   125  	if diff := cmp.Diff(e, got, cmpopts.EquateEmpty()); diff != "" {
   126  		t.Log("diff Patches:", diff)
   127  		t.Error("ExpectPatches (-want, +got) =", diff)
   128  	}
   129  }