github.com/crossplane/upjet@v1.3.0/pkg/config/common_test.go (about) 1 // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io> 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package config 6 7 import ( 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/google/go-cmp/cmp/cmpopts" 12 fwresource "github.com/hashicorp/terraform-plugin-framework/resource" 13 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 14 15 "github.com/crossplane/upjet/pkg/registry" 16 ) 17 18 func TestDefaultResource(t *testing.T) { 19 type args struct { 20 name string 21 sch *schema.Resource 22 frameworkResource fwresource.Resource 23 reg *registry.Resource 24 opts []ResourceOption 25 } 26 27 cases := map[string]struct { 28 reason string 29 args args 30 want *Resource 31 }{ 32 "ThreeSectionsName": { 33 reason: "It should return GVK properly for names with three sections", 34 args: args{ 35 name: "aws_ec2_instance", 36 }, 37 want: &Resource{ 38 Name: "aws_ec2_instance", 39 ShortGroup: "ec2", 40 Kind: "Instance", 41 Version: "v1alpha1", 42 ExternalName: NameAsIdentifier, 43 References: map[string]Reference{}, 44 Sensitive: NopSensitive, 45 UseAsync: true, 46 SchemaElementOptions: SchemaElementOptions{}, 47 ServerSideApplyMergeStrategies: ServerSideApplyMergeStrategies{}, 48 }, 49 }, 50 "TwoSectionsName": { 51 reason: "It should return GVK properly for names with three sections", 52 args: args{ 53 name: "aws_instance", 54 }, 55 want: &Resource{ 56 Name: "aws_instance", 57 ShortGroup: "aws", 58 Kind: "Instance", 59 Version: "v1alpha1", 60 ExternalName: NameAsIdentifier, 61 References: map[string]Reference{}, 62 Sensitive: NopSensitive, 63 UseAsync: true, 64 SchemaElementOptions: SchemaElementOptions{}, 65 ServerSideApplyMergeStrategies: ServerSideApplyMergeStrategies{}, 66 }, 67 }, 68 "NameWithPrefixAcronym": { 69 reason: "It should return prefix acronym in capital case", 70 args: args{ 71 name: "aws_db_sql_server", 72 }, 73 want: &Resource{ 74 Name: "aws_db_sql_server", 75 ShortGroup: "db", 76 Kind: "SQLServer", 77 Version: "v1alpha1", 78 ExternalName: NameAsIdentifier, 79 References: map[string]Reference{}, 80 Sensitive: NopSensitive, 81 UseAsync: true, 82 SchemaElementOptions: SchemaElementOptions{}, 83 ServerSideApplyMergeStrategies: ServerSideApplyMergeStrategies{}, 84 }, 85 }, 86 "NameWithSuffixAcronym": { 87 reason: "It should return suffix acronym in capital case", 88 args: args{ 89 name: "aws_db_server_id", 90 }, 91 want: &Resource{ 92 Name: "aws_db_server_id", 93 ShortGroup: "db", 94 Kind: "ServerID", 95 Version: "v1alpha1", 96 ExternalName: NameAsIdentifier, 97 References: map[string]Reference{}, 98 Sensitive: NopSensitive, 99 UseAsync: true, 100 SchemaElementOptions: SchemaElementOptions{}, 101 ServerSideApplyMergeStrategies: ServerSideApplyMergeStrategies{}, 102 }, 103 }, 104 "NameWithMultipleAcronyms": { 105 reason: "It should return both prefix & suffix acronyms in capital case", 106 args: args{ 107 name: "aws_db_sql_server_id", 108 }, 109 want: &Resource{ 110 Name: "aws_db_sql_server_id", 111 ShortGroup: "db", 112 Kind: "SQLServerID", 113 Version: "v1alpha1", 114 ExternalName: NameAsIdentifier, 115 References: map[string]Reference{}, 116 Sensitive: NopSensitive, 117 UseAsync: true, 118 SchemaElementOptions: SchemaElementOptions{}, 119 ServerSideApplyMergeStrategies: ServerSideApplyMergeStrategies{}, 120 }, 121 }, 122 } 123 124 // TODO(muvaf): Find a way to compare function pointers. 125 ignoreUnexported := []cmp.Option{ 126 cmpopts.IgnoreFields(Sensitive{}, "fieldPaths", "AdditionalConnectionDetailsFn"), 127 cmpopts.IgnoreFields(LateInitializer{}, "ignoredCanonicalFieldPaths"), 128 cmpopts.IgnoreFields(ExternalName{}, "SetIdentifierArgumentFn", "GetExternalNameFn", "GetIDFn"), 129 cmpopts.IgnoreFields(Resource{}, "useTerraformPluginSDKClient"), 130 cmpopts.IgnoreFields(Resource{}, "useTerraformPluginFrameworkClient"), 131 cmpopts.IgnoreFields(Resource{}, "requiredFields"), 132 } 133 134 for name, tc := range cases { 135 t.Run(name, func(t *testing.T) { 136 r := DefaultResource(tc.args.name, tc.args.sch, tc.args.frameworkResource, tc.args.reg, tc.args.opts...) 137 if diff := cmp.Diff(tc.want, r, ignoreUnexported...); diff != "" { 138 t.Errorf("\n%s\nDefaultResource(...): -want, +got:\n%s", tc.reason, diff) 139 } 140 }) 141 } 142 } 143 144 func TestMoveToStatus(t *testing.T) { 145 type args struct { 146 sch *schema.Resource 147 fields []string 148 } 149 type want struct { 150 sch *schema.Resource 151 } 152 153 cases := map[string]struct { 154 reason string 155 args 156 want 157 }{ 158 "DoesNotExist": { 159 args: args{ 160 fields: []string{"topD"}, 161 sch: &schema.Resource{ 162 Schema: map[string]*schema.Schema{ 163 "topA": {Type: schema.TypeString}, 164 "topB": {Type: schema.TypeInt}, 165 "topC": {Type: schema.TypeString, Optional: true}, 166 }, 167 }, 168 }, 169 want: want{ 170 sch: &schema.Resource{ 171 Schema: map[string]*schema.Schema{ 172 "topA": {Type: schema.TypeString}, 173 "topB": {Type: schema.TypeInt}, 174 "topC": {Type: schema.TypeString, Optional: true}, 175 }, 176 }, 177 }, 178 }, 179 "TopLevelBasicFields": { 180 args: args{ 181 fields: []string{"topA", "topB"}, 182 sch: &schema.Resource{ 183 Schema: map[string]*schema.Schema{ 184 "topA": {Type: schema.TypeString}, 185 "topB": {Type: schema.TypeInt}, 186 "topC": {Type: schema.TypeString, Optional: true}, 187 }, 188 }, 189 }, 190 want: want{ 191 sch: &schema.Resource{ 192 Schema: map[string]*schema.Schema{ 193 "topA": { 194 Type: schema.TypeString, 195 Optional: false, 196 Computed: true, 197 }, 198 "topB": { 199 Type: schema.TypeInt, 200 Optional: false, 201 Computed: true, 202 }, 203 "topC": { 204 Type: schema.TypeString, 205 Optional: true, 206 Computed: false, 207 }, 208 }, 209 }, 210 }, 211 }, 212 "ComplexFields": { 213 args: args{ 214 fields: []string{"topA"}, 215 sch: &schema.Resource{ 216 Schema: map[string]*schema.Schema{ 217 "topA": { 218 Type: schema.TypeMap, 219 Elem: &schema.Resource{ 220 Schema: map[string]*schema.Schema{ 221 "leafA": { 222 Type: schema.TypeMap, 223 Elem: &schema.Resource{ 224 Schema: map[string]*schema.Schema{ 225 "leafB": { 226 Type: schema.TypeString, 227 Computed: false, 228 Optional: true, 229 }, 230 "leafC": { 231 Type: schema.TypeString, 232 Computed: false, 233 Optional: true, 234 }, 235 }, 236 }, 237 }, 238 }, 239 }, 240 }, 241 "topB": {Type: schema.TypeString}, 242 }, 243 }, 244 }, 245 want: want{ 246 sch: &schema.Resource{ 247 Schema: map[string]*schema.Schema{ 248 "topA": { 249 Type: schema.TypeMap, 250 Computed: true, 251 Optional: false, 252 Elem: &schema.Resource{ 253 Schema: map[string]*schema.Schema{ 254 "leafA": { 255 Type: schema.TypeMap, 256 Computed: true, 257 Optional: false, 258 Elem: &schema.Resource{ 259 Schema: map[string]*schema.Schema{ 260 "leafB": { 261 Type: schema.TypeString, 262 Computed: true, 263 Optional: false, 264 }, 265 "leafC": { 266 Type: schema.TypeString, 267 Computed: true, 268 Optional: false, 269 }, 270 }, 271 }, 272 }, 273 }, 274 }, 275 }, 276 "topB": {Type: schema.TypeString}, 277 }, 278 }, 279 }, 280 }, 281 } 282 283 for name, tc := range cases { 284 t.Run(name, func(t *testing.T) { 285 MoveToStatus(tc.args.sch, tc.args.fields...) 286 if diff := cmp.Diff(tc.want.sch, tc.args.sch); diff != "" { 287 t.Errorf("\n%s\nMoveToStatus(...): -want, +got:\n%s", tc.reason, diff) 288 } 289 }) 290 } 291 } 292 293 func TestMarkAsRequired(t *testing.T) { 294 type args struct { 295 sch *schema.Resource 296 fields []string 297 } 298 type want struct { 299 sch *schema.Resource 300 } 301 302 cases := map[string]struct { 303 reason string 304 args 305 want 306 }{ 307 "DoesNotExist": { 308 args: args{ 309 fields: []string{"topD"}, 310 sch: &schema.Resource{ 311 Schema: map[string]*schema.Schema{ 312 "topA": {Type: schema.TypeString}, 313 "topB": {Type: schema.TypeInt, Computed: true}, 314 "topC": {Type: schema.TypeString, Optional: true}, 315 }, 316 }, 317 }, 318 want: want{ 319 sch: &schema.Resource{ 320 Schema: map[string]*schema.Schema{ 321 "topA": {Type: schema.TypeString}, 322 "topB": {Type: schema.TypeInt, Computed: true}, 323 "topC": {Type: schema.TypeString, Optional: true}, 324 }, 325 }, 326 }, 327 }, 328 "TopLevelBasicFields": { 329 args: args{ 330 fields: []string{"topB", "topC"}, 331 sch: &schema.Resource{ 332 Schema: map[string]*schema.Schema{ 333 "topA": {Type: schema.TypeString}, 334 "topB": {Type: schema.TypeInt, Computed: true}, 335 "topC": {Type: schema.TypeString, Optional: true}, 336 }, 337 }, 338 }, 339 want: want{ 340 sch: &schema.Resource{ 341 Schema: map[string]*schema.Schema{ 342 "topA": {Type: schema.TypeString}, 343 "topB": { 344 Type: schema.TypeInt, 345 Optional: false, 346 Computed: false, 347 }, 348 "topC": { 349 Type: schema.TypeString, 350 Optional: false, 351 Computed: false, 352 }, 353 }, 354 }, 355 }, 356 }, 357 "ComplexFields": { 358 args: args{ 359 fields: []string{"topA.leafA", "topA.leafA.leafC"}, 360 sch: &schema.Resource{ 361 Schema: map[string]*schema.Schema{ 362 "topA": { 363 Type: schema.TypeMap, 364 Elem: &schema.Resource{ 365 Schema: map[string]*schema.Schema{ 366 "leafA": { 367 Type: schema.TypeMap, 368 Elem: &schema.Resource{ 369 Schema: map[string]*schema.Schema{ 370 "leafB": {Type: schema.TypeString}, 371 "leafC": {Type: schema.TypeString}, 372 }, 373 }, 374 }, 375 }, 376 }, 377 }, 378 "topB": {Type: schema.TypeString}, 379 }, 380 }, 381 }, 382 want: want{ 383 sch: &schema.Resource{ 384 Schema: map[string]*schema.Schema{ 385 "topA": { 386 Type: schema.TypeMap, 387 Elem: &schema.Resource{ 388 Schema: map[string]*schema.Schema{ 389 "leafA": { 390 Type: schema.TypeMap, 391 Computed: false, 392 Optional: false, 393 Elem: &schema.Resource{ 394 Schema: map[string]*schema.Schema{ 395 "leafB": {Type: schema.TypeString}, 396 "leafC": { 397 Type: schema.TypeString, 398 Computed: false, 399 Optional: false, 400 }, 401 }, 402 }, 403 }, 404 }, 405 }, 406 }, 407 "topB": {Type: schema.TypeString}, 408 }, 409 }, 410 }, 411 }, 412 } 413 414 for name, tc := range cases { 415 t.Run(name, func(t *testing.T) { 416 MarkAsRequired(tc.args.sch, tc.args.fields...) 417 if diff := cmp.Diff(tc.want.sch, tc.args.sch); diff != "" { 418 t.Errorf("\n%s\nMarkAsRequired(...): -want, +got:\n%s", tc.reason, diff) 419 } 420 }) 421 } 422 } 423 424 func TestGetSchema(t *testing.T) { 425 type args struct { 426 sch *schema.Resource 427 fieldpath string 428 } 429 type want struct { 430 sch *schema.Schema 431 } 432 schLeaf := &schema.Schema{ 433 Type: schema.TypeString, 434 } 435 schA := &schema.Schema{ 436 Type: schema.TypeMap, 437 Elem: &schema.Resource{ 438 Schema: map[string]*schema.Schema{ 439 "fieldA": schLeaf, 440 }, 441 }, 442 } 443 res := &schema.Resource{ 444 Schema: map[string]*schema.Schema{ 445 "topA": schA, 446 }, 447 } 448 cases := map[string]struct { 449 reason string 450 args 451 want 452 }{ 453 "TopLevelField": { 454 args: args{ 455 fieldpath: "topA", 456 sch: res, 457 }, 458 want: want{ 459 sch: schA, 460 }, 461 }, 462 "LeafField": { 463 args: args{ 464 fieldpath: "topA.fieldA", 465 sch: res, 466 }, 467 want: want{ 468 sch: schLeaf, 469 }, 470 }, 471 "TopLevelFieldNotFound": { 472 args: args{ 473 fieldpath: "topB", 474 sch: res, 475 }, 476 want: want{ 477 sch: nil, 478 }, 479 }, 480 "LeafFieldNotFound": { 481 args: args{ 482 fieldpath: "topA.olala.omama", 483 sch: res, 484 }, 485 want: want{ 486 sch: nil, 487 }, 488 }, 489 "TopFieldIsNotMap": { 490 args: args{ 491 fieldpath: "topA.topB", 492 sch: &schema.Resource{ 493 Schema: map[string]*schema.Schema{ 494 "topA": {Type: schema.TypeString}, 495 }, 496 }, 497 }, 498 want: want{ 499 sch: nil, 500 }, 501 }, 502 "MiddleFieldIsNotResource": { 503 args: args{ 504 fieldpath: "topA.topB.topC", 505 sch: &schema.Resource{ 506 Schema: map[string]*schema.Schema{ 507 "topA": { 508 Elem: &schema.Resource{ 509 Schema: map[string]*schema.Schema{ 510 "topB": { 511 Elem: &schema.Schema{}, 512 }, 513 }, 514 }, 515 }, 516 }, 517 }, 518 }, 519 want: want{ 520 sch: nil, 521 }, 522 }, 523 } 524 525 for name, tc := range cases { 526 t.Run(name, func(t *testing.T) { 527 sch := GetSchema(tc.args.sch, tc.args.fieldpath) 528 if diff := cmp.Diff(tc.want.sch, sch); diff != "" { 529 t.Errorf("\n%s\nGetSchema(...): -want, +got:\n%s", tc.reason, diff) 530 } 531 }) 532 } 533 } 534 535 func TestManipulateAllFieldsInSchema(t *testing.T) { 536 type args struct { 537 sch *schema.Resource 538 op func(sch *schema.Schema) 539 } 540 type want struct { 541 sch *schema.Resource 542 } 543 544 cases := map[string]struct { 545 reason string 546 args 547 want 548 }{ 549 "SetEmptyDescription": { 550 args: args{ 551 sch: &schema.Resource{ 552 Schema: map[string]*schema.Schema{ 553 "topA": { 554 Description: "topADescription", 555 Type: schema.TypeMap, 556 Elem: &schema.Resource{ 557 Schema: map[string]*schema.Schema{ 558 "leafA": { 559 Description: "leafADescription", 560 Type: schema.TypeMap, 561 Elem: &schema.Resource{ 562 Schema: map[string]*schema.Schema{ 563 "leafB": { 564 Description: "", 565 Type: schema.TypeString, 566 }, 567 "leafC": { 568 Description: "leafCDescription", 569 Type: schema.TypeString, 570 }, 571 }, 572 }, 573 }, 574 }, 575 }, 576 }, 577 "topB": {Type: schema.TypeString}, 578 }, 579 }, 580 op: func(sch *schema.Schema) { 581 sch.Description = "" 582 }, 583 }, 584 want: want{ 585 sch: &schema.Resource{ 586 Schema: map[string]*schema.Schema{ 587 "topA": { 588 Description: "", 589 Type: schema.TypeMap, 590 Elem: &schema.Resource{ 591 Schema: map[string]*schema.Schema{ 592 "leafA": { 593 Description: "", 594 Type: schema.TypeMap, 595 Elem: &schema.Resource{ 596 Schema: map[string]*schema.Schema{ 597 "leafB": { 598 Description: "", 599 Type: schema.TypeString, 600 }, 601 "leafC": { 602 Description: "", 603 Type: schema.TypeString, 604 }, 605 }, 606 }, 607 }, 608 }, 609 }, 610 }, 611 "topB": {Type: schema.TypeString, Description: ""}, 612 }, 613 }, 614 }, 615 }, 616 } 617 618 for name, tc := range cases { 619 t.Run(name, func(t *testing.T) { 620 ManipulateEveryField(tc.args.sch, tc.args.op) 621 if diff := cmp.Diff(tc.want.sch, tc.args.sch); diff != "" { 622 t.Errorf("\n%s\nMoveToStatus(...): -want, +got:\n%s", tc.reason, diff) 623 } 624 }) 625 } 626 }