...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/resourceoverrides/utils_test.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/resourceoverrides

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package resourceoverrides
    16  
    17  import (
    18  	"reflect"
    19  	"testing"
    20  
    21  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
    22  	testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    26  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  )
    28  
    29  var (
    30  	fooKind     = "Foo"
    31  	emptyObject = make(map[string]interface{})
    32  )
    33  
    34  func TestPreserveUserSpecifiedLegacyField(t *testing.T) {
    35  	t.Parallel()
    36  	tests := []struct {
    37  		name       string
    38  		original   *k8s.Resource
    39  		reconciled *k8s.Resource
    40  		fieldPath  []string
    41  		expected   *k8s.Resource
    42  	}{
    43  		{
    44  			name: "no legacy field is set",
    45  			original: &k8s.Resource{
    46  				TypeMeta: v1.TypeMeta{
    47  					Kind: fooKind,
    48  				},
    49  				Spec: map[string]interface{}{
    50  					"field1": "value",
    51  				},
    52  			},
    53  			reconciled: &k8s.Resource{
    54  				TypeMeta: v1.TypeMeta{
    55  					Kind: fooKind,
    56  				},
    57  				Spec: map[string]interface{}{
    58  					"field1": "value",
    59  					"field2": "defaultValue",
    60  				},
    61  			},
    62  			fieldPath: []string{"legacyField"},
    63  			expected: &k8s.Resource{
    64  				TypeMeta: v1.TypeMeta{
    65  					Kind: fooKind,
    66  				},
    67  				Spec: map[string]interface{}{
    68  					"field1": "value",
    69  					"field2": "defaultValue",
    70  				},
    71  			},
    72  		},
    73  		{
    74  			name: "user-specified legacy field is preserved",
    75  			original: &k8s.Resource{
    76  				TypeMeta: v1.TypeMeta{
    77  					Kind: fooKind,
    78  				},
    79  				Spec: map[string]interface{}{
    80  					"field1":      "value",
    81  					"legacyField": "value",
    82  				},
    83  			},
    84  			reconciled: &k8s.Resource{
    85  				TypeMeta: v1.TypeMeta{
    86  					Kind: fooKind,
    87  				},
    88  				Spec: map[string]interface{}{
    89  					"field1": "value",
    90  					"field2": "defaultValue",
    91  				},
    92  			},
    93  			fieldPath: []string{"legacyField"},
    94  			expected: &k8s.Resource{
    95  				TypeMeta: v1.TypeMeta{
    96  					Kind: fooKind,
    97  				},
    98  				Spec: map[string]interface{}{
    99  					"field1":      "value",
   100  					"legacyField": "value",
   101  					"field2":      "defaultValue",
   102  				},
   103  			},
   104  		},
   105  		{
   106  			name: "user-specified nested legacy field is preserved",
   107  			original: &k8s.Resource{
   108  				TypeMeta: v1.TypeMeta{
   109  					Kind: fooKind,
   110  				},
   111  				Spec: map[string]interface{}{
   112  					"field1": "value",
   113  					"topField": map[string]interface{}{
   114  						"legacyField": "value",
   115  					},
   116  				},
   117  			},
   118  			reconciled: &k8s.Resource{
   119  				TypeMeta: v1.TypeMeta{
   120  					Kind: fooKind,
   121  				},
   122  				Spec: map[string]interface{}{
   123  					"field1": "value",
   124  					"topField": map[string]interface{}{
   125  						"newField": "value",
   126  					},
   127  				},
   128  			},
   129  			fieldPath: []string{"topField", "legacyField"},
   130  			expected: &k8s.Resource{
   131  				TypeMeta: v1.TypeMeta{
   132  					Kind: fooKind,
   133  				},
   134  				Spec: map[string]interface{}{
   135  					"field1": "value",
   136  					"topField": map[string]interface{}{
   137  						"legacyField": "value",
   138  						"newField":    "value",
   139  					},
   140  				},
   141  			},
   142  		},
   143  	}
   144  
   145  	for _, tc := range tests {
   146  		t.Run(tc.name, func(t *testing.T) {
   147  			if err := PreserveUserSpecifiedLegacyField(tc.original, tc.reconciled, tc.fieldPath...); err != nil {
   148  				t.Fatalf("unexpected error: %v", err)
   149  			}
   150  			if !reflect.DeepEqual(tc.reconciled, tc.expected) {
   151  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.reconciled))
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  func TestPreserveUserSpecifiedLegacyFieldUnderSlice(t *testing.T) {
   158  	t.Parallel()
   159  	tests := []struct {
   160  		name          string
   161  		original      *k8s.Resource
   162  		reconciled    *k8s.Resource
   163  		pathUpToSlice []string
   164  		fieldPath     []string
   165  		expected      *k8s.Resource
   166  	}{
   167  		{
   168  			name: "no legacy field is set",
   169  			original: &k8s.Resource{
   170  				TypeMeta: v1.TypeMeta{
   171  					Kind: fooKind,
   172  				},
   173  				Spec: map[string]interface{}{
   174  					"field1": "value",
   175  				},
   176  			},
   177  			reconciled: &k8s.Resource{
   178  				TypeMeta: v1.TypeMeta{
   179  					Kind: fooKind,
   180  				},
   181  				Spec: map[string]interface{}{
   182  					"field1": "value",
   183  					"field2": "defaultValue",
   184  				},
   185  			},
   186  			pathUpToSlice: []string{"pathUpToSlice"},
   187  			fieldPath:     []string{"legacyField"},
   188  			expected: &k8s.Resource{
   189  				TypeMeta: v1.TypeMeta{
   190  					Kind: fooKind,
   191  				},
   192  				Spec: map[string]interface{}{
   193  					"field1": "value",
   194  					"field2": "defaultValue",
   195  				},
   196  			},
   197  		},
   198  		{
   199  			name: "user-specified legacy field is preserved for the correct slice element",
   200  			original: &k8s.Resource{
   201  				TypeMeta: v1.TypeMeta{
   202  					Kind: fooKind,
   203  				},
   204  				Spec: map[string]interface{}{
   205  					"field1": "value",
   206  					"pathUpToSlice": []interface{}{
   207  						map[string]interface{}{
   208  							"subfield1": "value1",
   209  						},
   210  						map[string]interface{}{
   211  							"legacyField": "value",
   212  						},
   213  					},
   214  				},
   215  			},
   216  			reconciled: &k8s.Resource{
   217  				TypeMeta: v1.TypeMeta{
   218  					Kind: fooKind,
   219  				},
   220  				Spec: map[string]interface{}{
   221  					"field1": "value",
   222  					"pathUpToSlice": []interface{}{
   223  						map[string]interface{}{
   224  							"subfield1": "value1",
   225  						},
   226  						map[string]interface{}{},
   227  					},
   228  					"field2": "defaultValue",
   229  				},
   230  			},
   231  			pathUpToSlice: []string{"pathUpToSlice"},
   232  			fieldPath:     []string{"legacyField"},
   233  			expected: &k8s.Resource{
   234  				TypeMeta: v1.TypeMeta{
   235  					Kind: fooKind,
   236  				},
   237  				Spec: map[string]interface{}{
   238  					"field1": "value",
   239  					"pathUpToSlice": []interface{}{
   240  						map[string]interface{}{
   241  							"subfield1": "value1",
   242  						},
   243  						map[string]interface{}{
   244  							"legacyField": "value",
   245  						},
   246  					},
   247  					"field2": "defaultValue",
   248  				},
   249  			},
   250  		},
   251  		{
   252  			name: "user-specified nested legacy field is preserved",
   253  			original: &k8s.Resource{
   254  				TypeMeta: v1.TypeMeta{
   255  					Kind: fooKind,
   256  				},
   257  				Spec: map[string]interface{}{
   258  					"field1": "value",
   259  					"topField1": map[string]interface{}{
   260  						"pathUpToSlice": []interface{}{
   261  							map[string]interface{}{
   262  								"topField2": map[string]interface{}{
   263  									"legacyField": "value",
   264  								},
   265  							},
   266  						},
   267  					},
   268  				},
   269  			},
   270  			reconciled: &k8s.Resource{
   271  				TypeMeta: v1.TypeMeta{
   272  					Kind: fooKind,
   273  				},
   274  				Spec: map[string]interface{}{
   275  					"field1": "value",
   276  					"topField1": map[string]interface{}{
   277  						"pathUpToSlice": []interface{}{
   278  							map[string]interface{}{
   279  								"topField2": map[string]interface{}{
   280  									"newField": "value1",
   281  								},
   282  							},
   283  						},
   284  					},
   285  				},
   286  			},
   287  			pathUpToSlice: []string{"topField1", "pathUpToSlice"},
   288  			fieldPath:     []string{"topField2", "legacyField"},
   289  			expected: &k8s.Resource{
   290  				TypeMeta: v1.TypeMeta{
   291  					Kind: fooKind,
   292  				},
   293  				Spec: map[string]interface{}{
   294  					"field1": "value",
   295  					"topField1": map[string]interface{}{
   296  						"pathUpToSlice": []interface{}{
   297  							map[string]interface{}{
   298  								"topField2": map[string]interface{}{
   299  									"newField":    "value1",
   300  									"legacyField": "value",
   301  								},
   302  							},
   303  						},
   304  					},
   305  				},
   306  			},
   307  		},
   308  	}
   309  
   310  	for _, tc := range tests {
   311  		t.Run(tc.name, func(t *testing.T) {
   312  			if err := PreserveUserSpecifiedLegacyFieldUnderSlice(tc.original, tc.reconciled, tc.pathUpToSlice, tc.fieldPath); err != nil {
   313  				t.Fatalf("unexpected error: %v", err)
   314  			}
   315  			if !reflect.DeepEqual(tc.reconciled, tc.expected) {
   316  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.reconciled))
   317  			}
   318  		})
   319  	}
   320  }
   321  
   322  func TestPreserveUserSpecifiedLegacyArrayField(t *testing.T) {
   323  	t.Parallel()
   324  	tests := []struct {
   325  		name       string
   326  		original   *k8s.Resource
   327  		reconciled *k8s.Resource
   328  		fieldPath  []string
   329  		expected   *k8s.Resource
   330  		hasError   bool
   331  	}{
   332  		{
   333  			name: "no legacy array field is set",
   334  			original: &k8s.Resource{
   335  				TypeMeta: v1.TypeMeta{
   336  					Kind: fooKind,
   337  				},
   338  				Spec: map[string]interface{}{
   339  					"field1": "value",
   340  				},
   341  			},
   342  			reconciled: &k8s.Resource{
   343  				TypeMeta: v1.TypeMeta{
   344  					Kind: fooKind,
   345  				},
   346  				Spec: map[string]interface{}{
   347  					"field1": "value",
   348  					"field2": "defaultValue",
   349  				},
   350  			},
   351  			fieldPath: []string{"legacyArray"},
   352  			expected: &k8s.Resource{
   353  				TypeMeta: v1.TypeMeta{
   354  					Kind: fooKind,
   355  				},
   356  				Spec: map[string]interface{}{
   357  					"field1": "value",
   358  					"field2": "defaultValue",
   359  				},
   360  			},
   361  		},
   362  		{
   363  			name: "user-specified legacy array field is preserved",
   364  			original: &k8s.Resource{
   365  				TypeMeta: v1.TypeMeta{
   366  					Kind: fooKind,
   367  				},
   368  				Spec: map[string]interface{}{
   369  					"field1": "value",
   370  					"legacyArray": []interface{}{
   371  						"testValue1",
   372  						"testValue2",
   373  					},
   374  				},
   375  			},
   376  			reconciled: &k8s.Resource{
   377  				TypeMeta: v1.TypeMeta{
   378  					Kind: fooKind,
   379  				},
   380  				Spec: map[string]interface{}{
   381  					"field1": "value",
   382  					"field2": "defaultValue",
   383  				},
   384  			},
   385  			fieldPath: []string{"legacyArray"},
   386  			expected: &k8s.Resource{
   387  				TypeMeta: v1.TypeMeta{
   388  					Kind: fooKind,
   389  				},
   390  				Spec: map[string]interface{}{
   391  					"field1": "value",
   392  					"field2": "defaultValue",
   393  					"legacyArray": []interface{}{
   394  						"testValue1",
   395  						"testValue2",
   396  					},
   397  				},
   398  			},
   399  		},
   400  		{
   401  			name: "user-specified nested legacy array field is preserved",
   402  			original: &k8s.Resource{
   403  				TypeMeta: v1.TypeMeta{
   404  					Kind: fooKind,
   405  				},
   406  				Spec: map[string]interface{}{
   407  					"field1": "value",
   408  					"topField": map[string]interface{}{
   409  						"legacyArray": []interface{}{
   410  							"testValue1",
   411  							"testValue2",
   412  						},
   413  					},
   414  				},
   415  			},
   416  			reconciled: &k8s.Resource{
   417  				TypeMeta: v1.TypeMeta{
   418  					Kind: fooKind,
   419  				},
   420  				Spec: map[string]interface{}{
   421  					"field1": "value",
   422  					"topField": map[string]interface{}{
   423  						"newField": "value",
   424  					},
   425  				},
   426  			},
   427  			fieldPath: []string{"topField", "legacyArray"},
   428  			expected: &k8s.Resource{
   429  				TypeMeta: v1.TypeMeta{
   430  					Kind: fooKind,
   431  				},
   432  				Spec: map[string]interface{}{
   433  					"field1": "value",
   434  					"topField": map[string]interface{}{
   435  						"legacyArray": []interface{}{
   436  							"testValue1",
   437  							"testValue2",
   438  						},
   439  						"newField": "value",
   440  					},
   441  				},
   442  			},
   443  		},
   444  		{
   445  			name: "user-specified legacy field is not preserved because it's " +
   446  				"not an array",
   447  			original: &k8s.Resource{
   448  				TypeMeta: v1.TypeMeta{
   449  					Kind: fooKind,
   450  				},
   451  				Spec: map[string]interface{}{
   452  					"field1":      "value",
   453  					"legacyArray": "testValue",
   454  				},
   455  			},
   456  			reconciled: &k8s.Resource{
   457  				TypeMeta: v1.TypeMeta{
   458  					Kind: fooKind,
   459  				},
   460  				Spec: map[string]interface{}{
   461  					"field1": "value",
   462  					"field2": "defaultValue",
   463  				},
   464  			},
   465  			fieldPath: []string{"legacyArray"},
   466  			hasError:  true,
   467  		},
   468  	}
   469  
   470  	for _, tc := range tests {
   471  		t.Run(tc.name, func(t *testing.T) {
   472  			err := PreserveUserSpecifiedLegacyArrayField(tc.original, tc.reconciled, tc.fieldPath...)
   473  			if tc.hasError {
   474  				if err == nil {
   475  					t.Fatalf("got nil, but expect to have error")
   476  				}
   477  				return
   478  			}
   479  			if err != nil {
   480  				t.Fatalf("unexpected error: %v", err)
   481  			}
   482  			if got, want := tc.reconciled, tc.expected; !reflect.DeepEqual(got, want) {
   483  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(want, got))
   484  			}
   485  		})
   486  	}
   487  }
   488  
   489  func TestPruneDefaultedAuthoritativeFieldIfOnlyLegacyFieldSpecified(t *testing.T) {
   490  	t.Parallel()
   491  	tests := []struct {
   492  		name            string
   493  		original        *k8s.Resource
   494  		reconciled      *k8s.Resource
   495  		legacyFieldPath []string
   496  		fieldPath       []string
   497  		expected        *k8s.Resource
   498  	}{
   499  		{
   500  			name: "neither the legacy field nor the authoritative field is set; the authoritative field is not defaulted",
   501  			original: &k8s.Resource{
   502  				TypeMeta: v1.TypeMeta{
   503  					Kind: fooKind,
   504  				},
   505  				Spec: map[string]interface{}{
   506  					"field1": "value",
   507  				},
   508  			},
   509  			reconciled: &k8s.Resource{
   510  				TypeMeta: v1.TypeMeta{
   511  					Kind: fooKind,
   512  				},
   513  				Spec: map[string]interface{}{
   514  					"field1": "value",
   515  					"field2": "defaultValue",
   516  				},
   517  			},
   518  			legacyFieldPath: []string{"legacyField"},
   519  			fieldPath:       []string{"authoritativeField"},
   520  			expected: &k8s.Resource{
   521  				TypeMeta: v1.TypeMeta{
   522  					Kind: fooKind,
   523  				},
   524  				Spec: map[string]interface{}{
   525  					"field1": "value",
   526  					"field2": "defaultValue",
   527  				},
   528  			},
   529  		},
   530  		{
   531  			name: "neither the legacy field nor the authoritative field is set; the authoritative field is defaulted",
   532  			original: &k8s.Resource{
   533  				TypeMeta: v1.TypeMeta{
   534  					Kind: fooKind,
   535  				},
   536  				Spec: map[string]interface{}{
   537  					"field1": "value",
   538  				},
   539  			},
   540  			reconciled: &k8s.Resource{
   541  				TypeMeta: v1.TypeMeta{
   542  					Kind: fooKind,
   543  				},
   544  				Spec: map[string]interface{}{
   545  					"field1":             "value",
   546  					"authoritativeField": "defaultValue",
   547  				},
   548  			},
   549  			legacyFieldPath: []string{"legacyField"},
   550  			fieldPath:       []string{"authoritativeField"},
   551  			expected: &k8s.Resource{
   552  				TypeMeta: v1.TypeMeta{
   553  					Kind: fooKind,
   554  				},
   555  				Spec: map[string]interface{}{
   556  					"field1":             "value",
   557  					"authoritativeField": "defaultValue",
   558  				},
   559  			},
   560  		},
   561  		{
   562  			name: "prune the authoritative field if users only specify the legacy field",
   563  			original: &k8s.Resource{
   564  				TypeMeta: v1.TypeMeta{
   565  					Kind: fooKind,
   566  				},
   567  				Spec: map[string]interface{}{
   568  					"field1":      "value",
   569  					"legacyField": "value",
   570  				},
   571  			},
   572  			reconciled: &k8s.Resource{
   573  				TypeMeta: v1.TypeMeta{
   574  					Kind: fooKind,
   575  				},
   576  				Spec: map[string]interface{}{
   577  					"field1":             "value",
   578  					"authoritativeField": "value",
   579  				},
   580  			},
   581  			legacyFieldPath: []string{"legacyField"},
   582  			fieldPath:       []string{"authoritativeField"},
   583  			expected: &k8s.Resource{
   584  				TypeMeta: v1.TypeMeta{
   585  					Kind: fooKind,
   586  				},
   587  				Spec: map[string]interface{}{
   588  					"field1": "value",
   589  				},
   590  			},
   591  		},
   592  		{
   593  			name: "prune the nested authoritative field if users only specify the legacy field",
   594  			original: &k8s.Resource{
   595  				TypeMeta: v1.TypeMeta{
   596  					Kind: fooKind,
   597  				},
   598  				Spec: map[string]interface{}{
   599  					"field1": "value",
   600  					"topField": map[string]interface{}{
   601  						"field2":      "value",
   602  						"legacyField": "value",
   603  					},
   604  				},
   605  			},
   606  			reconciled: &k8s.Resource{
   607  				TypeMeta: v1.TypeMeta{
   608  					Kind: fooKind,
   609  				},
   610  				Spec: map[string]interface{}{
   611  					"field1": "value",
   612  					"topField": map[string]interface{}{
   613  						"field2":             "value",
   614  						"authoritativeField": "value",
   615  					},
   616  				},
   617  			},
   618  			legacyFieldPath: []string{"topField", "legacyField"},
   619  			fieldPath:       []string{"topField", "authoritativeField"},
   620  			expected: &k8s.Resource{
   621  				TypeMeta: v1.TypeMeta{
   622  					Kind: fooKind,
   623  				},
   624  				Spec: map[string]interface{}{
   625  					"field1": "value",
   626  					"topField": map[string]interface{}{
   627  						"field2": "value",
   628  					},
   629  				},
   630  			},
   631  		},
   632  		{
   633  			name: "users only specify the authoritative field",
   634  			original: &k8s.Resource{
   635  				TypeMeta: v1.TypeMeta{
   636  					Kind: fooKind,
   637  				},
   638  				Spec: map[string]interface{}{
   639  					"field1":             "value",
   640  					"authoritativeField": "value",
   641  				},
   642  			},
   643  			reconciled: &k8s.Resource{
   644  				TypeMeta: v1.TypeMeta{
   645  					Kind: fooKind,
   646  				},
   647  				Spec: map[string]interface{}{
   648  					"field1":             "value",
   649  					"authoritativeField": "value",
   650  				},
   651  			},
   652  			legacyFieldPath: []string{"legacyField"},
   653  			fieldPath:       []string{"authoritativeField"},
   654  			expected: &k8s.Resource{
   655  				TypeMeta: v1.TypeMeta{
   656  					Kind: fooKind,
   657  				},
   658  				Spec: map[string]interface{}{
   659  					"field1":             "value",
   660  					"authoritativeField": "value",
   661  				},
   662  			},
   663  		},
   664  		{
   665  			name: "users specify both the legacy and the authoritative fields",
   666  			original: &k8s.Resource{
   667  				TypeMeta: v1.TypeMeta{
   668  					Kind: fooKind,
   669  				},
   670  				Spec: map[string]interface{}{
   671  					"field1":             "value",
   672  					"legacyField":        "value",
   673  					"authoritativeField": "value",
   674  				},
   675  			},
   676  			reconciled: &k8s.Resource{
   677  				TypeMeta: v1.TypeMeta{
   678  					Kind: fooKind,
   679  				},
   680  				Spec: map[string]interface{}{
   681  					"field1":             "value",
   682  					"legacyField":        "value",
   683  					"authoritativeField": "value",
   684  				},
   685  			},
   686  			legacyFieldPath: []string{"legacyField"},
   687  			fieldPath:       []string{"authoritativeField"},
   688  			expected: &k8s.Resource{
   689  				TypeMeta: v1.TypeMeta{
   690  					Kind: fooKind,
   691  				},
   692  				Spec: map[string]interface{}{
   693  					"field1":             "value",
   694  					"legacyField":        "value",
   695  					"authoritativeField": "value",
   696  				},
   697  			},
   698  		},
   699  	}
   700  
   701  	for _, tc := range tests {
   702  		t.Run(tc.name, func(t *testing.T) {
   703  			if err := PruneDefaultedAuthoritativeFieldIfOnlyLegacyFieldSpecified(tc.original, tc.reconciled, tc.legacyFieldPath, tc.fieldPath); err != nil {
   704  				t.Fatalf("unexpected error: %v", err)
   705  			}
   706  			if !reflect.DeepEqual(tc.reconciled, tc.expected) {
   707  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.reconciled))
   708  			}
   709  		})
   710  	}
   711  }
   712  
   713  func TestPruneDefaultedAuthoritativeFieldIfOnlyLegacyFieldSpecifiedUnderSlice(t *testing.T) {
   714  	t.Parallel()
   715  	tests := []struct {
   716  		name                   string
   717  		original               *k8s.Resource
   718  		reconciled             *k8s.Resource
   719  		pathUpToSlice          []string
   720  		legacyFieldPath        []string
   721  		authoritativeFieldPath []string
   722  		expected               *k8s.Resource
   723  	}{
   724  		{
   725  			name: "neither the legacy field nor the authoritative field is set; the authoritative field is not defaulted",
   726  			original: &k8s.Resource{
   727  				TypeMeta: v1.TypeMeta{
   728  					Kind: fooKind,
   729  				},
   730  				Spec: map[string]interface{}{
   731  					"field1": "value",
   732  				},
   733  			},
   734  			reconciled: &k8s.Resource{
   735  				TypeMeta: v1.TypeMeta{
   736  					Kind: fooKind,
   737  				},
   738  				Spec: map[string]interface{}{
   739  					"field1": "value",
   740  					"field2": "defaultValue",
   741  				},
   742  			},
   743  			pathUpToSlice:          []string{"pathUpToSlice"},
   744  			legacyFieldPath:        []string{"legacyField"},
   745  			authoritativeFieldPath: []string{"authoritativeField"},
   746  			expected: &k8s.Resource{
   747  				TypeMeta: v1.TypeMeta{
   748  					Kind: fooKind,
   749  				},
   750  				Spec: map[string]interface{}{
   751  					"field1": "value",
   752  					"field2": "defaultValue",
   753  				},
   754  			},
   755  		},
   756  		{
   757  			name: "neither the legacy field nor the authoritative field is set; the authoritative field is defaulted",
   758  			original: &k8s.Resource{
   759  				TypeMeta: v1.TypeMeta{
   760  					Kind: fooKind,
   761  				},
   762  				Spec: map[string]interface{}{
   763  					"field1": "value",
   764  				},
   765  			},
   766  			reconciled: &k8s.Resource{
   767  				TypeMeta: v1.TypeMeta{
   768  					Kind: fooKind,
   769  				},
   770  				Spec: map[string]interface{}{
   771  					"field1": "value",
   772  					"pathUpToSlice": []interface{}{
   773  						map[string]interface{}{
   774  							"authoritativeField": "defaultValue",
   775  						},
   776  					},
   777  				},
   778  			},
   779  			pathUpToSlice:          []string{"pathUpToSlice"},
   780  			legacyFieldPath:        []string{"legacyField"},
   781  			authoritativeFieldPath: []string{"authoritativeField"},
   782  			expected: &k8s.Resource{
   783  				TypeMeta: v1.TypeMeta{
   784  					Kind: fooKind,
   785  				},
   786  				Spec: map[string]interface{}{
   787  					"field1": "value",
   788  					"pathUpToSlice": []interface{}{
   789  						map[string]interface{}{
   790  							"authoritativeField": "defaultValue",
   791  						},
   792  					},
   793  				},
   794  			},
   795  		},
   796  		{
   797  			name: "prune the authoritative field if users only specify the legacy field",
   798  			original: &k8s.Resource{
   799  				TypeMeta: v1.TypeMeta{
   800  					Kind: fooKind,
   801  				},
   802  				Spec: map[string]interface{}{
   803  					"field1": "value",
   804  					"pathUpToSlice": []interface{}{
   805  						map[string]interface{}{
   806  							"legacyField": "value",
   807  						},
   808  					},
   809  				},
   810  			},
   811  			reconciled: &k8s.Resource{
   812  				TypeMeta: v1.TypeMeta{
   813  					Kind: fooKind,
   814  				},
   815  				Spec: map[string]interface{}{
   816  					"field1": "value",
   817  					"pathUpToSlice": []interface{}{
   818  						map[string]interface{}{
   819  							"authoritativeField": "value",
   820  						},
   821  					},
   822  				},
   823  			},
   824  			pathUpToSlice:          []string{"pathUpToSlice"},
   825  			legacyFieldPath:        []string{"legacyField"},
   826  			authoritativeFieldPath: []string{"authoritativeField"},
   827  			expected: &k8s.Resource{
   828  				TypeMeta: v1.TypeMeta{
   829  					Kind: fooKind,
   830  				},
   831  				Spec: map[string]interface{}{
   832  					"field1": "value",
   833  					"pathUpToSlice": []interface{}{
   834  						map[string]interface{}{},
   835  					},
   836  				},
   837  			},
   838  		},
   839  		{
   840  			name: "prune the nested authoritative field if users only specify the legacy field",
   841  			original: &k8s.Resource{
   842  				TypeMeta: v1.TypeMeta{
   843  					Kind: fooKind,
   844  				},
   845  				Spec: map[string]interface{}{
   846  					"field1": "value",
   847  					"topField1": map[string]interface{}{
   848  						"pathUpToSlice": []interface{}{
   849  							map[string]interface{}{
   850  								"topField2": map[string]interface{}{
   851  									"legacyField": "value",
   852  									"field2":      "value",
   853  								},
   854  							},
   855  						},
   856  					},
   857  				},
   858  			},
   859  			reconciled: &k8s.Resource{
   860  				TypeMeta: v1.TypeMeta{
   861  					Kind: fooKind,
   862  				},
   863  				Spec: map[string]interface{}{
   864  					"field1": "value",
   865  					"topField1": map[string]interface{}{
   866  						"pathUpToSlice": []interface{}{
   867  							map[string]interface{}{
   868  								"topField2": map[string]interface{}{
   869  									"authoritativeField": "value",
   870  									"field2":             "value",
   871  								},
   872  							},
   873  						},
   874  					},
   875  				},
   876  			},
   877  			pathUpToSlice:          []string{"topField1", "pathUpToSlice"},
   878  			legacyFieldPath:        []string{"topField2", "legacyField"},
   879  			authoritativeFieldPath: []string{"topField2", "authoritativeField"},
   880  			expected: &k8s.Resource{
   881  				TypeMeta: v1.TypeMeta{
   882  					Kind: fooKind,
   883  				},
   884  				Spec: map[string]interface{}{
   885  					"field1": "value",
   886  					"topField1": map[string]interface{}{
   887  						"pathUpToSlice": []interface{}{
   888  							map[string]interface{}{
   889  								"topField2": map[string]interface{}{
   890  									"field2": "value",
   891  								},
   892  							},
   893  						},
   894  					},
   895  				},
   896  			},
   897  		},
   898  		{
   899  			name: "users only specify the authoritative field",
   900  			original: &k8s.Resource{
   901  				TypeMeta: v1.TypeMeta{
   902  					Kind: fooKind,
   903  				},
   904  				Spec: map[string]interface{}{
   905  					"field1": "value",
   906  					"pathUpToSlice": []interface{}{
   907  						map[string]interface{}{
   908  							"authoritativeField": "value",
   909  						},
   910  					},
   911  				},
   912  			},
   913  			reconciled: &k8s.Resource{
   914  				TypeMeta: v1.TypeMeta{
   915  					Kind: fooKind,
   916  				},
   917  				Spec: map[string]interface{}{
   918  					"field1": "value",
   919  					"pathUpToSlice": []interface{}{
   920  						map[string]interface{}{
   921  							"authoritativeField": "value",
   922  						},
   923  					},
   924  				},
   925  			},
   926  			pathUpToSlice:          []string{"pathUpToSlice"},
   927  			legacyFieldPath:        []string{"legacyField"},
   928  			authoritativeFieldPath: []string{"authoritativeField"},
   929  			expected: &k8s.Resource{
   930  				TypeMeta: v1.TypeMeta{
   931  					Kind: fooKind,
   932  				},
   933  				Spec: map[string]interface{}{
   934  					"field1": "value",
   935  					"pathUpToSlice": []interface{}{
   936  						map[string]interface{}{
   937  							"authoritativeField": "value",
   938  						},
   939  					},
   940  				},
   941  			},
   942  		},
   943  		{
   944  			name: "handle multiple elements with different use-cases",
   945  			original: &k8s.Resource{
   946  				TypeMeta: v1.TypeMeta{
   947  					Kind: fooKind,
   948  				},
   949  				Spec: map[string]interface{}{
   950  					"field1": "value",
   951  					"pathUpToSlice": []interface{}{
   952  						map[string]interface{}{
   953  							"legacyField": "value",
   954  						},
   955  						map[string]interface{}{
   956  							"authoritativeField": "value1",
   957  						},
   958  					},
   959  				},
   960  			},
   961  			reconciled: &k8s.Resource{
   962  				TypeMeta: v1.TypeMeta{
   963  					Kind: fooKind,
   964  				},
   965  				Spec: map[string]interface{}{
   966  					"field1": "value",
   967  					"pathUpToSlice": []interface{}{
   968  						map[string]interface{}{
   969  							"authoritativeField": "value",
   970  						},
   971  						map[string]interface{}{
   972  							"authoritativeField": "value1",
   973  						},
   974  						map[string]interface{}{
   975  							"authoritativeField": "defaultValue",
   976  						},
   977  					},
   978  				},
   979  			},
   980  			pathUpToSlice:          []string{"pathUpToSlice"},
   981  			legacyFieldPath:        []string{"legacyField"},
   982  			authoritativeFieldPath: []string{"authoritativeField"},
   983  			expected: &k8s.Resource{
   984  				TypeMeta: v1.TypeMeta{
   985  					Kind: fooKind,
   986  				},
   987  				Spec: map[string]interface{}{
   988  					"field1": "value",
   989  					"pathUpToSlice": []interface{}{
   990  						map[string]interface{}{},
   991  						map[string]interface{}{
   992  							"authoritativeField": "value1",
   993  						},
   994  						map[string]interface{}{
   995  							"authoritativeField": "defaultValue",
   996  						},
   997  					},
   998  				},
   999  			},
  1000  		},
  1001  	}
  1002  
  1003  	for _, tc := range tests {
  1004  		t.Run(tc.name, func(t *testing.T) {
  1005  			if err := PruneDefaultedAuthoritativeFieldIfOnlyLegacyFieldSpecifiedUnderSlice(tc.original, tc.reconciled, tc.pathUpToSlice, tc.legacyFieldPath, tc.authoritativeFieldPath); err != nil {
  1006  				t.Fatalf("unexpected error: %v", err)
  1007  			}
  1008  			if !reflect.DeepEqual(tc.reconciled, tc.expected) {
  1009  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.reconciled))
  1010  			}
  1011  		})
  1012  	}
  1013  }
  1014  
  1015  func TestPruneDefaultedAuthoritativeArrayFieldIfOnlyLegacyArrayFieldSpecified(t *testing.T) {
  1016  	t.Parallel()
  1017  	tests := []struct {
  1018  		name            string
  1019  		original        *k8s.Resource
  1020  		reconciled      *k8s.Resource
  1021  		legacyFieldPath []string
  1022  		fieldPath       []string
  1023  		expected        *k8s.Resource
  1024  		hasError        bool
  1025  	}{
  1026  		{
  1027  			name: "neither the legacy array field nor the authoritative array " +
  1028  				"field is set; the authoritative array field is not defaulted",
  1029  			original: &k8s.Resource{
  1030  				TypeMeta: v1.TypeMeta{
  1031  					Kind: fooKind,
  1032  				},
  1033  				Spec: map[string]interface{}{
  1034  					"field1": "value",
  1035  				},
  1036  			},
  1037  			reconciled: &k8s.Resource{
  1038  				TypeMeta: v1.TypeMeta{
  1039  					Kind: fooKind,
  1040  				},
  1041  				Spec: map[string]interface{}{
  1042  					"field1": "value",
  1043  					"field2": "defaultValue",
  1044  				},
  1045  			},
  1046  			legacyFieldPath: []string{"legacyArray"},
  1047  			fieldPath:       []string{"authoritativeArray"},
  1048  			expected: &k8s.Resource{
  1049  				TypeMeta: v1.TypeMeta{
  1050  					Kind: fooKind,
  1051  				},
  1052  				Spec: map[string]interface{}{
  1053  					"field1": "value",
  1054  					"field2": "defaultValue",
  1055  				},
  1056  			},
  1057  		},
  1058  		{
  1059  			name: "neither the legacy array field nor the authoritative array " +
  1060  				"field is set; the authoritative array field is defaulted",
  1061  			original: &k8s.Resource{
  1062  				TypeMeta: v1.TypeMeta{
  1063  					Kind: fooKind,
  1064  				},
  1065  				Spec: map[string]interface{}{
  1066  					"field1": "value",
  1067  				},
  1068  			},
  1069  			reconciled: &k8s.Resource{
  1070  				TypeMeta: v1.TypeMeta{
  1071  					Kind: fooKind,
  1072  				},
  1073  				Spec: map[string]interface{}{
  1074  					"field1": "value",
  1075  					"authoritativeArray": []interface{}{
  1076  						"defaultedValue1",
  1077  						"defaultedValue2",
  1078  					},
  1079  				},
  1080  			},
  1081  			legacyFieldPath: []string{"legacyArray"},
  1082  			fieldPath:       []string{"authoritativeArray"},
  1083  			expected: &k8s.Resource{
  1084  				TypeMeta: v1.TypeMeta{
  1085  					Kind: fooKind,
  1086  				},
  1087  				Spec: map[string]interface{}{
  1088  					"field1": "value",
  1089  					"authoritativeArray": []interface{}{
  1090  						"defaultedValue1",
  1091  						"defaultedValue2",
  1092  					},
  1093  				},
  1094  			},
  1095  		},
  1096  		{
  1097  			name: "prune the authoritative array field if users only specify " +
  1098  				"the legacy array field",
  1099  			original: &k8s.Resource{
  1100  				TypeMeta: v1.TypeMeta{
  1101  					Kind: fooKind,
  1102  				},
  1103  				Spec: map[string]interface{}{
  1104  					"field1": "value",
  1105  					"legacyArray": []interface{}{
  1106  						"testValue1",
  1107  						"testValue2",
  1108  					},
  1109  				},
  1110  			},
  1111  			reconciled: &k8s.Resource{
  1112  				TypeMeta: v1.TypeMeta{
  1113  					Kind: fooKind,
  1114  				},
  1115  				Spec: map[string]interface{}{
  1116  					"field1": "value",
  1117  					"authoritativeArray": []interface{}{
  1118  						"testValue1",
  1119  						"testValue2",
  1120  					},
  1121  				},
  1122  			},
  1123  			legacyFieldPath: []string{"legacyArray"},
  1124  			fieldPath:       []string{"authoritativeArray"},
  1125  			expected: &k8s.Resource{
  1126  				TypeMeta: v1.TypeMeta{
  1127  					Kind: fooKind,
  1128  				},
  1129  				Spec: map[string]interface{}{
  1130  					"field1": "value",
  1131  				},
  1132  			},
  1133  		},
  1134  		{
  1135  			name: "prune the authoritative array field of objects if users " +
  1136  				"only specify the legacy array field of strings",
  1137  			original: &k8s.Resource{
  1138  				TypeMeta: v1.TypeMeta{
  1139  					Kind: fooKind,
  1140  				},
  1141  				Spec: map[string]interface{}{
  1142  					"field1": "value",
  1143  					"legacyArray": []interface{}{
  1144  						"testValue1",
  1145  						"testValue2",
  1146  					},
  1147  				},
  1148  			},
  1149  			reconciled: &k8s.Resource{
  1150  				TypeMeta: v1.TypeMeta{
  1151  					Kind: fooKind,
  1152  				},
  1153  				Spec: map[string]interface{}{
  1154  					"field1": "value",
  1155  					"authoritativeArray": []interface{}{
  1156  						map[string]interface{}{
  1157  							"external": "testValue1",
  1158  						},
  1159  						map[string]interface{}{
  1160  							"external": "testValue2",
  1161  						},
  1162  					},
  1163  				},
  1164  			},
  1165  			legacyFieldPath: []string{"legacyArray"},
  1166  			fieldPath:       []string{"authoritativeArray"},
  1167  			expected: &k8s.Resource{
  1168  				TypeMeta: v1.TypeMeta{
  1169  					Kind: fooKind,
  1170  				},
  1171  				Spec: map[string]interface{}{
  1172  					"field1": "value",
  1173  				},
  1174  			},
  1175  		},
  1176  		{
  1177  			name: "prune the nested authoritative array field if users only " +
  1178  				"specify the legacy array field",
  1179  			original: &k8s.Resource{
  1180  				TypeMeta: v1.TypeMeta{
  1181  					Kind: fooKind,
  1182  				},
  1183  				Spec: map[string]interface{}{
  1184  					"field1": "value",
  1185  					"topField": map[string]interface{}{
  1186  						"field2": "value",
  1187  						"legacyArray": []interface{}{
  1188  							"testValue1",
  1189  							"testValue2",
  1190  						},
  1191  					},
  1192  				},
  1193  			},
  1194  			reconciled: &k8s.Resource{
  1195  				TypeMeta: v1.TypeMeta{
  1196  					Kind: fooKind,
  1197  				},
  1198  				Spec: map[string]interface{}{
  1199  					"field1": "value",
  1200  					"topField": map[string]interface{}{
  1201  						"field2": "value",
  1202  						"authoritativeArray": []interface{}{
  1203  							"testValue1",
  1204  							"testValue2",
  1205  						},
  1206  					},
  1207  				},
  1208  			},
  1209  			legacyFieldPath: []string{"topField", "legacyArray"},
  1210  			fieldPath:       []string{"topField", "authoritativeArray"},
  1211  			expected: &k8s.Resource{
  1212  				TypeMeta: v1.TypeMeta{
  1213  					Kind: fooKind,
  1214  				},
  1215  				Spec: map[string]interface{}{
  1216  					"field1": "value",
  1217  					"topField": map[string]interface{}{
  1218  						"field2": "value",
  1219  					},
  1220  				},
  1221  			},
  1222  		},
  1223  		{
  1224  			name: "users only specify the authoritative array field",
  1225  			original: &k8s.Resource{
  1226  				TypeMeta: v1.TypeMeta{
  1227  					Kind: fooKind,
  1228  				},
  1229  				Spec: map[string]interface{}{
  1230  					"field1": "value",
  1231  					"authoritativeArray": []interface{}{
  1232  						"testValue1",
  1233  						"testValue2",
  1234  					},
  1235  				},
  1236  			},
  1237  			reconciled: &k8s.Resource{
  1238  				TypeMeta: v1.TypeMeta{
  1239  					Kind: fooKind,
  1240  				},
  1241  				Spec: map[string]interface{}{
  1242  					"field1": "value",
  1243  					"authoritativeArray": []interface{}{
  1244  						"testValue1",
  1245  						"testValue2",
  1246  					},
  1247  				},
  1248  			},
  1249  			legacyFieldPath: []string{"legacyArray"},
  1250  			fieldPath:       []string{"authoritativeArray"},
  1251  			expected: &k8s.Resource{
  1252  				TypeMeta: v1.TypeMeta{
  1253  					Kind: fooKind,
  1254  				},
  1255  				Spec: map[string]interface{}{
  1256  					"field1": "value",
  1257  					"authoritativeArray": []interface{}{
  1258  						"testValue1",
  1259  						"testValue2",
  1260  					},
  1261  				},
  1262  			},
  1263  		},
  1264  		{
  1265  			name: "users specify both the legacy and the authoritative fields",
  1266  			original: &k8s.Resource{
  1267  				TypeMeta: v1.TypeMeta{
  1268  					Kind: fooKind,
  1269  				},
  1270  				Spec: map[string]interface{}{
  1271  					"field1": "value",
  1272  					"legacyArray": []interface{}{
  1273  						"testValue1",
  1274  						"testValue2",
  1275  					},
  1276  					"authoritativeArray": []interface{}{
  1277  						"testValue1",
  1278  						"testValue2",
  1279  					},
  1280  				},
  1281  			},
  1282  			reconciled: &k8s.Resource{
  1283  				TypeMeta: v1.TypeMeta{
  1284  					Kind: fooKind,
  1285  				},
  1286  				Spec: map[string]interface{}{
  1287  					"field1": "value",
  1288  					"legacyArray": []interface{}{
  1289  						"testValue1",
  1290  						"testValue2",
  1291  					},
  1292  					"authoritativeArray": []interface{}{
  1293  						"testValue1",
  1294  						"testValue2",
  1295  					},
  1296  				},
  1297  			},
  1298  			legacyFieldPath: []string{"legacyArray"},
  1299  			fieldPath:       []string{"authoritativeArray"},
  1300  			expected: &k8s.Resource{
  1301  				TypeMeta: v1.TypeMeta{
  1302  					Kind: fooKind,
  1303  				},
  1304  				Spec: map[string]interface{}{
  1305  					"field1": "value",
  1306  					"legacyArray": []interface{}{
  1307  						"testValue1",
  1308  						"testValue2",
  1309  					},
  1310  					"authoritativeArray": []interface{}{
  1311  						"testValue1",
  1312  						"testValue2",
  1313  					},
  1314  				},
  1315  			},
  1316  		},
  1317  		{
  1318  			name: "error pruning the authoritative array field if the legacy " +
  1319  				"field is not an array",
  1320  			original: &k8s.Resource{
  1321  				TypeMeta: v1.TypeMeta{
  1322  					Kind: fooKind,
  1323  				},
  1324  				Spec: map[string]interface{}{
  1325  					"field1":      "value",
  1326  					"legacyField": "testValue1",
  1327  				},
  1328  			},
  1329  			reconciled: &k8s.Resource{
  1330  				TypeMeta: v1.TypeMeta{
  1331  					Kind: fooKind,
  1332  				},
  1333  				Spec: map[string]interface{}{
  1334  					"field1": "value",
  1335  					"authoritativeArray": []interface{}{
  1336  						"testValue1",
  1337  						"testValue2",
  1338  					},
  1339  				},
  1340  			},
  1341  			legacyFieldPath: []string{"legacyField"},
  1342  			fieldPath:       []string{"authoritativeArray"},
  1343  			hasError:        true,
  1344  		},
  1345  		{
  1346  			name: "error when users only specify the non-array authoritative " +
  1347  				"field",
  1348  			original: &k8s.Resource{
  1349  				TypeMeta: v1.TypeMeta{
  1350  					Kind: fooKind,
  1351  				},
  1352  				Spec: map[string]interface{}{
  1353  					"field1":             "value",
  1354  					"authoritativeField": "testValue1",
  1355  				},
  1356  			},
  1357  			reconciled: &k8s.Resource{
  1358  				TypeMeta: v1.TypeMeta{
  1359  					Kind: fooKind,
  1360  				},
  1361  				Spec: map[string]interface{}{
  1362  					"field1":             "value",
  1363  					"authoritativeField": "testValue1",
  1364  				},
  1365  			},
  1366  			legacyFieldPath: []string{"legacyArray"},
  1367  			fieldPath:       []string{"authoritativeField"},
  1368  			hasError:        true,
  1369  		},
  1370  	}
  1371  
  1372  	for _, tc := range tests {
  1373  		t.Run(tc.name, func(t *testing.T) {
  1374  			err := PruneDefaultedAuthoritativeArrayFieldIfOnlyLegacyArrayFieldSpecified(tc.original, tc.reconciled, tc.legacyFieldPath, tc.fieldPath)
  1375  			if tc.hasError {
  1376  				if err == nil {
  1377  					t.Fatalf("got nil, but expect to have error")
  1378  				}
  1379  				return
  1380  			}
  1381  			if err != nil {
  1382  				t.Fatalf("unexpected error: %v", err)
  1383  			}
  1384  			if got, want := tc.reconciled, tc.expected; !reflect.DeepEqual(got, want) {
  1385  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(want, got))
  1386  			}
  1387  		})
  1388  	}
  1389  }
  1390  
  1391  func TestFavorAuthoritativeFieldOverLegacyField(t *testing.T) {
  1392  	t.Parallel()
  1393  	tests := []struct {
  1394  		name            string
  1395  		original        *k8s.Resource
  1396  		legacyFieldPath []string
  1397  		fieldPath       []string
  1398  		expected        *k8s.Resource
  1399  		hasError        bool
  1400  	}{
  1401  		{
  1402  			name: "neither the legacy field nor the authoritative field is set",
  1403  			original: &k8s.Resource{
  1404  				TypeMeta: v1.TypeMeta{
  1405  					Kind: fooKind,
  1406  				},
  1407  				Spec: map[string]interface{}{
  1408  					"field1": "value",
  1409  				},
  1410  			},
  1411  			legacyFieldPath: []string{"legacyField"},
  1412  			fieldPath:       []string{"authoritativeField"},
  1413  			expected: &k8s.Resource{
  1414  				TypeMeta: v1.TypeMeta{
  1415  					Kind: fooKind,
  1416  				},
  1417  				Spec: map[string]interface{}{
  1418  					"field1": "value",
  1419  				},
  1420  			},
  1421  		},
  1422  		{
  1423  			name: "only the legacy field is set",
  1424  			original: &k8s.Resource{
  1425  				TypeMeta: v1.TypeMeta{
  1426  					Kind: fooKind,
  1427  				},
  1428  				Spec: map[string]interface{}{
  1429  					"field1":      "value",
  1430  					"legacyField": "value",
  1431  				},
  1432  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1433  					"f:field1":      emptyObject,
  1434  					"f:legacyField": emptyObject,
  1435  				}),
  1436  			},
  1437  			legacyFieldPath: []string{"legacyField"},
  1438  			fieldPath:       []string{"authoritativeField"},
  1439  			expected: &k8s.Resource{
  1440  				TypeMeta: v1.TypeMeta{
  1441  					Kind: fooKind,
  1442  				},
  1443  				Spec: map[string]interface{}{
  1444  					"field1":             "value",
  1445  					"authoritativeField": "value",
  1446  				},
  1447  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1448  					"f:field1":             emptyObject,
  1449  					"f:legacyField":        emptyObject,
  1450  					"f:authoritativeField": emptyObject,
  1451  				}),
  1452  			},
  1453  		},
  1454  		{
  1455  			name: "only the nested legacy field is set",
  1456  			original: &k8s.Resource{
  1457  				TypeMeta: v1.TypeMeta{
  1458  					Kind: fooKind,
  1459  				},
  1460  				Spec: map[string]interface{}{
  1461  					"field1": "value",
  1462  					"topField": map[string]interface{}{
  1463  						"field2":      "value",
  1464  						"legacyField": "value",
  1465  					},
  1466  				},
  1467  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1468  					"f:field1": emptyObject,
  1469  					"f:topField": map[string]interface{}{
  1470  						".":             emptyObject,
  1471  						"f:legacyField": emptyObject,
  1472  					},
  1473  				}),
  1474  			},
  1475  			legacyFieldPath: []string{"topField", "legacyField"},
  1476  			fieldPath:       []string{"topField", "authoritativeField"},
  1477  			expected: &k8s.Resource{
  1478  				TypeMeta: v1.TypeMeta{
  1479  					Kind: fooKind,
  1480  				},
  1481  				Spec: map[string]interface{}{
  1482  					"field1": "value",
  1483  					"topField": map[string]interface{}{
  1484  						"field2":             "value",
  1485  						"authoritativeField": "value",
  1486  					},
  1487  				},
  1488  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1489  					"f:field1": emptyObject,
  1490  					"f:topField": map[string]interface{}{
  1491  						".":                    emptyObject,
  1492  						"f:legacyField":        emptyObject,
  1493  						"f:authoritativeField": emptyObject,
  1494  					},
  1495  				}),
  1496  			},
  1497  		},
  1498  		{
  1499  			name: "only the nested legacy field is set and the authoritative field is a reference field",
  1500  			original: &k8s.Resource{
  1501  				TypeMeta: v1.TypeMeta{
  1502  					Kind: fooKind,
  1503  				},
  1504  				Spec: map[string]interface{}{
  1505  					"field1": "value",
  1506  					"topField": map[string]interface{}{
  1507  						"field2":      "value",
  1508  						"legacyField": "value",
  1509  					},
  1510  				},
  1511  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1512  					"f:field1": emptyObject,
  1513  					"f:topField": map[string]interface{}{
  1514  						".":             emptyObject,
  1515  						"f:legacyField": emptyObject,
  1516  					},
  1517  				}),
  1518  			},
  1519  			legacyFieldPath: []string{"topField", "legacyField"},
  1520  			fieldPath:       []string{"topField", "authoritativeFieldRef"},
  1521  			expected: &k8s.Resource{
  1522  				TypeMeta: v1.TypeMeta{
  1523  					Kind: fooKind,
  1524  				},
  1525  				Spec: map[string]interface{}{
  1526  					"field1": "value",
  1527  					"topField": map[string]interface{}{
  1528  						"field2": "value",
  1529  						"authoritativeFieldRef": map[string]interface{}{
  1530  							"external": "value",
  1531  						},
  1532  					},
  1533  				},
  1534  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1535  					"f:field1": emptyObject,
  1536  					"f:topField": map[string]interface{}{
  1537  						".":             emptyObject,
  1538  						"f:legacyField": emptyObject,
  1539  						"f:authoritativeFieldRef": map[string]interface{}{
  1540  							".":          emptyObject,
  1541  							"f:external": emptyObject,
  1542  						},
  1543  					},
  1544  				}),
  1545  			},
  1546  		},
  1547  		{
  1548  			name: "only the authoritative field is set",
  1549  			original: &k8s.Resource{
  1550  				TypeMeta: v1.TypeMeta{
  1551  					Kind: fooKind,
  1552  				},
  1553  				Spec: map[string]interface{}{
  1554  					"field1":             "value",
  1555  					"authoritativeField": "value",
  1556  				},
  1557  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1558  					"f:field1":             emptyObject,
  1559  					"f:authoritativeField": emptyObject,
  1560  				}),
  1561  			},
  1562  			legacyFieldPath: []string{"legacyField"},
  1563  			fieldPath:       []string{"authoritativeField"},
  1564  			expected: &k8s.Resource{
  1565  				TypeMeta: v1.TypeMeta{
  1566  					Kind: fooKind,
  1567  				},
  1568  				Spec: map[string]interface{}{
  1569  					"field1":             "value",
  1570  					"authoritativeField": "value",
  1571  				},
  1572  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1573  					"f:field1":             emptyObject,
  1574  					"f:authoritativeField": emptyObject,
  1575  				}),
  1576  			},
  1577  		},
  1578  		{
  1579  			name: "only the nested authoritative field is set",
  1580  			original: &k8s.Resource{
  1581  				TypeMeta: v1.TypeMeta{
  1582  					Kind: fooKind,
  1583  				},
  1584  				Spec: map[string]interface{}{
  1585  					"field1": "value",
  1586  					"topField": map[string]interface{}{
  1587  						"field2":             "value",
  1588  						"authoritativeField": "value",
  1589  					},
  1590  				},
  1591  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1592  					"f:field1": emptyObject,
  1593  					"f:topField": map[string]interface{}{
  1594  						".":                    emptyObject,
  1595  						"f:authoritativeField": emptyObject,
  1596  					},
  1597  				}),
  1598  			},
  1599  			legacyFieldPath: []string{"topField", "legacyField"},
  1600  			fieldPath:       []string{"topField", "authoritativeField"},
  1601  			expected: &k8s.Resource{
  1602  				TypeMeta: v1.TypeMeta{
  1603  					Kind: fooKind,
  1604  				},
  1605  				Spec: map[string]interface{}{
  1606  					"field1": "value",
  1607  					"topField": map[string]interface{}{
  1608  						"field2":             "value",
  1609  						"authoritativeField": "value",
  1610  					},
  1611  				},
  1612  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1613  					"f:field1": emptyObject,
  1614  					"f:topField": map[string]interface{}{
  1615  						".":                    emptyObject,
  1616  						"f:authoritativeField": emptyObject,
  1617  					},
  1618  				}),
  1619  			},
  1620  		},
  1621  		{
  1622  			name: "both the legacy field and the authoritative field are set with the same value",
  1623  			original: &k8s.Resource{
  1624  				TypeMeta: v1.TypeMeta{
  1625  					Kind: fooKind,
  1626  				},
  1627  				Spec: map[string]interface{}{
  1628  					"field1":             "value",
  1629  					"legacyField":        "value1",
  1630  					"authoritativeField": "value1",
  1631  				},
  1632  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1633  					"f:field1":             emptyObject,
  1634  					"f:legacyField":        emptyObject,
  1635  					"f:authoritativeField": emptyObject,
  1636  				}),
  1637  			},
  1638  			legacyFieldPath: []string{"legacyField"},
  1639  			fieldPath:       []string{"authoritativeField"},
  1640  			expected: &k8s.Resource{
  1641  				TypeMeta: v1.TypeMeta{
  1642  					Kind: fooKind,
  1643  				},
  1644  				Spec: map[string]interface{}{
  1645  					"field1":             "value",
  1646  					"authoritativeField": "value1",
  1647  				},
  1648  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1649  					"f:field1":             emptyObject,
  1650  					"f:legacyField":        emptyObject,
  1651  					"f:authoritativeField": emptyObject,
  1652  				}),
  1653  			},
  1654  		},
  1655  		{
  1656  			name: "both the legacy field and the authoritative field are set with the different values",
  1657  			original: &k8s.Resource{
  1658  				TypeMeta: v1.TypeMeta{
  1659  					Kind: fooKind,
  1660  				},
  1661  				Spec: map[string]interface{}{
  1662  					"field1":             "value",
  1663  					"legacyField":        "value1",
  1664  					"authoritativeField": "value2",
  1665  				},
  1666  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1667  					"f:field1":             emptyObject,
  1668  					"f:legacyField":        emptyObject,
  1669  					"f:authoritativeField": emptyObject,
  1670  				}),
  1671  			},
  1672  			legacyFieldPath: []string{"legacyField"},
  1673  			fieldPath:       []string{"authoritativeField"},
  1674  			hasError:        true,
  1675  		},
  1676  		{
  1677  			name: "both the nested legacy field and the nested authoritative field are set with the same value",
  1678  			original: &k8s.Resource{
  1679  				TypeMeta: v1.TypeMeta{
  1680  					Kind: fooKind,
  1681  				},
  1682  				Spec: map[string]interface{}{
  1683  					"field1": "value",
  1684  					"topField": map[string]interface{}{
  1685  						"legacyField":        "value1",
  1686  						"authoritativeField": "value1",
  1687  					},
  1688  				},
  1689  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1690  					"f:field1": emptyObject,
  1691  					"f:topField": map[string]interface{}{
  1692  						".":                    emptyObject,
  1693  						"f:authoritativeField": emptyObject,
  1694  						"f:legacyField":        emptyObject,
  1695  					},
  1696  				}),
  1697  			},
  1698  			legacyFieldPath: []string{"topField", "legacyField"},
  1699  			fieldPath:       []string{"topField", "authoritativeField"},
  1700  			expected: &k8s.Resource{
  1701  				TypeMeta: v1.TypeMeta{
  1702  					Kind: fooKind,
  1703  				},
  1704  				Spec: map[string]interface{}{
  1705  					"field1": "value",
  1706  					"topField": map[string]interface{}{
  1707  						"authoritativeField": "value1",
  1708  					},
  1709  				},
  1710  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1711  					"f:field1": emptyObject,
  1712  					"f:topField": map[string]interface{}{
  1713  						".":                    emptyObject,
  1714  						"f:authoritativeField": emptyObject,
  1715  						"f:legacyField":        emptyObject,
  1716  					},
  1717  				}),
  1718  			},
  1719  		},
  1720  		{
  1721  			name: "both the nested legacy field and the nested authoritative field are set with different values",
  1722  			original: &k8s.Resource{
  1723  				TypeMeta: v1.TypeMeta{
  1724  					Kind: fooKind,
  1725  				},
  1726  				Spec: map[string]interface{}{
  1727  					"field1": "value",
  1728  					"topField": map[string]interface{}{
  1729  						"legacyField":        "value1",
  1730  						"authoritativeField": "value2",
  1731  					},
  1732  				},
  1733  				ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{
  1734  					"f:field1": emptyObject,
  1735  					"f:topField": map[string]interface{}{
  1736  						".":                    emptyObject,
  1737  						"f:authoritativeField": emptyObject,
  1738  						"f:legacyField":        emptyObject,
  1739  					},
  1740  				}),
  1741  			},
  1742  			legacyFieldPath: []string{"topField", "legacyField"},
  1743  			fieldPath:       []string{"topField", "authoritativeField"},
  1744  			hasError:        true,
  1745  		},
  1746  		{
  1747  			name: "the nested legacy field and the nested authoritative field are structurally different",
  1748  			original: &k8s.Resource{
  1749  				TypeMeta: v1.TypeMeta{
  1750  					Kind: fooKind,
  1751  				},
  1752  				Spec: map[string]interface{}{
  1753  					"field1": "value",
  1754  					"topField": map[string]interface{}{
  1755  						"legacyField": "value1",
  1756  						"authoritativeField": map[string]interface{}{
  1757  							"subfield": "value1",
  1758  						},
  1759  					},
  1760  				},
  1761  			},
  1762  			legacyFieldPath: []string{"topField", "legacyField"},
  1763  			fieldPath:       []string{"topField", "authoritativeField"},
  1764  			hasError:        true,
  1765  		},
  1766  		{
  1767  			name: "the nested authoritative field has a slice somewhere in the path",
  1768  			original: &k8s.Resource{
  1769  				TypeMeta: v1.TypeMeta{
  1770  					Kind: fooKind,
  1771  				},
  1772  				Spec: map[string]interface{}{
  1773  					"field1":      "value",
  1774  					"legacyField": "value1",
  1775  					"topField": []interface{}{
  1776  						map[string]interface{}{
  1777  							"authoritativeField": "value2",
  1778  						},
  1779  					},
  1780  				},
  1781  			},
  1782  			legacyFieldPath: []string{"legacyField"},
  1783  			fieldPath:       []string{"topField", "authoritativeField"},
  1784  			hasError:        true,
  1785  		},
  1786  		{
  1787  			name: "the nested legacy field has a slice somewhere in the path",
  1788  			original: &k8s.Resource{
  1789  				TypeMeta: v1.TypeMeta{
  1790  					Kind: fooKind,
  1791  				},
  1792  				Spec: map[string]interface{}{
  1793  					"field1":             "value",
  1794  					"authoritativeField": "value1",
  1795  					"topField": []interface{}{
  1796  						map[string]interface{}{
  1797  							"legacyField": "value2",
  1798  						},
  1799  					},
  1800  				},
  1801  			},
  1802  			legacyFieldPath: []string{"topField", "legacyField"},
  1803  			fieldPath:       []string{"authoritativeField"},
  1804  			hasError:        true,
  1805  		},
  1806  	}
  1807  
  1808  	for _, tc := range tests {
  1809  		t.Run(tc.name, func(t *testing.T) {
  1810  			err := FavorAuthoritativeFieldOverLegacyField(tc.original, tc.legacyFieldPath, tc.fieldPath)
  1811  			if tc.hasError {
  1812  				if err == nil {
  1813  					t.Fatalf("got nil, but expect to have error")
  1814  				}
  1815  				return
  1816  			}
  1817  			if err != nil {
  1818  				t.Fatalf("unexpected error: %v", err)
  1819  			}
  1820  
  1821  			// Compare ManagedFields separately.
  1822  			if tc.original.ManagedFields != nil && !tc.original.ManagedFields.Equals(tc.expected.ManagedFields) {
  1823  				t.Fatalf("got %v, want %v", tc.original.ManagedFields, tc.expected.ManagedFields)
  1824  			}
  1825  			tc.original.ManagedFields = nil
  1826  			tc.expected.ManagedFields = nil
  1827  			if !reflect.DeepEqual(tc.original, tc.expected) {
  1828  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.original))
  1829  			}
  1830  		})
  1831  	}
  1832  }
  1833  
  1834  func TestFavorReferenceFieldOverNonReferenceFieldUnderSlice(t *testing.T) {
  1835  	t.Parallel()
  1836  	tests := []struct {
  1837  		name                  string
  1838  		original              *k8s.Resource
  1839  		pathUpToSlice         []string
  1840  		nonReferenceFieldPath []string
  1841  		referenceFieldPath    []string
  1842  		expected              *k8s.Resource
  1843  		hasError              bool
  1844  	}{
  1845  		{
  1846  			name: "neither the legacy field nor the authoritative field is set without path up to slice",
  1847  			original: &k8s.Resource{
  1848  				TypeMeta: v1.TypeMeta{
  1849  					Kind: fooKind,
  1850  				},
  1851  				Spec: map[string]interface{}{
  1852  					"field1": "value",
  1853  				},
  1854  			},
  1855  			pathUpToSlice:         []string{"pathUpToSlice"},
  1856  			nonReferenceFieldPath: []string{"legacyField"},
  1857  			referenceFieldPath:    []string{"authoritativeField"},
  1858  			expected: &k8s.Resource{
  1859  				TypeMeta: v1.TypeMeta{
  1860  					Kind: fooKind,
  1861  				},
  1862  				Spec: map[string]interface{}{
  1863  					"field1": "value",
  1864  				},
  1865  			},
  1866  		},
  1867  		{
  1868  			name: "neither the legacy field nor the authoritative field is set with path up to slice",
  1869  			original: &k8s.Resource{
  1870  				TypeMeta: v1.TypeMeta{
  1871  					Kind: fooKind,
  1872  				},
  1873  				Spec: map[string]interface{}{
  1874  					"field1":        "value",
  1875  					"pathUpToSlice": []interface{}{},
  1876  				},
  1877  			},
  1878  			pathUpToSlice:         []string{"pathUpToSlice"},
  1879  			nonReferenceFieldPath: []string{"legacyField"},
  1880  			referenceFieldPath:    []string{"authoritativeField"},
  1881  			expected: &k8s.Resource{
  1882  				TypeMeta: v1.TypeMeta{
  1883  					Kind: fooKind,
  1884  				},
  1885  				Spec: map[string]interface{}{
  1886  					"field1":        "value",
  1887  					"pathUpToSlice": []interface{}{},
  1888  				},
  1889  			},
  1890  		},
  1891  		{
  1892  			name: "only the legacy field is set",
  1893  			original: &k8s.Resource{
  1894  				TypeMeta: v1.TypeMeta{
  1895  					Kind: fooKind,
  1896  				},
  1897  				Spec: map[string]interface{}{
  1898  					"pathUpToSlice": []interface{}{
  1899  						map[string]interface{}{
  1900  							"field1":      "value",
  1901  							"legacyField": "value",
  1902  						},
  1903  					},
  1904  				},
  1905  			},
  1906  			pathUpToSlice:         []string{"pathUpToSlice"},
  1907  			nonReferenceFieldPath: []string{"legacyField"},
  1908  			referenceFieldPath:    []string{"authoritativeField"},
  1909  			expected: &k8s.Resource{
  1910  				TypeMeta: v1.TypeMeta{
  1911  					Kind: fooKind,
  1912  				},
  1913  				Spec: map[string]interface{}{
  1914  					"pathUpToSlice": []interface{}{
  1915  						map[string]interface{}{
  1916  							"field1": "value",
  1917  							"authoritativeField": map[string]interface{}{
  1918  								"external": "value",
  1919  							},
  1920  						},
  1921  					},
  1922  				},
  1923  			},
  1924  		},
  1925  		{
  1926  			name: "only the authoritative field is set",
  1927  			original: &k8s.Resource{
  1928  				TypeMeta: v1.TypeMeta{
  1929  					Kind: fooKind,
  1930  				},
  1931  				Spec: map[string]interface{}{
  1932  					"field1": "value",
  1933  					"pathUpToSlice": []interface{}{
  1934  						map[string]interface{}{
  1935  							"authoritativeField": "value",
  1936  						},
  1937  					},
  1938  				},
  1939  			},
  1940  			pathUpToSlice:         []string{"pathUpToSlice"},
  1941  			nonReferenceFieldPath: []string{"legacyField"},
  1942  			referenceFieldPath:    []string{"authoritativeField"},
  1943  			expected: &k8s.Resource{
  1944  				TypeMeta: v1.TypeMeta{
  1945  					Kind: fooKind,
  1946  				},
  1947  				Spec: map[string]interface{}{
  1948  					"field1": "value",
  1949  					"pathUpToSlice": []interface{}{
  1950  						map[string]interface{}{
  1951  							"authoritativeField": "value",
  1952  						},
  1953  					},
  1954  				},
  1955  			},
  1956  		},
  1957  		{
  1958  			name: "only the legacy field within the nested slice field is set",
  1959  			original: &k8s.Resource{
  1960  				TypeMeta: v1.TypeMeta{
  1961  					Kind: fooKind,
  1962  				},
  1963  				Spec: map[string]interface{}{
  1964  					"field1": "value",
  1965  					"topField": map[string]interface{}{
  1966  						"field2": "value",
  1967  						"pathUpToSlice": []interface{}{
  1968  							map[string]interface{}{
  1969  								"legacyField": "value",
  1970  							},
  1971  						},
  1972  					},
  1973  				},
  1974  			},
  1975  			pathUpToSlice:         []string{"topField", "pathUpToSlice"},
  1976  			nonReferenceFieldPath: []string{"legacyField"},
  1977  			referenceFieldPath:    []string{"authoritativeField"},
  1978  			expected: &k8s.Resource{
  1979  				TypeMeta: v1.TypeMeta{
  1980  					Kind: fooKind,
  1981  				},
  1982  				Spec: map[string]interface{}{
  1983  					"field1": "value",
  1984  					"topField": map[string]interface{}{
  1985  						"field2": "value",
  1986  						"pathUpToSlice": []interface{}{
  1987  							map[string]interface{}{
  1988  								"authoritativeField": map[string]interface{}{
  1989  									"external": "value",
  1990  								},
  1991  							},
  1992  						},
  1993  					},
  1994  				},
  1995  			},
  1996  		},
  1997  		{
  1998  			name: "only the nested legacy field within the slice field is set",
  1999  			original: &k8s.Resource{
  2000  				TypeMeta: v1.TypeMeta{
  2001  					Kind: fooKind,
  2002  				},
  2003  				Spec: map[string]interface{}{
  2004  					"field1": "value",
  2005  					"pathUpToSlice": []interface{}{
  2006  						map[string]interface{}{
  2007  							"topField": map[string]interface{}{
  2008  								"legacyField": "value",
  2009  							},
  2010  						},
  2011  					},
  2012  				},
  2013  			},
  2014  			pathUpToSlice:         []string{"pathUpToSlice"},
  2015  			nonReferenceFieldPath: []string{"topField", "legacyField"},
  2016  			referenceFieldPath:    []string{"topField", "authoritativeField"},
  2017  			expected: &k8s.Resource{
  2018  				TypeMeta: v1.TypeMeta{
  2019  					Kind: fooKind,
  2020  				},
  2021  				Spec: map[string]interface{}{
  2022  					"field1": "value",
  2023  					"pathUpToSlice": []interface{}{
  2024  						map[string]interface{}{
  2025  							"topField": map[string]interface{}{
  2026  								"authoritativeField": map[string]interface{}{
  2027  									"external": "value",
  2028  								},
  2029  							},
  2030  						},
  2031  					},
  2032  				},
  2033  			},
  2034  		},
  2035  		{
  2036  			name: "slice has one authoritative field and one legacy field set on different elements",
  2037  			original: &k8s.Resource{
  2038  				TypeMeta: v1.TypeMeta{
  2039  					Kind: fooKind,
  2040  				},
  2041  				Spec: map[string]interface{}{
  2042  					"field1": "value",
  2043  					"pathUpToSlice": []interface{}{
  2044  						map[string]interface{}{
  2045  							"authoritativeField": map[string]interface{}{
  2046  								"name": "resourcename",
  2047  								"kind": "resourcekind",
  2048  							},
  2049  						},
  2050  						map[string]interface{}{
  2051  							"legacyField": "value1",
  2052  						},
  2053  					},
  2054  				},
  2055  			},
  2056  			pathUpToSlice:         []string{"pathUpToSlice"},
  2057  			nonReferenceFieldPath: []string{"legacyField"},
  2058  			referenceFieldPath:    []string{"authoritativeField"},
  2059  			expected: &k8s.Resource{
  2060  				TypeMeta: v1.TypeMeta{
  2061  					Kind: fooKind,
  2062  				},
  2063  				Spec: map[string]interface{}{
  2064  					"field1": "value",
  2065  					"pathUpToSlice": []interface{}{
  2066  						map[string]interface{}{
  2067  							"authoritativeField": map[string]interface{}{
  2068  								"name": "resourcename",
  2069  								"kind": "resourcekind",
  2070  							},
  2071  						},
  2072  						map[string]interface{}{
  2073  							"authoritativeField": map[string]interface{}{
  2074  								"external": "value1",
  2075  							},
  2076  						},
  2077  					},
  2078  				},
  2079  			},
  2080  		},
  2081  		{
  2082  			name: "both legacy field and authoritative field are set",
  2083  			original: &k8s.Resource{
  2084  				TypeMeta: v1.TypeMeta{
  2085  					Kind: fooKind,
  2086  				},
  2087  				Spec: map[string]interface{}{
  2088  					"pathUpToSlice": []interface{}{
  2089  						map[string]interface{}{
  2090  							"legacyField": "value1",
  2091  							"authoritativeField": map[string]interface{}{
  2092  								"external": "value2",
  2093  							},
  2094  						},
  2095  					},
  2096  				},
  2097  			},
  2098  			pathUpToSlice:         []string{"pathUpToSlice"},
  2099  			nonReferenceFieldPath: []string{"legacyField"},
  2100  			referenceFieldPath:    []string{"authoritativeField"},
  2101  			hasError:              true,
  2102  		},
  2103  	}
  2104  
  2105  	for _, tc := range tests {
  2106  		t.Run(tc.name, func(t *testing.T) {
  2107  			err := FavorReferenceFieldOverNonReferenceFieldUnderSlice(tc.original, tc.pathUpToSlice, tc.nonReferenceFieldPath, tc.referenceFieldPath)
  2108  			if tc.hasError {
  2109  				if err == nil {
  2110  					t.Fatalf("got nil, but expect to have error")
  2111  				}
  2112  				return
  2113  			}
  2114  			if err != nil {
  2115  				t.Fatalf("unexpected error: %v", err)
  2116  			}
  2117  
  2118  			if !reflect.DeepEqual(tc.original, tc.expected) {
  2119  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.original))
  2120  			}
  2121  		})
  2122  	}
  2123  }
  2124  
  2125  func TestFavorReferenceArrayFieldOverNonReferenceArrayField(t *testing.T) {
  2126  	t.Parallel()
  2127  	tests := []struct {
  2128  		name                  string
  2129  		original              *k8s.Resource
  2130  		nonReferenceFieldPath []string
  2131  		referenceFieldPath    []string
  2132  		expected              *k8s.Resource
  2133  		hasError              bool
  2134  	}{
  2135  		{
  2136  			name: "neither the non-reference array field nor the reference " +
  2137  				"array field is set",
  2138  			original: &k8s.Resource{
  2139  				TypeMeta: v1.TypeMeta{
  2140  					Kind: fooKind,
  2141  				},
  2142  				Spec: map[string]interface{}{
  2143  					"field1": "value",
  2144  				},
  2145  			},
  2146  			nonReferenceFieldPath: []string{"nonReferenceField"},
  2147  			referenceFieldPath:    []string{"referenceField"},
  2148  			expected: &k8s.Resource{
  2149  				TypeMeta: v1.TypeMeta{
  2150  					Kind: fooKind,
  2151  				},
  2152  				Spec: map[string]interface{}{
  2153  					"field1": "value",
  2154  				},
  2155  			},
  2156  		},
  2157  		{
  2158  			name: "only the non-reference array field is set",
  2159  			original: &k8s.Resource{
  2160  				TypeMeta: v1.TypeMeta{
  2161  					Kind: fooKind,
  2162  				},
  2163  				Spec: map[string]interface{}{
  2164  					"field1": "value",
  2165  					"nonReferenceField": []interface{}{
  2166  						"testValue1",
  2167  						"testValue2",
  2168  					},
  2169  				},
  2170  			},
  2171  			nonReferenceFieldPath: []string{"nonReferenceField"},
  2172  			referenceFieldPath:    []string{"referenceField"},
  2173  			expected: &k8s.Resource{
  2174  				TypeMeta: v1.TypeMeta{
  2175  					Kind: fooKind,
  2176  				},
  2177  				Spec: map[string]interface{}{
  2178  					"field1": "value",
  2179  					"referenceField": []interface{}{
  2180  						map[string]interface{}{
  2181  							"external": "testValue1",
  2182  						},
  2183  						map[string]interface{}{
  2184  							"external": "testValue2",
  2185  						},
  2186  					},
  2187  				},
  2188  			},
  2189  		},
  2190  		{
  2191  			name: "only the nested non-reference array field is set",
  2192  			original: &k8s.Resource{
  2193  				TypeMeta: v1.TypeMeta{
  2194  					Kind: fooKind,
  2195  				},
  2196  				Spec: map[string]interface{}{
  2197  					"field1": "value",
  2198  					"topField": map[string]interface{}{
  2199  						"field2": "value",
  2200  						"nonReferenceField": []interface{}{
  2201  							"testValue1",
  2202  							"testValue2",
  2203  						},
  2204  					},
  2205  				},
  2206  			},
  2207  			nonReferenceFieldPath: []string{"topField", "nonReferenceField"},
  2208  			referenceFieldPath:    []string{"topField", "referenceField"},
  2209  			expected: &k8s.Resource{
  2210  				TypeMeta: v1.TypeMeta{
  2211  					Kind: fooKind,
  2212  				},
  2213  				Spec: map[string]interface{}{
  2214  					"field1": "value",
  2215  					"topField": map[string]interface{}{
  2216  						"field2": "value",
  2217  						"referenceField": []interface{}{
  2218  							map[string]interface{}{
  2219  								"external": "testValue1",
  2220  							},
  2221  							map[string]interface{}{
  2222  								"external": "testValue2",
  2223  							},
  2224  						},
  2225  					},
  2226  				},
  2227  			},
  2228  		},
  2229  		{
  2230  			name: "only the reference array field is set",
  2231  			original: &k8s.Resource{
  2232  				TypeMeta: v1.TypeMeta{
  2233  					Kind: fooKind,
  2234  				},
  2235  				Spec: map[string]interface{}{
  2236  					"field1": "value",
  2237  					"referenceField": []interface{}{
  2238  						map[string]interface{}{
  2239  							"name": "reference1",
  2240  						},
  2241  						map[string]interface{}{
  2242  							"external": "reference2",
  2243  						},
  2244  					},
  2245  				},
  2246  			},
  2247  			nonReferenceFieldPath: []string{"nonReferenceField"},
  2248  			referenceFieldPath:    []string{"referenceField"},
  2249  			expected: &k8s.Resource{
  2250  				TypeMeta: v1.TypeMeta{
  2251  					Kind: fooKind,
  2252  				},
  2253  				Spec: map[string]interface{}{
  2254  					"field1": "value",
  2255  					"referenceField": []interface{}{
  2256  						map[string]interface{}{
  2257  							"name": "reference1",
  2258  						},
  2259  						map[string]interface{}{
  2260  							"external": "reference2",
  2261  						},
  2262  					},
  2263  				},
  2264  			},
  2265  		},
  2266  		{
  2267  			name: "only the nested reference array field is set",
  2268  			original: &k8s.Resource{
  2269  				TypeMeta: v1.TypeMeta{
  2270  					Kind: fooKind,
  2271  				},
  2272  				Spec: map[string]interface{}{
  2273  					"field1": "value",
  2274  					"topField": map[string]interface{}{
  2275  						"field2": "value",
  2276  						"referenceField": []interface{}{
  2277  							map[string]interface{}{
  2278  								"name": "reference1",
  2279  							},
  2280  							map[string]interface{}{
  2281  								"external": "reference2",
  2282  							},
  2283  						},
  2284  					},
  2285  				},
  2286  			},
  2287  			nonReferenceFieldPath: []string{"topField", "nonReferenceField"},
  2288  			referenceFieldPath:    []string{"topField", "referenceField"},
  2289  			expected: &k8s.Resource{
  2290  				TypeMeta: v1.TypeMeta{
  2291  					Kind: fooKind,
  2292  				},
  2293  				Spec: map[string]interface{}{
  2294  					"field1": "value",
  2295  					"topField": map[string]interface{}{
  2296  						"field2": "value",
  2297  						"referenceField": []interface{}{
  2298  							map[string]interface{}{
  2299  								"name": "reference1",
  2300  							},
  2301  							map[string]interface{}{
  2302  								"external": "reference2",
  2303  							},
  2304  						},
  2305  					},
  2306  				},
  2307  			},
  2308  		},
  2309  		{
  2310  			name: "both the non-reference array field and the reference array " +
  2311  				"field are set",
  2312  			original: &k8s.Resource{
  2313  				TypeMeta: v1.TypeMeta{
  2314  					Kind: fooKind,
  2315  				},
  2316  				Spec: map[string]interface{}{
  2317  					"field1": "value",
  2318  					"nonReferenceField": []interface{}{
  2319  						"testValue1",
  2320  						"testValue2",
  2321  					},
  2322  					"referenceField": []interface{}{
  2323  						map[string]interface{}{
  2324  							"name": "reference1",
  2325  						},
  2326  						map[string]interface{}{
  2327  							"external": "reference2",
  2328  						},
  2329  					},
  2330  				},
  2331  			},
  2332  			nonReferenceFieldPath: []string{"nonReferenceField"},
  2333  			referenceFieldPath:    []string{"referenceField"},
  2334  			hasError:              true,
  2335  		},
  2336  		{
  2337  			name: "both the nested non-reference array field and the nested " +
  2338  				"reference array field are set",
  2339  			original: &k8s.Resource{
  2340  				TypeMeta: v1.TypeMeta{
  2341  					Kind: fooKind,
  2342  				},
  2343  				Spec: map[string]interface{}{
  2344  					"field1": "value",
  2345  					"topField": map[string]interface{}{
  2346  						"nonReferenceField": []interface{}{
  2347  							"testValue1",
  2348  							"testValue2",
  2349  						},
  2350  						"referenceField": []interface{}{
  2351  							map[string]interface{}{
  2352  								"name": "reference1",
  2353  							},
  2354  							map[string]interface{}{
  2355  								"external": "reference2",
  2356  							},
  2357  						},
  2358  					},
  2359  				},
  2360  			},
  2361  			nonReferenceFieldPath: []string{"topField", "nonReferenceField"},
  2362  			referenceFieldPath:    []string{"topField", "referenceField"},
  2363  			hasError:              true,
  2364  		},
  2365  		{
  2366  			name: "the non-reference field is not an array",
  2367  			original: &k8s.Resource{
  2368  				TypeMeta: v1.TypeMeta{
  2369  					Kind: fooKind,
  2370  				},
  2371  				Spec: map[string]interface{}{
  2372  					"field1":            "value",
  2373  					"nonReferenceField": "testValue",
  2374  				},
  2375  			},
  2376  			nonReferenceFieldPath: []string{"nonReferenceField"},
  2377  			referenceFieldPath:    []string{"referenceField"},
  2378  			hasError:              true,
  2379  		},
  2380  		{
  2381  			name: "the reference field is not an array",
  2382  			original: &k8s.Resource{
  2383  				TypeMeta: v1.TypeMeta{
  2384  					Kind: fooKind,
  2385  				},
  2386  				Spec: map[string]interface{}{
  2387  					"field1":         "value",
  2388  					"referenceField": "testValue",
  2389  				},
  2390  			},
  2391  			nonReferenceFieldPath: []string{"nonReferenceField"},
  2392  			referenceFieldPath:    []string{"referenceField"},
  2393  			hasError:              true,
  2394  		},
  2395  	}
  2396  
  2397  	for _, tc := range tests {
  2398  		t.Run(tc.name, func(t *testing.T) {
  2399  			err := FavorReferenceArrayFieldOverNonReferenceArrayField(tc.original, tc.nonReferenceFieldPath, tc.referenceFieldPath)
  2400  			if tc.hasError {
  2401  				if err == nil {
  2402  					t.Fatalf("got nil, but expect to have error")
  2403  				}
  2404  				return
  2405  			}
  2406  			if err != nil {
  2407  				t.Fatalf("unexpected error: %v", err)
  2408  			}
  2409  			if got, want := tc.original, tc.expected; !reflect.DeepEqual(got, want) {
  2410  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(want, got))
  2411  			}
  2412  		})
  2413  	}
  2414  }
  2415  
  2416  func TestPruneNoOpsField(t *testing.T) {
  2417  	t.Parallel()
  2418  	tests := []struct {
  2419  		name      string
  2420  		original  *k8s.Resource
  2421  		fieldPath []string
  2422  		expected  *k8s.Resource
  2423  	}{
  2424  		{
  2425  			name: "no-ops fields are not set",
  2426  			original: &k8s.Resource{
  2427  				TypeMeta: v1.TypeMeta{
  2428  					Kind: fooKind,
  2429  				},
  2430  				Spec: map[string]interface{}{
  2431  					"field1": "value",
  2432  				},
  2433  			},
  2434  			fieldPath: []string{"noops"},
  2435  			expected: &k8s.Resource{
  2436  				TypeMeta: v1.TypeMeta{
  2437  					Kind: fooKind,
  2438  				},
  2439  				Spec: map[string]interface{}{
  2440  					"field1": "value",
  2441  				},
  2442  			},
  2443  		},
  2444  		{
  2445  			name: "prune no-ops fields",
  2446  			original: &k8s.Resource{
  2447  				TypeMeta: v1.TypeMeta{
  2448  					Kind: fooKind,
  2449  				},
  2450  				Spec: map[string]interface{}{
  2451  					"field1": "value",
  2452  					"noops":  "value",
  2453  				},
  2454  			},
  2455  			fieldPath: []string{"noops"},
  2456  			expected: &k8s.Resource{
  2457  				TypeMeta: v1.TypeMeta{
  2458  					Kind: fooKind,
  2459  				},
  2460  				Spec: map[string]interface{}{
  2461  					"field1": "value",
  2462  				},
  2463  			},
  2464  		},
  2465  		{
  2466  			name: "prune nested no-ops fields",
  2467  			original: &k8s.Resource{
  2468  				TypeMeta: v1.TypeMeta{
  2469  					Kind: fooKind,
  2470  				},
  2471  				Spec: map[string]interface{}{
  2472  					"field1": "value",
  2473  					"topField": map[string]interface{}{
  2474  						"field2": "value",
  2475  						"noops":  "value",
  2476  					},
  2477  				},
  2478  			},
  2479  			fieldPath: []string{"topField", "noops"},
  2480  			expected: &k8s.Resource{
  2481  				TypeMeta: v1.TypeMeta{
  2482  					Kind: fooKind,
  2483  				},
  2484  				Spec: map[string]interface{}{
  2485  					"field1": "value",
  2486  					"topField": map[string]interface{}{
  2487  						"field2": "value",
  2488  					},
  2489  				},
  2490  			},
  2491  		},
  2492  	}
  2493  
  2494  	for _, tc := range tests {
  2495  		t.Run(tc.name, func(t *testing.T) {
  2496  			if err := PruneNoOpsField(tc.original, tc.fieldPath...); err != nil {
  2497  				t.Fatalf("unexpected error: %v", err)
  2498  			}
  2499  			if !reflect.DeepEqual(tc.original, tc.expected) {
  2500  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.original))
  2501  			}
  2502  		})
  2503  	}
  2504  }
  2505  
  2506  func TestPreserveMutuallyExclusiveNonRefField(t *testing.T) {
  2507  	t.Parallel()
  2508  	crdWithOptionalReferenceField := &apiextensions.CustomResourceDefinition{
  2509  		Spec: apiextensions.CustomResourceDefinitionSpec{
  2510  			Versions: []apiextensions.CustomResourceDefinitionVersion{
  2511  				{
  2512  					Name: "v1beta1",
  2513  					Schema: &apiextensions.CustomResourceValidation{
  2514  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2515  							Properties: map[string]apiextensions.JSONSchemaProps{
  2516  								"spec": {
  2517  									Properties: map[string]apiextensions.JSONSchemaProps{
  2518  										"testRef": {
  2519  											Properties: map[string]apiextensions.JSONSchemaProps{
  2520  												"name": {
  2521  													Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
  2522  													Type:        "string",
  2523  												},
  2524  												"namespace": {
  2525  													Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
  2526  													Type:        "string",
  2527  												},
  2528  												"kind": {
  2529  													Description: "Kind of the referent. Allowed values: ReferenceKind",
  2530  													Type:        "string",
  2531  												},
  2532  												"external": {
  2533  													Description: "Test description",
  2534  													Type:        "string",
  2535  												},
  2536  											},
  2537  											OneOf: []apiextensions.JSONSchemaProps{
  2538  												{
  2539  													Required: []string{"name", "kind"},
  2540  													Not: &apiextensions.JSONSchemaProps{
  2541  														Required: []string{"external"},
  2542  													},
  2543  												},
  2544  												{
  2545  													Required: []string{"external"},
  2546  													Not: &apiextensions.JSONSchemaProps{
  2547  														AnyOf: []apiextensions.JSONSchemaProps{
  2548  															{Required: []string{"name"}},
  2549  															{Required: []string{"namespace"}},
  2550  															{Required: []string{"kind"}},
  2551  														},
  2552  													},
  2553  												},
  2554  											},
  2555  											Type: "object",
  2556  										},
  2557  									},
  2558  									Type: "object",
  2559  								},
  2560  							},
  2561  							Type: "object",
  2562  						},
  2563  					},
  2564  				},
  2565  			},
  2566  		},
  2567  	}
  2568  
  2569  	tests := []struct {
  2570  		name                  string
  2571  		originalCRD           *apiextensions.CustomResourceDefinition
  2572  		parentPath            []string
  2573  		referenceFieldName    string
  2574  		nonReferenceFieldName string
  2575  		expectedCRD           *apiextensions.CustomResourceDefinition
  2576  		hasError              bool
  2577  	}{
  2578  		{
  2579  			name: "required top-level non-reference field is added when there" +
  2580  				"is another required field",
  2581  			originalCRD: &apiextensions.CustomResourceDefinition{
  2582  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2583  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2584  						{
  2585  							Name: "v1beta1",
  2586  							Schema: &apiextensions.CustomResourceValidation{
  2587  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2588  									Properties: map[string]apiextensions.JSONSchemaProps{
  2589  										"spec": {
  2590  											Properties: map[string]apiextensions.JSONSchemaProps{
  2591  												"topLevelRef": {
  2592  													Properties: map[string]apiextensions.JSONSchemaProps{
  2593  														"kind": {},
  2594  													},
  2595  													Description: "fake reference schema for testing",
  2596  													Type:        "object",
  2597  												},
  2598  												"otherRequired": {Type: "string"},
  2599  											},
  2600  											Required: []string{
  2601  												"topLevelRef",
  2602  												"otherRequired",
  2603  											},
  2604  											Type: "object",
  2605  										},
  2606  									},
  2607  									Type: "object",
  2608  								},
  2609  							},
  2610  						},
  2611  					},
  2612  				},
  2613  			},
  2614  			referenceFieldName:    "topLevelRef",
  2615  			nonReferenceFieldName: "topLevel",
  2616  			expectedCRD: &apiextensions.CustomResourceDefinition{
  2617  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2618  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2619  						{
  2620  							Name: "v1beta1",
  2621  							Schema: &apiextensions.CustomResourceValidation{
  2622  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2623  									Properties: map[string]apiextensions.JSONSchemaProps{
  2624  										"spec": {
  2625  											Properties: map[string]apiextensions.JSONSchemaProps{
  2626  												"topLevelRef": {
  2627  													Properties: map[string]apiextensions.JSONSchemaProps{
  2628  														"kind": {},
  2629  													},
  2630  													Description: "fake reference schema for testing",
  2631  													Type:        "object",
  2632  												},
  2633  												"topLevel": {
  2634  													Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  2635  														"We recommend that you use `spec.topLevelRef` instead.",
  2636  													Type: "string",
  2637  												},
  2638  												"otherRequired": {Type: "string"},
  2639  											},
  2640  											OneOf: []apiextensions.JSONSchemaProps{
  2641  												{Required: []string{"topLevel"}},
  2642  												{Required: []string{"topLevelRef"}},
  2643  											},
  2644  											Required: []string{
  2645  												"otherRequired",
  2646  											},
  2647  											Type: "object",
  2648  										},
  2649  									},
  2650  									Type: "object",
  2651  								},
  2652  							},
  2653  						},
  2654  					},
  2655  				},
  2656  			},
  2657  		},
  2658  		{
  2659  			name: "optional top-level non-reference field is added",
  2660  			originalCRD: &apiextensions.CustomResourceDefinition{
  2661  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2662  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2663  						{
  2664  							Name: "v1beta1",
  2665  							Schema: &apiextensions.CustomResourceValidation{
  2666  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2667  									Properties: map[string]apiextensions.JSONSchemaProps{
  2668  										"spec": {
  2669  											Properties: map[string]apiextensions.JSONSchemaProps{
  2670  												"topLevelRef": {
  2671  													Properties: map[string]apiextensions.JSONSchemaProps{
  2672  														"kind": {},
  2673  													},
  2674  													Description: "fake reference schema for testing",
  2675  													Type:        "object",
  2676  												},
  2677  											},
  2678  											Type: "object",
  2679  										},
  2680  									},
  2681  									Type: "object",
  2682  								},
  2683  							},
  2684  						},
  2685  					},
  2686  				},
  2687  			},
  2688  			referenceFieldName:    "topLevelRef",
  2689  			nonReferenceFieldName: "topLevel",
  2690  			expectedCRD: &apiextensions.CustomResourceDefinition{
  2691  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2692  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2693  						{
  2694  							Name: "v1beta1",
  2695  							Schema: &apiextensions.CustomResourceValidation{
  2696  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2697  									Properties: map[string]apiextensions.JSONSchemaProps{
  2698  										"spec": {
  2699  											Properties: map[string]apiextensions.JSONSchemaProps{
  2700  												"topLevelRef": {
  2701  													Properties: map[string]apiextensions.JSONSchemaProps{
  2702  														"kind": {},
  2703  													},
  2704  													Description: "fake reference schema for testing",
  2705  													Type:        "object",
  2706  												},
  2707  												"topLevel": {
  2708  													Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  2709  														"We recommend that you use `spec.topLevelRef` instead.",
  2710  													Type: "string",
  2711  												},
  2712  											},
  2713  											Not: &apiextensions.JSONSchemaProps{
  2714  												Required: []string{"topLevel", "topLevelRef"},
  2715  											},
  2716  											Type: "object",
  2717  										},
  2718  									},
  2719  									Type: "object",
  2720  								},
  2721  							},
  2722  						},
  2723  					},
  2724  				},
  2725  			},
  2726  		},
  2727  		{
  2728  			name: "required nested non-reference array field is added",
  2729  			originalCRD: &apiextensions.CustomResourceDefinition{
  2730  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2731  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2732  						{
  2733  							Name: "v1beta1",
  2734  							Schema: &apiextensions.CustomResourceValidation{
  2735  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2736  									Properties: map[string]apiextensions.JSONSchemaProps{
  2737  										"spec": {
  2738  											Properties: map[string]apiextensions.JSONSchemaProps{
  2739  												"topLevelObject": {
  2740  													Properties: map[string]apiextensions.JSONSchemaProps{
  2741  														"nestedArrayRefs": {
  2742  															Items: &apiextensions.JSONSchemaPropsOrArray{
  2743  																Schema: &apiextensions.JSONSchemaProps{
  2744  																	Properties: map[string]apiextensions.JSONSchemaProps{
  2745  																		"kind": {},
  2746  																	},
  2747  																	Description: "fake reference schema for testing",
  2748  																	Type:        "object",
  2749  																},
  2750  															},
  2751  															Type: "array",
  2752  														},
  2753  													},
  2754  													Required: []string{
  2755  														"nestedArrayRefs",
  2756  													},
  2757  													Type: "object",
  2758  												},
  2759  											},
  2760  											Type: "object",
  2761  										},
  2762  									},
  2763  									Type: "object",
  2764  								},
  2765  							},
  2766  						},
  2767  					},
  2768  				},
  2769  			},
  2770  			parentPath:            []string{"topLevelObject"},
  2771  			referenceFieldName:    "nestedArrayRefs",
  2772  			nonReferenceFieldName: "nestedArray",
  2773  			expectedCRD: &apiextensions.CustomResourceDefinition{
  2774  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2775  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2776  						{
  2777  							Name: "v1beta1",
  2778  							Schema: &apiextensions.CustomResourceValidation{
  2779  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2780  									Properties: map[string]apiextensions.JSONSchemaProps{
  2781  										"spec": {
  2782  											Properties: map[string]apiextensions.JSONSchemaProps{
  2783  												"topLevelObject": {
  2784  													Properties: map[string]apiextensions.JSONSchemaProps{
  2785  														"nestedArrayRefs": {
  2786  															Items: &apiextensions.JSONSchemaPropsOrArray{
  2787  																Schema: &apiextensions.JSONSchemaProps{
  2788  																	Properties: map[string]apiextensions.JSONSchemaProps{
  2789  																		"kind": {},
  2790  																	},
  2791  																	Description: "fake reference schema for testing",
  2792  																	Type:        "object",
  2793  																},
  2794  															},
  2795  															Type: "array",
  2796  														},
  2797  														"nestedArray": {
  2798  															Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  2799  																"We recommend that you use `spec.topLevelObject.nestedArrayRefs` instead.",
  2800  															Items: &apiextensions.JSONSchemaPropsOrArray{
  2801  																Schema: &apiextensions.JSONSchemaProps{
  2802  																	Type: "string",
  2803  																},
  2804  															},
  2805  															Type: "array",
  2806  														},
  2807  													},
  2808  													OneOf: []apiextensions.JSONSchemaProps{
  2809  														{Required: []string{"nestedArray"}},
  2810  														{Required: []string{"nestedArrayRefs"}},
  2811  													},
  2812  													Type: "object",
  2813  												},
  2814  											},
  2815  											Type: "object",
  2816  										},
  2817  									},
  2818  									Type: "object",
  2819  								},
  2820  							},
  2821  						},
  2822  					},
  2823  				},
  2824  			},
  2825  		},
  2826  		{
  2827  			name: "optional nested non-reference field is added when the " +
  2828  				"parent's parent is an array field",
  2829  			originalCRD: &apiextensions.CustomResourceDefinition{
  2830  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2831  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2832  						{
  2833  							Name: "v1beta1",
  2834  							Schema: &apiextensions.CustomResourceValidation{
  2835  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2836  									Properties: map[string]apiextensions.JSONSchemaProps{
  2837  										"spec": {
  2838  											Properties: map[string]apiextensions.JSONSchemaProps{
  2839  												"topLevelArray": {
  2840  													Items: &apiextensions.JSONSchemaPropsOrArray{
  2841  														Schema: &apiextensions.JSONSchemaProps{
  2842  															Properties: map[string]apiextensions.JSONSchemaProps{
  2843  																"secondLevelObject": {
  2844  																	Properties: map[string]apiextensions.JSONSchemaProps{
  2845  																		"nestedRef": {
  2846  																			Properties: map[string]apiextensions.JSONSchemaProps{
  2847  																				"kind": {},
  2848  																			},
  2849  																			Description: "fake reference schema for testing",
  2850  																			Type:        "object",
  2851  																		},
  2852  																		"otherField": {Type: "string"},
  2853  																	},
  2854  																	Required: []string{"otherField"},
  2855  																	Type:     "object",
  2856  																},
  2857  																"secondLevelString": {Type: "string"},
  2858  															},
  2859  															Type: "object",
  2860  														},
  2861  													},
  2862  													Type: "array",
  2863  												},
  2864  											},
  2865  											Type: "object",
  2866  										},
  2867  									},
  2868  									Type: "object",
  2869  								},
  2870  							},
  2871  						},
  2872  					},
  2873  				},
  2874  			},
  2875  			parentPath:            []string{"topLevelArray", "secondLevelObject"},
  2876  			referenceFieldName:    "nestedRef",
  2877  			nonReferenceFieldName: "nested",
  2878  			expectedCRD: &apiextensions.CustomResourceDefinition{
  2879  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2880  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2881  						{
  2882  							Name: "v1beta1",
  2883  							Schema: &apiextensions.CustomResourceValidation{
  2884  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2885  									Properties: map[string]apiextensions.JSONSchemaProps{
  2886  										"spec": {
  2887  											Properties: map[string]apiextensions.JSONSchemaProps{
  2888  												"topLevelArray": {
  2889  													Items: &apiextensions.JSONSchemaPropsOrArray{
  2890  														Schema: &apiextensions.JSONSchemaProps{
  2891  															Properties: map[string]apiextensions.JSONSchemaProps{
  2892  																"secondLevelObject": {
  2893  																	Properties: map[string]apiextensions.JSONSchemaProps{
  2894  																		"nestedRef": {
  2895  																			Properties: map[string]apiextensions.JSONSchemaProps{
  2896  																				"kind": {},
  2897  																			},
  2898  																			Description: "fake reference schema for testing",
  2899  																			Type:        "object",
  2900  																		},
  2901  																		"nested": {
  2902  																			Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  2903  																				"We recommend that you use `spec.topLevelArray.secondLevelObject.nestedRef` instead.",
  2904  																			Type: "string",
  2905  																		},
  2906  																		"otherField": {Type: "string"},
  2907  																	},
  2908  																	Not: &apiextensions.JSONSchemaProps{
  2909  																		Required: []string{"nested", "nestedRef"},
  2910  																	},
  2911  																	Required: []string{"otherField"},
  2912  																	Type:     "object",
  2913  																},
  2914  																"secondLevelString": {Type: "string"},
  2915  															},
  2916  															Type: "object",
  2917  														},
  2918  													},
  2919  													Type: "array",
  2920  												},
  2921  											},
  2922  											Type: "object",
  2923  										},
  2924  									},
  2925  									Type: "object",
  2926  								},
  2927  							},
  2928  						},
  2929  					},
  2930  				},
  2931  			},
  2932  		},
  2933  		{
  2934  			name: "optional nested non-reference array field is added when " +
  2935  				"the parent is also an array field",
  2936  			originalCRD: &apiextensions.CustomResourceDefinition{
  2937  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2938  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2939  						{
  2940  							Name: "v1beta1",
  2941  							Schema: &apiextensions.CustomResourceValidation{
  2942  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2943  									Properties: map[string]apiextensions.JSONSchemaProps{
  2944  										"spec": {
  2945  											Properties: map[string]apiextensions.JSONSchemaProps{
  2946  												"topLevelArray": {
  2947  													Items: &apiextensions.JSONSchemaPropsOrArray{
  2948  														Schema: &apiextensions.JSONSchemaProps{
  2949  															Properties: map[string]apiextensions.JSONSchemaProps{
  2950  																"nestedArrayRefs": {
  2951  																	Items: &apiextensions.JSONSchemaPropsOrArray{
  2952  																		Schema: &apiextensions.JSONSchemaProps{
  2953  																			Properties: map[string]apiextensions.JSONSchemaProps{
  2954  																				"kind": {},
  2955  																			},
  2956  																			Description: "fake reference schema for testing",
  2957  																			Type:        "object",
  2958  																		},
  2959  																	},
  2960  																	Type: "array",
  2961  																},
  2962  															},
  2963  															Type: "object",
  2964  														},
  2965  													},
  2966  													Type: "array",
  2967  												},
  2968  											},
  2969  											Type: "object",
  2970  										},
  2971  									},
  2972  									Type: "object",
  2973  								},
  2974  							},
  2975  						},
  2976  					},
  2977  				},
  2978  			},
  2979  			parentPath:            []string{"topLevelArray"},
  2980  			referenceFieldName:    "nestedArrayRefs",
  2981  			nonReferenceFieldName: "nestedArray",
  2982  			expectedCRD: &apiextensions.CustomResourceDefinition{
  2983  				Spec: apiextensions.CustomResourceDefinitionSpec{
  2984  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  2985  						{
  2986  							Name: "v1beta1",
  2987  							Schema: &apiextensions.CustomResourceValidation{
  2988  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  2989  									Properties: map[string]apiextensions.JSONSchemaProps{
  2990  										"spec": {
  2991  											Properties: map[string]apiextensions.JSONSchemaProps{
  2992  												"topLevelArray": {
  2993  													Items: &apiextensions.JSONSchemaPropsOrArray{
  2994  														Schema: &apiextensions.JSONSchemaProps{
  2995  															Properties: map[string]apiextensions.JSONSchemaProps{
  2996  																"nestedArrayRefs": {
  2997  																	Items: &apiextensions.JSONSchemaPropsOrArray{
  2998  																		Schema: &apiextensions.JSONSchemaProps{
  2999  																			Properties: map[string]apiextensions.JSONSchemaProps{
  3000  																				"kind": {},
  3001  																			},
  3002  																			Description: "fake reference schema for testing",
  3003  																			Type:        "object",
  3004  																		},
  3005  																	},
  3006  																	Type: "array",
  3007  																},
  3008  																"nestedArray": {
  3009  																	Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  3010  																		"We recommend that you use `spec.topLevelArray.nestedArrayRefs` instead.",
  3011  																	Items: &apiextensions.JSONSchemaPropsOrArray{
  3012  																		Schema: &apiextensions.JSONSchemaProps{
  3013  																			Type: "string",
  3014  																		},
  3015  																	},
  3016  																	Type: "array",
  3017  																},
  3018  															},
  3019  															Not: &apiextensions.JSONSchemaProps{
  3020  																Required: []string{"nestedArray", "nestedArrayRefs"},
  3021  															},
  3022  															Type: "object",
  3023  														},
  3024  													},
  3025  													Type: "array",
  3026  												},
  3027  											},
  3028  											Type: "object",
  3029  										},
  3030  									},
  3031  									Type: "object",
  3032  								},
  3033  							},
  3034  						},
  3035  					},
  3036  				},
  3037  			},
  3038  		},
  3039  		{
  3040  			name: "reference with no 'kind' field",
  3041  			originalCRD: &apiextensions.CustomResourceDefinition{
  3042  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3043  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3044  						{
  3045  							Name: "v1beta1",
  3046  							Schema: &apiextensions.CustomResourceValidation{
  3047  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3048  									Properties: map[string]apiextensions.JSONSchemaProps{
  3049  										"spec": {
  3050  											Properties: map[string]apiextensions.JSONSchemaProps{
  3051  												"testRef": {
  3052  													Properties: map[string]apiextensions.JSONSchemaProps{
  3053  														"external": {Description: "Test description"},
  3054  													},
  3055  													Description: "reference schema with no 'kind' field",
  3056  													Type:        "object",
  3057  												},
  3058  											},
  3059  											Required: []string{
  3060  												"testRef",
  3061  											},
  3062  											Type: "object",
  3063  										},
  3064  									},
  3065  									Type: "object",
  3066  								},
  3067  							},
  3068  						},
  3069  					},
  3070  				},
  3071  			},
  3072  			referenceFieldName:    "testRef",
  3073  			nonReferenceFieldName: "test",
  3074  			expectedCRD: &apiextensions.CustomResourceDefinition{
  3075  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3076  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3077  						{
  3078  							Name: "v1beta1",
  3079  							Schema: &apiextensions.CustomResourceValidation{
  3080  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3081  									Properties: map[string]apiextensions.JSONSchemaProps{
  3082  										"spec": {
  3083  											Properties: map[string]apiextensions.JSONSchemaProps{
  3084  												"testRef": {
  3085  													Properties: map[string]apiextensions.JSONSchemaProps{
  3086  														"external": {Description: "Test description"},
  3087  													},
  3088  													Description: "reference schema with no 'kind' field",
  3089  													Type:        "object",
  3090  												},
  3091  												"test": {
  3092  													Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  3093  														"We recommend that you use `spec.testRef` instead.",
  3094  													Type: "string",
  3095  												},
  3096  											},
  3097  											OneOf: []apiextensions.JSONSchemaProps{
  3098  												{Required: []string{"test"}},
  3099  												{Required: []string{"testRef"}},
  3100  											},
  3101  											Type: "object",
  3102  										},
  3103  									},
  3104  									Type: "object",
  3105  								},
  3106  							},
  3107  						},
  3108  					},
  3109  				},
  3110  			},
  3111  		},
  3112  		{
  3113  			name: "required non-reference field can't be added due to " +
  3114  				"existing oneOf rule",
  3115  			originalCRD: &apiextensions.CustomResourceDefinition{
  3116  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3117  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3118  						{
  3119  							Name: "v1beta1",
  3120  							Schema: &apiextensions.CustomResourceValidation{
  3121  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3122  									Properties: map[string]apiextensions.JSONSchemaProps{
  3123  										"spec": {
  3124  											Properties: map[string]apiextensions.JSONSchemaProps{
  3125  												"testRef": {
  3126  													Description: "fake reference schema for testing",
  3127  													Type:        "object",
  3128  												},
  3129  											},
  3130  											OneOf: []apiextensions.JSONSchemaProps{
  3131  												{Required: []string{"randomField"}},
  3132  												{Required: []string{"randomField2"}},
  3133  											},
  3134  											Required: []string{
  3135  												"testRef",
  3136  											},
  3137  											Type: "object",
  3138  										},
  3139  									},
  3140  									Type: "object",
  3141  								},
  3142  							},
  3143  						},
  3144  					},
  3145  				},
  3146  			},
  3147  			referenceFieldName:    "testRef",
  3148  			nonReferenceFieldName: "test",
  3149  			hasError:              true,
  3150  		},
  3151  		{
  3152  			name: "optional non-reference field can't be added due to " +
  3153  				"existing not rule",
  3154  			originalCRD: &apiextensions.CustomResourceDefinition{
  3155  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3156  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3157  						{
  3158  							Name: "v1beta1",
  3159  							Schema: &apiextensions.CustomResourceValidation{
  3160  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3161  									Properties: map[string]apiextensions.JSONSchemaProps{
  3162  										"spec": {
  3163  											Properties: map[string]apiextensions.JSONSchemaProps{
  3164  												"testRef": {
  3165  													Description: "fake reference schema for testing",
  3166  													Type:        "object",
  3167  												},
  3168  											},
  3169  											Not: &apiextensions.JSONSchemaProps{
  3170  												Required: []string{"randomField"},
  3171  											},
  3172  											Type: "object",
  3173  										},
  3174  									},
  3175  									Type: "object",
  3176  								},
  3177  							},
  3178  						},
  3179  					},
  3180  				},
  3181  			},
  3182  			parentPath:            []string{},
  3183  			referenceFieldName:    "testRef",
  3184  			nonReferenceFieldName: "test",
  3185  			hasError:              true,
  3186  		},
  3187  		{
  3188  			name: "non-reference field can't be added due to empty reference " +
  3189  				"field name",
  3190  			originalCRD:           crdWithOptionalReferenceField,
  3191  			parentPath:            []string{},
  3192  			referenceFieldName:    "",
  3193  			nonReferenceFieldName: "test",
  3194  			hasError:              true,
  3195  		},
  3196  		{
  3197  			name: "non-reference field can't be added due to empty " +
  3198  				"non-reference field name",
  3199  			originalCRD:           crdWithOptionalReferenceField,
  3200  			parentPath:            []string{},
  3201  			referenceFieldName:    "testRef",
  3202  			nonReferenceFieldName: "",
  3203  			hasError:              true,
  3204  		},
  3205  		{
  3206  			name: "non-reference field can't be added due to incorrect " +
  3207  				"reference field name",
  3208  			originalCRD:           crdWithOptionalReferenceField,
  3209  			parentPath:            []string{},
  3210  			referenceFieldName:    "wrongRef",
  3211  			nonReferenceFieldName: "wrong",
  3212  			hasError:              true,
  3213  		},
  3214  		{
  3215  			name: "non-reference field can't be added due to incorrect " +
  3216  				"reference field type",
  3217  			originalCRD: &apiextensions.CustomResourceDefinition{
  3218  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3219  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3220  						{
  3221  							Name: "v1beta1",
  3222  							Schema: &apiextensions.CustomResourceValidation{
  3223  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3224  									Properties: map[string]apiextensions.JSONSchemaProps{
  3225  										"spec": {
  3226  											Properties: map[string]apiextensions.JSONSchemaProps{
  3227  												"testRef": {
  3228  													Description: "reference schema of wrong type",
  3229  													Type:        "string",
  3230  												},
  3231  											},
  3232  											Type: "object",
  3233  										},
  3234  									},
  3235  									Type: "object",
  3236  								},
  3237  							},
  3238  						},
  3239  					},
  3240  				},
  3241  			},
  3242  			parentPath:            []string{},
  3243  			referenceFieldName:    "testRef",
  3244  			nonReferenceFieldName: "test",
  3245  			hasError:              true,
  3246  		},
  3247  	}
  3248  
  3249  	for _, tc := range tests {
  3250  		tc := tc
  3251  		t.Run(tc.name, func(t *testing.T) {
  3252  			err := PreserveMutuallyExclusiveNonReferenceField(tc.originalCRD, tc.parentPath, tc.referenceFieldName, tc.nonReferenceFieldName)
  3253  			if err != nil {
  3254  				if !tc.hasError {
  3255  					t.Fatalf("error preserving the mutually exclusive non-reference field: got an error, but want no error: %v", err)
  3256  				}
  3257  				return
  3258  			}
  3259  			if !reflect.DeepEqual(tc.expectedCRD, tc.originalCRD) {
  3260  				t.Fatalf("unexpected diff in CRD after supporting the mutually exclusive non-reference field (-want +got): \n%v", cmp.Diff(tc.expectedCRD, tc.originalCRD))
  3261  			}
  3262  		})
  3263  	}
  3264  }
  3265  
  3266  func TestEnsureReferenceFieldIsMultiKind(t *testing.T) {
  3267  	t.Parallel()
  3268  	crdWithOptionalReferenceField := &apiextensions.CustomResourceDefinition{
  3269  		Spec: apiextensions.CustomResourceDefinitionSpec{
  3270  			Versions: []apiextensions.CustomResourceDefinitionVersion{
  3271  				{
  3272  					Name: "v1beta1",
  3273  					Schema: &apiextensions.CustomResourceValidation{
  3274  						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3275  							Properties: map[string]apiextensions.JSONSchemaProps{
  3276  								"spec": {
  3277  									Properties: map[string]apiextensions.JSONSchemaProps{
  3278  										"testRef": {
  3279  											Properties: map[string]apiextensions.JSONSchemaProps{
  3280  												"name": {
  3281  													Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
  3282  													Type:        "string",
  3283  												},
  3284  												"namespace": {
  3285  													Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
  3286  													Type:        "string",
  3287  												},
  3288  												"kind": {
  3289  													Description: "Kind of the referent. Allowed values: ReferenceKind",
  3290  													Type:        "string",
  3291  												},
  3292  												"external": {
  3293  													Description: "Test description",
  3294  													Type:        "string",
  3295  												},
  3296  											},
  3297  											OneOf: []apiextensions.JSONSchemaProps{
  3298  												{
  3299  													Required: []string{"name", "kind"},
  3300  													Not: &apiextensions.JSONSchemaProps{
  3301  														Required: []string{"external"},
  3302  													},
  3303  												},
  3304  												{
  3305  													Required: []string{"external"},
  3306  													Not: &apiextensions.JSONSchemaProps{
  3307  														AnyOf: []apiextensions.JSONSchemaProps{
  3308  															{Required: []string{"name"}},
  3309  															{Required: []string{"namespace"}},
  3310  															{Required: []string{"kind"}},
  3311  														},
  3312  													},
  3313  												},
  3314  											},
  3315  											Type: "object",
  3316  										},
  3317  									},
  3318  									Type: "object",
  3319  								},
  3320  							},
  3321  							Type: "object",
  3322  						},
  3323  					},
  3324  				},
  3325  			},
  3326  		},
  3327  	}
  3328  
  3329  	tests := []struct {
  3330  		name               string
  3331  		originalCRD        *apiextensions.CustomResourceDefinition
  3332  		parentPath         []string
  3333  		referenceFieldName string
  3334  		supportedKinds     []string
  3335  		expectedCRD        *apiextensions.CustomResourceDefinition
  3336  		hasError           bool
  3337  	}{
  3338  		{
  3339  			name: "top-level reference field has 'kind' field",
  3340  			originalCRD: &apiextensions.CustomResourceDefinition{
  3341  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3342  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3343  						{
  3344  							Name: "v1beta1",
  3345  							Schema: &apiextensions.CustomResourceValidation{
  3346  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3347  									Properties: map[string]apiextensions.JSONSchemaProps{
  3348  										"spec": {
  3349  											Properties: map[string]apiextensions.JSONSchemaProps{
  3350  												"topLevelRef": {
  3351  													Properties: map[string]apiextensions.JSONSchemaProps{
  3352  														"kind": {},
  3353  													},
  3354  													Description: "fake reference schema for testing",
  3355  													Type:        "object",
  3356  												},
  3357  												"topLevel": {
  3358  													Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  3359  														"We recommend that you use `spec.topLevelRef` instead.",
  3360  													Type: "string",
  3361  												},
  3362  												"otherRequired": {Type: "string"},
  3363  											},
  3364  											OneOf: []apiextensions.JSONSchemaProps{
  3365  												{Required: []string{"topLevel"}},
  3366  												{Required: []string{"topLevelRef"}},
  3367  											},
  3368  											Required: []string{
  3369  												"otherRequired",
  3370  											},
  3371  											Type: "object",
  3372  										},
  3373  									},
  3374  									Type: "object",
  3375  								},
  3376  							},
  3377  						},
  3378  					},
  3379  				},
  3380  			},
  3381  			referenceFieldName: "topLevelRef",
  3382  			expectedCRD: &apiextensions.CustomResourceDefinition{
  3383  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3384  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3385  						{
  3386  							Name: "v1beta1",
  3387  							Schema: &apiextensions.CustomResourceValidation{
  3388  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3389  									Properties: map[string]apiextensions.JSONSchemaProps{
  3390  										"spec": {
  3391  											Properties: map[string]apiextensions.JSONSchemaProps{
  3392  												"topLevelRef": {
  3393  													Properties: map[string]apiextensions.JSONSchemaProps{
  3394  														"kind": {},
  3395  													},
  3396  													Description: "fake reference schema for testing",
  3397  													Type:        "object",
  3398  												},
  3399  												"topLevel": {
  3400  													Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  3401  														"We recommend that you use `spec.topLevelRef` instead.",
  3402  													Type: "string",
  3403  												},
  3404  												"otherRequired": {Type: "string"},
  3405  											},
  3406  											OneOf: []apiextensions.JSONSchemaProps{
  3407  												{Required: []string{"topLevel"}},
  3408  												{Required: []string{"topLevelRef"}},
  3409  											},
  3410  											Required: []string{
  3411  												"otherRequired",
  3412  											},
  3413  											Type: "object",
  3414  										},
  3415  									},
  3416  									Type: "object",
  3417  								},
  3418  							},
  3419  						},
  3420  					},
  3421  				},
  3422  			},
  3423  		},
  3424  		{
  3425  			name: "top-level reference field doesn't have 'kind' field",
  3426  			originalCRD: &apiextensions.CustomResourceDefinition{
  3427  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3428  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3429  						{
  3430  							Name: "v1beta1",
  3431  							Schema: &apiextensions.CustomResourceValidation{
  3432  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3433  									Properties: map[string]apiextensions.JSONSchemaProps{
  3434  										"spec": {
  3435  											Properties: map[string]apiextensions.JSONSchemaProps{
  3436  												"topLevelRef": {
  3437  													Properties: map[string]apiextensions.JSONSchemaProps{
  3438  														"external": {Description: "Test description"},
  3439  													},
  3440  													Description: "reference schema with no 'kind' field",
  3441  													Type:        "object",
  3442  												},
  3443  												"topLevel": {
  3444  													Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  3445  														"We recommend that you use `spec.topLevelRef` instead.",
  3446  													Type: "string",
  3447  												},
  3448  											},
  3449  											Not: &apiextensions.JSONSchemaProps{
  3450  												Required: []string{"topLevel", "topLevelRef"},
  3451  											},
  3452  											Type: "object",
  3453  										},
  3454  									},
  3455  									Type: "object",
  3456  								},
  3457  							},
  3458  						},
  3459  					},
  3460  				},
  3461  			},
  3462  			referenceFieldName: "topLevelRef",
  3463  			supportedKinds:     []string{"ReferenceKind"},
  3464  			expectedCRD: &apiextensions.CustomResourceDefinition{
  3465  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3466  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3467  						{
  3468  							Name: "v1beta1",
  3469  							Schema: &apiextensions.CustomResourceValidation{
  3470  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3471  									Properties: map[string]apiextensions.JSONSchemaProps{
  3472  										"spec": {
  3473  											Properties: map[string]apiextensions.JSONSchemaProps{
  3474  												"topLevelRef": {
  3475  													Properties: map[string]apiextensions.JSONSchemaProps{
  3476  														"name": {
  3477  															Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
  3478  															Type:        "string",
  3479  														},
  3480  														"namespace": {
  3481  															Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
  3482  															Type:        "string",
  3483  														},
  3484  														"kind": {
  3485  															Description: "Kind of the referent. Allowed values: ReferenceKind",
  3486  															Type:        "string",
  3487  														},
  3488  														"external": {
  3489  															Description: "Test description",
  3490  															Type:        "string",
  3491  														},
  3492  													},
  3493  													OneOf: []apiextensions.JSONSchemaProps{
  3494  														{
  3495  															Required: []string{"name", "kind"},
  3496  															Not: &apiextensions.JSONSchemaProps{
  3497  																Required: []string{"external"},
  3498  															},
  3499  														},
  3500  														{
  3501  															Required: []string{"external"},
  3502  															Not: &apiextensions.JSONSchemaProps{
  3503  																AnyOf: []apiextensions.JSONSchemaProps{
  3504  																	{Required: []string{"name"}},
  3505  																	{Required: []string{"namespace"}},
  3506  																	{Required: []string{"kind"}},
  3507  																},
  3508  															},
  3509  														},
  3510  													},
  3511  													Type: "object",
  3512  												},
  3513  												"topLevel": {
  3514  													Description: "DEPRECATED. Although this field is still available, there is limited support. " +
  3515  														"We recommend that you use `spec.topLevelRef` instead.",
  3516  													Type: "string",
  3517  												},
  3518  											},
  3519  											Not: &apiextensions.JSONSchemaProps{
  3520  												Required: []string{"topLevel", "topLevelRef"},
  3521  											},
  3522  											Type: "object",
  3523  										},
  3524  									},
  3525  									Type: "object",
  3526  								},
  3527  							},
  3528  						},
  3529  					},
  3530  				},
  3531  			},
  3532  		},
  3533  		{
  3534  			name: "nested reference array field has 'kind' field",
  3535  			originalCRD: &apiextensions.CustomResourceDefinition{
  3536  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3537  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3538  						{
  3539  							Name: "v1beta1",
  3540  							Schema: &apiextensions.CustomResourceValidation{
  3541  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3542  									Properties: map[string]apiextensions.JSONSchemaProps{
  3543  										"spec": {
  3544  											Properties: map[string]apiextensions.JSONSchemaProps{
  3545  												"topLevelObject": {
  3546  													Properties: map[string]apiextensions.JSONSchemaProps{
  3547  														"nestedArrayRefs": {
  3548  															Items: &apiextensions.JSONSchemaPropsOrArray{
  3549  																Schema: &apiextensions.JSONSchemaProps{
  3550  																	Properties: map[string]apiextensions.JSONSchemaProps{
  3551  																		"kind": {},
  3552  																	},
  3553  																	Description: "fake reference schema for testing",
  3554  																	Type:        "object",
  3555  																},
  3556  															},
  3557  															Type: "array",
  3558  														},
  3559  													},
  3560  													Required: []string{
  3561  														"nestedArrayRefs",
  3562  													},
  3563  													Type: "object",
  3564  												},
  3565  											},
  3566  											Type: "object",
  3567  										},
  3568  									},
  3569  									Type: "object",
  3570  								},
  3571  							},
  3572  						},
  3573  					},
  3574  				},
  3575  			},
  3576  			parentPath:         []string{"topLevelObject"},
  3577  			referenceFieldName: "nestedArrayRefs",
  3578  			expectedCRD: &apiextensions.CustomResourceDefinition{
  3579  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3580  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3581  						{
  3582  							Name: "v1beta1",
  3583  							Schema: &apiextensions.CustomResourceValidation{
  3584  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3585  									Properties: map[string]apiextensions.JSONSchemaProps{
  3586  										"spec": {
  3587  											Properties: map[string]apiextensions.JSONSchemaProps{
  3588  												"topLevelObject": {
  3589  													Properties: map[string]apiextensions.JSONSchemaProps{
  3590  														"nestedArrayRefs": {
  3591  															Items: &apiextensions.JSONSchemaPropsOrArray{
  3592  																Schema: &apiextensions.JSONSchemaProps{
  3593  																	Properties: map[string]apiextensions.JSONSchemaProps{
  3594  																		"kind": {},
  3595  																	},
  3596  																	Description: "fake reference schema for testing",
  3597  																	Type:        "object",
  3598  																},
  3599  															},
  3600  															Type: "array",
  3601  														},
  3602  													},
  3603  													Required: []string{
  3604  														"nestedArrayRefs",
  3605  													},
  3606  													Type: "object",
  3607  												},
  3608  											},
  3609  											Type: "object",
  3610  										},
  3611  									},
  3612  									Type: "object",
  3613  								},
  3614  							},
  3615  						},
  3616  					},
  3617  				},
  3618  			},
  3619  		},
  3620  		{
  3621  			name: "nested reference field doesn't have 'kind' field when the " +
  3622  				"parent's parent is an array field",
  3623  			originalCRD: &apiextensions.CustomResourceDefinition{
  3624  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3625  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3626  						{
  3627  							Name: "v1beta1",
  3628  							Schema: &apiextensions.CustomResourceValidation{
  3629  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3630  									Properties: map[string]apiextensions.JSONSchemaProps{
  3631  										"spec": {
  3632  											Properties: map[string]apiextensions.JSONSchemaProps{
  3633  												"topLevelArray": {
  3634  													Items: &apiextensions.JSONSchemaPropsOrArray{
  3635  														Schema: &apiextensions.JSONSchemaProps{
  3636  															Properties: map[string]apiextensions.JSONSchemaProps{
  3637  																"secondLevelObject": {
  3638  																	Properties: map[string]apiextensions.JSONSchemaProps{
  3639  																		"nestedRef": {
  3640  																			Properties: map[string]apiextensions.JSONSchemaProps{
  3641  																				"external": {Description: "Test description"},
  3642  																			},
  3643  																			Description: "reference schema with no 'kind' field",
  3644  																			Type:        "object",
  3645  																		},
  3646  																		"otherField": {Type: "string"},
  3647  																	},
  3648  																	Required: []string{"otherField"},
  3649  																	Type:     "object",
  3650  																},
  3651  																"secondLevelString": {Type: "string"},
  3652  															},
  3653  															Type: "object",
  3654  														},
  3655  													},
  3656  													Type: "array",
  3657  												},
  3658  											},
  3659  											Type: "object",
  3660  										},
  3661  									},
  3662  									Type: "object",
  3663  								},
  3664  							},
  3665  						},
  3666  					},
  3667  				},
  3668  			},
  3669  			parentPath:         []string{"topLevelArray", "secondLevelObject"},
  3670  			referenceFieldName: "nestedRef",
  3671  			supportedKinds:     []string{"ReferenceKind"},
  3672  			expectedCRD: &apiextensions.CustomResourceDefinition{
  3673  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3674  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3675  						{
  3676  							Name: "v1beta1",
  3677  							Schema: &apiextensions.CustomResourceValidation{
  3678  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3679  									Properties: map[string]apiextensions.JSONSchemaProps{
  3680  										"spec": {
  3681  											Properties: map[string]apiextensions.JSONSchemaProps{
  3682  												"topLevelArray": {
  3683  													Items: &apiextensions.JSONSchemaPropsOrArray{
  3684  														Schema: &apiextensions.JSONSchemaProps{
  3685  															Properties: map[string]apiextensions.JSONSchemaProps{
  3686  																"secondLevelObject": {
  3687  																	Properties: map[string]apiextensions.JSONSchemaProps{
  3688  																		"nestedRef": {
  3689  																			Properties: map[string]apiextensions.JSONSchemaProps{
  3690  																				"name": {
  3691  																					Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
  3692  																					Type:        "string",
  3693  																				},
  3694  																				"namespace": {
  3695  																					Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
  3696  																					Type:        "string",
  3697  																				},
  3698  																				"kind": {
  3699  																					Description: "Kind of the referent. Allowed values: ReferenceKind",
  3700  																					Type:        "string",
  3701  																				},
  3702  																				"external": {
  3703  																					Description: "Test description",
  3704  																					Type:        "string",
  3705  																				},
  3706  																			},
  3707  																			OneOf: []apiextensions.JSONSchemaProps{
  3708  																				{
  3709  																					Required: []string{"name", "kind"},
  3710  																					Not: &apiextensions.JSONSchemaProps{
  3711  																						Required: []string{"external"},
  3712  																					},
  3713  																				},
  3714  																				{
  3715  																					Required: []string{"external"},
  3716  																					Not: &apiextensions.JSONSchemaProps{
  3717  																						AnyOf: []apiextensions.JSONSchemaProps{
  3718  																							{Required: []string{"name"}},
  3719  																							{Required: []string{"namespace"}},
  3720  																							{Required: []string{"kind"}},
  3721  																						},
  3722  																					},
  3723  																				},
  3724  																			},
  3725  																			Type: "object",
  3726  																		},
  3727  																		"otherField": {Type: "string"},
  3728  																	},
  3729  																	Required: []string{"otherField"},
  3730  																	Type:     "object",
  3731  																},
  3732  																"secondLevelString": {Type: "string"},
  3733  															},
  3734  															Type: "object",
  3735  														},
  3736  													},
  3737  													Type: "array",
  3738  												},
  3739  											},
  3740  											Type: "object",
  3741  										},
  3742  									},
  3743  									Type: "object",
  3744  								},
  3745  							},
  3746  						},
  3747  					},
  3748  				},
  3749  			},
  3750  		},
  3751  		{
  3752  			name: "nested reference array field doesn't have 'kind' field " +
  3753  				"when the parent is also an array field",
  3754  			originalCRD: &apiextensions.CustomResourceDefinition{
  3755  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3756  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3757  						{
  3758  							Name: "v1beta1",
  3759  							Schema: &apiextensions.CustomResourceValidation{
  3760  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3761  									Properties: map[string]apiextensions.JSONSchemaProps{
  3762  										"spec": {
  3763  											Properties: map[string]apiextensions.JSONSchemaProps{
  3764  												"topLevelArray": {
  3765  													Items: &apiextensions.JSONSchemaPropsOrArray{
  3766  														Schema: &apiextensions.JSONSchemaProps{
  3767  															Properties: map[string]apiextensions.JSONSchemaProps{
  3768  																"nestedArrayRefs": {
  3769  																	Items: &apiextensions.JSONSchemaPropsOrArray{
  3770  																		Schema: &apiextensions.JSONSchemaProps{
  3771  																			Properties: map[string]apiextensions.JSONSchemaProps{
  3772  																				"external": {Description: "Test description"},
  3773  																			},
  3774  																			Description: "reference schema with no 'kind' field",
  3775  																			Type:        "object",
  3776  																		},
  3777  																	},
  3778  																	Type: "array",
  3779  																},
  3780  															},
  3781  															Type: "object",
  3782  														},
  3783  													},
  3784  													Type: "array",
  3785  												},
  3786  											},
  3787  											Type: "object",
  3788  										},
  3789  									},
  3790  									Type: "object",
  3791  								},
  3792  							},
  3793  						},
  3794  					},
  3795  				},
  3796  			},
  3797  			parentPath:         []string{"topLevelArray"},
  3798  			referenceFieldName: "nestedArrayRefs",
  3799  			supportedKinds:     []string{"ReferenceKind"},
  3800  			expectedCRD: &apiextensions.CustomResourceDefinition{
  3801  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3802  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3803  						{
  3804  							Name: "v1beta1",
  3805  							Schema: &apiextensions.CustomResourceValidation{
  3806  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3807  									Properties: map[string]apiextensions.JSONSchemaProps{
  3808  										"spec": {
  3809  											Properties: map[string]apiextensions.JSONSchemaProps{
  3810  												"topLevelArray": {
  3811  													Items: &apiextensions.JSONSchemaPropsOrArray{
  3812  														Schema: &apiextensions.JSONSchemaProps{
  3813  															Properties: map[string]apiextensions.JSONSchemaProps{
  3814  																"nestedArrayRefs": {
  3815  																	Items: &apiextensions.JSONSchemaPropsOrArray{
  3816  																		Schema: &apiextensions.JSONSchemaProps{
  3817  																			Properties: map[string]apiextensions.JSONSchemaProps{
  3818  																				"name": {
  3819  																					Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
  3820  																					Type:        "string",
  3821  																				},
  3822  																				"namespace": {
  3823  																					Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/",
  3824  																					Type:        "string",
  3825  																				},
  3826  																				"kind": {
  3827  																					Description: "Kind of the referent. Allowed values: ReferenceKind",
  3828  																					Type:        "string",
  3829  																				},
  3830  																				"external": {
  3831  																					Description: "Test description",
  3832  																					Type:        "string",
  3833  																				},
  3834  																			},
  3835  																			OneOf: []apiextensions.JSONSchemaProps{
  3836  																				{
  3837  																					Required: []string{"name", "kind"},
  3838  																					Not: &apiextensions.JSONSchemaProps{
  3839  																						Required: []string{"external"},
  3840  																					},
  3841  																				},
  3842  																				{
  3843  																					Required: []string{"external"},
  3844  																					Not: &apiextensions.JSONSchemaProps{
  3845  																						AnyOf: []apiextensions.JSONSchemaProps{
  3846  																							{Required: []string{"name"}},
  3847  																							{Required: []string{"namespace"}},
  3848  																							{Required: []string{"kind"}},
  3849  																						},
  3850  																					},
  3851  																				},
  3852  																			},
  3853  																			Type: "object",
  3854  																		},
  3855  																	},
  3856  																	Type: "array",
  3857  																},
  3858  															},
  3859  															Type: "object",
  3860  														},
  3861  													},
  3862  													Type: "array",
  3863  												},
  3864  											},
  3865  											Type: "object",
  3866  										},
  3867  									},
  3868  									Type: "object",
  3869  								},
  3870  							},
  3871  						},
  3872  					},
  3873  				},
  3874  			},
  3875  		},
  3876  		{
  3877  			name:               "error to empty reference field name",
  3878  			originalCRD:        crdWithOptionalReferenceField,
  3879  			parentPath:         []string{},
  3880  			referenceFieldName: "",
  3881  			hasError:           true,
  3882  		},
  3883  		{
  3884  			name:               "error due to incorrect reference field name",
  3885  			originalCRD:        crdWithOptionalReferenceField,
  3886  			parentPath:         []string{},
  3887  			referenceFieldName: "wrongRef",
  3888  			hasError:           true,
  3889  		},
  3890  		{
  3891  			name: "error due to incorrect reference field type",
  3892  			originalCRD: &apiextensions.CustomResourceDefinition{
  3893  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3894  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3895  						{
  3896  							Name: "v1beta1",
  3897  							Schema: &apiextensions.CustomResourceValidation{
  3898  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3899  									Properties: map[string]apiextensions.JSONSchemaProps{
  3900  										"spec": {
  3901  											Properties: map[string]apiextensions.JSONSchemaProps{
  3902  												"testRef": {
  3903  													Description: "reference schema of wrong type",
  3904  													Type:        "string",
  3905  												},
  3906  											},
  3907  											Type: "object",
  3908  										},
  3909  									},
  3910  									Type: "object",
  3911  								},
  3912  							},
  3913  						},
  3914  					},
  3915  				},
  3916  			},
  3917  			parentPath:         []string{},
  3918  			referenceFieldName: "testRef",
  3919  			hasError:           true,
  3920  		},
  3921  		{
  3922  			name: "non-reference field can't be added because the 'external' " +
  3923  				"field is missing when the 'kind' field doesn't exist",
  3924  			originalCRD: &apiextensions.CustomResourceDefinition{
  3925  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3926  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3927  						{
  3928  							Name: "v1beta1",
  3929  							Schema: &apiextensions.CustomResourceValidation{
  3930  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3931  									Properties: map[string]apiextensions.JSONSchemaProps{
  3932  										"spec": {
  3933  											Properties: map[string]apiextensions.JSONSchemaProps{
  3934  												"testRef": {
  3935  													Description: "reference schema with no 'kind' and 'external' fields",
  3936  													Type:        "object",
  3937  												},
  3938  											},
  3939  											Type: "object",
  3940  										},
  3941  									},
  3942  									Type: "object",
  3943  								},
  3944  							},
  3945  						},
  3946  					},
  3947  				},
  3948  			},
  3949  			parentPath:         []string{},
  3950  			referenceFieldName: "testRef",
  3951  			hasError:           true,
  3952  		},
  3953  		{
  3954  			name: "non-reference field can't be added because supportedKinds " +
  3955  				"param is unset when the 'kind' field doesn't exist",
  3956  			originalCRD: &apiextensions.CustomResourceDefinition{
  3957  				Spec: apiextensions.CustomResourceDefinitionSpec{
  3958  					Versions: []apiextensions.CustomResourceDefinitionVersion{
  3959  						{
  3960  							Name: "v1beta1",
  3961  							Schema: &apiextensions.CustomResourceValidation{
  3962  								OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
  3963  									Properties: map[string]apiextensions.JSONSchemaProps{
  3964  										"spec": {
  3965  											Properties: map[string]apiextensions.JSONSchemaProps{
  3966  												"testRef": {
  3967  													Properties: map[string]apiextensions.JSONSchemaProps{
  3968  														"external": {Description: "Test description"},
  3969  													},
  3970  													Description: "reference schema with no 'kind' field",
  3971  													Type:        "object",
  3972  												},
  3973  											},
  3974  											Type: "object",
  3975  										},
  3976  									},
  3977  									Type: "object",
  3978  								},
  3979  							},
  3980  						},
  3981  					},
  3982  				},
  3983  			},
  3984  			parentPath:         []string{},
  3985  			referenceFieldName: "testRef",
  3986  			hasError:           true,
  3987  		},
  3988  	}
  3989  
  3990  	for _, tc := range tests {
  3991  		tc := tc
  3992  		t.Run(tc.name, func(t *testing.T) {
  3993  			err := EnsureReferenceFieldIsMultiKind(tc.originalCRD, tc.parentPath, tc.referenceFieldName, tc.supportedKinds)
  3994  			if err != nil {
  3995  				if !tc.hasError {
  3996  					t.Fatalf("error ensuring the reference field supports multi-kind: got an error, but want no error: %v", err)
  3997  				}
  3998  				return
  3999  			}
  4000  			if !reflect.DeepEqual(tc.expectedCRD, tc.originalCRD) {
  4001  				t.Fatalf("unexpected diff in CRD after ensuring the reference field supports multi-kind (-want +got): \n%v", cmp.Diff(tc.expectedCRD, tc.originalCRD))
  4002  			}
  4003  		})
  4004  	}
  4005  }
  4006  
  4007  func TestRemovePrefixFromStringFieldInSpec(t *testing.T) {
  4008  	t.Parallel()
  4009  	prefixToRemove := "prefix/"
  4010  	tests := []struct {
  4011  		name      string
  4012  		original  *k8s.Resource
  4013  		fieldPath []string
  4014  		expected  *k8s.Resource
  4015  		expectErr bool
  4016  	}{
  4017  		{
  4018  			name: "field exists, is string, has matching prefix",
  4019  			original: &k8s.Resource{
  4020  				TypeMeta: v1.TypeMeta{
  4021  					Kind: fooKind,
  4022  				},
  4023  				Spec: map[string]interface{}{
  4024  					"field1": prefixToRemove + "value1",
  4025  					"field2": prefixToRemove + "value2",
  4026  				},
  4027  			},
  4028  			fieldPath: []string{"field1"},
  4029  			expected: &k8s.Resource{
  4030  				TypeMeta: v1.TypeMeta{
  4031  					Kind: fooKind,
  4032  				},
  4033  				Spec: map[string]interface{}{
  4034  					"field1": "value1",
  4035  					"field2": prefixToRemove + "value2",
  4036  				},
  4037  			},
  4038  		},
  4039  		{
  4040  			name: "nested field exists, is string, has matching prefix",
  4041  			original: &k8s.Resource{
  4042  				TypeMeta: v1.TypeMeta{
  4043  					Kind: fooKind,
  4044  				},
  4045  				Spec: map[string]interface{}{
  4046  					"field": prefixToRemove + "value",
  4047  					"topField": map[string]interface{}{
  4048  						"field": prefixToRemove + "value",
  4049  					},
  4050  				},
  4051  			},
  4052  			fieldPath: []string{"topField", "field"},
  4053  			expected: &k8s.Resource{
  4054  				TypeMeta: v1.TypeMeta{
  4055  					Kind: fooKind,
  4056  				},
  4057  				Spec: map[string]interface{}{
  4058  					"field": prefixToRemove + "value",
  4059  					"topField": map[string]interface{}{
  4060  						"field": "value",
  4061  					},
  4062  				},
  4063  			},
  4064  		},
  4065  		{
  4066  			name: "field does not exist",
  4067  			original: &k8s.Resource{
  4068  				TypeMeta: v1.TypeMeta{
  4069  					Kind: fooKind,
  4070  				},
  4071  				Spec: map[string]interface{}{
  4072  					"field1": "value1",
  4073  				},
  4074  			},
  4075  			fieldPath: []string{"field2"},
  4076  			expected: &k8s.Resource{
  4077  				TypeMeta: v1.TypeMeta{
  4078  					Kind: fooKind,
  4079  				},
  4080  				Spec: map[string]interface{}{
  4081  					"field1": "value1",
  4082  				},
  4083  			},
  4084  		},
  4085  		{
  4086  			name: "field exits, is not a string",
  4087  			original: &k8s.Resource{
  4088  				TypeMeta: v1.TypeMeta{
  4089  					Kind: fooKind,
  4090  				},
  4091  				Spec: map[string]interface{}{
  4092  					"field1": true,
  4093  				},
  4094  			},
  4095  			fieldPath: []string{"field1"},
  4096  			expectErr: true,
  4097  		},
  4098  		{
  4099  			name: "field exists, prefix does not match",
  4100  			original: &k8s.Resource{
  4101  				TypeMeta: v1.TypeMeta{
  4102  					Kind: fooKind,
  4103  				},
  4104  				Spec: map[string]interface{}{
  4105  					"field1": "prefix1/value1",
  4106  				},
  4107  			},
  4108  			fieldPath: []string{"field1"},
  4109  			expected: &k8s.Resource{
  4110  				TypeMeta: v1.TypeMeta{
  4111  					Kind: fooKind,
  4112  				},
  4113  				Spec: map[string]interface{}{
  4114  					"field1": "prefix1/value1",
  4115  				},
  4116  			},
  4117  		},
  4118  	}
  4119  
  4120  	for _, tc := range tests {
  4121  		t.Run(tc.name, func(t *testing.T) {
  4122  			err := RemovePrefixFromStringFieldInSpec(tc.original, prefixToRemove, tc.fieldPath...)
  4123  			if tc.expectErr {
  4124  				if err == nil {
  4125  					t.Fatalf("got nil, but expect to have error")
  4126  				}
  4127  				return
  4128  			}
  4129  			if err != nil {
  4130  				t.Fatalf("unexpected error: %v", err)
  4131  			}
  4132  			if !reflect.DeepEqual(tc.original, tc.expected) {
  4133  				t.Fatalf("unexpected diff (-want +got): \n%v", cmp.Diff(tc.expected, tc.original))
  4134  			}
  4135  		})
  4136  	}
  4137  }
  4138  

View as plain text