k8s.io/client-go@v0.22.2/dynamic/client_test.go (about)

     1  /*
     2  Copyright 2016 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 dynamic
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"reflect"
    27  	"testing"
    28  
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/apimachinery/pkg/watch"
    36  	restclient "k8s.io/client-go/rest"
    37  	restclientwatch "k8s.io/client-go/rest/watch"
    38  )
    39  
    40  func getJSON(version, kind, name string) []byte {
    41  	return []byte(fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "metadata": {"name": %q}}`, version, kind, name))
    42  }
    43  
    44  func getListJSON(version, kind string, items ...[]byte) []byte {
    45  	json := fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "items": [%s]}`,
    46  		version, kind, bytes.Join(items, []byte(",")))
    47  	return []byte(json)
    48  }
    49  
    50  func getObject(version, kind, name string) *unstructured.Unstructured {
    51  	return &unstructured.Unstructured{
    52  		Object: map[string]interface{}{
    53  			"apiVersion": version,
    54  			"kind":       kind,
    55  			"metadata": map[string]interface{}{
    56  				"name": name,
    57  			},
    58  		},
    59  	}
    60  }
    61  
    62  func getClientServer(h func(http.ResponseWriter, *http.Request)) (Interface, *httptest.Server, error) {
    63  	srv := httptest.NewServer(http.HandlerFunc(h))
    64  	cl, err := NewForConfig(&restclient.Config{
    65  		Host: srv.URL,
    66  	})
    67  	if err != nil {
    68  		srv.Close()
    69  		return nil, nil, err
    70  	}
    71  	return cl, srv, nil
    72  }
    73  
    74  func TestList(t *testing.T) {
    75  	tcs := []struct {
    76  		name      string
    77  		namespace string
    78  		path      string
    79  		resp      []byte
    80  		want      *unstructured.UnstructuredList
    81  	}{
    82  		{
    83  			name: "normal_list",
    84  			path: "/apis/gtest/vtest/rtest",
    85  			resp: getListJSON("vTest", "rTestList",
    86  				getJSON("vTest", "rTest", "item1"),
    87  				getJSON("vTest", "rTest", "item2")),
    88  			want: &unstructured.UnstructuredList{
    89  				Object: map[string]interface{}{
    90  					"apiVersion": "vTest",
    91  					"kind":       "rTestList",
    92  				},
    93  				Items: []unstructured.Unstructured{
    94  					*getObject("vTest", "rTest", "item1"),
    95  					*getObject("vTest", "rTest", "item2"),
    96  				},
    97  			},
    98  		},
    99  		{
   100  			name:      "namespaced_list",
   101  			namespace: "nstest",
   102  			path:      "/apis/gtest/vtest/namespaces/nstest/rtest",
   103  			resp: getListJSON("vTest", "rTestList",
   104  				getJSON("vTest", "rTest", "item1"),
   105  				getJSON("vTest", "rTest", "item2")),
   106  			want: &unstructured.UnstructuredList{
   107  				Object: map[string]interface{}{
   108  					"apiVersion": "vTest",
   109  					"kind":       "rTestList",
   110  				},
   111  				Items: []unstructured.Unstructured{
   112  					*getObject("vTest", "rTest", "item1"),
   113  					*getObject("vTest", "rTest", "item2"),
   114  				},
   115  			},
   116  		},
   117  	}
   118  	for _, tc := range tcs {
   119  		resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
   120  		cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
   121  			if r.Method != "GET" {
   122  				t.Errorf("List(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
   123  			}
   124  
   125  			if r.URL.Path != tc.path {
   126  				t.Errorf("List(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
   127  			}
   128  
   129  			w.Header().Set("Content-Type", runtime.ContentTypeJSON)
   130  			w.Write(tc.resp)
   131  		})
   132  		if err != nil {
   133  			t.Errorf("unexpected error when creating client: %v", err)
   134  			continue
   135  		}
   136  		defer srv.Close()
   137  
   138  		got, err := cl.Resource(resource).Namespace(tc.namespace).List(context.TODO(), metav1.ListOptions{})
   139  		if err != nil {
   140  			t.Errorf("unexpected error when listing %q: %v", tc.name, err)
   141  			continue
   142  		}
   143  
   144  		if !reflect.DeepEqual(got, tc.want) {
   145  			t.Errorf("List(%q) want: %v\ngot: %v", tc.name, tc.want, got)
   146  		}
   147  	}
   148  }
   149  
   150  func TestGet(t *testing.T) {
   151  	tcs := []struct {
   152  		resource    string
   153  		subresource []string
   154  		namespace   string
   155  		name        string
   156  		path        string
   157  		resp        []byte
   158  		want        *unstructured.Unstructured
   159  	}{
   160  		{
   161  			resource: "rtest",
   162  			name:     "normal_get",
   163  			path:     "/apis/gtest/vtest/rtest/normal_get",
   164  			resp:     getJSON("vTest", "rTest", "normal_get"),
   165  			want:     getObject("vTest", "rTest", "normal_get"),
   166  		},
   167  		{
   168  			resource:  "rtest",
   169  			namespace: "nstest",
   170  			name:      "namespaced_get",
   171  			path:      "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_get",
   172  			resp:      getJSON("vTest", "rTest", "namespaced_get"),
   173  			want:      getObject("vTest", "rTest", "namespaced_get"),
   174  		},
   175  		{
   176  			resource:    "rtest",
   177  			subresource: []string{"srtest"},
   178  			name:        "normal_subresource_get",
   179  			path:        "/apis/gtest/vtest/rtest/normal_subresource_get/srtest",
   180  			resp:        getJSON("vTest", "srTest", "normal_subresource_get"),
   181  			want:        getObject("vTest", "srTest", "normal_subresource_get"),
   182  		},
   183  		{
   184  			resource:    "rtest",
   185  			subresource: []string{"srtest"},
   186  			namespace:   "nstest",
   187  			name:        "namespaced_subresource_get",
   188  			path:        "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_get/srtest",
   189  			resp:        getJSON("vTest", "srTest", "namespaced_subresource_get"),
   190  			want:        getObject("vTest", "srTest", "namespaced_subresource_get"),
   191  		},
   192  	}
   193  	for _, tc := range tcs {
   194  		resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource}
   195  		cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
   196  			if r.Method != "GET" {
   197  				t.Errorf("Get(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
   198  			}
   199  
   200  			if r.URL.Path != tc.path {
   201  				t.Errorf("Get(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
   202  			}
   203  
   204  			w.Header().Set("Content-Type", runtime.ContentTypeJSON)
   205  			w.Write(tc.resp)
   206  		})
   207  		if err != nil {
   208  			t.Errorf("unexpected error when creating client: %v", err)
   209  			continue
   210  		}
   211  		defer srv.Close()
   212  
   213  		got, err := cl.Resource(resource).Namespace(tc.namespace).Get(context.TODO(), tc.name, metav1.GetOptions{}, tc.subresource...)
   214  		if err != nil {
   215  			t.Errorf("unexpected error when getting %q: %v", tc.name, err)
   216  			continue
   217  		}
   218  
   219  		if !reflect.DeepEqual(got, tc.want) {
   220  			t.Errorf("Get(%q) want: %v\ngot: %v", tc.name, tc.want, got)
   221  		}
   222  	}
   223  }
   224  
   225  func TestDelete(t *testing.T) {
   226  	background := metav1.DeletePropagationBackground
   227  	uid := types.UID("uid")
   228  
   229  	statusOK := &metav1.Status{
   230  		TypeMeta: metav1.TypeMeta{Kind: "Status"},
   231  		Status:   metav1.StatusSuccess,
   232  	}
   233  	tcs := []struct {
   234  		subresource   []string
   235  		namespace     string
   236  		name          string
   237  		path          string
   238  		deleteOptions metav1.DeleteOptions
   239  	}{
   240  		{
   241  			name: "normal_delete",
   242  			path: "/apis/gtest/vtest/rtest/normal_delete",
   243  		},
   244  		{
   245  			namespace: "nstest",
   246  			name:      "namespaced_delete",
   247  			path:      "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete",
   248  		},
   249  		{
   250  			subresource: []string{"srtest"},
   251  			name:        "normal_delete",
   252  			path:        "/apis/gtest/vtest/rtest/normal_delete/srtest",
   253  		},
   254  		{
   255  			subresource: []string{"srtest"},
   256  			namespace:   "nstest",
   257  			name:        "namespaced_delete",
   258  			path:        "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete/srtest",
   259  		},
   260  		{
   261  			namespace:     "nstest",
   262  			name:          "namespaced_delete_with_options",
   263  			path:          "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete_with_options",
   264  			deleteOptions: metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}, PropagationPolicy: &background},
   265  		},
   266  	}
   267  	for _, tc := range tcs {
   268  		resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
   269  		cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
   270  			if r.Method != "DELETE" {
   271  				t.Errorf("Delete(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method)
   272  			}
   273  
   274  			if r.URL.Path != tc.path {
   275  				t.Errorf("Delete(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
   276  			}
   277  
   278  			w.Header().Set("Content-Type", runtime.ContentTypeJSON)
   279  			unstructured.UnstructuredJSONScheme.Encode(statusOK, w)
   280  		})
   281  		if err != nil {
   282  			t.Errorf("unexpected error when creating client: %v", err)
   283  			continue
   284  		}
   285  		defer srv.Close()
   286  
   287  		err = cl.Resource(resource).Namespace(tc.namespace).Delete(context.TODO(), tc.name, tc.deleteOptions, tc.subresource...)
   288  		if err != nil {
   289  			t.Errorf("unexpected error when deleting %q: %v", tc.name, err)
   290  			continue
   291  		}
   292  	}
   293  }
   294  
   295  func TestDeleteCollection(t *testing.T) {
   296  	statusOK := &metav1.Status{
   297  		TypeMeta: metav1.TypeMeta{Kind: "Status"},
   298  		Status:   metav1.StatusSuccess,
   299  	}
   300  	tcs := []struct {
   301  		namespace string
   302  		name      string
   303  		path      string
   304  	}{
   305  		{
   306  			name: "normal_delete_collection",
   307  			path: "/apis/gtest/vtest/rtest",
   308  		},
   309  		{
   310  			namespace: "nstest",
   311  			name:      "namespaced_delete_collection",
   312  			path:      "/apis/gtest/vtest/namespaces/nstest/rtest",
   313  		},
   314  	}
   315  	for _, tc := range tcs {
   316  		resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
   317  		cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
   318  			if r.Method != "DELETE" {
   319  				t.Errorf("DeleteCollection(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method)
   320  			}
   321  
   322  			if r.URL.Path != tc.path {
   323  				t.Errorf("DeleteCollection(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
   324  			}
   325  
   326  			w.Header().Set("Content-Type", runtime.ContentTypeJSON)
   327  			unstructured.UnstructuredJSONScheme.Encode(statusOK, w)
   328  		})
   329  		if err != nil {
   330  			t.Errorf("unexpected error when creating client: %v", err)
   331  			continue
   332  		}
   333  		defer srv.Close()
   334  
   335  		err = cl.Resource(resource).Namespace(tc.namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
   336  		if err != nil {
   337  			t.Errorf("unexpected error when deleting collection %q: %v", tc.name, err)
   338  			continue
   339  		}
   340  	}
   341  }
   342  
   343  func TestCreate(t *testing.T) {
   344  	tcs := []struct {
   345  		resource    string
   346  		subresource []string
   347  		name        string
   348  		namespace   string
   349  		obj         *unstructured.Unstructured
   350  		path        string
   351  	}{
   352  		{
   353  			resource: "rtest",
   354  			name:     "normal_create",
   355  			path:     "/apis/gtest/vtest/rtest",
   356  			obj:      getObject("gtest/vTest", "rTest", "normal_create"),
   357  		},
   358  		{
   359  			resource:  "rtest",
   360  			name:      "namespaced_create",
   361  			namespace: "nstest",
   362  			path:      "/apis/gtest/vtest/namespaces/nstest/rtest",
   363  			obj:       getObject("gtest/vTest", "rTest", "namespaced_create"),
   364  		},
   365  		{
   366  			resource:    "rtest",
   367  			subresource: []string{"srtest"},
   368  			name:        "normal_subresource_create",
   369  			path:        "/apis/gtest/vtest/rtest/normal_subresource_create/srtest",
   370  			obj:         getObject("vTest", "srTest", "normal_subresource_create"),
   371  		},
   372  		{
   373  			resource:    "rtest/",
   374  			subresource: []string{"srtest"},
   375  			name:        "namespaced_subresource_create",
   376  			namespace:   "nstest",
   377  			path:        "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_create/srtest",
   378  			obj:         getObject("vTest", "srTest", "namespaced_subresource_create"),
   379  		},
   380  	}
   381  	for _, tc := range tcs {
   382  		resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource}
   383  		cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
   384  			if r.Method != "POST" {
   385  				t.Errorf("Create(%q) got HTTP method %s. wanted POST", tc.name, r.Method)
   386  			}
   387  
   388  			if r.URL.Path != tc.path {
   389  				t.Errorf("Create(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
   390  			}
   391  
   392  			w.Header().Set("Content-Type", runtime.ContentTypeJSON)
   393  			data, err := ioutil.ReadAll(r.Body)
   394  			if err != nil {
   395  				t.Errorf("Create(%q) unexpected error reading body: %v", tc.name, err)
   396  				w.WriteHeader(http.StatusInternalServerError)
   397  				return
   398  			}
   399  
   400  			w.Write(data)
   401  		})
   402  		if err != nil {
   403  			t.Errorf("unexpected error when creating client: %v", err)
   404  			continue
   405  		}
   406  		defer srv.Close()
   407  
   408  		got, err := cl.Resource(resource).Namespace(tc.namespace).Create(context.TODO(), tc.obj, metav1.CreateOptions{}, tc.subresource...)
   409  		if err != nil {
   410  			t.Errorf("unexpected error when creating %q: %v", tc.name, err)
   411  			continue
   412  		}
   413  
   414  		if !reflect.DeepEqual(got, tc.obj) {
   415  			t.Errorf("Create(%q) want: %v\ngot: %v", tc.name, tc.obj, got)
   416  		}
   417  	}
   418  }
   419  
   420  func TestUpdate(t *testing.T) {
   421  	tcs := []struct {
   422  		resource    string
   423  		subresource []string
   424  		name        string
   425  		namespace   string
   426  		obj         *unstructured.Unstructured
   427  		path        string
   428  	}{
   429  		{
   430  			resource: "rtest",
   431  			name:     "normal_update",
   432  			path:     "/apis/gtest/vtest/rtest/normal_update",
   433  			obj:      getObject("gtest/vTest", "rTest", "normal_update"),
   434  		},
   435  		{
   436  			resource:  "rtest",
   437  			name:      "namespaced_update",
   438  			namespace: "nstest",
   439  			path:      "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update",
   440  			obj:       getObject("gtest/vTest", "rTest", "namespaced_update"),
   441  		},
   442  		{
   443  			resource:    "rtest",
   444  			subresource: []string{"srtest"},
   445  			name:        "normal_subresource_update",
   446  			path:        "/apis/gtest/vtest/rtest/normal_update/srtest",
   447  			obj:         getObject("gtest/vTest", "srTest", "normal_update"),
   448  		},
   449  		{
   450  			resource:    "rtest",
   451  			subresource: []string{"srtest"},
   452  			name:        "namespaced_subresource_update",
   453  			namespace:   "nstest",
   454  			path:        "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update/srtest",
   455  			obj:         getObject("gtest/vTest", "srTest", "namespaced_update"),
   456  		},
   457  	}
   458  	for _, tc := range tcs {
   459  		resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource}
   460  		cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
   461  			if r.Method != "PUT" {
   462  				t.Errorf("Update(%q) got HTTP method %s. wanted PUT", tc.name, r.Method)
   463  			}
   464  
   465  			if r.URL.Path != tc.path {
   466  				t.Errorf("Update(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
   467  			}
   468  
   469  			w.Header().Set("Content-Type", runtime.ContentTypeJSON)
   470  			data, err := ioutil.ReadAll(r.Body)
   471  			if err != nil {
   472  				t.Errorf("Update(%q) unexpected error reading body: %v", tc.name, err)
   473  				w.WriteHeader(http.StatusInternalServerError)
   474  				return
   475  			}
   476  
   477  			w.Write(data)
   478  		})
   479  		if err != nil {
   480  			t.Errorf("unexpected error when creating client: %v", err)
   481  			continue
   482  		}
   483  		defer srv.Close()
   484  
   485  		got, err := cl.Resource(resource).Namespace(tc.namespace).Update(context.TODO(), tc.obj, metav1.UpdateOptions{}, tc.subresource...)
   486  		if err != nil {
   487  			t.Errorf("unexpected error when updating %q: %v", tc.name, err)
   488  			continue
   489  		}
   490  
   491  		if !reflect.DeepEqual(got, tc.obj) {
   492  			t.Errorf("Update(%q) want: %v\ngot: %v", tc.name, tc.obj, got)
   493  		}
   494  	}
   495  }
   496  
   497  func TestWatch(t *testing.T) {
   498  	tcs := []struct {
   499  		name      string
   500  		namespace string
   501  		events    []watch.Event
   502  		path      string
   503  		query     string
   504  	}{
   505  		{
   506  			name:  "normal_watch",
   507  			path:  "/apis/gtest/vtest/rtest",
   508  			query: "watch=true",
   509  			events: []watch.Event{
   510  				{Type: watch.Added, Object: getObject("gtest/vTest", "rTest", "normal_watch")},
   511  				{Type: watch.Modified, Object: getObject("gtest/vTest", "rTest", "normal_watch")},
   512  				{Type: watch.Deleted, Object: getObject("gtest/vTest", "rTest", "normal_watch")},
   513  			},
   514  		},
   515  		{
   516  			name:      "namespaced_watch",
   517  			namespace: "nstest",
   518  			path:      "/apis/gtest/vtest/namespaces/nstest/rtest",
   519  			query:     "watch=true",
   520  			events: []watch.Event{
   521  				{Type: watch.Added, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")},
   522  				{Type: watch.Modified, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")},
   523  				{Type: watch.Deleted, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")},
   524  			},
   525  		},
   526  	}
   527  	for _, tc := range tcs {
   528  		resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
   529  		cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
   530  			if r.Method != "GET" {
   531  				t.Errorf("Watch(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
   532  			}
   533  
   534  			if r.URL.Path != tc.path {
   535  				t.Errorf("Watch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
   536  			}
   537  			if r.URL.RawQuery != tc.query {
   538  				t.Errorf("Watch(%q) got query %s. wanted %s", tc.name, r.URL.RawQuery, tc.query)
   539  			}
   540  
   541  			w.Header().Set("Content-Type", "application/json")
   542  
   543  			enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, unstructured.UnstructuredJSONScheme), unstructured.UnstructuredJSONScheme)
   544  			for _, e := range tc.events {
   545  				enc.Encode(&e)
   546  			}
   547  		})
   548  		if err != nil {
   549  			t.Errorf("unexpected error when creating client: %v", err)
   550  			continue
   551  		}
   552  		defer srv.Close()
   553  
   554  		watcher, err := cl.Resource(resource).Namespace(tc.namespace).Watch(context.TODO(), metav1.ListOptions{})
   555  		if err != nil {
   556  			t.Errorf("unexpected error when watching %q: %v", tc.name, err)
   557  			continue
   558  		}
   559  
   560  		for _, want := range tc.events {
   561  			got := <-watcher.ResultChan()
   562  			if !reflect.DeepEqual(got, want) {
   563  				t.Errorf("Watch(%q) want: %v\ngot: %v", tc.name, want, got)
   564  			}
   565  		}
   566  	}
   567  }
   568  
   569  func TestPatch(t *testing.T) {
   570  	tcs := []struct {
   571  		resource    string
   572  		subresource []string
   573  		name        string
   574  		namespace   string
   575  		patch       []byte
   576  		want        *unstructured.Unstructured
   577  		path        string
   578  	}{
   579  		{
   580  			resource: "rtest",
   581  			name:     "normal_patch",
   582  			path:     "/apis/gtest/vtest/rtest/normal_patch",
   583  			patch:    getJSON("gtest/vTest", "rTest", "normal_patch"),
   584  			want:     getObject("gtest/vTest", "rTest", "normal_patch"),
   585  		},
   586  		{
   587  			resource:  "rtest",
   588  			name:      "namespaced_patch",
   589  			namespace: "nstest",
   590  			path:      "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_patch",
   591  			patch:     getJSON("gtest/vTest", "rTest", "namespaced_patch"),
   592  			want:      getObject("gtest/vTest", "rTest", "namespaced_patch"),
   593  		},
   594  		{
   595  			resource:    "rtest",
   596  			subresource: []string{"srtest"},
   597  			name:        "normal_subresource_patch",
   598  			path:        "/apis/gtest/vtest/rtest/normal_subresource_patch/srtest",
   599  			patch:       getJSON("gtest/vTest", "srTest", "normal_subresource_patch"),
   600  			want:        getObject("gtest/vTest", "srTest", "normal_subresource_patch"),
   601  		},
   602  		{
   603  			resource:    "rtest",
   604  			subresource: []string{"srtest"},
   605  			name:        "namespaced_subresource_patch",
   606  			namespace:   "nstest",
   607  			path:        "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_patch/srtest",
   608  			patch:       getJSON("gtest/vTest", "srTest", "namespaced_subresource_patch"),
   609  			want:        getObject("gtest/vTest", "srTest", "namespaced_subresource_patch"),
   610  		},
   611  	}
   612  	for _, tc := range tcs {
   613  		resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource}
   614  		cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
   615  			if r.Method != "PATCH" {
   616  				t.Errorf("Patch(%q) got HTTP method %s. wanted PATCH", tc.name, r.Method)
   617  			}
   618  
   619  			if r.URL.Path != tc.path {
   620  				t.Errorf("Patch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
   621  			}
   622  
   623  			content := r.Header.Get("Content-Type")
   624  			if content != string(types.StrategicMergePatchType) {
   625  				t.Errorf("Patch(%q) got Content-Type %s. wanted %s", tc.name, content, types.StrategicMergePatchType)
   626  			}
   627  
   628  			data, err := ioutil.ReadAll(r.Body)
   629  			if err != nil {
   630  				t.Errorf("Patch(%q) unexpected error reading body: %v", tc.name, err)
   631  				w.WriteHeader(http.StatusInternalServerError)
   632  				return
   633  			}
   634  
   635  			w.Header().Set("Content-Type", "application/json")
   636  			w.Write(data)
   637  		})
   638  		if err != nil {
   639  			t.Errorf("unexpected error when creating client: %v", err)
   640  			continue
   641  		}
   642  		defer srv.Close()
   643  
   644  		got, err := cl.Resource(resource).Namespace(tc.namespace).Patch(context.TODO(), tc.name, types.StrategicMergePatchType, tc.patch, metav1.PatchOptions{}, tc.subresource...)
   645  		if err != nil {
   646  			t.Errorf("unexpected error when patching %q: %v", tc.name, err)
   647  			continue
   648  		}
   649  
   650  		if !reflect.DeepEqual(got, tc.want) {
   651  			t.Errorf("Patch(%q) want: %v\ngot: %v", tc.name, tc.want, got)
   652  		}
   653  	}
   654  }