github.com/opentofu/opentofu@v1.7.1/internal/plugin6/convert/schema_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package convert
     7  
     8  import (
     9  	"testing"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/google/go-cmp/cmp/cmpopts"
    13  	"github.com/opentofu/opentofu/internal/configs/configschema"
    14  	proto "github.com/opentofu/opentofu/internal/tfplugin6"
    15  	"github.com/zclconf/go-cty/cty"
    16  )
    17  
    18  var (
    19  	equateEmpty   = cmpopts.EquateEmpty()
    20  	typeComparer  = cmp.Comparer(cty.Type.Equals)
    21  	valueComparer = cmp.Comparer(cty.Value.RawEquals)
    22  )
    23  
    24  // Test that we can convert configschema to protobuf types and back again.
    25  func TestConvertSchemaBlocks(t *testing.T) {
    26  	tests := map[string]struct {
    27  		Block *proto.Schema_Block
    28  		Want  *configschema.Block
    29  	}{
    30  		"attributes": {
    31  			&proto.Schema_Block{
    32  				Attributes: []*proto.Schema_Attribute{
    33  					{
    34  						Name:     "computed",
    35  						Type:     []byte(`["list","bool"]`),
    36  						Computed: true,
    37  					},
    38  					{
    39  						Name:     "optional",
    40  						Type:     []byte(`"string"`),
    41  						Optional: true,
    42  					},
    43  					{
    44  						Name:     "optional_computed",
    45  						Type:     []byte(`["map","bool"]`),
    46  						Optional: true,
    47  						Computed: true,
    48  					},
    49  					{
    50  						Name:     "required",
    51  						Type:     []byte(`"number"`),
    52  						Required: true,
    53  					},
    54  					{
    55  						Name: "nested_type",
    56  						NestedType: &proto.Schema_Object{
    57  							Nesting: proto.Schema_Object_SINGLE,
    58  							Attributes: []*proto.Schema_Attribute{
    59  								{
    60  									Name:     "computed",
    61  									Type:     []byte(`["list","bool"]`),
    62  									Computed: true,
    63  								},
    64  								{
    65  									Name:     "optional",
    66  									Type:     []byte(`"string"`),
    67  									Optional: true,
    68  								},
    69  								{
    70  									Name:     "optional_computed",
    71  									Type:     []byte(`["map","bool"]`),
    72  									Optional: true,
    73  									Computed: true,
    74  								},
    75  								{
    76  									Name:     "required",
    77  									Type:     []byte(`"number"`),
    78  									Required: true,
    79  								},
    80  							},
    81  						},
    82  						Required: true,
    83  					},
    84  					{
    85  						Name: "deeply_nested_type",
    86  						NestedType: &proto.Schema_Object{
    87  							Nesting: proto.Schema_Object_SINGLE,
    88  							Attributes: []*proto.Schema_Attribute{
    89  								{
    90  									Name: "first_level",
    91  									NestedType: &proto.Schema_Object{
    92  										Nesting: proto.Schema_Object_SINGLE,
    93  										Attributes: []*proto.Schema_Attribute{
    94  											{
    95  												Name:     "computed",
    96  												Type:     []byte(`["list","bool"]`),
    97  												Computed: true,
    98  											},
    99  											{
   100  												Name:     "optional",
   101  												Type:     []byte(`"string"`),
   102  												Optional: true,
   103  											},
   104  											{
   105  												Name:     "optional_computed",
   106  												Type:     []byte(`["map","bool"]`),
   107  												Optional: true,
   108  												Computed: true,
   109  											},
   110  											{
   111  												Name:     "required",
   112  												Type:     []byte(`"number"`),
   113  												Required: true,
   114  											},
   115  										},
   116  									},
   117  									Computed: true,
   118  								},
   119  							},
   120  						},
   121  						Required: true,
   122  					},
   123  					{
   124  						Name: "nested_list",
   125  						NestedType: &proto.Schema_Object{
   126  							Nesting: proto.Schema_Object_LIST,
   127  							Attributes: []*proto.Schema_Attribute{
   128  								{
   129  									Name:     "required",
   130  									Type:     []byte(`"string"`),
   131  									Computed: true,
   132  								},
   133  							},
   134  						},
   135  						Required: true,
   136  					},
   137  					{
   138  						Name: "nested_set",
   139  						NestedType: &proto.Schema_Object{
   140  							Nesting: proto.Schema_Object_SET,
   141  							Attributes: []*proto.Schema_Attribute{
   142  								{
   143  									Name:     "required",
   144  									Type:     []byte(`"string"`),
   145  									Computed: true,
   146  								},
   147  							},
   148  						},
   149  						Required: true,
   150  					},
   151  					{
   152  						Name: "nested_map",
   153  						NestedType: &proto.Schema_Object{
   154  							Nesting: proto.Schema_Object_MAP,
   155  							Attributes: []*proto.Schema_Attribute{
   156  								{
   157  									Name:     "required",
   158  									Type:     []byte(`"string"`),
   159  									Computed: true,
   160  								},
   161  							},
   162  						},
   163  						Required: true,
   164  					},
   165  				},
   166  			},
   167  			&configschema.Block{
   168  				Attributes: map[string]*configschema.Attribute{
   169  					"computed": {
   170  						Type:     cty.List(cty.Bool),
   171  						Computed: true,
   172  					},
   173  					"optional": {
   174  						Type:     cty.String,
   175  						Optional: true,
   176  					},
   177  					"optional_computed": {
   178  						Type:     cty.Map(cty.Bool),
   179  						Optional: true,
   180  						Computed: true,
   181  					},
   182  					"required": {
   183  						Type:     cty.Number,
   184  						Required: true,
   185  					},
   186  					"nested_type": {
   187  						NestedType: &configschema.Object{
   188  							Attributes: map[string]*configschema.Attribute{
   189  								"computed": {
   190  									Type:     cty.List(cty.Bool),
   191  									Computed: true,
   192  								},
   193  								"optional": {
   194  									Type:     cty.String,
   195  									Optional: true,
   196  								},
   197  								"optional_computed": {
   198  									Type:     cty.Map(cty.Bool),
   199  									Optional: true,
   200  									Computed: true,
   201  								},
   202  								"required": {
   203  									Type:     cty.Number,
   204  									Required: true,
   205  								},
   206  							},
   207  							Nesting: configschema.NestingSingle,
   208  						},
   209  						Required: true,
   210  					},
   211  					"deeply_nested_type": {
   212  						NestedType: &configschema.Object{
   213  							Attributes: map[string]*configschema.Attribute{
   214  								"first_level": {
   215  									NestedType: &configschema.Object{
   216  										Nesting: configschema.NestingSingle,
   217  										Attributes: map[string]*configschema.Attribute{
   218  											"computed": {
   219  												Type:     cty.List(cty.Bool),
   220  												Computed: true,
   221  											},
   222  											"optional": {
   223  												Type:     cty.String,
   224  												Optional: true,
   225  											},
   226  											"optional_computed": {
   227  												Type:     cty.Map(cty.Bool),
   228  												Optional: true,
   229  												Computed: true,
   230  											},
   231  											"required": {
   232  												Type:     cty.Number,
   233  												Required: true,
   234  											},
   235  										},
   236  									},
   237  									Computed: true,
   238  								},
   239  							},
   240  							Nesting: configschema.NestingSingle,
   241  						},
   242  						Required: true,
   243  					},
   244  					"nested_list": {
   245  						NestedType: &configschema.Object{
   246  							Nesting: configschema.NestingList,
   247  							Attributes: map[string]*configschema.Attribute{
   248  								"required": {
   249  									Type:     cty.String,
   250  									Computed: true,
   251  								},
   252  							},
   253  						},
   254  						Required: true,
   255  					},
   256  					"nested_map": {
   257  						NestedType: &configschema.Object{
   258  							Nesting: configschema.NestingMap,
   259  							Attributes: map[string]*configschema.Attribute{
   260  								"required": {
   261  									Type:     cty.String,
   262  									Computed: true,
   263  								},
   264  							},
   265  						},
   266  						Required: true,
   267  					},
   268  					"nested_set": {
   269  						NestedType: &configschema.Object{
   270  							Nesting: configschema.NestingSet,
   271  							Attributes: map[string]*configschema.Attribute{
   272  								"required": {
   273  									Type:     cty.String,
   274  									Computed: true,
   275  								},
   276  							},
   277  						},
   278  						Required: true,
   279  					},
   280  				},
   281  			},
   282  		},
   283  		"blocks": {
   284  			&proto.Schema_Block{
   285  				BlockTypes: []*proto.Schema_NestedBlock{
   286  					{
   287  						TypeName: "list",
   288  						Nesting:  proto.Schema_NestedBlock_LIST,
   289  						Block:    &proto.Schema_Block{},
   290  					},
   291  					{
   292  						TypeName: "map",
   293  						Nesting:  proto.Schema_NestedBlock_MAP,
   294  						Block:    &proto.Schema_Block{},
   295  					},
   296  					{
   297  						TypeName: "set",
   298  						Nesting:  proto.Schema_NestedBlock_SET,
   299  						Block:    &proto.Schema_Block{},
   300  					},
   301  					{
   302  						TypeName: "single",
   303  						Nesting:  proto.Schema_NestedBlock_SINGLE,
   304  						Block: &proto.Schema_Block{
   305  							Attributes: []*proto.Schema_Attribute{
   306  								{
   307  									Name:     "foo",
   308  									Type:     []byte(`"dynamic"`),
   309  									Required: true,
   310  								},
   311  							},
   312  						},
   313  					},
   314  				},
   315  			},
   316  			&configschema.Block{
   317  				BlockTypes: map[string]*configschema.NestedBlock{
   318  					"list": &configschema.NestedBlock{
   319  						Nesting: configschema.NestingList,
   320  					},
   321  					"map": &configschema.NestedBlock{
   322  						Nesting: configschema.NestingMap,
   323  					},
   324  					"set": &configschema.NestedBlock{
   325  						Nesting: configschema.NestingSet,
   326  					},
   327  					"single": &configschema.NestedBlock{
   328  						Nesting: configschema.NestingSingle,
   329  						Block: configschema.Block{
   330  							Attributes: map[string]*configschema.Attribute{
   331  								"foo": {
   332  									Type:     cty.DynamicPseudoType,
   333  									Required: true,
   334  								},
   335  							},
   336  						},
   337  					},
   338  				},
   339  			},
   340  		},
   341  		"deep block nesting": {
   342  			&proto.Schema_Block{
   343  				BlockTypes: []*proto.Schema_NestedBlock{
   344  					{
   345  						TypeName: "single",
   346  						Nesting:  proto.Schema_NestedBlock_SINGLE,
   347  						Block: &proto.Schema_Block{
   348  							BlockTypes: []*proto.Schema_NestedBlock{
   349  								{
   350  									TypeName: "list",
   351  									Nesting:  proto.Schema_NestedBlock_LIST,
   352  									Block: &proto.Schema_Block{
   353  										BlockTypes: []*proto.Schema_NestedBlock{
   354  											{
   355  												TypeName: "set",
   356  												Nesting:  proto.Schema_NestedBlock_SET,
   357  												Block:    &proto.Schema_Block{},
   358  											},
   359  										},
   360  									},
   361  								},
   362  							},
   363  						},
   364  					},
   365  				},
   366  			},
   367  			&configschema.Block{
   368  				BlockTypes: map[string]*configschema.NestedBlock{
   369  					"single": &configschema.NestedBlock{
   370  						Nesting: configschema.NestingSingle,
   371  						Block: configschema.Block{
   372  							BlockTypes: map[string]*configschema.NestedBlock{
   373  								"list": &configschema.NestedBlock{
   374  									Nesting: configschema.NestingList,
   375  									Block: configschema.Block{
   376  										BlockTypes: map[string]*configschema.NestedBlock{
   377  											"set": &configschema.NestedBlock{
   378  												Nesting: configschema.NestingSet,
   379  											},
   380  										},
   381  									},
   382  								},
   383  							},
   384  						},
   385  					},
   386  				},
   387  			},
   388  		},
   389  	}
   390  
   391  	for name, tc := range tests {
   392  		t.Run(name, func(t *testing.T) {
   393  			converted := ProtoToConfigSchema(tc.Block)
   394  			if !cmp.Equal(converted, tc.Want, typeComparer, valueComparer, equateEmpty) {
   395  				t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, valueComparer, equateEmpty))
   396  			}
   397  		})
   398  	}
   399  }
   400  
   401  // Test that we can convert configschema to protobuf types and back again.
   402  func TestConvertProtoSchemaBlocks(t *testing.T) {
   403  	tests := map[string]struct {
   404  		Want  *proto.Schema_Block
   405  		Block *configschema.Block
   406  	}{
   407  		"attributes": {
   408  			&proto.Schema_Block{
   409  				Attributes: []*proto.Schema_Attribute{
   410  					{
   411  						Name:     "computed",
   412  						Type:     []byte(`["list","bool"]`),
   413  						Computed: true,
   414  					},
   415  					{
   416  						Name:     "optional",
   417  						Type:     []byte(`"string"`),
   418  						Optional: true,
   419  					},
   420  					{
   421  						Name:     "optional_computed",
   422  						Type:     []byte(`["map","bool"]`),
   423  						Optional: true,
   424  						Computed: true,
   425  					},
   426  					{
   427  						Name:     "required",
   428  						Type:     []byte(`"number"`),
   429  						Required: true,
   430  					},
   431  				},
   432  			},
   433  			&configschema.Block{
   434  				Attributes: map[string]*configschema.Attribute{
   435  					"computed": {
   436  						Type:     cty.List(cty.Bool),
   437  						Computed: true,
   438  					},
   439  					"optional": {
   440  						Type:     cty.String,
   441  						Optional: true,
   442  					},
   443  					"optional_computed": {
   444  						Type:     cty.Map(cty.Bool),
   445  						Optional: true,
   446  						Computed: true,
   447  					},
   448  					"required": {
   449  						Type:     cty.Number,
   450  						Required: true,
   451  					},
   452  				},
   453  			},
   454  		},
   455  		"blocks": {
   456  			&proto.Schema_Block{
   457  				BlockTypes: []*proto.Schema_NestedBlock{
   458  					{
   459  						TypeName: "list",
   460  						Nesting:  proto.Schema_NestedBlock_LIST,
   461  						Block:    &proto.Schema_Block{},
   462  					},
   463  					{
   464  						TypeName: "map",
   465  						Nesting:  proto.Schema_NestedBlock_MAP,
   466  						Block:    &proto.Schema_Block{},
   467  					},
   468  					{
   469  						TypeName: "set",
   470  						Nesting:  proto.Schema_NestedBlock_SET,
   471  						Block:    &proto.Schema_Block{},
   472  					},
   473  					{
   474  						TypeName: "single",
   475  						Nesting:  proto.Schema_NestedBlock_SINGLE,
   476  						Block: &proto.Schema_Block{
   477  							Attributes: []*proto.Schema_Attribute{
   478  								{
   479  									Name:     "foo",
   480  									Type:     []byte(`"dynamic"`),
   481  									Required: true,
   482  								},
   483  							},
   484  						},
   485  					},
   486  				},
   487  			},
   488  			&configschema.Block{
   489  				BlockTypes: map[string]*configschema.NestedBlock{
   490  					"list": &configschema.NestedBlock{
   491  						Nesting: configschema.NestingList,
   492  					},
   493  					"map": &configschema.NestedBlock{
   494  						Nesting: configschema.NestingMap,
   495  					},
   496  					"set": &configschema.NestedBlock{
   497  						Nesting: configschema.NestingSet,
   498  					},
   499  					"single": &configschema.NestedBlock{
   500  						Nesting: configschema.NestingSingle,
   501  						Block: configschema.Block{
   502  							Attributes: map[string]*configschema.Attribute{
   503  								"foo": {
   504  									Type:     cty.DynamicPseudoType,
   505  									Required: true,
   506  								},
   507  							},
   508  						},
   509  					},
   510  				},
   511  			},
   512  		},
   513  		"deep block nesting": {
   514  			&proto.Schema_Block{
   515  				BlockTypes: []*proto.Schema_NestedBlock{
   516  					{
   517  						TypeName: "single",
   518  						Nesting:  proto.Schema_NestedBlock_SINGLE,
   519  						Block: &proto.Schema_Block{
   520  							BlockTypes: []*proto.Schema_NestedBlock{
   521  								{
   522  									TypeName: "list",
   523  									Nesting:  proto.Schema_NestedBlock_LIST,
   524  									Block: &proto.Schema_Block{
   525  										BlockTypes: []*proto.Schema_NestedBlock{
   526  											{
   527  												TypeName: "set",
   528  												Nesting:  proto.Schema_NestedBlock_SET,
   529  												Block:    &proto.Schema_Block{},
   530  											},
   531  										},
   532  									},
   533  								},
   534  							},
   535  						},
   536  					},
   537  				},
   538  			},
   539  			&configschema.Block{
   540  				BlockTypes: map[string]*configschema.NestedBlock{
   541  					"single": &configschema.NestedBlock{
   542  						Nesting: configschema.NestingSingle,
   543  						Block: configschema.Block{
   544  							BlockTypes: map[string]*configschema.NestedBlock{
   545  								"list": &configschema.NestedBlock{
   546  									Nesting: configschema.NestingList,
   547  									Block: configschema.Block{
   548  										BlockTypes: map[string]*configschema.NestedBlock{
   549  											"set": &configschema.NestedBlock{
   550  												Nesting: configschema.NestingSet,
   551  											},
   552  										},
   553  									},
   554  								},
   555  							},
   556  						},
   557  					},
   558  				},
   559  			},
   560  		},
   561  	}
   562  
   563  	for name, tc := range tests {
   564  		t.Run(name, func(t *testing.T) {
   565  			converted := ConfigSchemaToProto(tc.Block)
   566  			if !cmp.Equal(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported) {
   567  				t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported))
   568  			}
   569  		})
   570  	}
   571  }