sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/cluster/patches/patch_test.go (about)

     1  /*
     2  Copyright 2021 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 patches
    18  
    19  import (
    20  	"testing"
    21  
    22  	. "github.com/onsi/gomega"
    23  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    24  
    25  	"sigs.k8s.io/cluster-api/internal/contract"
    26  )
    27  
    28  func TestCopySpec(t *testing.T) {
    29  	tests := []struct {
    30  		name    string
    31  		input   copySpecInput
    32  		want    *unstructured.Unstructured
    33  		wantErr bool
    34  	}{
    35  		{
    36  			name: "Field both in src and dest, no-op when equal",
    37  			input: copySpecInput{
    38  				src: &unstructured.Unstructured{
    39  					Object: map[string]interface{}{
    40  						"spec": map[string]interface{}{
    41  							"A": "A",
    42  						},
    43  					},
    44  				},
    45  				dest: &unstructured.Unstructured{
    46  					Object: map[string]interface{}{
    47  						"spec": map[string]interface{}{
    48  							"A": "A",
    49  						},
    50  					},
    51  				},
    52  				srcSpecPath:  "spec",
    53  				destSpecPath: "spec",
    54  			},
    55  			want: &unstructured.Unstructured{
    56  				Object: map[string]interface{}{
    57  					"spec": map[string]interface{}{
    58  						"A": "A",
    59  					},
    60  				},
    61  			},
    62  		},
    63  		{
    64  			name: "Field both in src and dest, overwrite dest when different",
    65  			input: copySpecInput{
    66  				src: &unstructured.Unstructured{
    67  					Object: map[string]interface{}{
    68  						"spec": map[string]interface{}{
    69  							"A": "A",
    70  						},
    71  					},
    72  				},
    73  				dest: &unstructured.Unstructured{
    74  					Object: map[string]interface{}{
    75  						"spec": map[string]interface{}{
    76  							"A": "A-different",
    77  						},
    78  					},
    79  				},
    80  				srcSpecPath:  "spec",
    81  				destSpecPath: "spec",
    82  			},
    83  			want: &unstructured.Unstructured{
    84  				Object: map[string]interface{}{
    85  					"spec": map[string]interface{}{
    86  						"A": "A",
    87  					},
    88  				},
    89  			},
    90  		},
    91  		{
    92  			name: "Nested field both in src and dest, no-op when equal",
    93  			input: copySpecInput{
    94  				src: &unstructured.Unstructured{
    95  					Object: map[string]interface{}{
    96  						"spec": map[string]interface{}{
    97  							"template": map[string]interface{}{
    98  								"spec": map[string]interface{}{
    99  									"A": "A",
   100  								},
   101  							},
   102  						},
   103  					},
   104  				},
   105  				dest: &unstructured.Unstructured{
   106  					Object: map[string]interface{}{
   107  						"spec": map[string]interface{}{
   108  							"template": map[string]interface{}{
   109  								"spec": map[string]interface{}{
   110  									"A": "A",
   111  								},
   112  							},
   113  						},
   114  					},
   115  				},
   116  				srcSpecPath:  "spec",
   117  				destSpecPath: "spec",
   118  			},
   119  			want: &unstructured.Unstructured{
   120  				Object: map[string]interface{}{
   121  					"spec": map[string]interface{}{
   122  						"template": map[string]interface{}{
   123  							"spec": map[string]interface{}{
   124  								"A": "A",
   125  							},
   126  						},
   127  					},
   128  				},
   129  			},
   130  		},
   131  		{
   132  			name: "Nested field both in src and dest, overwrite dest when different",
   133  			input: copySpecInput{
   134  				src: &unstructured.Unstructured{
   135  					Object: map[string]interface{}{
   136  						"spec": map[string]interface{}{
   137  							"template": map[string]interface{}{
   138  								"spec": map[string]interface{}{
   139  									"A": "A",
   140  								},
   141  							},
   142  						},
   143  					},
   144  				},
   145  				dest: &unstructured.Unstructured{
   146  					Object: map[string]interface{}{
   147  						"spec": map[string]interface{}{
   148  							"template": map[string]interface{}{
   149  								"spec": map[string]interface{}{
   150  									"A": "A-different",
   151  								},
   152  							},
   153  						},
   154  					},
   155  				},
   156  				srcSpecPath:  "spec",
   157  				destSpecPath: "spec",
   158  			},
   159  			want: &unstructured.Unstructured{
   160  				Object: map[string]interface{}{
   161  					"spec": map[string]interface{}{
   162  						"template": map[string]interface{}{
   163  							"spec": map[string]interface{}{
   164  								"A": "A",
   165  							},
   166  						},
   167  					},
   168  				},
   169  			},
   170  		},
   171  		{
   172  			name: "Field only in src, copy to dest",
   173  			input: copySpecInput{
   174  				src: &unstructured.Unstructured{
   175  					Object: map[string]interface{}{
   176  						"spec": map[string]interface{}{
   177  							"foo": "bar",
   178  						},
   179  					},
   180  				},
   181  				dest: &unstructured.Unstructured{
   182  					Object: map[string]interface{}{},
   183  				},
   184  				srcSpecPath:  "spec",
   185  				destSpecPath: "spec",
   186  			},
   187  			want: &unstructured.Unstructured{
   188  				Object: map[string]interface{}{
   189  					"spec": map[string]interface{}{
   190  						"foo": "bar",
   191  					},
   192  				},
   193  			},
   194  		},
   195  		{
   196  			name: "Nested field only in src, copy to dest",
   197  			input: copySpecInput{
   198  				src: &unstructured.Unstructured{
   199  					Object: map[string]interface{}{
   200  						"spec": map[string]interface{}{
   201  							"template": map[string]interface{}{
   202  								"spec": map[string]interface{}{
   203  									"A": "A",
   204  								},
   205  							},
   206  						},
   207  					},
   208  				},
   209  				dest: &unstructured.Unstructured{
   210  					Object: map[string]interface{}{},
   211  				},
   212  				srcSpecPath:  "spec",
   213  				destSpecPath: "spec",
   214  			},
   215  			want: &unstructured.Unstructured{
   216  				Object: map[string]interface{}{
   217  					"spec": map[string]interface{}{
   218  						"template": map[string]interface{}{
   219  							"spec": map[string]interface{}{
   220  								"A": "A",
   221  							},
   222  						},
   223  					},
   224  				},
   225  			},
   226  		},
   227  		{
   228  			name: "Copy field from spec.template.spec in src to spec in dest",
   229  			input: copySpecInput{
   230  				src: &unstructured.Unstructured{
   231  					Object: map[string]interface{}{
   232  						"spec": map[string]interface{}{
   233  							"template": map[string]interface{}{
   234  								"spec": map[string]interface{}{
   235  									"A": "A",
   236  								},
   237  							},
   238  						},
   239  					},
   240  				},
   241  				dest: &unstructured.Unstructured{
   242  					Object: map[string]interface{}{},
   243  				},
   244  				srcSpecPath:  "spec.template.spec",
   245  				destSpecPath: "spec",
   246  			},
   247  			want: &unstructured.Unstructured{
   248  				Object: map[string]interface{}{
   249  					"spec": map[string]interface{}{
   250  						"A": "A",
   251  					},
   252  				},
   253  			},
   254  		},
   255  		{
   256  			name: "Copy field from spec.template.spec in src to spec in dest (overwrite when different)",
   257  			input: copySpecInput{
   258  				src: &unstructured.Unstructured{
   259  					Object: map[string]interface{}{
   260  						"spec": map[string]interface{}{
   261  							"template": map[string]interface{}{
   262  								"spec": map[string]interface{}{
   263  									"A": "A",
   264  								},
   265  							},
   266  						},
   267  					},
   268  				},
   269  				dest: &unstructured.Unstructured{
   270  					Object: map[string]interface{}{
   271  						"spec": map[string]interface{}{
   272  							"A": "A-different",
   273  						},
   274  					},
   275  				},
   276  				srcSpecPath:  "spec.template.spec",
   277  				destSpecPath: "spec",
   278  			},
   279  			want: &unstructured.Unstructured{
   280  				Object: map[string]interface{}{
   281  					"spec": map[string]interface{}{
   282  						"A": "A",
   283  					},
   284  				},
   285  			},
   286  		},
   287  		{
   288  			name: "Field both in src and dest, overwrite when different and preserve fields",
   289  			input: copySpecInput{
   290  				src: &unstructured.Unstructured{
   291  					Object: map[string]interface{}{
   292  						"spec": map[string]interface{}{
   293  							"template": map[string]interface{}{
   294  								"spec": map[string]interface{}{
   295  									"machineTemplate": map[string]interface{}{
   296  										"infrastructureRef": map[string]interface{}{
   297  											"apiVersion": "invalid",
   298  											"kind":       "invalid",
   299  											"namespace":  "invalid",
   300  											"name":       "invalid",
   301  										},
   302  									},
   303  									"replicas": float64(10),
   304  									"version":  "v1.15.0",
   305  									"A":        "A",
   306  								},
   307  							},
   308  						},
   309  					},
   310  				},
   311  				dest: &unstructured.Unstructured{
   312  					Object: map[string]interface{}{
   313  						"spec": map[string]interface{}{
   314  							"machineTemplate": map[string]interface{}{
   315  								"infrastructureRef": map[string]interface{}{
   316  									"apiVersion": "v1",
   317  									"kind":       "kind",
   318  									"namespace":  "namespace",
   319  									"name":       "name",
   320  								},
   321  							},
   322  							"replicas": float64(3),
   323  							"version":  "v1.22.0",
   324  							"A":        "A-different",
   325  						},
   326  					},
   327  				},
   328  				srcSpecPath:  "spec.template.spec",
   329  				destSpecPath: "spec",
   330  				fieldsToPreserve: []contract.Path{
   331  					{"spec", "machineTemplate", "infrastructureRef"},
   332  					{"spec", "replicas"},
   333  					{"spec", "version"},
   334  				},
   335  			},
   336  			want: &unstructured.Unstructured{
   337  				Object: map[string]interface{}{
   338  					"spec": map[string]interface{}{
   339  						"machineTemplate": map[string]interface{}{
   340  							"infrastructureRef": map[string]interface{}{
   341  								"apiVersion": "v1",
   342  								"kind":       "kind",
   343  								"namespace":  "namespace",
   344  								"name":       "name",
   345  							},
   346  						},
   347  						"replicas": float64(3),
   348  						"version":  "v1.22.0",
   349  						"A":        "A",
   350  					},
   351  				},
   352  			},
   353  		},
   354  	}
   355  
   356  	for _, tt := range tests {
   357  		t.Run(tt.name, func(t *testing.T) {
   358  			g := NewWithT(t)
   359  
   360  			err := copySpec(tt.input)
   361  			if tt.wantErr {
   362  				g.Expect(err).To(HaveOccurred())
   363  				return
   364  			}
   365  			g.Expect(err).ToNot(HaveOccurred())
   366  
   367  			g.Expect(tt.input.dest).To(BeComparableTo(tt.want))
   368  		})
   369  	}
   370  }