github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/jsonmergepatch/patch_test.go (about) 1 /* 2 Copyright 2017 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 jsonmergepatch 18 19 import ( 20 "fmt" 21 "reflect" 22 "testing" 23 24 "github.com/davecgh/go-spew/spew" 25 "github.com/evanphx/json-patch" 26 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/json" 27 "sigs.k8s.io/yaml" 28 ) 29 30 type FilterNullTestCases struct { 31 TestCases []FilterNullTestCase 32 } 33 34 type FilterNullTestCase struct { 35 Description string 36 OriginalObj map[string]interface{} 37 ExpectedWithNull map[string]interface{} 38 ExpectedWithoutNull map[string]interface{} 39 } 40 41 var filterNullTestCaseData = []byte(` 42 testCases: 43 - description: nil original 44 originalObj: {} 45 expectedWithNull: {} 46 expectedWithoutNull: {} 47 - description: simple map 48 originalObj: 49 nilKey: null 50 nonNilKey: foo 51 expectedWithNull: 52 nilKey: null 53 expectedWithoutNull: 54 nonNilKey: foo 55 - description: simple map with all nil values 56 originalObj: 57 nilKey1: null 58 nilKey2: null 59 expectedWithNull: 60 nilKey1: null 61 nilKey2: null 62 expectedWithoutNull: {} 63 - description: simple map with all non-nil values 64 originalObj: 65 nonNilKey1: foo 66 nonNilKey2: bar 67 expectedWithNull: {} 68 expectedWithoutNull: 69 nonNilKey1: foo 70 nonNilKey2: bar 71 - description: nested map 72 originalObj: 73 mapKey: 74 nilKey: null 75 nonNilKey: foo 76 expectedWithNull: 77 mapKey: 78 nilKey: null 79 expectedWithoutNull: 80 mapKey: 81 nonNilKey: foo 82 - description: nested map that all subkeys are nil 83 originalObj: 84 mapKey: 85 nilKey1: null 86 nilKey2: null 87 expectedWithNull: 88 mapKey: 89 nilKey1: null 90 nilKey2: null 91 expectedWithoutNull: {} 92 - description: nested map that all subkeys are non-nil 93 originalObj: 94 mapKey: 95 nonNilKey1: foo 96 nonNilKey2: bar 97 expectedWithNull: {} 98 expectedWithoutNull: 99 mapKey: 100 nonNilKey1: foo 101 nonNilKey2: bar 102 - description: explicitly empty map as value 103 originalObj: 104 mapKey: {} 105 expectedWithNull: {} 106 expectedWithoutNull: 107 mapKey: {} 108 - description: explicitly empty nested map 109 originalObj: 110 mapKey: 111 nonNilKey: {} 112 expectedWithNull: {} 113 expectedWithoutNull: 114 mapKey: 115 nonNilKey: {} 116 - description: multiple expliclty empty nested maps 117 originalObj: 118 mapKey: 119 nonNilKey1: {} 120 nonNilKey2: {} 121 expectedWithNull: {} 122 expectedWithoutNull: 123 mapKey: 124 nonNilKey1: {} 125 nonNilKey2: {} 126 - description: nested map with non-null value as empty map 127 originalObj: 128 mapKey: 129 nonNilKey: {} 130 nilKey: null 131 expectedWithNull: 132 mapKey: 133 nilKey: null 134 expectedWithoutNull: 135 mapKey: 136 nonNilKey: {} 137 - description: empty list 138 originalObj: 139 listKey: [] 140 expectedWithNull: {} 141 expectedWithoutNull: 142 listKey: [] 143 - description: list of primitives 144 originalObj: 145 listKey: 146 - 1 147 - 2 148 expectedWithNull: {} 149 expectedWithoutNull: 150 listKey: 151 - 1 152 - 2 153 - description: list of maps 154 originalObj: 155 listKey: 156 - k1: v1 157 - k2: null 158 - k3: v3 159 k4: null 160 expectedWithNull: {} 161 expectedWithoutNull: 162 listKey: 163 - k1: v1 164 - k2: null 165 - k3: v3 166 k4: null 167 - description: list of different types 168 originalObj: 169 listKey: 170 - k1: v1 171 - k2: null 172 - v3 173 expectedWithNull: {} 174 expectedWithoutNull: 175 listKey: 176 - k1: v1 177 - k2: null 178 - v3 179 `) 180 181 func TestKeepOrDeleteNullInObj(t *testing.T) { 182 tc := FilterNullTestCases{} 183 err := yaml.Unmarshal(filterNullTestCaseData, &tc) 184 if err != nil { 185 t.Fatalf("can't unmarshal test cases: %s\n", err) 186 } 187 188 for _, test := range tc.TestCases { 189 resultWithNull, err := keepOrDeleteNullInObj(test.OriginalObj, true) 190 if err != nil { 191 t.Errorf("Failed in test case %q when trying to keep null values: %s", test.Description, err) 192 } 193 if !reflect.DeepEqual(test.ExpectedWithNull, resultWithNull) { 194 t.Errorf("Failed in test case %q when trying to keep null values:\nexpected expectedWithNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithNull, resultWithNull) 195 } 196 197 resultWithoutNull, err := keepOrDeleteNullInObj(test.OriginalObj, false) 198 if err != nil { 199 t.Errorf("Failed in test case %q when trying to keep non-null values: %s", test.Description, err) 200 } 201 if !reflect.DeepEqual(test.ExpectedWithoutNull, resultWithoutNull) { 202 t.Errorf("Failed in test case %q when trying to keep non-null values:\n expected expectedWithoutNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithoutNull, resultWithoutNull) 203 } 204 } 205 } 206 207 type JSONMergePatchTestCases struct { 208 TestCases []JSONMergePatchTestCase 209 } 210 211 type JSONMergePatchTestCase struct { 212 Description string 213 JSONMergePatchTestCaseData 214 } 215 216 type JSONMergePatchTestCaseData struct { 217 // Original is the original object (last-applied config in annotation) 218 Original map[string]interface{} 219 // Modified is the modified object (new config we want) 220 Modified map[string]interface{} 221 // Current is the current object (live config in the server) 222 Current map[string]interface{} 223 // ThreeWay is the expected three-way merge patch 224 ThreeWay map[string]interface{} 225 // Result is the expected object after applying the three-way patch on current object. 226 Result map[string]interface{} 227 } 228 229 var createJSONMergePatchTestCaseData = []byte(` 230 testCases: 231 - description: nil original 232 modified: 233 name: 1 234 value: 1 235 current: 236 name: 1 237 other: a 238 threeWay: 239 value: 1 240 result: 241 name: 1 242 value: 1 243 other: a 244 - description: nil patch 245 original: 246 name: 1 247 modified: 248 name: 1 249 current: 250 name: 1 251 threeWay: 252 {} 253 result: 254 name: 1 255 - description: add field to map 256 original: 257 name: 1 258 modified: 259 name: 1 260 value: 1 261 current: 262 name: 1 263 other: a 264 threeWay: 265 value: 1 266 result: 267 name: 1 268 value: 1 269 other: a 270 - description: add field to map with conflict 271 original: 272 name: 1 273 modified: 274 name: 1 275 value: 1 276 current: 277 name: a 278 other: a 279 threeWay: 280 name: 1 281 value: 1 282 result: 283 name: 1 284 value: 1 285 other: a 286 - description: add field and delete field from map 287 original: 288 name: 1 289 modified: 290 value: 1 291 current: 292 name: 1 293 other: a 294 threeWay: 295 name: null 296 value: 1 297 result: 298 value: 1 299 other: a 300 - description: add field and delete field from map with conflict 301 original: 302 name: 1 303 modified: 304 value: 1 305 current: 306 name: a 307 other: a 308 threeWay: 309 name: null 310 value: 1 311 result: 312 value: 1 313 other: a 314 - description: delete field from nested map 315 original: 316 simpleMap: 317 key1: 1 318 key2: 1 319 modified: 320 simpleMap: 321 key1: 1 322 current: 323 simpleMap: 324 key1: 1 325 key2: 1 326 other: a 327 threeWay: 328 simpleMap: 329 key2: null 330 result: 331 simpleMap: 332 key1: 1 333 other: a 334 - description: delete field from nested map with conflict 335 original: 336 simpleMap: 337 key1: 1 338 key2: 1 339 modified: 340 simpleMap: 341 key1: 1 342 current: 343 simpleMap: 344 key1: a 345 key2: 1 346 other: a 347 threeWay: 348 simpleMap: 349 key1: 1 350 key2: null 351 result: 352 simpleMap: 353 key1: 1 354 other: a 355 - description: delete all fields from map 356 original: 357 name: 1 358 value: 1 359 modified: {} 360 current: 361 name: 1 362 value: 1 363 other: a 364 threeWay: 365 name: null 366 value: null 367 result: 368 other: a 369 - description: delete all fields from map with conflict 370 original: 371 name: 1 372 value: 1 373 modified: {} 374 current: 375 name: 1 376 value: a 377 other: a 378 threeWay: 379 name: null 380 value: null 381 result: 382 other: a 383 - description: add field and delete all fields from map 384 original: 385 name: 1 386 value: 1 387 modified: 388 other: a 389 current: 390 name: 1 391 value: 1 392 other: a 393 threeWay: 394 name: null 395 value: null 396 result: 397 other: a 398 - description: add field and delete all fields from map with conflict 399 original: 400 name: 1 401 value: 1 402 modified: 403 other: a 404 current: 405 name: 1 406 value: 1 407 other: b 408 threeWay: 409 name: null 410 value: null 411 other: a 412 result: 413 other: a 414 - description: replace list of scalars 415 original: 416 intList: 417 - 1 418 - 2 419 modified: 420 intList: 421 - 2 422 - 3 423 current: 424 intList: 425 - 1 426 - 2 427 threeWay: 428 intList: 429 - 2 430 - 3 431 result: 432 intList: 433 - 2 434 - 3 435 - description: replace list of scalars with conflict 436 original: 437 intList: 438 - 1 439 - 2 440 modified: 441 intList: 442 - 2 443 - 3 444 current: 445 intList: 446 - 1 447 - 4 448 threeWay: 449 intList: 450 - 2 451 - 3 452 result: 453 intList: 454 - 2 455 - 3 456 - description: patch with different scalar type 457 original: 458 foo: 1 459 modified: 460 foo: true 461 current: 462 foo: 1 463 bar: 2 464 threeWay: 465 foo: true 466 result: 467 foo: true 468 bar: 2 469 - description: patch from scalar to list 470 original: 471 foo: 0 472 modified: 473 foo: 474 - 1 475 - 2 476 current: 477 foo: 0 478 bar: 2 479 threeWay: 480 foo: 481 - 1 482 - 2 483 result: 484 foo: 485 - 1 486 - 2 487 bar: 2 488 - description: patch from list to scalar 489 original: 490 foo: 491 - 1 492 - 2 493 modified: 494 foo: 0 495 current: 496 foo: 497 - 1 498 - 2 499 bar: 2 500 threeWay: 501 foo: 0 502 result: 503 foo: 0 504 bar: 2 505 - description: patch from scalar to map 506 original: 507 foo: 0 508 modified: 509 foo: 510 baz: 1 511 current: 512 foo: 0 513 bar: 2 514 threeWay: 515 foo: 516 baz: 1 517 result: 518 foo: 519 baz: 1 520 bar: 2 521 - description: patch from map to scalar 522 original: 523 foo: 524 baz: 1 525 modified: 526 foo: 0 527 current: 528 foo: 529 baz: 1 530 bar: 2 531 threeWay: 532 foo: 0 533 result: 534 foo: 0 535 bar: 2 536 - description: patch from map to list 537 original: 538 foo: 539 baz: 1 540 modified: 541 foo: 542 - 1 543 - 2 544 current: 545 foo: 546 baz: 1 547 bar: 2 548 threeWay: 549 foo: 550 - 1 551 - 2 552 result: 553 foo: 554 - 1 555 - 2 556 bar: 2 557 - description: patch from list to map 558 original: 559 foo: 560 - 1 561 - 2 562 modified: 563 foo: 564 baz: 0 565 current: 566 foo: 567 - 1 568 - 2 569 bar: 2 570 threeWay: 571 foo: 572 baz: 0 573 result: 574 foo: 575 baz: 0 576 bar: 2 577 - description: patch with different nested types 578 original: 579 foo: 580 - a: true 581 - 2 582 - false 583 modified: 584 foo: 585 - 1 586 - false 587 - b: 1 588 current: 589 foo: 590 - a: true 591 - 2 592 - false 593 bar: 0 594 threeWay: 595 foo: 596 - 1 597 - false 598 - b: 1 599 result: 600 foo: 601 - 1 602 - false 603 - b: 1 604 bar: 0 605 - description: patch array with nil 606 original: 607 foo: 608 - a: true 609 - null 610 - false 611 bar: [] 612 drop: 613 - 1 614 modified: 615 foo: 616 - 1 617 - false 618 - b: 1 619 bar: 620 - c 621 - null 622 - null 623 - a 624 drop: 625 - null 626 current: 627 foo: 628 - a: true 629 - 2 630 - false 631 bar: 632 - c 633 - null 634 - null 635 - a 636 drop: 637 threeWay: 638 foo: 639 - 1 640 - false 641 - b: 1 642 drop: 643 - null 644 result: 645 foo: 646 - 1 647 - false 648 - b: 1 649 drop: 650 - null 651 bar: 652 - c 653 - null 654 - null 655 - a 656 `) 657 658 func TestCreateThreeWayJSONMergePatch(t *testing.T) { 659 tc := JSONMergePatchTestCases{} 660 err := yaml.Unmarshal(createJSONMergePatchTestCaseData, &tc) 661 if err != nil { 662 t.Errorf("can't unmarshal test cases: %s\n", err) 663 return 664 } 665 666 for _, c := range tc.TestCases { 667 testThreeWayPatch(t, c) 668 } 669 } 670 671 func testThreeWayPatch(t *testing.T, c JSONMergePatchTestCase) { 672 original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c) 673 actual, err := CreateThreeWayJSONMergePatch(original, modified, current) 674 if err != nil { 675 t.Fatalf("error: %s", err) 676 } 677 testPatchCreation(t, expected, actual, c.Description) 678 testPatchApplication(t, current, actual, result, c.Description) 679 } 680 681 func testPatchCreation(t *testing.T, expected, actual []byte, description string) { 682 if !reflect.DeepEqual(actual, expected) { 683 t.Errorf("error in test case: %s\nexpected patch:\n%s\ngot:\n%s\n", 684 description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual)) 685 return 686 } 687 } 688 689 func testPatchApplication(t *testing.T, original, patch, expected []byte, description string) { 690 result, err := jsonpatch.MergePatch(original, patch) 691 if err != nil { 692 t.Errorf("error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n", 693 err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) 694 return 695 } 696 697 if !reflect.DeepEqual(result, expected) { 698 format := "error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n" 699 t.Errorf(format, description, 700 jsonToYAMLOrError(original), jsonToYAMLOrError(patch), 701 jsonToYAMLOrError(expected), jsonToYAMLOrError(result)) 702 return 703 } 704 } 705 706 func threeWayTestCaseToJSONOrFail(t *testing.T, c JSONMergePatchTestCase) ([]byte, []byte, []byte, []byte, []byte) { 707 return testObjectToJSONOrFail(t, c.Original), 708 testObjectToJSONOrFail(t, c.Modified), 709 testObjectToJSONOrFail(t, c.Current), 710 testObjectToJSONOrFail(t, c.ThreeWay), 711 testObjectToJSONOrFail(t, c.Result) 712 } 713 714 func testObjectToJSONOrFail(t *testing.T, o map[string]interface{}) []byte { 715 if o == nil { 716 return nil 717 } 718 j, err := toJSON(o) 719 if err != nil { 720 t.Error(err) 721 } 722 return j 723 } 724 725 func jsonToYAMLOrError(j []byte) string { 726 y, err := jsonToYAML(j) 727 if err != nil { 728 return err.Error() 729 } 730 return string(y) 731 } 732 733 func toJSON(v interface{}) ([]byte, error) { 734 j, err := json.Marshal(v) 735 if err != nil { 736 return nil, fmt.Errorf("json marshal failed: %v\n%v\n", err, spew.Sdump(v)) 737 } 738 return j, nil 739 } 740 741 func jsonToYAML(j []byte) ([]byte, error) { 742 y, err := yaml.JSONToYAML(j) 743 if err != nil { 744 return nil, fmt.Errorf("json to yaml failed: %v\n%v\n", err, j) 745 } 746 return y, nil 747 }