github.com/crossplane/upjet@v1.3.0/pkg/resource/lateinit_test.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package resource
     6  
     7  import (
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  )
    12  
    13  func TestLateInitialize(t *testing.T) {
    14  	type args struct {
    15  		desiredObject  any
    16  		observedObject any
    17  		opts           []GenericLateInitializerOption
    18  	}
    19  
    20  	testKeyDesiredField := "test-key-desiredField"
    21  	testStringDesiredField := "test-string-desiredField"
    22  	testKeyObservedField := "test-key-observedField"
    23  	testStringObservedField := "test-string-observedField"
    24  	testInt64ObservedField := 1
    25  	testStringEmpty := ""
    26  
    27  	type nestedStruct1 struct {
    28  		F1 *string
    29  		F2 []*string
    30  	}
    31  
    32  	type nestedStruct2 struct {
    33  		F1 *int
    34  		F2 []*int
    35  	}
    36  
    37  	type nestedStruct3 struct {
    38  		F1 *string
    39  		F2 *string
    40  	}
    41  
    42  	type nestedStruct4 struct {
    43  		F1 *string `json:"f_1,omitempty"`
    44  	}
    45  
    46  	type nestedStruct5 struct {
    47  		F1 [][]string
    48  	}
    49  
    50  	type nestedStruct6 struct {
    51  		F1 []string `json:"f_1,omitempty"`
    52  	}
    53  
    54  	type nestedStruct7 struct {
    55  		F1 map[string]string
    56  	}
    57  
    58  	type nestedStruct8 struct {
    59  		F1 map[string]*string
    60  	}
    61  
    62  	type nestedStruct9 struct {
    63  		F1 map[string][]string `json:"f_1,omitempty"`
    64  	}
    65  
    66  	type nestedStruct10 struct {
    67  		F1 *string
    68  		F2 *nestedStruct1
    69  	}
    70  
    71  	tests := map[string]struct {
    72  		args         args
    73  		wantModified bool
    74  		wantErr      bool
    75  		wantCRObject any
    76  	}{
    77  		"TypeMismatch": {
    78  			args: args{
    79  				desiredObject: &nestedStruct1{
    80  					F1: &testStringDesiredField,
    81  					F2: []*string{
    82  						&testStringDesiredField,
    83  					},
    84  				},
    85  				observedObject: &nestedStruct2{
    86  					F1: &testInt64ObservedField,
    87  				},
    88  			},
    89  			wantErr: true,
    90  		},
    91  		"NilCRObject": {
    92  			args: args{
    93  				observedObject: &struct{}{},
    94  			},
    95  		},
    96  		"NilResponseObject": {
    97  			args: args{
    98  				desiredObject: &struct{}{},
    99  			},
   100  			wantCRObject: &struct{}{},
   101  		},
   102  		"TestNonStructCRObject": {
   103  			args: args{
   104  				desiredObject:  &testStringDesiredField,
   105  				observedObject: &struct{}{},
   106  			},
   107  			wantErr: true,
   108  		},
   109  		"TestNonStructResponseObject": {
   110  			args: args{
   111  				desiredObject:  &struct{}{},
   112  				observedObject: &testStringObservedField,
   113  			},
   114  			wantErr: true,
   115  		},
   116  		"TestEmptyStructCRAndResponseObjects": {
   117  			args: args{
   118  				desiredObject:  &struct{}{},
   119  				observedObject: &struct{}{},
   120  			},
   121  			wantCRObject: &struct{}{},
   122  		},
   123  		"TestInitializedCRStringField": {
   124  			args: args{
   125  				desiredObject: &struct {
   126  					F1 *string
   127  				}{
   128  					F1: &testStringDesiredField,
   129  				},
   130  				observedObject: &struct {
   131  					F1 *string
   132  				}{
   133  					F1: &testStringObservedField,
   134  				},
   135  			},
   136  			wantModified: false,
   137  			wantCRObject: &struct {
   138  				F1 *string
   139  			}{
   140  				F1: &testStringDesiredField,
   141  			},
   142  		},
   143  		"TestUninitializedCRStringField": {
   144  			args: args{
   145  				desiredObject: &struct {
   146  					F1 *string
   147  				}{
   148  					F1: nil,
   149  				},
   150  				observedObject: &struct {
   151  					F1 *string
   152  				}{
   153  					F1: &testStringObservedField,
   154  				},
   155  			},
   156  			wantModified: true,
   157  			wantCRObject: &struct {
   158  				F1 *string
   159  			}{
   160  				F1: &testStringObservedField,
   161  			},
   162  		},
   163  		"TestInitializedCRNestedFields": {
   164  			args: args{
   165  				desiredObject: &struct {
   166  					C1 *nestedStruct1
   167  				}{
   168  					C1: &nestedStruct1{
   169  						F1: &testStringDesiredField,
   170  						F2: []*string{
   171  							&testStringDesiredField,
   172  						},
   173  					},
   174  				},
   175  				observedObject: &struct {
   176  					C1 *nestedStruct1
   177  				}{
   178  					C1: &nestedStruct1{
   179  						F1: &testStringObservedField,
   180  						F2: []*string{
   181  							&testStringObservedField,
   182  						},
   183  					},
   184  				},
   185  			},
   186  			wantModified: false,
   187  			wantCRObject: &struct {
   188  				C1 *nestedStruct1
   189  			}{
   190  				C1: &nestedStruct1{
   191  					F1: &testStringDesiredField,
   192  					F2: []*string{
   193  						&testStringDesiredField,
   194  					},
   195  				},
   196  			},
   197  		},
   198  		"TestUninitializedCRNestedFields": {
   199  			args: args{
   200  				desiredObject: &struct {
   201  					C1 *nestedStruct1
   202  				}{},
   203  				observedObject: &struct {
   204  					C1 *nestedStruct1
   205  				}{
   206  					C1: &nestedStruct1{
   207  						F1: &testStringObservedField,
   208  						F2: []*string{
   209  							&testStringObservedField,
   210  						},
   211  					},
   212  				},
   213  			},
   214  			wantModified: true,
   215  			wantCRObject: &struct {
   216  				C1 *nestedStruct1
   217  			}{
   218  				C1: &nestedStruct1{
   219  					F1: &testStringObservedField,
   220  					F2: []*string{
   221  						&testStringObservedField,
   222  					},
   223  				},
   224  			},
   225  		},
   226  		"TestNilObservedNestedFields": {
   227  			args: args{
   228  				desiredObject: &struct {
   229  					C1 *nestedStruct10
   230  				}{},
   231  				observedObject: &struct {
   232  					C1 *nestedStruct10
   233  				}{
   234  					C1: &nestedStruct10{
   235  						F1: &testStringDesiredField,
   236  					},
   237  				},
   238  			},
   239  			wantModified: true,
   240  			wantCRObject: &struct {
   241  				C1 *nestedStruct10
   242  			}{
   243  				C1: &nestedStruct10{
   244  					F1: &testStringDesiredField,
   245  				},
   246  			},
   247  		},
   248  		"TestFieldKindMismatch": {
   249  			args: args{
   250  				desiredObject: &nestedStruct1{
   251  					F1: nil,
   252  				},
   253  				observedObject: &nestedStruct2{
   254  					F1: &testInt64ObservedField,
   255  				},
   256  			},
   257  			wantErr: true,
   258  		},
   259  		"TestNestedFieldKindMismatch": {
   260  			args: args{
   261  				desiredObject: &struct {
   262  					C1 *nestedStruct1
   263  				}{
   264  					C1: &nestedStruct1{
   265  						F1: nil,
   266  					},
   267  				},
   268  				observedObject: &struct {
   269  					C1 *nestedStruct2
   270  				}{
   271  					C1: &nestedStruct2{
   272  						F1: &testInt64ObservedField,
   273  					},
   274  				},
   275  			},
   276  			wantErr: true,
   277  		},
   278  		"TestSliceItemKindMismatch": {
   279  			args: args{
   280  				desiredObject: &nestedStruct1{},
   281  				observedObject: &nestedStruct3{
   282  					F1: &testStringObservedField,
   283  					F2: &testStringObservedField,
   284  				},
   285  			},
   286  			wantErr: true,
   287  		},
   288  		"TestInitializedSliceOfStringField": {
   289  			args: args{
   290  				desiredObject: &nestedStruct6{
   291  					F1: []string{
   292  						testStringDesiredField,
   293  					},
   294  				},
   295  				observedObject: &nestedStruct6{
   296  					F1: []string{
   297  						testStringObservedField,
   298  					},
   299  				},
   300  			},
   301  			wantModified: false,
   302  			wantCRObject: &nestedStruct6{
   303  				F1: []string{
   304  					testStringDesiredField,
   305  				},
   306  			},
   307  		},
   308  		"TestUninitializedSliceOfStringField": {
   309  			args: args{
   310  				desiredObject: &nestedStruct6{},
   311  				observedObject: &nestedStruct6{
   312  					F1: []string{
   313  						testStringObservedField,
   314  					},
   315  				},
   316  			},
   317  			wantModified: true,
   318  			wantCRObject: &nestedStruct6{
   319  				F1: []string{
   320  					testStringObservedField,
   321  				},
   322  			},
   323  		},
   324  		"TestInitializedSliceOfSliceField": {
   325  			args: args{
   326  				desiredObject: &nestedStruct5{
   327  					F1: [][]string{
   328  						{
   329  							testStringDesiredField,
   330  						},
   331  					},
   332  				},
   333  				observedObject: &nestedStruct5{
   334  					F1: [][]string{
   335  						{
   336  							testStringObservedField,
   337  						},
   338  					},
   339  				},
   340  			},
   341  			wantModified: false,
   342  			wantCRObject: &nestedStruct5{
   343  				F1: [][]string{
   344  					{
   345  						testStringDesiredField,
   346  					},
   347  				},
   348  			},
   349  		},
   350  		"TestInitializedMapOfStringField": {
   351  			args: args{
   352  				desiredObject: &nestedStruct7{
   353  					F1: map[string]string{
   354  						testKeyDesiredField: testStringDesiredField,
   355  					},
   356  				},
   357  				observedObject: &nestedStruct7{
   358  					F1: map[string]string{
   359  						testKeyObservedField: testStringObservedField,
   360  					},
   361  				},
   362  			},
   363  			wantModified: false,
   364  			wantCRObject: &nestedStruct7{
   365  				F1: map[string]string{
   366  					testKeyDesiredField: testStringDesiredField,
   367  				},
   368  			},
   369  		},
   370  		"TestUninitializedMapOfStringField": {
   371  			args: args{
   372  				desiredObject: &nestedStruct7{},
   373  				observedObject: &nestedStruct7{
   374  					F1: map[string]string{
   375  						testKeyObservedField: testStringObservedField,
   376  					},
   377  				},
   378  			},
   379  			wantModified: true,
   380  			wantCRObject: &nestedStruct7{
   381  				F1: map[string]string{
   382  					testKeyObservedField: testStringObservedField,
   383  				},
   384  			},
   385  		},
   386  		"TestInitializedMapOfPointerStringField": {
   387  			args: args{
   388  				desiredObject: &nestedStruct8{
   389  					F1: map[string]*string{
   390  						testKeyDesiredField: &testStringDesiredField,
   391  					},
   392  				},
   393  				observedObject: &nestedStruct8{
   394  					F1: map[string]*string{
   395  						testKeyObservedField: &testStringObservedField,
   396  					},
   397  				},
   398  			},
   399  			wantModified: false,
   400  			wantCRObject: &nestedStruct8{
   401  				F1: map[string]*string{
   402  					testKeyDesiredField: &testStringDesiredField,
   403  				},
   404  			},
   405  		},
   406  		"TestUninitializedMapOfPointerStringField": {
   407  			args: args{
   408  				desiredObject: &nestedStruct8{},
   409  				observedObject: &nestedStruct8{
   410  					F1: map[string]*string{
   411  						testKeyObservedField: &testStringObservedField,
   412  					},
   413  				},
   414  			},
   415  			wantModified: true,
   416  			wantCRObject: &nestedStruct8{
   417  				F1: map[string]*string{
   418  					testKeyObservedField: &testStringObservedField,
   419  				},
   420  			},
   421  		},
   422  		"TestInitializedMapOfStringSliceField": {
   423  			args: args{
   424  				desiredObject: &nestedStruct9{
   425  					F1: map[string][]string{
   426  						testKeyDesiredField: {testStringDesiredField},
   427  					},
   428  				},
   429  				observedObject: &nestedStruct9{
   430  					F1: map[string][]string{
   431  						testKeyObservedField: {testStringObservedField},
   432  					},
   433  				},
   434  			},
   435  			wantModified: false,
   436  			wantCRObject: &nestedStruct9{
   437  				F1: map[string][]string{
   438  					testKeyDesiredField: {testStringDesiredField},
   439  				},
   440  			},
   441  		},
   442  		"TestUninitializedMapOfStringSliceField": {
   443  			args: args{
   444  				desiredObject: &nestedStruct9{},
   445  				observedObject: &nestedStruct9{
   446  					F1: map[string][]string{
   447  						testKeyObservedField: {testStringObservedField},
   448  					},
   449  				},
   450  			},
   451  			wantModified: true,
   452  			wantCRObject: &nestedStruct9{
   453  				F1: map[string][]string{
   454  					testKeyObservedField: {testStringObservedField},
   455  				},
   456  			},
   457  		},
   458  		"TestInitializeWithZeroValues": {
   459  			args: args{
   460  				desiredObject: &nestedStruct4{},
   461  				observedObject: &nestedStruct4{
   462  					F1: &testStringEmpty,
   463  				},
   464  			},
   465  			wantModified: true,
   466  			wantCRObject: &nestedStruct4{
   467  				F1: &testStringEmpty,
   468  			},
   469  		},
   470  		"TestSkipZeroElem": {
   471  			args: args{
   472  				desiredObject: &nestedStruct4{},
   473  				observedObject: &nestedStruct4{
   474  					F1: &testStringEmpty,
   475  				},
   476  				opts: []GenericLateInitializerOption{WithZeroElemPtrFilter("F1")},
   477  			},
   478  			wantModified: false,
   479  			wantCRObject: &nestedStruct4{},
   480  		},
   481  		"TestSkipOmitemptyTaggedPtrElem": {
   482  			args: args{
   483  				desiredObject: &nestedStruct4{},
   484  				observedObject: &nestedStruct4{
   485  					F1: &testStringEmpty,
   486  				},
   487  				opts: []GenericLateInitializerOption{WithZeroValueJSONOmitEmptyFilter(CNameWildcard)},
   488  			},
   489  			wantModified: false,
   490  			wantCRObject: &nestedStruct4{},
   491  		},
   492  		"TestSkipOmitemptyTaggedSliceElem": {
   493  			args: args{
   494  				desiredObject: &nestedStruct6{},
   495  				observedObject: &nestedStruct6{
   496  					F1: []string{},
   497  				},
   498  				opts: []GenericLateInitializerOption{WithZeroValueJSONOmitEmptyFilter("F1")},
   499  			},
   500  			wantModified: false,
   501  			wantCRObject: &nestedStruct6{},
   502  		},
   503  		"TestSkipOmitemptyTaggedMapElem": {
   504  			args: args{
   505  				desiredObject: &nestedStruct9{},
   506  				observedObject: &nestedStruct9{
   507  					F1: map[string][]string{},
   508  				},
   509  				opts: []GenericLateInitializerOption{WithZeroValueJSONOmitEmptyFilter("F1")},
   510  			},
   511  			wantModified: false,
   512  			wantCRObject: &nestedStruct9{},
   513  		},
   514  	}
   515  
   516  	for name, tt := range tests {
   517  		t.Run(name, func(t *testing.T) {
   518  			li := NewGenericLateInitializer(tt.args.opts...)
   519  			got, err := li.LateInitialize(tt.args.desiredObject, tt.args.observedObject)
   520  
   521  			if (err != nil) != tt.wantErr {
   522  				t.Errorf("lateInitializeFromResponse() error = %v, wantErr %v", err, tt.wantErr)
   523  
   524  				return
   525  			}
   526  
   527  			if tt.wantErr {
   528  				return
   529  			}
   530  
   531  			if got != tt.wantModified {
   532  				t.Errorf("lateInitializeFromResponse() got = %v, want %v", got, tt.wantModified)
   533  			}
   534  
   535  			if diff := cmp.Diff(tt.wantCRObject, tt.args.desiredObject); diff != "" {
   536  				t.Errorf("lateInitializeFromResponse(...): -want, +got:\n%s", diff)
   537  			}
   538  		})
   539  	}
   540  }