github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/plugin6/convert/schema_test.go (about)

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