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  }