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 }