k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/generators/extension_test.go (about)

     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 generators
    18  
    19  import (
    20  	"reflect"
    21  	"strings"
    22  	"testing"
    23  
    24  	"k8s.io/gengo/v2/types"
    25  	"k8s.io/kube-openapi/pkg/util/sets"
    26  )
    27  
    28  func TestSingleTagExtension(t *testing.T) {
    29  
    30  	// Comments only contain one tag extension and one value.
    31  	var tests = []struct {
    32  		comments        []string
    33  		extensionTag    string
    34  		extensionName   string
    35  		extensionValues []string
    36  	}{
    37  		{
    38  			comments:        []string{"+patchMergeKey=name"},
    39  			extensionTag:    "patchMergeKey",
    40  			extensionName:   "x-kubernetes-patch-merge-key",
    41  			extensionValues: []string{"name"},
    42  		},
    43  		{
    44  			comments:        []string{"+patchStrategy=merge"},
    45  			extensionTag:    "patchStrategy",
    46  			extensionName:   "x-kubernetes-patch-strategy",
    47  			extensionValues: []string{"merge"},
    48  		},
    49  		{
    50  			comments:        []string{"+listType=atomic"},
    51  			extensionTag:    "listType",
    52  			extensionName:   "x-kubernetes-list-type",
    53  			extensionValues: []string{"atomic"},
    54  		},
    55  		{
    56  			comments:        []string{"+listMapKey=port"},
    57  			extensionTag:    "listMapKey",
    58  			extensionName:   "x-kubernetes-list-map-keys",
    59  			extensionValues: []string{"port"},
    60  		},
    61  		{
    62  			comments:        []string{"+mapType=granular"},
    63  			extensionTag:    "mapType",
    64  			extensionName:   "x-kubernetes-map-type",
    65  			extensionValues: []string{"granular"},
    66  		},
    67  		{
    68  			comments:        []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test"},
    69  			extensionTag:    "k8s:openapi-gen",
    70  			extensionName:   "x-kubernetes-member-tag",
    71  			extensionValues: []string{"member_test"},
    72  		},
    73  		{
    74  			comments:        []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test:member_test2"},
    75  			extensionTag:    "k8s:openapi-gen",
    76  			extensionName:   "x-kubernetes-member-tag",
    77  			extensionValues: []string{"member_test:member_test2"},
    78  		},
    79  		{
    80  			// Test that poorly formatted extensions aren't added.
    81  			comments: []string{
    82  				"+k8s:openapi-gen=x-kubernetes-no-value",
    83  				"+k8s:openapi-gen=x-kubernetes-member-success:success",
    84  				"+k8s:openapi-gen=x-kubernetes-wrong-separator;error",
    85  			},
    86  			extensionTag:    "k8s:openapi-gen",
    87  			extensionName:   "x-kubernetes-member-success",
    88  			extensionValues: []string{"success"},
    89  		},
    90  	}
    91  	for _, test := range tests {
    92  		extensions, _ := parseExtensions(test.comments)
    93  		actual := extensions[0]
    94  		if actual.idlTag != test.extensionTag {
    95  			t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag)
    96  		}
    97  		if actual.xName != test.extensionName {
    98  			t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName)
    99  		}
   100  		if !reflect.DeepEqual(actual.values, test.extensionValues) {
   101  			t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values)
   102  		}
   103  		if actual.hasMultipleValues() {
   104  			t.Errorf("%s: hasMultipleValues() should be false\n", actual.xName)
   105  		}
   106  	}
   107  
   108  }
   109  
   110  func TestMultipleTagExtensions(t *testing.T) {
   111  
   112  	var tests = []struct {
   113  		comments        []string
   114  		extensionTag    string
   115  		extensionName   string
   116  		extensionValues []string
   117  	}{
   118  		{
   119  			comments: []string{
   120  				"+listMapKey=port",
   121  				"+listMapKey=protocol",
   122  			},
   123  			extensionTag:    "listMapKey",
   124  			extensionName:   "x-kubernetes-list-map-keys",
   125  			extensionValues: []string{"port", "protocol"},
   126  		},
   127  	}
   128  	for _, test := range tests {
   129  		extensions, errors := parseExtensions(test.comments)
   130  		if len(errors) > 0 {
   131  			t.Errorf("Unexpected errors: %v\n", errors)
   132  		}
   133  		actual := extensions[0]
   134  		if actual.idlTag != test.extensionTag {
   135  			t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag)
   136  		}
   137  		if actual.xName != test.extensionName {
   138  			t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName)
   139  		}
   140  		if !reflect.DeepEqual(actual.values, test.extensionValues) {
   141  			t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values)
   142  		}
   143  		if !actual.hasMultipleValues() {
   144  			t.Errorf("%s: hasMultipleValues() should be true\n", actual.xName)
   145  		}
   146  	}
   147  
   148  }
   149  
   150  func TestExtensionParseErrors(t *testing.T) {
   151  
   152  	var tests = []struct {
   153  		comments     []string
   154  		errorMessage string
   155  	}{
   156  		{
   157  			// Missing extension value should be an error.
   158  			comments: []string{
   159  				"+k8s:openapi-gen=x-kubernetes-no-value",
   160  			},
   161  			errorMessage: "x-kubernetes-no-value",
   162  		},
   163  		{
   164  			// Wrong separator should be an error.
   165  			comments: []string{
   166  				"+k8s:openapi-gen=x-kubernetes-wrong-separator;error",
   167  			},
   168  			errorMessage: "x-kubernetes-wrong-separator;error",
   169  		},
   170  	}
   171  
   172  	for _, test := range tests {
   173  		_, errors := parseExtensions(test.comments)
   174  		if len(errors) == 0 {
   175  			t.Errorf("Expected errors while parsing: %v\n", test.comments)
   176  		}
   177  		error := errors[0]
   178  		if !strings.Contains(error.Error(), test.errorMessage) {
   179  			t.Errorf("Error (%v) should contain substring (%s)\n", error, test.errorMessage)
   180  		}
   181  	}
   182  }
   183  
   184  func TestExtensionAllowedValues(t *testing.T) {
   185  
   186  	var methodTests = []struct {
   187  		e             extension
   188  		allowedValues sets.String
   189  	}{
   190  		{
   191  			e: extension{
   192  				idlTag: "patchStrategy",
   193  			},
   194  			allowedValues: sets.NewString("merge", "retainKeys"),
   195  		},
   196  		{
   197  			e: extension{
   198  				idlTag: "patchMergeKey",
   199  			},
   200  			allowedValues: nil,
   201  		},
   202  		{
   203  			e: extension{
   204  				idlTag: "listType",
   205  			},
   206  			allowedValues: sets.NewString("atomic", "set", "map"),
   207  		},
   208  		{
   209  			e: extension{
   210  				idlTag: "listMapKey",
   211  			},
   212  			allowedValues: nil,
   213  		},
   214  		{
   215  			e: extension{
   216  				idlTag: "mapType",
   217  			},
   218  			allowedValues: sets.NewString("atomic", "granular"),
   219  		},
   220  		{
   221  			e: extension{
   222  				idlTag: "structType",
   223  			},
   224  			allowedValues: sets.NewString("atomic", "granular"),
   225  		},
   226  		{
   227  			e: extension{
   228  				idlTag: "k8s:openapi-gen",
   229  			},
   230  			allowedValues: nil,
   231  		},
   232  	}
   233  	for _, test := range methodTests {
   234  		if test.allowedValues != nil {
   235  			if !test.e.hasAllowedValues() {
   236  				t.Errorf("hasAllowedValues() expected (true), but received: false")
   237  			}
   238  			if !reflect.DeepEqual(test.allowedValues, test.e.allowedValues()) {
   239  				t.Errorf("allowedValues() expected (%v), but received: %v",
   240  					test.allowedValues, test.e.allowedValues())
   241  			}
   242  		}
   243  		if test.allowedValues == nil && test.e.hasAllowedValues() {
   244  			t.Errorf("hasAllowedValues() expected (false), but received: true")
   245  		}
   246  	}
   247  
   248  	var successTests = []struct {
   249  		e extension
   250  	}{
   251  		{
   252  			e: extension{
   253  				idlTag: "patchStrategy",
   254  				xName:  "x-kubernetes-patch-strategy",
   255  				values: []string{"merge"},
   256  			},
   257  		},
   258  		{
   259  			// Validate multiple values.
   260  			e: extension{
   261  				idlTag: "patchStrategy",
   262  				xName:  "x-kubernetes-patch-strategy",
   263  				values: []string{"merge", "retainKeys"},
   264  			},
   265  		},
   266  		{
   267  			e: extension{
   268  				idlTag: "patchMergeKey",
   269  				xName:  "x-kubernetes-patch-merge-key",
   270  				values: []string{"key1"},
   271  			},
   272  		},
   273  		{
   274  			e: extension{
   275  				idlTag: "listType",
   276  				xName:  "x-kubernetes-list-type",
   277  				values: []string{"atomic"},
   278  			},
   279  		},
   280  		{
   281  			e: extension{
   282  				idlTag: "mapType",
   283  				xName:  "x-kubernetes-map-type",
   284  				values: []string{"atomic"},
   285  			},
   286  		},
   287  		{
   288  			e: extension{
   289  				idlTag: "structType",
   290  				xName:  "x-kubernetes-map-type",
   291  				values: []string{"granular"},
   292  			},
   293  		},
   294  	}
   295  	for _, test := range successTests {
   296  		actualErr := test.e.validateAllowedValues()
   297  		if actualErr != nil {
   298  			t.Errorf("Expected no error for (%v), but received: %v\n", test.e, actualErr)
   299  		}
   300  	}
   301  
   302  	var failureTests = []struct {
   303  		e extension
   304  	}{
   305  		{
   306  			// Every value must be allowed.
   307  			e: extension{
   308  				idlTag: "patchStrategy",
   309  				xName:  "x-kubernetes-patch-strategy",
   310  				values: []string{"disallowed", "merge"},
   311  			},
   312  		},
   313  		{
   314  			e: extension{
   315  				idlTag: "patchStrategy",
   316  				xName:  "x-kubernetes-patch-strategy",
   317  				values: []string{"foo"},
   318  			},
   319  		},
   320  		{
   321  			e: extension{
   322  				idlTag: "listType",
   323  				xName:  "x-kubernetes-list-type",
   324  				values: []string{"not-allowed"},
   325  			},
   326  		},
   327  		{
   328  			e: extension{
   329  				idlTag: "mapType",
   330  				xName:  "x-kubernetes-map-type",
   331  				values: []string{"something-pretty-wrong"},
   332  			},
   333  		},
   334  		{
   335  			e: extension{
   336  				idlTag: "structType",
   337  				xName:  "x-kubernetes-map-type",
   338  				values: []string{"not-quite-right"},
   339  			},
   340  		},
   341  	}
   342  	for _, test := range failureTests {
   343  		actualErr := test.e.validateAllowedValues()
   344  		if actualErr == nil {
   345  			t.Errorf("Expected error, but received none: %v\n", test.e)
   346  		}
   347  	}
   348  
   349  }
   350  
   351  func TestExtensionKind(t *testing.T) {
   352  
   353  	var methodTests = []struct {
   354  		e    extension
   355  		kind types.Kind
   356  	}{
   357  		{
   358  			e: extension{
   359  				idlTag: "patchStrategy",
   360  			},
   361  			kind: types.Slice,
   362  		},
   363  		{
   364  			e: extension{
   365  				idlTag: "patchMergeKey",
   366  			},
   367  			kind: types.Slice,
   368  		},
   369  		{
   370  			e: extension{
   371  				idlTag: "listType",
   372  			},
   373  			kind: types.Slice,
   374  		},
   375  		{
   376  			e: extension{
   377  				idlTag: "mapType",
   378  			},
   379  			kind: types.Map,
   380  		},
   381  		{
   382  			e: extension{
   383  				idlTag: "structType",
   384  			},
   385  			kind: types.Struct,
   386  		},
   387  		{
   388  			e: extension{
   389  				idlTag: "listMapKey",
   390  			},
   391  			kind: types.Slice,
   392  		},
   393  		{
   394  			e: extension{
   395  				idlTag: "k8s:openapi-gen",
   396  			},
   397  			kind: "",
   398  		},
   399  	}
   400  	for _, test := range methodTests {
   401  		if len(test.kind) > 0 {
   402  			if !test.e.hasKind() {
   403  				t.Errorf("%v: hasKind() expected (true), but received: false", test.e)
   404  			}
   405  			if test.kind != test.e.kind() {
   406  				t.Errorf("%v: kind() expected (%v), but received: %v", test.e, test.kind, test.e.kind())
   407  			}
   408  		} else {
   409  			if test.e.hasKind() {
   410  				t.Errorf("%v: hasKind() expected (false), but received: true", test.e)
   411  			}
   412  		}
   413  	}
   414  }
   415  
   416  func TestValidateMemberExtensions(t *testing.T) {
   417  
   418  	patchStrategyExtension := extension{
   419  		idlTag: "patchStrategy",
   420  		xName:  "x-kubernetes-patch-strategy",
   421  		values: []string{"merge"},
   422  	}
   423  	patchMergeKeyExtension := extension{
   424  		idlTag: "patchMergeKey",
   425  		xName:  "x-kubernetes-patch-merge-key",
   426  		values: []string{"key1", "key2"},
   427  	}
   428  	listTypeExtension := extension{
   429  		idlTag: "listType",
   430  		xName:  "x-kubernetes-list-type",
   431  		values: []string{"atomic"},
   432  	}
   433  	listMapKeysExtension := extension{
   434  		idlTag: "listMapKey",
   435  		xName:  "x-kubernetes-map-keys",
   436  		values: []string{"key1"},
   437  	}
   438  	genExtension := extension{
   439  		idlTag: "k8s:openapi-gen",
   440  		xName:  "x-kubernetes-member-type",
   441  		values: []string{"value1"},
   442  	}
   443  
   444  	sliceField := types.Member{
   445  		Name: "Containers",
   446  		Type: &types.Type{
   447  			Kind: types.Slice,
   448  		},
   449  	}
   450  	mapField := types.Member{
   451  		Name: "Containers",
   452  		Type: &types.Type{
   453  			Kind: types.Map,
   454  		},
   455  	}
   456  
   457  	var successTests = []struct {
   458  		extensions []extension
   459  		member     types.Member
   460  	}{
   461  		// Test single member extension
   462  		{
   463  			extensions: []extension{patchStrategyExtension},
   464  			member:     sliceField,
   465  		},
   466  		// Test multiple member extensions
   467  		{
   468  			extensions: []extension{
   469  				patchMergeKeyExtension,
   470  				listTypeExtension,
   471  				listMapKeysExtension,
   472  				genExtension, // Should not generate errors during type validation
   473  			},
   474  			member: sliceField,
   475  		},
   476  	}
   477  	for _, test := range successTests {
   478  		errors := validateMemberExtensions(test.extensions, &test.member)
   479  		if len(errors) > 0 {
   480  			t.Errorf("validateMemberExtensions: %v should have produced no errors. Errors: %v",
   481  				test.extensions, errors)
   482  		}
   483  	}
   484  
   485  	var failureTests = []struct {
   486  		extensions []extension
   487  		member     types.Member
   488  	}{
   489  		// Test single member extension
   490  		{
   491  			extensions: []extension{patchStrategyExtension},
   492  			member:     mapField,
   493  		},
   494  		// Test multiple member extensions
   495  		{
   496  			extensions: []extension{
   497  				patchMergeKeyExtension,
   498  				listTypeExtension,
   499  				listMapKeysExtension,
   500  			},
   501  			member: mapField,
   502  		},
   503  	}
   504  	for _, test := range failureTests {
   505  		errors := validateMemberExtensions(test.extensions, &test.member)
   506  		if len(errors) != len(test.extensions) {
   507  			t.Errorf("validateMemberExtensions: %v should have produced all errors. Errors: %v",
   508  				test.extensions, errors)
   509  		}
   510  	}
   511  
   512  }