k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/generators/markers_test.go (about) 1 /* 2 Copyright 2023 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 package generators_test 17 18 import ( 19 "testing" 20 21 "github.com/stretchr/testify/require" 22 "k8s.io/gengo/v2/types" 23 "k8s.io/kube-openapi/pkg/generators" 24 "k8s.io/kube-openapi/pkg/validation/spec" 25 "k8s.io/utils/ptr" 26 ) 27 28 var structKind *types.Type = &types.Type{Kind: types.Struct, Name: types.Name{Name: "struct"}} 29 var mapType *types.Type = &types.Type{Kind: types.Map, Name: types.Name{Name: "map[string]int"}} 30 var arrayType *types.Type = &types.Type{Kind: types.Slice, Name: types.Name{Name: "[]int"}} 31 32 func TestParseCommentTags(t *testing.T) { 33 34 cases := []struct { 35 t *types.Type 36 name string 37 comments []string 38 expected *spec.Schema 39 40 // regex pattern matching the error, or empty string/unset if no error 41 // is expected 42 expectedError string 43 }{ 44 { 45 t: structKind, 46 name: "basic example", 47 comments: []string{ 48 "comment", 49 "another + comment", 50 "+k8s:validation:minimum=10.0", 51 "+k8s:validation:maximum=20.0", 52 "+k8s:validation:minLength=20", 53 "+k8s:validation:maxLength=30", 54 `+k8s:validation:pattern="asdf"`, 55 "+k8s:validation:multipleOf=1.0", 56 "+k8s:validation:minItems=1", 57 "+k8s:validation:maxItems=2", 58 "+k8s:validation:uniqueItems=true", 59 "exclusiveMaximum=true", 60 "not+k8s:validation:Minimum=0.0", 61 }, 62 expected: &spec.Schema{ 63 SchemaProps: spec.SchemaProps{ 64 Maximum: ptr.To(20.0), 65 Minimum: ptr.To(10.0), 66 MinLength: ptr.To[int64](20), 67 MaxLength: ptr.To[int64](30), 68 Pattern: "asdf", 69 MultipleOf: ptr.To(1.0), 70 MinItems: ptr.To[int64](1), 71 MaxItems: ptr.To[int64](2), 72 UniqueItems: true, 73 }, 74 }, 75 }, 76 { 77 t: structKind, 78 name: "empty", 79 expected: &spec.Schema{}, 80 }, 81 { 82 t: types.Float64, 83 name: "single", 84 comments: []string{ 85 "+k8s:validation:minimum=10.0", 86 }, 87 expected: &spec.Schema{ 88 SchemaProps: spec.SchemaProps{ 89 Minimum: ptr.To(10.0), 90 }, 91 }, 92 }, 93 { 94 t: types.Float64, 95 name: "multiple", 96 comments: []string{ 97 "+k8s:validation:minimum=10.0", 98 "+k8s:validation:maximum=20.0", 99 }, 100 expected: &spec.Schema{ 101 SchemaProps: spec.SchemaProps{ 102 Maximum: ptr.To(20.0), 103 Minimum: ptr.To(10.0), 104 }, 105 }, 106 }, 107 { 108 t: types.Float64, 109 name: "invalid duplicate key", 110 comments: []string{ 111 "+k8s:validation:minimum=10.0", 112 "+k8s:validation:maximum=20.0", 113 "+k8s:validation:minimum=30.0", 114 }, 115 expectedError: `failed to parse marker comments: cannot have multiple values for key 'minimum'`, 116 }, 117 { 118 t: structKind, 119 name: "unrecognized key is ignored", 120 comments: []string{ 121 "+ignored=30.0", 122 }, 123 expected: &spec.Schema{}, 124 }, 125 { 126 t: types.Float64, 127 name: "invalid: non-JSON value", 128 comments: []string{ 129 `+k8s:validation:minimum=asdf`, 130 }, 131 expectedError: `failed to parse marker comments: failed to parse value for key minimum as JSON: invalid character 'a' looking for beginning of value`, 132 }, 133 { 134 t: types.Float64, 135 name: "invalid: invalid value type", 136 comments: []string{ 137 `+k8s:validation:minimum="asdf"`, 138 }, 139 expectedError: `failed to unmarshal marker comments: json: cannot unmarshal string into Go struct field commentTags.minimum of type float64`, 140 }, 141 { 142 143 t: structKind, 144 name: "invalid: empty key", 145 comments: []string{ 146 "+k8s:validation:", 147 }, 148 expectedError: `failed to parse marker comments: cannot have empty key for marker comment`, 149 }, 150 { 151 t: types.Float64, 152 // temporary test. ref support may be added in the future 153 name: "ignore refs", 154 comments: []string{ 155 "+k8s:validation:pattern=ref(asdf)", 156 }, 157 expected: &spec.Schema{}, 158 }, 159 { 160 t: types.Float64, 161 name: "cel rule", 162 comments: []string{ 163 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 164 `+k8s:validation:cel[0]:message="immutable field"`, 165 }, 166 expected: &spec.Schema{ 167 VendorExtensible: spec.VendorExtensible{ 168 Extensions: map[string]interface{}{ 169 "x-kubernetes-validations": []interface{}{ 170 map[string]interface{}{ 171 "rule": "oldSelf == self", 172 "message": "immutable field", 173 }, 174 }, 175 }, 176 }, 177 }, 178 }, 179 { 180 t: types.Float64, 181 name: "skipped CEL rule", 182 comments: []string{ 183 // This should parse, but return an error in validation since 184 // index 1 is missing 185 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 186 `+k8s:validation:cel[0]:message="immutable field"`, 187 `+k8s:validation:cel[2]:rule="self > 5"`, 188 `+k8s:validation:cel[2]:message="must be greater than 5"`, 189 }, 190 expectedError: `failed to parse marker comments: error parsing cel[2]:rule="self > 5": non-consecutive index 2 for key 'cel'`, 191 }, 192 { 193 t: types.Float64, 194 name: "multiple CEL params", 195 comments: []string{ 196 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 197 `+k8s:validation:cel[0]:message="immutable field"`, 198 `+k8s:validation:cel[1]:rule="self > 5"`, 199 `+k8s:validation:cel[1]:optionalOldSelf=true`, 200 `+k8s:validation:cel[1]:message="must be greater than 5"`, 201 }, 202 expected: &spec.Schema{ 203 VendorExtensible: spec.VendorExtensible{ 204 Extensions: map[string]interface{}{ 205 "x-kubernetes-validations": []interface{}{ 206 map[string]interface{}{ 207 "rule": "oldSelf == self", 208 "message": "immutable field", 209 }, 210 map[string]interface{}{ 211 "rule": "self > 5", 212 "optionalOldSelf": true, 213 "message": "must be greater than 5", 214 }, 215 }, 216 }, 217 }, 218 }, 219 }, 220 { 221 t: types.Float64, 222 name: "multiple rules with multiple params", 223 comments: []string{ 224 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 225 `+k8s:validation:cel[0]:optionalOldSelf`, 226 `+k8s:validation:cel[0]:messageExpression="self + ' must be equal to old value'"`, 227 `+k8s:validation:cel[1]:rule="self > 5"`, 228 `+k8s:validation:cel[1]:optionalOldSelf=true`, 229 `+k8s:validation:cel[1]:message="must be greater than 5"`, 230 }, 231 expected: &spec.Schema{ 232 VendorExtensible: spec.VendorExtensible{ 233 Extensions: map[string]interface{}{ 234 "x-kubernetes-validations": []interface{}{ 235 map[string]interface{}{ 236 "rule": "oldSelf == self", 237 "optionalOldSelf": true, 238 "messageExpression": "self + ' must be equal to old value'", 239 }, 240 map[string]interface{}{ 241 "rule": "self > 5", 242 "optionalOldSelf": true, 243 "message": "must be greater than 5", 244 }, 245 }, 246 }, 247 }, 248 }, 249 }, 250 { 251 t: types.Float64, 252 name: "skipped array index", 253 comments: []string{ 254 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 255 `+k8s:validation:cel[0]:optionalOldSelf`, 256 `+k8s:validation:cel[0]:messageExpression="self + ' must be equal to old value'"`, 257 `+k8s:validation:cel[2]:rule="self > 5"`, 258 `+k8s:validation:cel[2]:optionalOldSelf=true`, 259 `+k8s:validation:cel[2]:message="must be greater than 5"`, 260 }, 261 expectedError: `failed to parse marker comments: error parsing cel[2]:rule="self > 5": non-consecutive index 2 for key 'cel'`, 262 }, 263 { 264 t: types.Float64, 265 name: "non-consecutive array index", 266 comments: []string{ 267 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 268 `+k8s:validation:cel[1]:rule="self > 5"`, 269 `+k8s:validation:cel[1]:message="self > 5"`, 270 `+k8s:validation:cel[0]:optionalOldSelf=true`, 271 `+k8s:validation:cel[0]:message="must be greater than 5"`, 272 }, 273 expectedError: "failed to parse marker comments: error parsing cel[0]:optionalOldSelf=true: non-consecutive index 0 for key 'cel'", 274 }, 275 { 276 t: types.Float64, 277 name: "interjected array index", 278 comments: []string{ 279 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 280 `+k8s:validation:cel[0]:message="cant change"`, 281 `+k8s:validation:cel[1]:rule="self > 5"`, 282 `+k8s:validation:cel[1]:message="must be greater than 5"`, 283 `+k8s:validation:minimum=5`, 284 `+k8s:validation:cel[2]:rule="a rule"`, 285 `+k8s:validation:cel[2]:message="message 2"`, 286 }, 287 expectedError: "failed to parse marker comments: error parsing cel[2]:rule=\"a rule\": non-consecutive index 2 for key 'cel'", 288 }, 289 { 290 t: types.Float64, 291 name: "interjected array index with non-prefixed comment", 292 comments: []string{ 293 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 294 `+k8s:validation:cel[0]:message="cant change"`, 295 `+k8s:validation:cel[1]:rule="self > 5"`, 296 `+k8s:validation:cel[1]:message="must be greater than 5"`, 297 `+minimum=5`, 298 `+k8s:validation:cel[2]:rule="a rule"`, 299 `+k8s:validation:cel[2]:message="message 2"`, 300 }, 301 expectedError: "failed to parse marker comments: error parsing cel[2]:rule=\"a rule\": non-consecutive index 2 for key 'cel'", 302 }, 303 { 304 t: types.Float64, 305 name: "non-consecutive raw string indexing", 306 comments: []string{ 307 `+k8s:validation:cel[0]:rule> raw string rule`, 308 `+k8s:validation:cel[1]:rule="self > 5"`, 309 `+k8s:validation:cel[1]:message="must be greater than 5"`, 310 `+k8s:validation:cel[0]:message>another raw string message`, 311 }, 312 expectedError: "failed to parse marker comments: error parsing cel[0]:message>another raw string message: non-consecutive index 0 for key 'cel'", 313 }, 314 { 315 t: types.String, 316 name: "non-consecutive string indexing false positive", 317 comments: []string{ 318 `+k8s:validation:cel[0]:message>[3]string rule [1]`, 319 `+k8s:validation:cel[0]:rule="string rule [1]"`, 320 `+k8s:validation:pattern="self[3] == 'hi'"`, 321 }, 322 expected: &spec.Schema{ 323 SchemaProps: spec.SchemaProps{ 324 Pattern: `self[3] == 'hi'`, 325 }, 326 VendorExtensible: spec.VendorExtensible{ 327 Extensions: map[string]interface{}{ 328 "x-kubernetes-validations": []interface{}{ 329 map[string]interface{}{ 330 "rule": "string rule [1]", 331 "message": "[3]string rule [1]", 332 }, 333 }, 334 }, 335 }, 336 }, 337 }, 338 { 339 t: types.String, 340 name: "non-consecutive raw string indexing false positive", 341 comments: []string{ 342 `+k8s:validation:cel[0]:message>[3]raw string message with subscirpt [3]"`, 343 `+k8s:validation:cel[0]:rule> raw string rule [1]`, 344 `+k8s:validation:pattern>"self[3] == 'hi'"`, 345 }, 346 expected: &spec.Schema{ 347 SchemaProps: spec.SchemaProps{ 348 Pattern: `"self[3] == 'hi'"`, 349 }, 350 VendorExtensible: spec.VendorExtensible{ 351 Extensions: map[string]interface{}{ 352 "x-kubernetes-validations": []interface{}{ 353 map[string]interface{}{ 354 "rule": "raw string rule [1]", 355 "message": "[3]raw string message with subscirpt [3]\"", 356 }, 357 }, 358 }, 359 }, 360 }, 361 }, 362 { 363 t: types.Float64, 364 name: "boolean key at invalid index", 365 comments: []string{ 366 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 367 `+k8s:validation:cel[0]:message="cant change"`, 368 `+k8s:validation:cel[2]:optionalOldSelf`, 369 }, 370 expectedError: `failed to parse marker comments: error parsing cel[2]:optionalOldSelf: non-consecutive index 2 for key 'cel'`, 371 }, 372 { 373 t: types.Float64, 374 name: "boolean key after non-prefixed comment", 375 comments: []string{ 376 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 377 `+k8s:validation:cel[0]:message="cant change"`, 378 `+k8s:validation:cel[1]:rule="self > 5"`, 379 `+k8s:validation:cel[1]:message="must be greater than 5"`, 380 `+minimum=5`, 381 `+k8s:validation:cel[1]:optionalOldSelf`, 382 }, 383 expectedError: `failed to parse marker comments: error parsing cel[1]:optionalOldSelf: non-consecutive index 1 for key 'cel'`, 384 }, 385 { 386 t: types.Float64, 387 name: "boolean key at index allowed", 388 comments: []string{ 389 `+k8s:validation:cel[0]:rule="oldSelf == self"`, 390 `+k8s:validation:cel[0]:message="cant change"`, 391 `+k8s:validation:cel[1]:rule="self > 5"`, 392 `+k8s:validation:cel[1]:message="must be greater than 5"`, 393 `+k8s:validation:cel[1]:optionalOldSelf`, 394 }, 395 expected: &spec.Schema{ 396 VendorExtensible: spec.VendorExtensible{ 397 Extensions: map[string]interface{}{ 398 "x-kubernetes-validations": []interface{}{ 399 map[string]interface{}{ 400 "rule": "oldSelf == self", 401 "message": "cant change", 402 }, 403 map[string]interface{}{ 404 "rule": "self > 5", 405 "message": "must be greater than 5", 406 "optionalOldSelf": true, 407 }, 408 }, 409 }, 410 }, 411 }, 412 }, 413 { 414 t: types.Float64, 415 name: "raw string rule", 416 comments: []string{ 417 `+k8s:validation:cel[0]:rule> raw string rule`, 418 `+k8s:validation:cel[0]:message="raw string message"`, 419 }, 420 expected: &spec.Schema{ 421 VendorExtensible: spec.VendorExtensible{ 422 Extensions: map[string]interface{}{ 423 "x-kubernetes-validations": []interface{}{ 424 map[string]interface{}{ 425 "rule": "raw string rule", 426 "message": "raw string message", 427 }, 428 }, 429 }, 430 }, 431 }, 432 }, 433 { 434 t: types.Float64, 435 name: "multiline string rule", 436 comments: []string{ 437 `+k8s:validation:cel[0]:rule> self.length() % 2 == 0`, 438 `+k8s:validation:cel[0]:rule> ? self.field == self.name + ' is even'`, 439 `+k8s:validation:cel[0]:rule> : self.field == self.name + ' is odd'`, 440 `+k8s:validation:cel[0]:message>raw string message`, 441 }, 442 expected: &spec.Schema{ 443 VendorExtensible: spec.VendorExtensible{ 444 Extensions: map[string]interface{}{ 445 "x-kubernetes-validations": []interface{}{ 446 map[string]interface{}{ 447 "rule": "self.length() % 2 == 0\n? self.field == self.name + ' is even'\n: self.field == self.name + ' is odd'", 448 "message": "raw string message", 449 }, 450 }, 451 }, 452 }, 453 }, 454 }, 455 { 456 t: types.Float64, 457 name: "mix raw and non-raw string marker", 458 comments: []string{ 459 `+k8s:validation:cel[0]:message>raw string message`, 460 `+k8s:validation:cel[0]:rule="self.length() % 2 == 0"`, 461 `+k8s:validation:cel[0]:rule> ? self.field == self.name + ' is even'`, 462 `+k8s:validation:cel[0]:rule> : self.field == self.name + ' is odd'`, 463 }, 464 expected: &spec.Schema{ 465 VendorExtensible: spec.VendorExtensible{ 466 Extensions: map[string]interface{}{ 467 "x-kubernetes-validations": []interface{}{ 468 map[string]interface{}{ 469 "rule": "self.length() % 2 == 0\n? self.field == self.name + ' is even'\n: self.field == self.name + ' is odd'", 470 "message": "raw string message", 471 }, 472 }, 473 }, 474 }, 475 }, 476 }, 477 { 478 name: "raw string with different key in between", 479 t: types.Float64, 480 comments: []string{ 481 `+k8s:validation:cel[0]:message>raw string message`, 482 `+k8s:validation:cel[0]:rule="self.length() % 2 == 0"`, 483 `+k8s:validation:cel[0]:message>raw string message 2`, 484 }, 485 expectedError: `failed to parse marker comments: concatenations to key 'cel[0]:message' must be consecutive with its assignment`, 486 }, 487 { 488 name: "raw string with different raw string key in between", 489 t: types.Float64, 490 comments: []string{ 491 `+k8s:validation:cel[0]:message>raw string message`, 492 `+k8s:validation:cel[0]:rule>self.length() % 2 == 0`, 493 `+k8s:validation:cel[0]:message>raw string message 2`, 494 }, 495 expectedError: `failed to parse marker comments: concatenations to key 'cel[0]:message' must be consecutive with its assignment`, 496 }, 497 } 498 499 for _, tc := range cases { 500 t.Run(tc.name, func(t *testing.T) { 501 actual, err := generators.ParseCommentTags(tc.t, tc.comments, "+k8s:validation:") 502 if tc.expectedError != "" { 503 require.Error(t, err) 504 require.EqualError(t, err, tc.expectedError) 505 return 506 } else { 507 require.NoError(t, err) 508 } 509 510 require.Equal(t, tc.expected, actual) 511 }) 512 } 513 } 514 515 // Test comment tag validation function 516 func TestCommentTags_Validate(t *testing.T) { 517 518 testCases := []struct { 519 name string 520 comments []string 521 t *types.Type 522 errorMessage string 523 }{ 524 { 525 name: "invalid minimum type", 526 comments: []string{ 527 `+k8s:validation:minimum=10.5`, 528 }, 529 t: types.String, 530 errorMessage: "minimum can only be used on numeric types", 531 }, 532 { 533 name: "invalid minLength type", 534 comments: []string{ 535 `+k8s:validation:minLength=10`, 536 }, 537 t: types.Bool, 538 errorMessage: "minLength can only be used on string types", 539 }, 540 { 541 name: "invalid minItems type", 542 comments: []string{ 543 `+k8s:validation:minItems=10`, 544 }, 545 t: types.String, 546 errorMessage: "minItems can only be used on array types", 547 }, 548 { 549 name: "invalid minProperties type", 550 comments: []string{ 551 `+k8s:validation:minProperties=10`, 552 }, 553 t: types.String, 554 errorMessage: "minProperties can only be used on map types", 555 }, 556 { 557 name: "invalid exclusiveMinimum type", 558 comments: []string{ 559 `+k8s:validation:exclusiveMinimum=true`, 560 }, 561 t: arrayType, 562 errorMessage: "exclusiveMinimum can only be used on numeric types", 563 }, 564 { 565 name: "invalid maximum type", 566 comments: []string{ 567 `+k8s:validation:maximum=10.5`, 568 }, 569 t: arrayType, 570 errorMessage: "maximum can only be used on numeric types", 571 }, 572 { 573 name: "invalid maxLength type", 574 comments: []string{ 575 `+k8s:validation:maxLength=10`, 576 }, 577 t: mapType, 578 errorMessage: "maxLength can only be used on string types", 579 }, 580 { 581 name: "invalid maxItems type", 582 comments: []string{ 583 `+k8s:validation:maxItems=10`, 584 }, 585 t: types.Bool, 586 errorMessage: "maxItems can only be used on array types", 587 }, 588 { 589 name: "invalid maxProperties type", 590 comments: []string{ 591 `+k8s:validation:maxProperties=10`, 592 }, 593 t: types.Bool, 594 errorMessage: "maxProperties can only be used on map types", 595 }, 596 { 597 name: "invalid exclusiveMaximum type", 598 comments: []string{ 599 `+k8s:validation:exclusiveMaximum=true`, 600 }, 601 t: mapType, 602 errorMessage: "exclusiveMaximum can only be used on numeric types", 603 }, 604 { 605 name: "invalid pattern type", 606 comments: []string{ 607 `+k8s:validation:pattern=".*"`, 608 }, 609 t: types.Int, 610 errorMessage: "pattern can only be used on string types", 611 }, 612 { 613 name: "invalid multipleOf type", 614 comments: []string{ 615 `+k8s:validation:multipleOf=10.5`, 616 }, 617 t: types.String, 618 errorMessage: "multipleOf can only be used on numeric types", 619 }, 620 { 621 name: "invalid uniqueItems type", 622 comments: []string{ 623 `+k8s:validation:uniqueItems=true`, 624 }, 625 t: types.Int, 626 errorMessage: "uniqueItems can only be used on array types", 627 }, 628 { 629 name: "negative minLength", 630 comments: []string{ 631 `+k8s:validation:minLength=-10`, 632 }, 633 t: types.String, 634 errorMessage: "minLength cannot be negative", 635 }, 636 { 637 name: "negative minItems", 638 comments: []string{ 639 `+k8s:validation:minItems=-10`, 640 }, 641 t: arrayType, 642 errorMessage: "minItems cannot be negative", 643 }, 644 { 645 name: "negative minProperties", 646 comments: []string{ 647 `+k8s:validation:minProperties=-10`, 648 }, 649 t: mapType, 650 errorMessage: "minProperties cannot be negative", 651 }, 652 { 653 name: "negative maxLength", 654 comments: []string{ 655 `+k8s:validation:maxLength=-10`, 656 }, 657 t: types.String, 658 errorMessage: "maxLength cannot be negative", 659 }, 660 { 661 name: "negative maxItems", 662 comments: []string{ 663 `+k8s:validation:maxItems=-10`, 664 }, 665 t: arrayType, 666 errorMessage: "maxItems cannot be negative", 667 }, 668 { 669 name: "negative maxProperties", 670 comments: []string{ 671 `+k8s:validation:maxProperties=-10`, 672 }, 673 t: mapType, 674 errorMessage: "maxProperties cannot be negative", 675 }, 676 { 677 name: "minimum > maximum", 678 comments: []string{ 679 `+k8s:validation:minimum=10.5`, 680 `+k8s:validation:maximum=5.5`, 681 }, 682 t: types.Float64, 683 errorMessage: "minimum 10.500000 is greater than maximum 5.500000", 684 }, 685 { 686 name: "exclusiveMinimum when minimum == maximum", 687 comments: []string{ 688 `+k8s:validation:minimum=10.5`, 689 `+k8s:validation:maximum=10.5`, 690 `+k8s:validation:exclusiveMinimum=true`, 691 }, 692 t: types.Float64, 693 errorMessage: "exclusiveMinimum/Maximum cannot be set when minimum == maximum", 694 }, 695 { 696 name: "exclusiveMaximum when minimum == maximum", 697 comments: []string{ 698 `+k8s:validation:minimum=10.5`, 699 `+k8s:validation:maximum=10.5`, 700 `+k8s:validation:exclusiveMaximum=true`, 701 }, 702 t: types.Float64, 703 errorMessage: "exclusiveMinimum/Maximum cannot be set when minimum == maximum", 704 }, 705 { 706 name: "minLength > maxLength", 707 comments: []string{ 708 `+k8s:validation:minLength=10`, 709 `+k8s:validation:maxLength=5`, 710 }, 711 t: types.String, 712 errorMessage: "minLength 10 is greater than maxLength 5", 713 }, 714 { 715 name: "minItems > maxItems", 716 comments: []string{ 717 `+k8s:validation:minItems=10`, 718 `+k8s:validation:maxItems=5`, 719 }, 720 t: arrayType, 721 errorMessage: "minItems 10 is greater than maxItems 5", 722 }, 723 { 724 name: "minProperties > maxProperties", 725 comments: []string{ 726 `+k8s:validation:minProperties=10`, 727 `+k8s:validation:maxProperties=5`, 728 }, 729 t: mapType, 730 errorMessage: "minProperties 10 is greater than maxProperties 5", 731 }, 732 { 733 name: "invalid pattern", 734 comments: []string{ 735 `+k8s:validation:pattern="([a-z]+"`, 736 }, 737 t: types.String, 738 errorMessage: "invalid pattern \"([a-z]+\": error parsing regexp: missing closing ): `([a-z]+`", 739 }, 740 { 741 name: "multipleOf = 0", 742 comments: []string{ 743 `+k8s:validation:multipleOf=0.0`, 744 }, 745 t: types.Int, 746 errorMessage: "multipleOf cannot be 0", 747 }, 748 { 749 name: "valid comment tags with no invalid validations", 750 comments: []string{ 751 `+k8s:validation:pattern=".*"`, 752 }, 753 t: types.String, 754 errorMessage: "", 755 }, 756 } 757 758 for _, tc := range testCases { 759 t.Run(tc.name, func(t *testing.T) { 760 _, err := generators.ParseCommentTags(tc.t, tc.comments, "+k8s:validation:") 761 if tc.errorMessage != "" { 762 require.Error(t, err) 763 require.Equal(t, "invalid marker comments: "+tc.errorMessage, err.Error()) 764 } else { 765 require.NoError(t, err) 766 } 767 }) 768 } 769 }