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  }