github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/apis/meta/v1/validation/validation_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 validation
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"testing"
    23  
    24  	metav1 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/apis/meta/v1"
    25  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/types"
    26  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/validation/field"
    27  )
    28  
    29  func TestValidateLabels(t *testing.T) {
    30  	successCases := []map[string]string{
    31  		{"simple": "bar"},
    32  		{"now-with-dashes": "bar"},
    33  		{"1-starts-with-num": "bar"},
    34  		{"1234": "bar"},
    35  		{"simple/simple": "bar"},
    36  		{"now-with-dashes/simple": "bar"},
    37  		{"now-with-dashes/now-with-dashes": "bar"},
    38  		{"now.with.dots/simple": "bar"},
    39  		{"now-with.dashes-and.dots/simple": "bar"},
    40  		{"1-num.2-num/3-num": "bar"},
    41  		{"1234/5678": "bar"},
    42  		{"1.2.3.4/5678": "bar"},
    43  		{"UpperCaseAreOK123": "bar"},
    44  		{"goodvalue": "123_-.BaR"},
    45  	}
    46  	for i := range successCases {
    47  		errs := ValidateLabels(successCases[i], field.NewPath("field"))
    48  		if len(errs) != 0 {
    49  			t.Errorf("case[%d] expected success, got %#v", i, errs)
    50  		}
    51  	}
    52  
    53  	namePartErrMsg := "name part must consist of"
    54  	nameErrMsg := "a qualified name must consist of"
    55  	labelErrMsg := "a valid label must be an empty string or consist of"
    56  	maxLengthErrMsg := "must be no more than"
    57  
    58  	labelNameErrorCases := []struct {
    59  		labels map[string]string
    60  		expect string
    61  	}{
    62  		{map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg},
    63  		{map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg},
    64  		{map[string]string{"only/one/slash": "bar"}, nameErrMsg},
    65  		{map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg},
    66  	}
    67  	for i := range labelNameErrorCases {
    68  		errs := ValidateLabels(labelNameErrorCases[i].labels, field.NewPath("field"))
    69  		if len(errs) != 1 {
    70  			t.Errorf("case[%d]: expected failure", i)
    71  		} else {
    72  			if !strings.Contains(errs[0].Detail, labelNameErrorCases[i].expect) {
    73  				t.Errorf("case[%d]: error details do not include %q: %q", i, labelNameErrorCases[i].expect, errs[0].Detail)
    74  			}
    75  		}
    76  	}
    77  
    78  	labelValueErrorCases := []struct {
    79  		labels map[string]string
    80  		expect string
    81  	}{
    82  		{map[string]string{"toolongvalue": strings.Repeat("a", 64)}, maxLengthErrMsg},
    83  		{map[string]string{"backslashesinvalue": "some\\bad\\value"}, labelErrMsg},
    84  		{map[string]string{"nocommasallowed": "bad,value"}, labelErrMsg},
    85  		{map[string]string{"strangecharsinvalue": "?#$notsogood"}, labelErrMsg},
    86  	}
    87  	for i := range labelValueErrorCases {
    88  		errs := ValidateLabels(labelValueErrorCases[i].labels, field.NewPath("field"))
    89  		if len(errs) != 1 {
    90  			t.Errorf("case[%d]: expected failure", i)
    91  		} else {
    92  			if !strings.Contains(errs[0].Detail, labelValueErrorCases[i].expect) {
    93  				t.Errorf("case[%d]: error details do not include %q: %q", i, labelValueErrorCases[i].expect, errs[0].Detail)
    94  			}
    95  		}
    96  	}
    97  }
    98  
    99  func TestValidDryRun(t *testing.T) {
   100  	tests := [][]string{
   101  		{},
   102  		{"All"},
   103  		{"All", "All"},
   104  	}
   105  
   106  	for _, test := range tests {
   107  		t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
   108  			if errs := ValidateDryRun(field.NewPath("dryRun"), test); len(errs) != 0 {
   109  				t.Errorf("%v should be a valid dry-run value: %v", test, errs)
   110  			}
   111  		})
   112  	}
   113  }
   114  
   115  func TestInvalidDryRun(t *testing.T) {
   116  	tests := [][]string{
   117  		{"False"},
   118  		{"All", "False"},
   119  	}
   120  
   121  	for _, test := range tests {
   122  		t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
   123  			if len(ValidateDryRun(field.NewPath("dryRun"), test)) == 0 {
   124  				t.Errorf("%v shouldn't be a valid dry-run value", test)
   125  			}
   126  		})
   127  	}
   128  
   129  }
   130  
   131  func boolPtr(b bool) *bool {
   132  	return &b
   133  }
   134  
   135  func TestValidPatchOptions(t *testing.T) {
   136  	tests := []struct {
   137  		opts      metav1.PatchOptions
   138  		patchType types.PatchType
   139  	}{
   140  		{
   141  			opts: metav1.PatchOptions{
   142  				Force:        boolPtr(true),
   143  				FieldManager: "kubectl",
   144  			},
   145  			patchType: types.ApplyPatchType,
   146  		},
   147  		{
   148  			opts: metav1.PatchOptions{
   149  				FieldManager: "kubectl",
   150  			},
   151  			patchType: types.ApplyPatchType,
   152  		},
   153  		{
   154  			opts:      metav1.PatchOptions{},
   155  			patchType: types.MergePatchType,
   156  		},
   157  		{
   158  			opts: metav1.PatchOptions{
   159  				FieldManager: "patcher",
   160  			},
   161  			patchType: types.MergePatchType,
   162  		},
   163  	}
   164  
   165  	for _, test := range tests {
   166  		t.Run(fmt.Sprintf("%v", test.opts), func(t *testing.T) {
   167  			errs := ValidatePatchOptions(&test.opts, test.patchType)
   168  			if len(errs) != 0 {
   169  				t.Fatalf("Expected no failures, got: %v", errs)
   170  			}
   171  		})
   172  	}
   173  }
   174  
   175  func TestInvalidPatchOptions(t *testing.T) {
   176  	tests := []struct {
   177  		opts      metav1.PatchOptions
   178  		patchType types.PatchType
   179  	}{
   180  		// missing manager
   181  		{
   182  			opts:      metav1.PatchOptions{},
   183  			patchType: types.ApplyPatchType,
   184  		},
   185  		// force on non-apply
   186  		{
   187  			opts: metav1.PatchOptions{
   188  				Force: boolPtr(true),
   189  			},
   190  			patchType: types.MergePatchType,
   191  		},
   192  		// manager and force on non-apply
   193  		{
   194  			opts: metav1.PatchOptions{
   195  				FieldManager: "kubectl",
   196  				Force:        boolPtr(false),
   197  			},
   198  			patchType: types.MergePatchType,
   199  		},
   200  	}
   201  
   202  	for _, test := range tests {
   203  		t.Run(fmt.Sprintf("%v", test.opts), func(t *testing.T) {
   204  			errs := ValidatePatchOptions(&test.opts, test.patchType)
   205  			if len(errs) == 0 {
   206  				t.Fatal("Expected failures, got none.")
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  func TestValidateFieldManagerValid(t *testing.T) {
   213  	tests := []string{
   214  		"filedManager",
   215  		"你好", // Hello
   216  		"🍔",
   217  	}
   218  
   219  	for _, test := range tests {
   220  		t.Run(test, func(t *testing.T) {
   221  			errs := ValidateFieldManager(test, field.NewPath("fieldManager"))
   222  			if len(errs) != 0 {
   223  				t.Errorf("Validation failed: %v", errs)
   224  			}
   225  		})
   226  	}
   227  }
   228  
   229  func TestValidateFieldManagerInvalid(t *testing.T) {
   230  	tests := []string{
   231  		"field\nmanager", // Contains invalid character \n
   232  		"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // Has 129 chars
   233  	}
   234  
   235  	for _, test := range tests {
   236  		t.Run(test, func(t *testing.T) {
   237  			errs := ValidateFieldManager(test, field.NewPath("fieldManager"))
   238  			if len(errs) == 0 {
   239  				t.Errorf("Validation should have failed")
   240  			}
   241  		})
   242  	}
   243  }
   244  
   245  func TestValidateManagedFieldsInvalid(t *testing.T) {
   246  	tests := []metav1.ManagedFieldsEntry{
   247  		{
   248  			Operation:  metav1.ManagedFieldsOperationUpdate,
   249  			FieldsType: "RandomVersion",
   250  			APIVersion: "v1",
   251  		},
   252  		{
   253  			Operation:  "RandomOperation",
   254  			FieldsType: "FieldsV1",
   255  			APIVersion: "v1",
   256  		},
   257  		{
   258  			// Operation is missing
   259  			FieldsType: "FieldsV1",
   260  			APIVersion: "v1",
   261  		},
   262  		{
   263  			Operation:  metav1.ManagedFieldsOperationUpdate,
   264  			FieldsType: "FieldsV1",
   265  			// Invalid fieldManager
   266  			Manager:    "field\nmanager",
   267  			APIVersion: "v1",
   268  		},
   269  		{
   270  			Operation:   metav1.ManagedFieldsOperationApply,
   271  			FieldsType:  "FieldsV1",
   272  			APIVersion:  "v1",
   273  			Subresource: "TooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLong",
   274  		},
   275  	}
   276  
   277  	for _, test := range tests {
   278  		t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) {
   279  			errs := ValidateManagedFields([]metav1.ManagedFieldsEntry{test}, field.NewPath("managedFields"))
   280  			if len(errs) == 0 {
   281  				t.Errorf("Validation should have failed")
   282  			}
   283  		})
   284  	}
   285  }
   286  
   287  func TestValidateMangedFieldsValid(t *testing.T) {
   288  	tests := []metav1.ManagedFieldsEntry{
   289  		{
   290  			Operation:  metav1.ManagedFieldsOperationUpdate,
   291  			APIVersion: "v1",
   292  			// FieldsType is missing
   293  		},
   294  		{
   295  			Operation:  metav1.ManagedFieldsOperationUpdate,
   296  			FieldsType: "FieldsV1",
   297  			APIVersion: "v1",
   298  		},
   299  		{
   300  			Operation:   metav1.ManagedFieldsOperationApply,
   301  			FieldsType:  "FieldsV1",
   302  			APIVersion:  "v1",
   303  			Subresource: "scale",
   304  		},
   305  		{
   306  			Operation:  metav1.ManagedFieldsOperationApply,
   307  			FieldsType: "FieldsV1",
   308  			APIVersion: "v1",
   309  			Manager:    "🍔",
   310  		},
   311  	}
   312  
   313  	for _, test := range tests {
   314  		t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) {
   315  			err := ValidateManagedFields([]metav1.ManagedFieldsEntry{test}, field.NewPath("managedFields"))
   316  			if err != nil {
   317  				t.Errorf("Validation failed: %v", err)
   318  			}
   319  		})
   320  	}
   321  }
   322  
   323  func TestValidateConditions(t *testing.T) {
   324  	tests := []struct {
   325  		name         string
   326  		conditions   []metav1.Condition
   327  		validateErrs func(t *testing.T, errs field.ErrorList)
   328  	}{
   329  		{
   330  			name: "bunch-of-invalid-fields",
   331  			conditions: []metav1.Condition{{
   332  				Type:               ":invalid",
   333  				Status:             "unknown",
   334  				ObservedGeneration: -1,
   335  				LastTransitionTime: metav1.Time{},
   336  				Reason:             "invalid;val",
   337  				Message:            "",
   338  			}},
   339  			validateErrs: func(t *testing.T, errs field.ErrorList) {
   340  				needle := `status.conditions[0].type: Invalid value: ":invalid": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`
   341  				if !hasError(errs, needle) {
   342  					t.Errorf("missing %q in\n%v", needle, errorsAsString(errs))
   343  				}
   344  				needle = `status.conditions[0].status: Unsupported value: "unknown": supported values: "False", "True", "Unknown"`
   345  				if !hasError(errs, needle) {
   346  					t.Errorf("missing %q in\n%v", needle, errorsAsString(errs))
   347  				}
   348  				needle = `status.conditions[0].observedGeneration: Invalid value: -1: must be greater than or equal to zero`
   349  				if !hasError(errs, needle) {
   350  					t.Errorf("missing %q in\n%v", needle, errorsAsString(errs))
   351  				}
   352  				needle = `status.conditions[0].lastTransitionTime: Required value: must be set`
   353  				if !hasError(errs, needle) {
   354  					t.Errorf("missing %q in\n%v", needle, errorsAsString(errs))
   355  				}
   356  				needle = `status.conditions[0].reason: Invalid value: "invalid;val": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name',  or 'MY_NAME',  or 'MyName',  or 'ReasonA,ReasonB',  or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')`
   357  				if !hasError(errs, needle) {
   358  					t.Errorf("missing %q in\n%v", needle, errorsAsString(errs))
   359  				}
   360  			},
   361  		},
   362  		{
   363  			name: "duplicates",
   364  			conditions: []metav1.Condition{{
   365  				Type: "First",
   366  			},
   367  				{
   368  					Type: "Second",
   369  				},
   370  				{
   371  					Type: "First",
   372  				},
   373  			},
   374  			validateErrs: func(t *testing.T, errs field.ErrorList) {
   375  				needle := `status.conditions[2].type: Duplicate value: "First"`
   376  				if !hasError(errs, needle) {
   377  					t.Errorf("missing %q in\n%v", needle, errorsAsString(errs))
   378  				}
   379  			},
   380  		},
   381  		{
   382  			name: "colon-allowed-in-reason",
   383  			conditions: []metav1.Condition{{
   384  				Type:   "First",
   385  				Reason: "valid:val",
   386  			}},
   387  			validateErrs: func(t *testing.T, errs field.ErrorList) {
   388  				needle := `status.conditions[0].reason`
   389  				if hasPrefixError(errs, needle) {
   390  					t.Errorf("has %q in\n%v", needle, errorsAsString(errs))
   391  				}
   392  			},
   393  		},
   394  		{
   395  			name: "comma-allowed-in-reason",
   396  			conditions: []metav1.Condition{{
   397  				Type:   "First",
   398  				Reason: "valid,val",
   399  			}},
   400  			validateErrs: func(t *testing.T, errs field.ErrorList) {
   401  				needle := `status.conditions[0].reason`
   402  				if hasPrefixError(errs, needle) {
   403  					t.Errorf("has %q in\n%v", needle, errorsAsString(errs))
   404  				}
   405  			},
   406  		},
   407  		{
   408  			name: "reason-does-not-end-in-delimiter",
   409  			conditions: []metav1.Condition{{
   410  				Type:   "First",
   411  				Reason: "valid,val:",
   412  			}},
   413  			validateErrs: func(t *testing.T, errs field.ErrorList) {
   414  				needle := `status.conditions[0].reason: Invalid value: "valid,val:": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name',  or 'MY_NAME',  or 'MyName',  or 'ReasonA,ReasonB',  or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')`
   415  				if !hasError(errs, needle) {
   416  					t.Errorf("missing %q in\n%v", needle, errorsAsString(errs))
   417  				}
   418  			},
   419  		},
   420  	}
   421  
   422  	for _, test := range tests {
   423  		t.Run(test.name, func(t *testing.T) {
   424  			errs := ValidateConditions(test.conditions, field.NewPath("status").Child("conditions"))
   425  			test.validateErrs(t, errs)
   426  		})
   427  	}
   428  }
   429  
   430  func TestLabelSelectorMatchExpression(t *testing.T) {
   431  	testCases := []struct {
   432  		name            string
   433  		labelSelector   *metav1.LabelSelector
   434  		wantErrorNumber int
   435  		validateErrs    func(t *testing.T, errs field.ErrorList)
   436  	}{
   437  		{
   438  			name: "Valid LabelSelector",
   439  			labelSelector: &metav1.LabelSelector{
   440  				MatchExpressions: []metav1.LabelSelectorRequirement{
   441  					{
   442  						Key:      "key",
   443  						Operator: metav1.LabelSelectorOpIn,
   444  						Values:   []string{"value"},
   445  					},
   446  				},
   447  			},
   448  			wantErrorNumber: 0,
   449  			validateErrs:    nil,
   450  		},
   451  		{
   452  			name: "MatchExpression's key name isn't valid",
   453  			labelSelector: &metav1.LabelSelector{
   454  				MatchExpressions: []metav1.LabelSelectorRequirement{
   455  					{
   456  						Key:      "-key",
   457  						Operator: metav1.LabelSelectorOpIn,
   458  						Values:   []string{"value"},
   459  					},
   460  				},
   461  			},
   462  			wantErrorNumber: 1,
   463  			validateErrs: func(t *testing.T, errs field.ErrorList) {
   464  				errMessage := "name part must consist of alphanumeric characters"
   465  				if !partStringInErrorMessage(errs, errMessage) {
   466  					t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs))
   467  				}
   468  			},
   469  		},
   470  		{
   471  			name: "MatchExpression's operator isn't valid",
   472  			labelSelector: &metav1.LabelSelector{
   473  				MatchExpressions: []metav1.LabelSelectorRequirement{
   474  					{
   475  						Key:      "key",
   476  						Operator: "abc",
   477  						Values:   []string{"value"},
   478  					},
   479  				},
   480  			},
   481  			wantErrorNumber: 1,
   482  			validateErrs: func(t *testing.T, errs field.ErrorList) {
   483  				errMessage := "not a valid selector operator"
   484  				if !partStringInErrorMessage(errs, errMessage) {
   485  					t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs))
   486  				}
   487  			},
   488  		},
   489  		{
   490  			name: "MatchExpression's value name isn't valid",
   491  			labelSelector: &metav1.LabelSelector{
   492  				MatchExpressions: []metav1.LabelSelectorRequirement{
   493  					{
   494  						Key:      "key",
   495  						Operator: metav1.LabelSelectorOpIn,
   496  						Values:   []string{"-value"},
   497  					},
   498  				},
   499  			},
   500  			wantErrorNumber: 1,
   501  			validateErrs: func(t *testing.T, errs field.ErrorList) {
   502  				errMessage := "a valid label must be an empty string or consist of"
   503  				if !partStringInErrorMessage(errs, errMessage) {
   504  					t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs))
   505  				}
   506  			},
   507  		},
   508  	}
   509  	for index, testCase := range testCases {
   510  		t.Run(testCase.name, func(t *testing.T) {
   511  			allErrs := ValidateLabelSelector(testCase.labelSelector, LabelSelectorValidationOptions{false}, field.NewPath("labelSelector"))
   512  			if len(allErrs) != testCase.wantErrorNumber {
   513  				t.Errorf("case[%d]: expected failure", index)
   514  			}
   515  			if len(allErrs) >= 1 && testCase.validateErrs != nil {
   516  				testCase.validateErrs(t, allErrs)
   517  			}
   518  		})
   519  	}
   520  }
   521  
   522  func hasError(errs field.ErrorList, needle string) bool {
   523  	for _, curr := range errs {
   524  		if curr.Error() == needle {
   525  			return true
   526  		}
   527  	}
   528  	return false
   529  }
   530  
   531  func hasPrefixError(errs field.ErrorList, prefix string) bool {
   532  	for _, curr := range errs {
   533  		if strings.HasPrefix(curr.Error(), prefix) {
   534  			return true
   535  		}
   536  	}
   537  	return false
   538  }
   539  
   540  func partStringInErrorMessage(errs field.ErrorList, prefix string) bool {
   541  	for _, curr := range errs {
   542  		if strings.Contains(curr.Error(), prefix) {
   543  			return true
   544  		}
   545  	}
   546  	return false
   547  }
   548  
   549  func errorsAsString(errs field.ErrorList) string {
   550  	messages := []string{}
   551  	for _, curr := range errs {
   552  		messages = append(messages, curr.Error())
   553  	}
   554  	return strings.Join(messages, "\n")
   555  }