...

Source file src/k8s.io/client-go/metadata/metadata_test.go

Documentation: k8s.io/client-go/metadata

     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  }
   307  

View as plain text