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 }