k8s.io/client-go@v0.31.1/metadata/metadata_test.go (about) 1 /* 2 Copyright 2019 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 metadata 18 19 import ( 20 "context" 21 "encoding/json" 22 "io" 23 "net/http" 24 "net/http/httptest" 25 "reflect" 26 "strings" 27 "testing" 28 29 "github.com/google/go-cmp/cmp" 30 corev1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/client-go/rest" 35 "k8s.io/klog/v2/ktesting" 36 ) 37 38 func TestClient(t *testing.T) { 39 gvr := schema.GroupVersionResource{Group: "group", Version: "v1", Resource: "resource"} 40 statusOK := &metav1.Status{ 41 Status: metav1.StatusSuccess, 42 Code: http.StatusOK, 43 } 44 45 writeJSON := func(t *testing.T, w http.ResponseWriter, obj runtime.Object) { 46 data, err := json.Marshal(obj) 47 if err != nil { 48 t.Fatal(err) 49 } 50 w.Header().Set("Content-Type", "application/json") 51 if _, err := w.Write(data); err != nil { 52 t.Fatal(err) 53 } 54 } 55 56 testCases := []struct { 57 name string 58 handler func(t *testing.T, w http.ResponseWriter, req *http.Request) 59 want func(ctx context.Context, t *testing.T, client *Client) 60 }{ 61 { 62 name: "GET is able to convert a JSON object to PartialObjectMetadata", 63 handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) { 64 if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" { 65 t.Fatal(req.Header.Get("Accept")) 66 } 67 if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" { 68 t.Fatal(req.URL.String()) 69 } 70 writeJSON(t, w, &corev1.Pod{ 71 TypeMeta: metav1.TypeMeta{ 72 Kind: "Pod", 73 APIVersion: "v1", 74 }, 75 ObjectMeta: metav1.ObjectMeta{ 76 Name: "name", 77 Namespace: "ns", 78 }, 79 }) 80 }, 81 want: func(ctx context.Context, t *testing.T, client *Client) { 82 obj, err := client.Resource(gvr).Namespace("ns").Get(ctx, "name", metav1.GetOptions{}) 83 if err != nil { 84 t.Fatal(err) 85 } 86 expect := &metav1.PartialObjectMetadata{ 87 ObjectMeta: metav1.ObjectMeta{ 88 Name: "name", 89 Namespace: "ns", 90 }, 91 } 92 if !reflect.DeepEqual(expect, obj) { 93 t.Fatal(cmp.Diff(expect, obj)) 94 } 95 }, 96 }, 97 98 { 99 name: "LIST is able to convert a JSON object to PartialObjectMetadata", 100 handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) { 101 if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json" { 102 t.Fatal(req.Header.Get("Accept")) 103 } 104 if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource" { 105 t.Fatal(req.URL.String()) 106 } 107 writeJSON(t, w, &corev1.PodList{ 108 TypeMeta: metav1.TypeMeta{ 109 Kind: "PodList", 110 APIVersion: "v1", 111 }, 112 ListMeta: metav1.ListMeta{ 113 ResourceVersion: "253", 114 }, 115 Items: []corev1.Pod{ 116 { 117 TypeMeta: metav1.TypeMeta{ 118 Kind: "Pod", 119 APIVersion: "v1", 120 }, 121 ObjectMeta: metav1.ObjectMeta{ 122 Name: "name", 123 Namespace: "ns", 124 }, 125 }, 126 }, 127 }) 128 }, 129 want: func(ctx context.Context, t *testing.T, client *Client) { 130 objs, err := client.Resource(gvr).Namespace("ns").List(ctx, metav1.ListOptions{}) 131 if err != nil { 132 t.Fatal(err) 133 } 134 if objs.GetResourceVersion() != "253" { 135 t.Fatal(objs) 136 } 137 expect := []metav1.PartialObjectMetadata{ 138 { 139 TypeMeta: metav1.TypeMeta{ 140 Kind: "Pod", 141 APIVersion: "v1", 142 }, 143 ObjectMeta: metav1.ObjectMeta{ 144 Name: "name", 145 Namespace: "ns", 146 }, 147 }, 148 } 149 if !reflect.DeepEqual(expect, objs.Items) { 150 t.Fatal(cmp.Diff(expect, objs.Items)) 151 } 152 }, 153 }, 154 155 { 156 name: "GET fails if the object is JSON and has no kind", 157 handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) { 158 if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" { 159 t.Fatal(req.Header.Get("Accept")) 160 } 161 if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" { 162 t.Fatal(req.URL.String()) 163 } 164 writeJSON(t, w, &corev1.Pod{ 165 TypeMeta: metav1.TypeMeta{}, 166 ObjectMeta: metav1.ObjectMeta{ 167 UID: "123", 168 }, 169 }) 170 }, 171 want: func(ctx context.Context, t *testing.T, client *Client) { 172 obj, err := client.Resource(gvr).Namespace("ns").Get(ctx, "name", metav1.GetOptions{}) 173 if err == nil || !runtime.IsMissingKind(err) { 174 t.Fatal(err) 175 } 176 if obj != nil { 177 t.Fatal(obj) 178 } 179 }, 180 }, 181 182 { 183 name: "GET fails if the object is JSON and has no apiVersion", 184 handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) { 185 if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" { 186 t.Fatal(req.Header.Get("Accept")) 187 } 188 if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" { 189 t.Fatal(req.URL.String()) 190 } 191 writeJSON(t, w, &corev1.Pod{ 192 TypeMeta: metav1.TypeMeta{ 193 Kind: "Pod", 194 }, 195 ObjectMeta: metav1.ObjectMeta{ 196 UID: "123", 197 }, 198 }) 199 }, 200 want: func(ctx context.Context, t *testing.T, client *Client) { 201 obj, err := client.Resource(gvr).Namespace("ns").Get(ctx, "name", metav1.GetOptions{}) 202 if err == nil || !runtime.IsMissingVersion(err) { 203 t.Fatal(err) 204 } 205 if obj != nil { 206 t.Fatal(obj) 207 } 208 }, 209 }, 210 211 { 212 name: "GET fails if the object is JSON and not clearly metadata", 213 handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) { 214 if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" { 215 t.Fatal(req.Header.Get("Accept")) 216 } 217 if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" { 218 t.Fatal(req.URL.String()) 219 } 220 writeJSON(t, w, &corev1.Pod{ 221 TypeMeta: metav1.TypeMeta{ 222 Kind: "Pod", 223 APIVersion: "v1", 224 }, 225 ObjectMeta: metav1.ObjectMeta{}, 226 }) 227 }, 228 want: func(ctx context.Context, t *testing.T, client *Client) { 229 obj, err := client.Resource(gvr).Namespace("ns").Get(ctx, "name", metav1.GetOptions{}) 230 if err == nil || !strings.Contains(err.Error(), "object does not appear to match the ObjectMeta schema") { 231 t.Fatal(err) 232 } 233 if obj != nil { 234 t.Fatal(obj) 235 } 236 }, 237 }, 238 239 { 240 name: "Delete fails if DeleteOptions cannot be serialized to JSON", 241 handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) { 242 if req.Header.Get("Content-Type") != runtime.ContentTypeJSON { 243 t.Fatal(req.Header.Get("Content-Type")) 244 } 245 if req.Method != "DELETE" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" { 246 t.Fatal(req.URL.String()) 247 } 248 defer req.Body.Close() 249 buf, err := io.ReadAll(req.Body) 250 if err != nil { 251 t.Fatal(err) 252 } 253 if !json.Valid(buf) { 254 t.Fatalf("request body is not a valid JSON: %s", buf) 255 } 256 writeJSON(t, w, statusOK) 257 }, 258 want: func(ctx context.Context, t *testing.T, client *Client) { 259 err := client.Resource(gvr).Namespace("ns").Delete(ctx, "name", metav1.DeleteOptions{}) 260 if err != nil { 261 t.Fatal(err) 262 } 263 }, 264 }, 265 266 { 267 name: "DeleteCollection fails if DeleteOptions cannot be serialized to JSON", 268 handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) { 269 if req.Header.Get("Content-Type") != runtime.ContentTypeJSON { 270 t.Fatal(req.Header.Get("Content-Type")) 271 } 272 if req.Method != "DELETE" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" { 273 t.Fatal(req.URL.String()) 274 } 275 defer req.Body.Close() 276 buf, err := io.ReadAll(req.Body) 277 if err != nil { 278 t.Fatal(err) 279 } 280 if !json.Valid(buf) { 281 t.Fatalf("request body is not a valid JSON: %s", buf) 282 } 283 284 writeJSON(t, w, statusOK) 285 }, 286 want: func(ctx context.Context, t *testing.T, client *Client) { 287 err := client.Resource(gvr).Namespace("ns").DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{}) 288 if err != nil { 289 t.Fatal(err) 290 } 291 }, 292 }, 293 } 294 295 for _, tt := range testCases { 296 t.Run(tt.name, func(t *testing.T) { 297 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { tt.handler(t, w, req) })) 298 defer s.Close() 299 300 _, ctx := ktesting.NewTestContext(t) 301 cfg := ConfigFor(&rest.Config{Host: s.URL}) 302 client := NewForConfigOrDie(cfg).(*Client) 303 tt.want(ctx, t, client) 304 }) 305 } 306 }