github.com/opentofu/opentofu@v1.7.1/internal/plugin/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/tfplugin5"
    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  			},
    56  			&configschema.Block{
    57  				Attributes: map[string]*configschema.Attribute{
    58  					"computed": {
    59  						Type:     cty.List(cty.Bool),
    60  						Computed: true,
    61  					},
    62  					"optional": {
    63  						Type:     cty.String,
    64  						Optional: true,
    65  					},
    66  					"optional_computed": {
    67  						Type:     cty.Map(cty.Bool),
    68  						Optional: true,
    69  						Computed: true,
    70  					},
    71  					"required": {
    72  						Type:     cty.Number,
    73  						Required: true,
    74  					},
    75  				},
    76  			},
    77  		},
    78  		"blocks": {
    79  			&proto.Schema_Block{
    80  				BlockTypes: []*proto.Schema_NestedBlock{
    81  					{
    82  						TypeName: "list",
    83  						Nesting:  proto.Schema_NestedBlock_LIST,
    84  						Block:    &proto.Schema_Block{},
    85  					},
    86  					{
    87  						TypeName: "map",
    88  						Nesting:  proto.Schema_NestedBlock_MAP,
    89  						Block:    &proto.Schema_Block{},
    90  					},
    91  					{
    92  						TypeName: "set",
    93  						Nesting:  proto.Schema_NestedBlock_SET,
    94  						Block:    &proto.Schema_Block{},
    95  					},
    96  					{
    97  						TypeName: "single",
    98  						Nesting:  proto.Schema_NestedBlock_SINGLE,
    99  						Block: &proto.Schema_Block{
   100  							Attributes: []*proto.Schema_Attribute{
   101  								{
   102  									Name:     "foo",
   103  									Type:     []byte(`"dynamic"`),
   104  									Required: true,
   105  								},
   106  							},
   107  						},
   108  					},
   109  				},
   110  			},
   111  			&configschema.Block{
   112  				BlockTypes: map[string]*configschema.NestedBlock{
   113  					"list": &configschema.NestedBlock{
   114  						Nesting: configschema.NestingList,
   115  					},
   116  					"map": &configschema.NestedBlock{
   117  						Nesting: configschema.NestingMap,
   118  					},
   119  					"set": &configschema.NestedBlock{
   120  						Nesting: configschema.NestingSet,
   121  					},
   122  					"single": &configschema.NestedBlock{
   123  						Nesting: configschema.NestingSingle,
   124  						Block: configschema.Block{
   125  							Attributes: map[string]*configschema.Attribute{
   126  								"foo": {
   127  									Type:     cty.DynamicPseudoType,
   128  									Required: true,
   129  								},
   130  							},
   131  						},
   132  					},
   133  				},
   134  			},
   135  		},
   136  		"deep block nesting": {
   137  			&proto.Schema_Block{
   138  				BlockTypes: []*proto.Schema_NestedBlock{
   139  					{
   140  						TypeName: "single",
   141  						Nesting:  proto.Schema_NestedBlock_SINGLE,
   142  						Block: &proto.Schema_Block{
   143  							BlockTypes: []*proto.Schema_NestedBlock{
   144  								{
   145  									TypeName: "list",
   146  									Nesting:  proto.Schema_NestedBlock_LIST,
   147  									Block: &proto.Schema_Block{
   148  										BlockTypes: []*proto.Schema_NestedBlock{
   149  											{
   150  												TypeName: "set",
   151  												Nesting:  proto.Schema_NestedBlock_SET,
   152  												Block:    &proto.Schema_Block{},
   153  											},
   154  										},
   155  									},
   156  								},
   157  							},
   158  						},
   159  					},
   160  				},
   161  			},
   162  			&configschema.Block{
   163  				BlockTypes: map[string]*configschema.NestedBlock{
   164  					"single": &configschema.NestedBlock{
   165  						Nesting: configschema.NestingSingle,
   166  						Block: configschema.Block{
   167  							BlockTypes: map[string]*configschema.NestedBlock{
   168  								"list": &configschema.NestedBlock{
   169  									Nesting: configschema.NestingList,
   170  									Block: configschema.Block{
   171  										BlockTypes: map[string]*configschema.NestedBlock{
   172  											"set": &configschema.NestedBlock{
   173  												Nesting: configschema.NestingSet,
   174  											},
   175  										},
   176  									},
   177  								},
   178  							},
   179  						},
   180  					},
   181  				},
   182  			},
   183  		},
   184  	}
   185  
   186  	for name, tc := range tests {
   187  		t.Run(name, func(t *testing.T) {
   188  			converted := ProtoToConfigSchema(tc.Block)
   189  			if !cmp.Equal(converted, tc.Want, typeComparer, valueComparer, equateEmpty) {
   190  				t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, valueComparer, equateEmpty))
   191  			}
   192  		})
   193  	}
   194  }
   195  
   196  // Test that we can convert configschema to protobuf types and back again.
   197  func TestConvertProtoSchemaBlocks(t *testing.T) {
   198  	tests := map[string]struct {
   199  		Want  *proto.Schema_Block
   200  		Block *configschema.Block
   201  	}{
   202  		"attributes": {
   203  			&proto.Schema_Block{
   204  				Attributes: []*proto.Schema_Attribute{
   205  					{
   206  						Name:     "computed",
   207  						Type:     []byte(`["list","bool"]`),
   208  						Computed: true,
   209  					},
   210  					{
   211  						Name:     "optional",
   212  						Type:     []byte(`"string"`),
   213  						Optional: true,
   214  					},
   215  					{
   216  						Name:     "optional_computed",
   217  						Type:     []byte(`["map","bool"]`),
   218  						Optional: true,
   219  						Computed: true,
   220  					},
   221  					{
   222  						Name:     "required",
   223  						Type:     []byte(`"number"`),
   224  						Required: true,
   225  					},
   226  				},
   227  			},
   228  			&configschema.Block{
   229  				Attributes: map[string]*configschema.Attribute{
   230  					"computed": {
   231  						Type:     cty.List(cty.Bool),
   232  						Computed: true,
   233  					},
   234  					"optional": {
   235  						Type:     cty.String,
   236  						Optional: true,
   237  					},
   238  					"optional_computed": {
   239  						Type:     cty.Map(cty.Bool),
   240  						Optional: true,
   241  						Computed: true,
   242  					},
   243  					"required": {
   244  						Type:     cty.Number,
   245  						Required: true,
   246  					},
   247  				},
   248  			},
   249  		},
   250  		"blocks": {
   251  			&proto.Schema_Block{
   252  				BlockTypes: []*proto.Schema_NestedBlock{
   253  					{
   254  						TypeName: "list",
   255  						Nesting:  proto.Schema_NestedBlock_LIST,
   256  						Block:    &proto.Schema_Block{},
   257  					},
   258  					{
   259  						TypeName: "map",
   260  						Nesting:  proto.Schema_NestedBlock_MAP,
   261  						Block:    &proto.Schema_Block{},
   262  					},
   263  					{
   264  						TypeName: "set",
   265  						Nesting:  proto.Schema_NestedBlock_SET,
   266  						Block:    &proto.Schema_Block{},
   267  					},
   268  					{
   269  						TypeName: "single",
   270  						Nesting:  proto.Schema_NestedBlock_SINGLE,
   271  						Block: &proto.Schema_Block{
   272  							Attributes: []*proto.Schema_Attribute{
   273  								{
   274  									Name:     "foo",
   275  									Type:     []byte(`"dynamic"`),
   276  									Required: true,
   277  								},
   278  							},
   279  						},
   280  					},
   281  				},
   282  			},
   283  			&configschema.Block{
   284  				BlockTypes: map[string]*configschema.NestedBlock{
   285  					"list": &configschema.NestedBlock{
   286  						Nesting: configschema.NestingList,
   287  					},
   288  					"map": &configschema.NestedBlock{
   289  						Nesting: configschema.NestingMap,
   290  					},
   291  					"set": &configschema.NestedBlock{
   292  						Nesting: configschema.NestingSet,
   293  					},
   294  					"single": &configschema.NestedBlock{
   295  						Nesting: configschema.NestingSingle,
   296  						Block: configschema.Block{
   297  							Attributes: map[string]*configschema.Attribute{
   298  								"foo": {
   299  									Type:     cty.DynamicPseudoType,
   300  									Required: true,
   301  								},
   302  							},
   303  						},
   304  					},
   305  				},
   306  			},
   307  		},
   308  		"deep block nesting": {
   309  			&proto.Schema_Block{
   310  				BlockTypes: []*proto.Schema_NestedBlock{
   311  					{
   312  						TypeName: "single",
   313  						Nesting:  proto.Schema_NestedBlock_SINGLE,
   314  						Block: &proto.Schema_Block{
   315  							BlockTypes: []*proto.Schema_NestedBlock{
   316  								{
   317  									TypeName: "list",
   318  									Nesting:  proto.Schema_NestedBlock_LIST,
   319  									Block: &proto.Schema_Block{
   320  										BlockTypes: []*proto.Schema_NestedBlock{
   321  											{
   322  												TypeName: "set",
   323  												Nesting:  proto.Schema_NestedBlock_SET,
   324  												Block:    &proto.Schema_Block{},
   325  											},
   326  										},
   327  									},
   328  								},
   329  							},
   330  						},
   331  					},
   332  				},
   333  			},
   334  			&configschema.Block{
   335  				BlockTypes: map[string]*configschema.NestedBlock{
   336  					"single": &configschema.NestedBlock{
   337  						Nesting: configschema.NestingSingle,
   338  						Block: configschema.Block{
   339  							BlockTypes: map[string]*configschema.NestedBlock{
   340  								"list": &configschema.NestedBlock{
   341  									Nesting: configschema.NestingList,
   342  									Block: configschema.Block{
   343  										BlockTypes: map[string]*configschema.NestedBlock{
   344  											"set": &configschema.NestedBlock{
   345  												Nesting: configschema.NestingSet,
   346  											},
   347  										},
   348  									},
   349  								},
   350  							},
   351  						},
   352  					},
   353  				},
   354  			},
   355  		},
   356  	}
   357  
   358  	for name, tc := range tests {
   359  		t.Run(name, func(t *testing.T) {
   360  			converted := ConfigSchemaToProto(tc.Block)
   361  			if !cmp.Equal(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported) {
   362  				t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported))
   363  			}
   364  		})
   365  	}
   366  }