k8s.io/client-go@v0.22.2/metadata/fake/simple_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes 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 fake
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"k8s.io/apimachinery/pkg/api/equality"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	"k8s.io/apimachinery/pkg/util/diff"
    30  )
    31  
    32  const (
    33  	testGroup      = "testgroup"
    34  	testVersion    = "testversion"
    35  	testResource   = "testkinds"
    36  	testNamespace  = "testns"
    37  	testName       = "testname"
    38  	testKind       = "TestKind"
    39  	testAPIVersion = "testgroup/testversion"
    40  )
    41  
    42  var scheme *runtime.Scheme
    43  
    44  func init() {
    45  	scheme = runtime.NewScheme()
    46  	metav1.AddMetaToScheme(scheme)
    47  }
    48  
    49  func newPartialObjectMetadata(apiVersion, kind, namespace, name string) *metav1.PartialObjectMetadata {
    50  	return &metav1.PartialObjectMetadata{
    51  		TypeMeta: metav1.TypeMeta{
    52  			APIVersion: apiVersion,
    53  			Kind:       kind,
    54  		},
    55  		ObjectMeta: metav1.ObjectMeta{
    56  			Namespace: namespace,
    57  			Name:      name,
    58  		},
    59  	}
    60  }
    61  
    62  func newPartialObjectMetadataWithAnnotations(annotations map[string]string) *metav1.PartialObjectMetadata {
    63  	u := newPartialObjectMetadata(testAPIVersion, testKind, testNamespace, testName)
    64  	u.Annotations = annotations
    65  	return u
    66  }
    67  
    68  func TestList(t *testing.T) {
    69  	client := NewSimpleMetadataClient(scheme,
    70  		newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
    71  		newPartialObjectMetadata("group2/version", "TheKind", "ns-foo", "name2-foo"),
    72  		newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
    73  		newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-baz"),
    74  		newPartialObjectMetadata("group2/version", "TheKind", "ns-foo", "name2-baz"),
    75  	)
    76  	listFirst, err := client.Resource(schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"}).List(context.TODO(), metav1.ListOptions{})
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  
    81  	expected := []metav1.PartialObjectMetadata{
    82  		*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
    83  		*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-baz"),
    84  		*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
    85  	}
    86  	if !equality.Semantic.DeepEqual(listFirst.Items, expected) {
    87  		t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))
    88  	}
    89  }
    90  
    91  type patchTestCase struct {
    92  	name                  string
    93  	object                runtime.Object
    94  	patchType             types.PatchType
    95  	patchBytes            []byte
    96  	wantErrMsg            string
    97  	expectedPatchedObject runtime.Object
    98  }
    99  
   100  func (tc *patchTestCase) runner(t *testing.T) {
   101  	client := NewSimpleMetadataClient(scheme, tc.object)
   102  	resourceInterface := client.Resource(schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}).Namespace(testNamespace)
   103  
   104  	got, recErr := resourceInterface.Patch(context.TODO(), testName, tc.patchType, tc.patchBytes, metav1.PatchOptions{})
   105  
   106  	if err := tc.verifyErr(recErr); err != nil {
   107  		t.Error(err)
   108  	}
   109  
   110  	if err := tc.verifyResult(got); err != nil {
   111  		t.Error(err)
   112  	}
   113  
   114  }
   115  
   116  // verifyErr verifies that the given error returned from Patch is the error
   117  // expected by the test case.
   118  func (tc *patchTestCase) verifyErr(err error) error {
   119  	if tc.wantErrMsg != "" && err == nil {
   120  		return fmt.Errorf("want error, got nil")
   121  	}
   122  
   123  	if tc.wantErrMsg == "" && err != nil {
   124  		return fmt.Errorf("want no error, got %v", err)
   125  	}
   126  
   127  	if err != nil {
   128  		if want, got := tc.wantErrMsg, err.Error(); want != got {
   129  			return fmt.Errorf("incorrect error: want: %q got: %q", want, got)
   130  		}
   131  	}
   132  	return nil
   133  }
   134  
   135  func (tc *patchTestCase) verifyResult(result *metav1.PartialObjectMetadata) error {
   136  	if tc.expectedPatchedObject == nil && result == nil {
   137  		return nil
   138  	}
   139  	if !equality.Semantic.DeepEqual(result, tc.expectedPatchedObject) {
   140  		return fmt.Errorf("unexpected diff in received object: %s", diff.ObjectGoPrintDiff(tc.expectedPatchedObject, result))
   141  	}
   142  	return nil
   143  }
   144  
   145  func TestPatch(t *testing.T) {
   146  	testCases := []patchTestCase{
   147  		{
   148  			name:       "jsonpatch fails with merge type",
   149  			object:     newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
   150  			patchType:  types.StrategicMergePatchType,
   151  			patchBytes: []byte(`[]`),
   152  			wantErrMsg: "invalid JSON document",
   153  		}, {
   154  			name:      "jsonpatch works with empty patch",
   155  			object:    newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
   156  			patchType: types.JSONPatchType,
   157  			// No-op
   158  			patchBytes:            []byte(`[]`),
   159  			expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
   160  		}, {
   161  			name:      "jsonpatch works with simple change patch",
   162  			object:    newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
   163  			patchType: types.JSONPatchType,
   164  			// change spec.foo from bar to foobar
   165  			patchBytes:            []byte(`[{"op": "replace", "path": "/metadata/annotations/foo", "value": "foobar"}]`),
   166  			expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "foobar"}),
   167  		}, {
   168  			name:      "jsonpatch works with simple addition",
   169  			object:    newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
   170  			patchType: types.JSONPatchType,
   171  			// add spec.newvalue = dummy
   172  			patchBytes:            []byte(`[{"op": "add", "path": "/metadata/annotations/newvalue", "value": "dummy"}]`),
   173  			expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar", "newvalue": "dummy"}),
   174  		}, {
   175  			name:      "jsonpatch works with simple deletion",
   176  			object:    newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar", "toremove": "shouldnotbehere"}),
   177  			patchType: types.JSONPatchType,
   178  			// remove spec.newvalue = dummy
   179  			patchBytes:            []byte(`[{"op": "remove", "path": "/metadata/annotations/toremove"}]`),
   180  			expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
   181  		}, {
   182  			name:      "strategic merge patch fails with JSONPatch",
   183  			object:    newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
   184  			patchType: types.StrategicMergePatchType,
   185  			// add spec.newvalue = dummy
   186  			patchBytes: []byte(`[{"op": "add", "path": "/metadata/annotations/newvalue", "value": "dummy"}]`),
   187  			wantErrMsg: "invalid JSON document",
   188  		}, {
   189  			name:                  "merge patch works with simple replacement",
   190  			object:                newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
   191  			patchType:             types.MergePatchType,
   192  			patchBytes:            []byte(`{ "metadata": {"annotations": { "foo": "baz" } } }`),
   193  			expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "baz"}),
   194  		},
   195  		// TODO: Add tests for strategic merge using v1.Pod for example to ensure the test cases
   196  		// demonstrate expected use cases.
   197  	}
   198  
   199  	for _, tc := range testCases {
   200  		t.Run(tc.name, tc.runner)
   201  	}
   202  }