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 }