...

Source file src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/webhook_converter_test.go

Documentation: k8s.io/apiextensions-apiserver/pkg/apiserver/conversion

     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 conversion
    18  
    19  import (
    20  	"reflect"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/util/validation"
    30  
    31  	v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    32  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    33  )
    34  
    35  func TestRestoreObjectMeta(t *testing.T) {
    36  	tests := []struct {
    37  		name          string
    38  		original      map[string]interface{}
    39  		converted     map[string]interface{}
    40  		expected      map[string]interface{}
    41  		expectedError bool
    42  	}{
    43  		{"no converted metadata",
    44  			map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
    45  			map[string]interface{}{"spec": map[string]interface{}{}},
    46  			map[string]interface{}{"spec": map[string]interface{}{}},
    47  			true,
    48  		},
    49  		{"invalid converted metadata",
    50  			map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
    51  			map[string]interface{}{"metadata": []interface{}{"foo"}},
    52  			map[string]interface{}{"metadata": []interface{}{"foo"}},
    53  			true,
    54  		},
    55  		{"no original metadata",
    56  			map[string]interface{}{"spec": map[string]interface{}{}},
    57  			map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
    58  			map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
    59  			false,
    60  		},
    61  		{"invalid original metadata",
    62  			map[string]interface{}{"metadata": []interface{}{"foo"}},
    63  			map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
    64  			map[string]interface{}{"metadata": []interface{}{"foo"}, "spec": map[string]interface{}{}},
    65  			true,
    66  		},
    67  		{"changed label, annotations and non-label",
    68  			map[string]interface{}{"metadata": map[string]interface{}{
    69  				"foo":         "bar",
    70  				"labels":      map[string]interface{}{"a": "A", "b": "B"},
    71  				"annotations": map[string]interface{}{"a": "1", "b": "2"},
    72  			}, "spec": map[string]interface{}{}},
    73  			map[string]interface{}{"metadata": map[string]interface{}{
    74  				"foo":         "abc",
    75  				"labels":      map[string]interface{}{"a": "AA", "b": "B"},
    76  				"annotations": map[string]interface{}{"a": "1", "b": "22"},
    77  			}, "spec": map[string]interface{}{}},
    78  			map[string]interface{}{"metadata": map[string]interface{}{
    79  				"foo":         "bar",
    80  				"labels":      map[string]interface{}{"a": "AA", "b": "B"},
    81  				"annotations": map[string]interface{}{"a": "1", "b": "22"},
    82  			}, "spec": map[string]interface{}{}},
    83  			false,
    84  		},
    85  		{"added labels and annotations",
    86  			map[string]interface{}{"metadata": map[string]interface{}{
    87  				"foo": "bar",
    88  			}, "spec": map[string]interface{}{}},
    89  			map[string]interface{}{"metadata": map[string]interface{}{
    90  				"foo":         "abc",
    91  				"labels":      map[string]interface{}{"a": "AA", "b": "B"},
    92  				"annotations": map[string]interface{}{"a": "1", "b": "22"},
    93  			}, "spec": map[string]interface{}{}},
    94  			map[string]interface{}{"metadata": map[string]interface{}{
    95  				"foo":         "bar",
    96  				"labels":      map[string]interface{}{"a": "AA", "b": "B"},
    97  				"annotations": map[string]interface{}{"a": "1", "b": "22"},
    98  			}, "spec": map[string]interface{}{}},
    99  			false,
   100  		},
   101  		{"added labels and annotations, with nil before",
   102  			map[string]interface{}{"metadata": map[string]interface{}{
   103  				"foo":         "bar",
   104  				"labels":      nil,
   105  				"annotations": nil,
   106  			}, "spec": map[string]interface{}{}},
   107  			map[string]interface{}{"metadata": map[string]interface{}{
   108  				"foo":         "abc",
   109  				"labels":      map[string]interface{}{"a": "AA", "b": "B"},
   110  				"annotations": map[string]interface{}{"a": "1", "b": "22"},
   111  			}, "spec": map[string]interface{}{}},
   112  			map[string]interface{}{"metadata": map[string]interface{}{
   113  				"foo":         "bar",
   114  				"labels":      map[string]interface{}{"a": "AA", "b": "B"},
   115  				"annotations": map[string]interface{}{"a": "1", "b": "22"},
   116  			}, "spec": map[string]interface{}{}},
   117  			false,
   118  		},
   119  		{"removed labels and annotations",
   120  			map[string]interface{}{"metadata": map[string]interface{}{
   121  				"foo":         "bar",
   122  				"labels":      map[string]interface{}{"a": "AA", "b": "B"},
   123  				"annotations": map[string]interface{}{"a": "1", "b": "22"},
   124  			}, "spec": map[string]interface{}{}},
   125  			map[string]interface{}{"metadata": map[string]interface{}{
   126  				"foo": "abc",
   127  			}, "spec": map[string]interface{}{}},
   128  			map[string]interface{}{"metadata": map[string]interface{}{
   129  				"foo": "bar",
   130  			}, "spec": map[string]interface{}{}},
   131  			false,
   132  		},
   133  		{"nil'ed labels and annotations",
   134  			map[string]interface{}{"metadata": map[string]interface{}{
   135  				"foo":         "bar",
   136  				"labels":      map[string]interface{}{"a": "AA", "b": "B"},
   137  				"annotations": map[string]interface{}{"a": "1", "b": "22"},
   138  			}, "spec": map[string]interface{}{}},
   139  			map[string]interface{}{"metadata": map[string]interface{}{
   140  				"foo":         "abc",
   141  				"labels":      nil,
   142  				"annotations": nil,
   143  			}, "spec": map[string]interface{}{}},
   144  			map[string]interface{}{"metadata": map[string]interface{}{
   145  				"foo": "bar",
   146  			}, "spec": map[string]interface{}{}},
   147  			false,
   148  		},
   149  		{"added labels and annotations",
   150  			map[string]interface{}{"metadata": map[string]interface{}{
   151  				"foo": "bar",
   152  			}, "spec": map[string]interface{}{}},
   153  			map[string]interface{}{"metadata": map[string]interface{}{
   154  				"foo":         "abc",
   155  				"labels":      map[string]interface{}{"a": nil, "b": "B"},
   156  				"annotations": map[string]interface{}{"a": nil, "b": "22"},
   157  			}, "spec": map[string]interface{}{}},
   158  			map[string]interface{}{"metadata": map[string]interface{}{
   159  				"foo": "bar",
   160  			}, "spec": map[string]interface{}{}},
   161  			true,
   162  		},
   163  		{"invalid label key",
   164  			map[string]interface{}{"metadata": map[string]interface{}{}},
   165  			map[string]interface{}{"metadata": map[string]interface{}{"labels": map[string]interface{}{"some/non-qualified/label": "x"}}},
   166  			map[string]interface{}{"metadata": map[string]interface{}{}},
   167  			true,
   168  		},
   169  		{"invalid annotation key",
   170  			map[string]interface{}{"metadata": map[string]interface{}{}},
   171  			map[string]interface{}{"metadata": map[string]interface{}{"labels": map[string]interface{}{"some/non-qualified/label": "x"}}},
   172  			map[string]interface{}{"metadata": map[string]interface{}{}},
   173  			true,
   174  		},
   175  		{"invalid label value",
   176  			map[string]interface{}{"metadata": map[string]interface{}{}},
   177  			map[string]interface{}{"metadata": map[string]interface{}{"labels": map[string]interface{}{"foo": "üäö"}}},
   178  			map[string]interface{}{"metadata": map[string]interface{}{}},
   179  			true,
   180  		},
   181  		{"too big label value",
   182  			map[string]interface{}{"metadata": map[string]interface{}{}},
   183  			map[string]interface{}{"metadata": map[string]interface{}{"labels": map[string]interface{}{"foo": strings.Repeat("x", validation.LabelValueMaxLength+1)}}},
   184  			map[string]interface{}{"metadata": map[string]interface{}{}},
   185  			true,
   186  		},
   187  		{"too big annotation value",
   188  			map[string]interface{}{"metadata": map[string]interface{}{}},
   189  			map[string]interface{}{"metadata": map[string]interface{}{"annotations": map[string]interface{}{"foo": strings.Repeat("x", 256*(1<<10)+1)}}},
   190  			map[string]interface{}{"metadata": map[string]interface{}{}},
   191  			true,
   192  		},
   193  	}
   194  	for _, tt := range tests {
   195  		t.Run(tt.name, func(t *testing.T) {
   196  			if err := restoreObjectMeta(&unstructured.Unstructured{Object: tt.original}, &unstructured.Unstructured{Object: tt.converted}); err == nil && tt.expectedError {
   197  				t.Fatalf("expected error, but didn't get one")
   198  			} else if err != nil && !tt.expectedError {
   199  				t.Fatalf("unexpected error: %v", err)
   200  			}
   201  
   202  			if !reflect.DeepEqual(tt.converted, tt.expected) {
   203  				t.Errorf("unexpected result: %s", cmp.Diff(tt.expected, tt.converted))
   204  			}
   205  		})
   206  	}
   207  }
   208  
   209  func TestGetObjectsToConvert(t *testing.T) {
   210  	v1Object := &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v1", "kind": "Widget", "metadata": map[string]interface{}{"name": "myv1"}}}
   211  	v2Object := &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v2", "kind": "Widget", "metadata": map[string]interface{}{"name": "myv2"}}}
   212  	v3Object := &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v3", "kind": "Widget", "metadata": map[string]interface{}{"name": "myv3"}}}
   213  
   214  	testcases := []struct {
   215  		Name       string
   216  		Object     runtime.Object
   217  		APIVersion string
   218  
   219  		ExpectObjects []runtime.RawExtension
   220  	}{
   221  		{
   222  			Name:          "empty list",
   223  			Object:        &unstructured.UnstructuredList{},
   224  			APIVersion:    "foo/v1",
   225  			ExpectObjects: nil,
   226  		},
   227  		{
   228  			Name: "one-item list, in desired version",
   229  			Object: &unstructured.UnstructuredList{
   230  				Items: []unstructured.Unstructured{*v1Object},
   231  			},
   232  			APIVersion:    "foo/v1",
   233  			ExpectObjects: nil,
   234  		},
   235  		{
   236  			Name: "one-item list, not in desired version",
   237  			Object: &unstructured.UnstructuredList{
   238  				Items: []unstructured.Unstructured{*v2Object},
   239  			},
   240  			APIVersion:    "foo/v1",
   241  			ExpectObjects: []runtime.RawExtension{{Object: v2Object}},
   242  		},
   243  		{
   244  			Name: "multi-item list, in desired version",
   245  			Object: &unstructured.UnstructuredList{
   246  				Items: []unstructured.Unstructured{*v1Object, *v1Object, *v1Object},
   247  			},
   248  			APIVersion:    "foo/v1",
   249  			ExpectObjects: nil,
   250  		},
   251  		{
   252  			Name: "multi-item list, mixed versions",
   253  			Object: &unstructured.UnstructuredList{
   254  				Items: []unstructured.Unstructured{*v1Object, *v2Object, *v3Object},
   255  			},
   256  			APIVersion:    "foo/v1",
   257  			ExpectObjects: []runtime.RawExtension{{Object: v2Object}, {Object: v3Object}},
   258  		},
   259  		{
   260  			Name:          "single item, in desired version",
   261  			Object:        v1Object,
   262  			APIVersion:    "foo/v1",
   263  			ExpectObjects: nil,
   264  		},
   265  		{
   266  			Name:          "single item, not in desired version",
   267  			Object:        v2Object,
   268  			APIVersion:    "foo/v1",
   269  			ExpectObjects: []runtime.RawExtension{{Object: v2Object}},
   270  		},
   271  	}
   272  	for _, tc := range testcases {
   273  		t.Run(tc.Name, func(t *testing.T) {
   274  			if objects := getObjectsToConvert(tc.Object, tc.APIVersion); !reflect.DeepEqual(objects, tc.ExpectObjects) {
   275  				t.Errorf("unexpected diff: %s", cmp.Diff(tc.ExpectObjects, objects))
   276  			}
   277  		})
   278  	}
   279  }
   280  
   281  func TestCreateConversionReviewObjects(t *testing.T) {
   282  	objects := []runtime.RawExtension{
   283  		{Object: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v2", "Kind": "Widget"}}},
   284  	}
   285  
   286  	testcases := []struct {
   287  		Name     string
   288  		Versions []string
   289  
   290  		ExpectRequest  runtime.Object
   291  		ExpectResponse runtime.Object
   292  		ExpectErr      string
   293  	}{
   294  		{
   295  			Name:      "no supported versions",
   296  			Versions:  []string{"vx"},
   297  			ExpectErr: "no supported conversion review versions",
   298  		},
   299  		{
   300  			Name:     "v1",
   301  			Versions: []string{"v1", "v1beta1", "v2"},
   302  			ExpectRequest: &v1.ConversionReview{
   303  				Request:  &v1.ConversionRequest{UID: "uid", DesiredAPIVersion: "foo/v1", Objects: objects},
   304  				Response: &v1.ConversionResponse{},
   305  			},
   306  			ExpectResponse: &v1.ConversionReview{},
   307  		},
   308  		{
   309  			Name:     "v1beta1",
   310  			Versions: []string{"v1beta1", "v1", "v2"},
   311  			ExpectRequest: &v1beta1.ConversionReview{
   312  				Request:  &v1beta1.ConversionRequest{UID: "uid", DesiredAPIVersion: "foo/v1", Objects: objects},
   313  				Response: &v1beta1.ConversionResponse{},
   314  			},
   315  			ExpectResponse: &v1beta1.ConversionReview{},
   316  		},
   317  	}
   318  
   319  	for _, tc := range testcases {
   320  		t.Run(tc.Name, func(t *testing.T) {
   321  			request, response, err := createConversionReviewObjects(tc.Versions, objects, "foo/v1", "uid")
   322  
   323  			if err == nil && len(tc.ExpectErr) > 0 {
   324  				t.Errorf("expected error, got none")
   325  			} else if err != nil && len(tc.ExpectErr) == 0 {
   326  				t.Errorf("unexpected error %v", err)
   327  			} else if err != nil && !strings.Contains(err.Error(), tc.ExpectErr) {
   328  				t.Errorf("expected error containing %q, got %v", tc.ExpectErr, err)
   329  			}
   330  
   331  			if e, a := tc.ExpectRequest, request; !reflect.DeepEqual(e, a) {
   332  				t.Errorf("unexpected diff: %s", cmp.Diff(e, a))
   333  			}
   334  			if e, a := tc.ExpectResponse, response; !reflect.DeepEqual(e, a) {
   335  				t.Errorf("unexpected diff: %s", cmp.Diff(e, a))
   336  			}
   337  		})
   338  	}
   339  }
   340  
   341  func TestGetConvertedObjectsFromResponse(t *testing.T) {
   342  	v1Object := &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v1", "kind": "Widget", "metadata": map[string]interface{}{"name": "myv1"}}}
   343  
   344  	testcases := []struct {
   345  		Name     string
   346  		Response runtime.Object
   347  
   348  		ExpectObjects []runtime.RawExtension
   349  		ExpectErr     string
   350  	}{
   351  		{
   352  			Name:      "nil response",
   353  			Response:  nil,
   354  			ExpectErr: "unrecognized response type",
   355  		},
   356  		{
   357  			Name:      "unknown type",
   358  			Response:  &unstructured.Unstructured{},
   359  			ExpectErr: "unrecognized response type",
   360  		},
   361  
   362  		{
   363  			Name: "minimal valid v1beta1",
   364  			Response: &v1beta1.ConversionReview{
   365  				// apiVersion/kind were not validated originally, preserve backward compatibility
   366  				Response: &v1beta1.ConversionResponse{
   367  					// uid was not validated originally, preserve backward compatibility
   368  					Result: metav1.Status{Status: metav1.StatusSuccess},
   369  				},
   370  			},
   371  			ExpectObjects: nil,
   372  		},
   373  		{
   374  			Name: "valid v1beta1 with objects",
   375  			Response: &v1beta1.ConversionReview{
   376  				// apiVersion/kind were not validated originally, preserve backward compatibility
   377  				Response: &v1beta1.ConversionResponse{
   378  					// uid was not validated originally, preserve backward compatibility
   379  					Result:           metav1.Status{Status: metav1.StatusSuccess},
   380  					ConvertedObjects: []runtime.RawExtension{{Object: v1Object}},
   381  				},
   382  			},
   383  			ExpectObjects: []runtime.RawExtension{{Object: v1Object}},
   384  		},
   385  		{
   386  			Name: "error v1beta1, empty status",
   387  			Response: &v1beta1.ConversionReview{
   388  				Response: &v1beta1.ConversionResponse{
   389  					Result: metav1.Status{Status: ""},
   390  				},
   391  			},
   392  			ExpectErr: `response.result.status was '', not 'Success'`,
   393  		},
   394  		{
   395  			Name: "error v1beta1, failure status",
   396  			Response: &v1beta1.ConversionReview{
   397  				Response: &v1beta1.ConversionResponse{
   398  					Result: metav1.Status{Status: metav1.StatusFailure},
   399  				},
   400  			},
   401  			ExpectErr: `response.result.status was 'Failure', not 'Success'`,
   402  		},
   403  		{
   404  			Name: "error v1beta1, custom status",
   405  			Response: &v1beta1.ConversionReview{
   406  				Response: &v1beta1.ConversionResponse{
   407  					Result: metav1.Status{Status: metav1.StatusFailure, Message: "some failure message"},
   408  				},
   409  			},
   410  			ExpectErr: `some failure message`,
   411  		},
   412  		{
   413  			Name:      "invalid v1beta1, no response",
   414  			Response:  &v1beta1.ConversionReview{},
   415  			ExpectErr: "no response provided",
   416  		},
   417  
   418  		{
   419  			Name: "minimal valid v1",
   420  			Response: &v1.ConversionReview{
   421  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
   422  				Response: &v1.ConversionResponse{
   423  					UID:    "uid",
   424  					Result: metav1.Status{Status: metav1.StatusSuccess},
   425  				},
   426  			},
   427  			ExpectObjects: nil,
   428  		},
   429  		{
   430  			Name: "valid v1 with objects",
   431  			Response: &v1.ConversionReview{
   432  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
   433  				Response: &v1.ConversionResponse{
   434  					UID:              "uid",
   435  					Result:           metav1.Status{Status: metav1.StatusSuccess},
   436  					ConvertedObjects: []runtime.RawExtension{{Object: v1Object}},
   437  				},
   438  			},
   439  			ExpectObjects: []runtime.RawExtension{{Object: v1Object}},
   440  		},
   441  		{
   442  			Name: "invalid v1, no uid",
   443  			Response: &v1.ConversionReview{
   444  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
   445  				Response: &v1.ConversionResponse{
   446  					Result: metav1.Status{Status: metav1.StatusSuccess},
   447  				},
   448  			},
   449  			ExpectErr: `expected response.uid="uid"`,
   450  		},
   451  		{
   452  			Name: "invalid v1, no apiVersion",
   453  			Response: &v1.ConversionReview{
   454  				TypeMeta: metav1.TypeMeta{Kind: "ConversionReview"},
   455  				Response: &v1.ConversionResponse{
   456  					UID:    "uid",
   457  					Result: metav1.Status{Status: metav1.StatusSuccess},
   458  				},
   459  			},
   460  			ExpectErr: `expected webhook response of apiextensions.k8s.io/v1, Kind=ConversionReview`,
   461  		},
   462  		{
   463  			Name: "invalid v1, no kind",
   464  			Response: &v1.ConversionReview{
   465  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1"},
   466  				Response: &v1.ConversionResponse{
   467  					UID:    "uid",
   468  					Result: metav1.Status{Status: metav1.StatusSuccess},
   469  				},
   470  			},
   471  			ExpectErr: `expected webhook response of apiextensions.k8s.io/v1, Kind=ConversionReview`,
   472  		},
   473  		{
   474  			Name: "invalid v1, mismatched apiVersion",
   475  			Response: &v1.ConversionReview{
   476  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v2", Kind: "ConversionReview"},
   477  				Response: &v1.ConversionResponse{
   478  					UID:    "uid",
   479  					Result: metav1.Status{Status: metav1.StatusSuccess},
   480  				},
   481  			},
   482  			ExpectErr: `expected webhook response of apiextensions.k8s.io/v1, Kind=ConversionReview`,
   483  		},
   484  		{
   485  			Name: "invalid v1, mismatched kind",
   486  			Response: &v1.ConversionReview{
   487  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview2"},
   488  				Response: &v1.ConversionResponse{
   489  					UID:    "uid",
   490  					Result: metav1.Status{Status: metav1.StatusSuccess},
   491  				},
   492  			},
   493  			ExpectErr: `expected webhook response of apiextensions.k8s.io/v1, Kind=ConversionReview`,
   494  		},
   495  		{
   496  			Name: "error v1, empty status",
   497  			Response: &v1.ConversionReview{
   498  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
   499  				Response: &v1.ConversionResponse{
   500  					UID:    "uid",
   501  					Result: metav1.Status{Status: ""},
   502  				},
   503  			},
   504  			ExpectErr: `response.result.status was '', not 'Success'`,
   505  		},
   506  		{
   507  			Name: "error v1, failure status",
   508  			Response: &v1.ConversionReview{
   509  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
   510  				Response: &v1.ConversionResponse{
   511  					UID:    "uid",
   512  					Result: metav1.Status{Status: metav1.StatusFailure},
   513  				},
   514  			},
   515  			ExpectErr: `response.result.status was 'Failure', not 'Success'`,
   516  		},
   517  		{
   518  			Name: "error v1, custom status",
   519  			Response: &v1.ConversionReview{
   520  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
   521  				Response: &v1.ConversionResponse{
   522  					UID:    "uid",
   523  					Result: metav1.Status{Status: metav1.StatusFailure, Message: "some failure message"},
   524  				},
   525  			},
   526  			ExpectErr: `some failure message`,
   527  		},
   528  		{
   529  			Name: "invalid v1, no response",
   530  			Response: &v1.ConversionReview{
   531  				TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
   532  			},
   533  			ExpectErr: "no response provided",
   534  		},
   535  	}
   536  
   537  	for _, tc := range testcases {
   538  		t.Run(tc.Name, func(t *testing.T) {
   539  
   540  			objects, err := getConvertedObjectsFromResponse("uid", tc.Response)
   541  
   542  			if err == nil && len(tc.ExpectErr) > 0 {
   543  				t.Errorf("expected error, got none")
   544  			} else if err != nil && len(tc.ExpectErr) == 0 {
   545  				t.Errorf("unexpected error %v", err)
   546  			} else if err != nil && !strings.Contains(err.Error(), tc.ExpectErr) {
   547  				t.Errorf("expected error containing %q, got %v", tc.ExpectErr, err)
   548  			}
   549  
   550  			if !reflect.DeepEqual(objects, tc.ExpectObjects) {
   551  				t.Errorf("unexpected diff: %s", cmp.Diff(tc.ExpectObjects, objects))
   552  			}
   553  
   554  		})
   555  	}
   556  }
   557  

View as plain text