...

Source file src/k8s.io/client-go/dynamic/client_test.go

Documentation: k8s.io/client-go/dynamic

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

View as plain text