github.com/kubevela/workflow@v0.6.0/pkg/cue/model/sets/operation_test.go (about) 1 /* 2 Copyright 2022 The KubeVela 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 sets 18 19 import ( 20 "fmt" 21 "testing" 22 23 "cuelang.org/go/cue" 24 "cuelang.org/go/cue/cuecontext" 25 "cuelang.org/go/cue/parser" 26 "github.com/stretchr/testify/require" 27 ) 28 29 func TestPatch(t *testing.T) { 30 31 testCase := []struct { 32 base string 33 patch string 34 result string 35 expectedErr string 36 }{ 37 { 38 base: `containers: [{name: "x1"},{name: "x2"},...]`, 39 patch: `containers: [{name: "x1"},{name: "x2",image: "pause:0.1"}]`, 40 result: `containers: [{ 41 name: "x1" 42 }, { 43 name: "x2" 44 image: "pause:0.1" 45 }] 46 `, 47 }, 48 49 { 50 base: `containers: [{name: "x1"},{name: "x2"},...]`, 51 patch: `containers: [{name: "x2"},{name: "x1"}]`, 52 result: `containers: [{ 53 name: _|_ // containers.0.name: conflicting values "x2" and "x1" 54 }, { 55 name: _|_ // containers.1.name: conflicting values "x1" and "x2" 56 }] 57 `, 58 expectedErr: `conflicting values "x2" and "x1"`, 59 }, 60 61 { 62 base: `containers: [{name: _|_},{name: "x2"},...]`, 63 patch: `containers: [{name: _|_},{name: "x2"}]`, 64 result: `containers: [{ 65 name: _|_ // explicit error (_|_ literal) in source (and 1 more errors) 66 }, { 67 name: "x2" 68 }] 69 `, 70 expectedErr: "explicit error (_|_ literal) in source", 71 }, 72 73 { 74 base: `containers: [{name: "x1"},{name: "x2"},...]`, 75 patch: ` 76 // +patchKey=name 77 containers: [{name: "x2"},{name: "x1"}]`, 78 result: `// +patchKey=name 79 containers: [{ 80 name: "x1" 81 }, { 82 name: "x2" 83 }, ...] 84 `, 85 }, 86 87 { 88 // lose close here 89 base: `containers: [close({namex: "x1"}),...]`, 90 patch: ` 91 // +patchKey=name 92 containers: [{name: "x2"},{name: "x1"}]`, 93 result: `// +patchKey=name 94 containers: [{ 95 namex: "x1" 96 name: "x2" 97 }, { 98 name: "x1" 99 }, ...] 100 `, 101 }, 102 103 { 104 base: `containers: [{name: "x1"},{name: "x2"},...]`, 105 patch: ` 106 // +patchKey=name 107 containers: [{name: "x4"},{name: "x3"},{name: "x1"}]`, 108 result: `// +patchKey=name 109 containers: [{ 110 name: "x1" 111 }, { 112 name: "x2" 113 }, { 114 name: "x4" 115 }, { 116 name: "x3" 117 }, ...] 118 `, 119 }, 120 121 { 122 base: `containers: [{name: "x1"},{name: "x2"},...]`, 123 patch: ` 124 // +patchKey=name 125 containers: [{noname: "x3"},...]`, 126 result: `// +patchKey=name 127 containers: [{ 128 name: "x1" 129 noname: "x3" 130 }, { 131 name: "x2" 132 }, ...] 133 `, 134 }, 135 { 136 base: `containers: [{name: "x1"},{name: "x2"},...]`, 137 patch: `// +patchKey=name 138 containers: [{noname: "x3"},{name: "x1"}]`, 139 result: `// +patchKey=name 140 containers: [{ 141 name: "x1" 142 }, { 143 name: "x2" 144 }, ...] 145 `, 146 }, 147 { 148 base: `containers: [{name: "x1"},{name: "x2", envs:[ {name: "OPS",value: string},...]},...]`, 149 patch: ` 150 // +patchKey=name 151 containers: [{name: "x2", envs: [{name: "OPS", value: "OAM"}]}]`, 152 result: `// +patchKey=name 153 containers: [{ 154 name: "x1" 155 }, { 156 name: "x2" 157 envs: [{ 158 name: "OPS" 159 value: "OAM" 160 }, ...] 161 }, ...] 162 `, 163 }, 164 { 165 base: `containers: [close({name: "x1"}),close({name: "x2", envs:[{name: "OPS",value: string},...]}),...]`, 166 patch: ` 167 // +patchKey=name 168 containers: [{name: "x2", envs: [close({name: "OPS", value: "OAM"})]}]`, 169 // TODO: fix losing close struct in cue 170 result: `// +patchKey=name 171 containers: [{ 172 name: "x1" 173 }, { 174 name: "x2" 175 envs: [{ 176 name: "OPS" 177 value: "OAM" 178 }, ...] 179 }, ...] 180 `, 181 }, 182 183 { 184 base: `containers: [{name: "x1"},{name: "x2", envs:[ {name: "OPS",value: string},...]},...]`, 185 patch: ` 186 // +patchKey=name 187 containers: [{name: "x2", envs: [{name: "USER", value: "DEV"},{name: "OPS", value: "OAM"}]}]`, 188 result: `// +patchKey=name 189 containers: [{ 190 name: "x1" 191 }, { 192 name: "x2" 193 envs: [{ 194 name: "OPS" 195 value: "OAM" 196 }, { 197 name: "USER" 198 value: "DEV" 199 }, ...] 200 }, ...] 201 `, 202 }, 203 204 { 205 base: `containers: [{name: "x1"},{name: "x2", envs:[ {key: "OPS",value: string},...]},...]`, 206 patch: ` 207 // +patchKey=name 208 containers: [{name: "x2", 209 // +patchKey=key 210 envs: [{key: "USER", value: "DEV"},{key: "OPS", value: "OAM"}]}]`, 211 result: `// +patchKey=name 212 containers: [{ 213 name: "x1" 214 }, { 215 name: "x2" 216 // +patchKey=key 217 envs: [{ 218 key: "OPS" 219 value: "OAM" 220 }, { 221 key: "USER" 222 value: "DEV" 223 }, ...] 224 }, ...] 225 `, 226 }, 227 { 228 base: `envFrom: [{ 229 secretRef: { 230 name: "nginx-rds" 231 }},...]`, 232 patch: ` 233 // +patchKey=secretRef.name 234 envFrom: [{ 235 secretRef: { 236 name: "nginx-redis" 237 }},...] 238 `, 239 result: `// +patchKey=secretRef.name 240 envFrom: [{ 241 secretRef: { 242 name: "nginx-rds" 243 } 244 }, { 245 secretRef: { 246 name: "nginx-redis" 247 } 248 }, ...] 249 `}, 250 { 251 base: ` 252 containers: [{ 253 name: "c1" 254 },{ 255 name: "c2" 256 envFrom: [{ 257 secretRef: { 258 name: "nginx-rds" 259 }},...] 260 },...]`, 261 patch: ` 262 // +patchKey=name 263 containers: [{ 264 name: "c2" 265 // +patchKey=secretRef.name 266 envFrom: [{ 267 secretRef: { 268 name: "nginx-redis" 269 }},...] 270 }]`, 271 result: `// +patchKey=name 272 containers: [{ 273 name: "c1" 274 }, { 275 name: "c2" 276 // +patchKey=secretRef.name 277 envFrom: [{ 278 secretRef: { 279 name: "nginx-rds" 280 } 281 }, { 282 secretRef: { 283 name: "nginx-redis" 284 } 285 }, ...] 286 }, ...] 287 `}, 288 289 { 290 base: ` 291 containers: [{ 292 volumeMounts: [{name: "k1", path: "p1"},{name: "k1", path: "p2"},...] 293 },...] 294 volumes: [{name: "x1",value: "v1"},{name: "x2",value: "v2"},...] 295 `, 296 297 patch: ` 298 // +patchKey=name 299 volumes: [{name: "x1",value: "v1"},{name: "x3",value: "x2"}] 300 301 containers: [{ 302 volumeMounts: [{name: "k1", path: "p1"},{name: "k1", path: "p2"},{ name:"k2", path: "p3"}] 303 },...]`, 304 result: `containers: [{ 305 volumeMounts: [{ 306 name: "k1" 307 path: "p1" 308 }, { 309 name: "k1" 310 path: "p2" 311 }, { 312 name: "k2" 313 path: "p3" 314 }] 315 }, ...] 316 317 // +patchKey=name 318 volumes: [{ 319 name: "x1" 320 value: "v1" 321 }, { 322 name: "x2" 323 value: "v2" 324 }, { 325 name: "x3" 326 value: "x2" 327 }, ...] 328 `}, 329 330 { 331 base: ` 332 containers: [{ 333 name: "c1" 334 },{ 335 name: "c2" 336 envFrom: [{ 337 secretRef: { 338 name: "nginx-rds" 339 }, 340 }, { 341 configMapRef: { 342 name: "nginx-rds" 343 }, 344 },...] 345 },...]`, 346 patch: ` 347 // +patchKey=name 348 containers: [{ 349 name: "c2" 350 // +patchKey=secretRef.name,configMapRef.name 351 envFrom: [{ 352 secretRef: { 353 name: "nginx-redis" 354 }, 355 }, { 356 configMapRef: { 357 name: "nginx-redis" 358 }, 359 },...] 360 }]`, 361 result: `// +patchKey=name 362 containers: [{ 363 name: "c1" 364 }, { 365 name: "c2" 366 // +patchKey=secretRef.name,configMapRef.name 367 envFrom: [{ 368 secretRef: { 369 name: "nginx-rds" 370 } 371 }, { 372 configMapRef: { 373 name: "nginx-rds" 374 } 375 }, { 376 secretRef: { 377 name: "nginx-redis" 378 } 379 }, { 380 configMapRef: { 381 name: "nginx-redis" 382 } 383 }, ...] 384 }, ...] 385 `}, 386 { 387 base: `containers: [{name: "x1"}]`, 388 patch: ` 389 containers: [{ 390 // +patchKey=name 391 env: [{ 392 name: "k" 393 value: "v" 394 }] 395 }, ...]`, 396 result: `containers: [{ 397 name: "x1" 398 // +patchKey=name 399 env: [{ 400 name: "k" 401 value: "v" 402 }] 403 }, ...] 404 `, 405 }, 406 } 407 408 for i, tcase := range testCase { 409 t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) { 410 r := require.New(t) 411 ctx := cuecontext.New() 412 base := ctx.CompileString(tcase.base) 413 patch := ctx.CompileString(tcase.patch) 414 v, err := StrategyUnify(base, patch) 415 if tcase.expectedErr != "" { 416 r.Error(err) 417 r.Contains(err.Error(), tcase.expectedErr) 418 return 419 } 420 r.NoError(err) 421 s, err := toString(v) 422 r.NoError(err) 423 r.Equal(s, tcase.result, fmt.Sprintf("testPatch for case(no:%d) %s", i, v)) 424 }) 425 } 426 } 427 428 func TestStrategyPatch(t *testing.T) { 429 testCase := []struct { 430 base string 431 patch string 432 options []UnifyOption 433 result string 434 }{ 435 { 436 base: ` 437 spec: { 438 strategy: { 439 type: "rollingUpdate" 440 rollingUpdate: maxSurge: "30%" 441 } 442 } 443 `, 444 patch: ` 445 spec: { 446 // +patchStrategy=retainKeys 447 strategy: type: "recreate" 448 } 449 `, 450 result: `spec: { 451 strategy: { 452 // +patchStrategy=retainKeys 453 type: "recreate" 454 rollingUpdate: { 455 maxSurge: "30%" 456 } 457 } 458 } 459 `}, 460 461 { 462 base: ` 463 spec: { 464 strategy: close({ 465 type: "rollingUpdate" 466 rollingUpdate: maxSurge: "30%" 467 }) 468 } 469 `, 470 patch: ` 471 spec: { 472 // +patchStrategy=retainKeys 473 strategy: type: "recreate" 474 } 475 `, 476 result: `spec: { 477 strategy: { 478 // +patchStrategy=retainKeys 479 type: "recreate" 480 rollingUpdate: { 481 maxSurge: "30%" 482 } 483 } 484 } 485 `}, 486 487 { 488 base: ` 489 volumes: [{ 490 name: "test-volume" 491 cinder: { 492 volumeID: "<volume id>" 493 fsType: "ext4" 494 } 495 }] 496 `, 497 patch: ` 498 // +patchStrategy=retainKeys 499 // +patchKey=name 500 volumes: [ 501 { 502 name: "test-volume" 503 configMap: name: "conf-name" 504 }] 505 `, 506 result: `// +patchStrategy=retainKeys 507 // +patchKey=name 508 volumes: [{ 509 name: "test-volume" 510 configMap: { 511 name: "conf-name" 512 } 513 }, ...] 514 `}, 515 516 { 517 base: ` 518 volumes: [{ 519 name: "empty-volume" 520 emptyDir: {} 521 }, 522 { 523 name: "test-volume" 524 cinder: { 525 volumeID: "<volume id>" 526 fsType: "ext4" 527 } 528 }] 529 `, 530 patch: ` 531 // +patchStrategy=retainKeys 532 // +patchKey=name 533 volumes: [ 534 { 535 name: "test-volume" 536 configMap: name: "conf-name" 537 }] 538 `, 539 result: `// +patchStrategy=retainKeys 540 // +patchKey=name 541 volumes: [{ 542 name: "empty-volume" 543 emptyDir: {} 544 }, { 545 name: "test-volume" 546 configMap: { 547 name: "conf-name" 548 } 549 }, ...] 550 `}, 551 552 { 553 base: ` 554 containers: [{ 555 name: "c1" 556 image: "image1" 557 }, 558 { 559 name: "c2" 560 envs:[{name: "e1",value: "v1"}] 561 }] 562 `, 563 patch: ` 564 // +patchKey=name 565 containers: [{ 566 name: "c2" 567 // +patchStrategy=retainKeys 568 envs:[{name: "e1",value: "v2"},...] 569 }] 570 `, 571 result: `// +patchKey=name 572 containers: [{ 573 name: "c1" 574 image: "image1" 575 }, { 576 name: "c2" 577 // +patchStrategy=retainKeys 578 envs: [{ 579 name: "e1" 580 value: "v2" 581 }, ...] 582 }, ...] 583 `}, 584 585 { 586 base: ` 587 spec: containers: [{ 588 name: "c1" 589 image: "image1" 590 }, 591 { 592 name: "c2" 593 envs:[{name: "e1",value: "v1"}] 594 }] 595 `, 596 patch: ` 597 // +patchKey=name 598 // +patchStrategy=retainKeys 599 spec: { 600 containers: [{ 601 name: "c2" 602 envs:[{name: "e1",value: "v2"}] 603 }]} 604 `, 605 result: `spec: { 606 // +patchKey=name 607 // +patchStrategy=retainKeys 608 containers: [{ 609 name: "c2" 610 envs: [{ 611 name: "e1" 612 value: "v2" 613 }, ...] 614 }, ...] 615 } 616 `}, { 617 base: ` 618 kind: "Old" 619 metadata: { 620 name: "Old" 621 labels: keep: "true" 622 } 623 `, 624 patch: `// +patchStrategy=retainKeys 625 kind: "New" 626 metadata: { 627 // +patchStrategy=retainKeys 628 name: "New" 629 } 630 `, 631 result: ` // +patchStrategy=retainKeys 632 kind: "New" 633 metadata: { 634 // +patchStrategy=retainKeys 635 name: "New" 636 labels: { 637 keep: "true" 638 } 639 } 640 `}, { 641 base: ` 642 spec: containers: [{ 643 name: "c1" 644 image: "image1" 645 }, 646 { 647 name: "c2" 648 envs:[{name: "e1",value: "v1"}] 649 }] 650 `, 651 patch: ` 652 spec: containers: [{ 653 name: "c3" 654 image: "image3" 655 }] 656 `, 657 result: `spec: { 658 containers: [{ 659 image: "image3" 660 name: "c3" 661 }, ...] 662 } 663 `, 664 options: []UnifyOption{UnifyByJSONMergePatch{}}, 665 }, 666 { 667 base: ` 668 spec: containers: [{ 669 name: "c1" 670 image: "image1" 671 }] 672 `, 673 patch: ` 674 operations: [{ 675 {op: "add", path: "/spec/containers/0", value: {name: "c4", image: "image4"}} 676 }] 677 `, 678 result: `spec: { 679 containers: [{ 680 name: "c4" 681 image: "image4" 682 }, { 683 name: "c1" 684 image: "image1" 685 }, ...] 686 } 687 `, 688 options: []UnifyOption{UnifyByJSONPatch{}}, 689 }, 690 { 691 base: ` 692 spec: containers: [{ 693 name: "c1" 694 envs:[{name: "e1",value: "v1"}] 695 }] 696 `, 697 patch: ` 698 // +patchKey=name 699 spec: { 700 containers: [{ 701 name: "c1" 702 // +patchStrategy=replace 703 envs:[{name: "e1",value: "v2"}] 704 }]} 705 `, 706 result: `spec: { 707 // +patchKey=name 708 containers: [{ 709 name: "c1" 710 // +patchStrategy=replace 711 envs: [{ 712 name: "e1" 713 value: "v2" 714 }] 715 }, ...] 716 } 717 `}, 718 } 719 720 for i, tcase := range testCase { 721 r := require.New(t) 722 ctx := cuecontext.New() 723 base := ctx.CompileString(tcase.base) 724 patch := ctx.CompileString(tcase.patch) 725 v, err := StrategyUnify(base, patch, tcase.options...) 726 r.NoError(err) 727 s, err := toString(v) 728 r.NoError(err) 729 r.Equal(s, tcase.result, fmt.Sprintf("testPatch for case(no:%d) %s", i, s)) 730 } 731 } 732 733 func TestParseCommentTags(t *testing.T) { 734 temp := ` 735 // +patchKey=name 736 // +testKey1=testValue1 737 // +testKey2=testValue2 738 // +testKey3 =testValue3 739 // +testKey4 = testValue4 740 // invalid=x 741 // +invalid=x y 742 // +invalid 743 x: null 744 ` 745 746 r := require.New(t) 747 file, err := parser.ParseFile("-", temp, parser.ParseComments) 748 r.NoError(err) 749 v := cuecontext.New().BuildFile(file) 750 ms := findCommentTag(v.LookupPath(cue.ParsePath("x")).Doc()) 751 r.Equal(ms, map[string]string{ 752 "patchKey": "name", 753 "testKey1": "testValue1", 754 "testKey2": "testValue2", 755 "testKey3": "testValue3", 756 "testKey4": "testValue4", 757 }) 758 }