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  }