github.com/opentofu/opentofu@v1.7.1/internal/genconfig/generate_config_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 genconfig
     7  
     8  import (
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/opentofu/opentofu/internal/addrs"
    16  	"github.com/opentofu/opentofu/internal/configs/configschema"
    17  )
    18  
    19  func TestConfigGeneration(t *testing.T) {
    20  	tcs := map[string]struct {
    21  		schema   *configschema.Block
    22  		addr     addrs.AbsResourceInstance
    23  		provider addrs.LocalProviderConfig
    24  		value    cty.Value
    25  		expected string
    26  	}{
    27  		"simple_resource": {
    28  			schema: &configschema.Block{
    29  				BlockTypes: map[string]*configschema.NestedBlock{
    30  					"list_block": {
    31  						Block: configschema.Block{
    32  							Attributes: map[string]*configschema.Attribute{
    33  								"nested_value": {
    34  									Type:     cty.String,
    35  									Optional: true,
    36  								},
    37  							},
    38  						},
    39  						Nesting: configschema.NestingSingle,
    40  					},
    41  				},
    42  				Attributes: map[string]*configschema.Attribute{
    43  					"id": {
    44  						Type:     cty.String,
    45  						Computed: true,
    46  					},
    47  					"value": {
    48  						Type:     cty.String,
    49  						Optional: true,
    50  					},
    51  				},
    52  			},
    53  			addr: addrs.AbsResourceInstance{
    54  				Module: nil,
    55  				Resource: addrs.ResourceInstance{
    56  					Resource: addrs.Resource{
    57  						Mode: addrs.ManagedResourceMode,
    58  						Type: "tfcoremock_simple_resource",
    59  						Name: "empty",
    60  					},
    61  					Key: nil,
    62  				},
    63  			},
    64  			provider: addrs.LocalProviderConfig{
    65  				LocalName: "tfcoremock",
    66  			},
    67  			value: cty.NilVal,
    68  			expected: `
    69  resource "tfcoremock_simple_resource" "empty" {
    70    value = null          # OPTIONAL string
    71    list_block {          # OPTIONAL block
    72      nested_value = null # OPTIONAL string
    73    }
    74  }`,
    75  		},
    76  		"simple_resource_with_state": {
    77  			schema: &configschema.Block{
    78  				BlockTypes: map[string]*configschema.NestedBlock{
    79  					"list_block": {
    80  						Block: configschema.Block{
    81  							Attributes: map[string]*configschema.Attribute{
    82  								"nested_value": {
    83  									Type:     cty.String,
    84  									Optional: true,
    85  								},
    86  							},
    87  						},
    88  						Nesting: configschema.NestingSingle,
    89  					},
    90  				},
    91  				Attributes: map[string]*configschema.Attribute{
    92  					"id": {
    93  						Type:     cty.String,
    94  						Computed: true,
    95  					},
    96  					"value": {
    97  						Type:     cty.String,
    98  						Optional: true,
    99  					},
   100  				},
   101  			},
   102  			addr: addrs.AbsResourceInstance{
   103  				Module: nil,
   104  				Resource: addrs.ResourceInstance{
   105  					Resource: addrs.Resource{
   106  						Mode: addrs.ManagedResourceMode,
   107  						Type: "tfcoremock_simple_resource",
   108  						Name: "empty",
   109  					},
   110  					Key: nil,
   111  				},
   112  			},
   113  			provider: addrs.LocalProviderConfig{
   114  				LocalName: "tfcoremock",
   115  			},
   116  			value: cty.ObjectVal(map[string]cty.Value{
   117  				"id":    cty.StringVal("D2320658"),
   118  				"value": cty.StringVal("Hello, world!"),
   119  				"list_block": cty.ObjectVal(map[string]cty.Value{
   120  					"nested_value": cty.StringVal("Hello, solar system!"),
   121  				}),
   122  			}),
   123  			expected: `
   124  resource "tfcoremock_simple_resource" "empty" {
   125    value = "Hello, world!"
   126    list_block {
   127      nested_value = "Hello, solar system!"
   128    }
   129  }`,
   130  		},
   131  		"simple_resource_with_partial_state": {
   132  			schema: &configschema.Block{
   133  				BlockTypes: map[string]*configschema.NestedBlock{
   134  					"list_block": {
   135  						Block: configschema.Block{
   136  							Attributes: map[string]*configschema.Attribute{
   137  								"nested_value": {
   138  									Type:     cty.String,
   139  									Optional: true,
   140  								},
   141  							},
   142  						},
   143  						Nesting: configschema.NestingSingle,
   144  					},
   145  				},
   146  				Attributes: map[string]*configschema.Attribute{
   147  					"id": {
   148  						Type:     cty.String,
   149  						Computed: true,
   150  					},
   151  					"value": {
   152  						Type:     cty.String,
   153  						Optional: true,
   154  					},
   155  				},
   156  			},
   157  			addr: addrs.AbsResourceInstance{
   158  				Module: nil,
   159  				Resource: addrs.ResourceInstance{
   160  					Resource: addrs.Resource{
   161  						Mode: addrs.ManagedResourceMode,
   162  						Type: "tfcoremock_simple_resource",
   163  						Name: "empty",
   164  					},
   165  					Key: nil,
   166  				},
   167  			},
   168  			provider: addrs.LocalProviderConfig{
   169  				LocalName: "tfcoremock",
   170  			},
   171  			value: cty.ObjectVal(map[string]cty.Value{
   172  				"id": cty.StringVal("D2320658"),
   173  				"list_block": cty.ObjectVal(map[string]cty.Value{
   174  					"nested_value": cty.StringVal("Hello, solar system!"),
   175  				}),
   176  			}),
   177  			expected: `
   178  resource "tfcoremock_simple_resource" "empty" {
   179    value = null
   180    list_block {
   181      nested_value = "Hello, solar system!"
   182    }
   183  }`,
   184  		},
   185  		"simple_resource_with_alternate_provider": {
   186  			schema: &configschema.Block{
   187  				BlockTypes: map[string]*configschema.NestedBlock{
   188  					"list_block": {
   189  						Block: configschema.Block{
   190  							Attributes: map[string]*configschema.Attribute{
   191  								"nested_value": {
   192  									Type:     cty.String,
   193  									Optional: true,
   194  								},
   195  							},
   196  						},
   197  						Nesting: configschema.NestingSingle,
   198  					},
   199  				},
   200  				Attributes: map[string]*configschema.Attribute{
   201  					"id": {
   202  						Type:     cty.String,
   203  						Computed: true,
   204  					},
   205  					"value": {
   206  						Type:     cty.String,
   207  						Optional: true,
   208  					},
   209  				},
   210  			},
   211  			addr: addrs.AbsResourceInstance{
   212  				Module: nil,
   213  				Resource: addrs.ResourceInstance{
   214  					Resource: addrs.Resource{
   215  						Mode: addrs.ManagedResourceMode,
   216  						Type: "tfcoremock_simple_resource",
   217  						Name: "empty",
   218  					},
   219  					Key: nil,
   220  				},
   221  			},
   222  			provider: addrs.LocalProviderConfig{
   223  				LocalName: "mock",
   224  			},
   225  			value: cty.ObjectVal(map[string]cty.Value{
   226  				"id":    cty.StringVal("D2320658"),
   227  				"value": cty.StringVal("Hello, world!"),
   228  				"list_block": cty.ObjectVal(map[string]cty.Value{
   229  					"nested_value": cty.StringVal("Hello, solar system!"),
   230  				}),
   231  			}),
   232  			expected: `
   233  resource "tfcoremock_simple_resource" "empty" {
   234    provider = mock
   235    value    = "Hello, world!"
   236    list_block {
   237      nested_value = "Hello, solar system!"
   238    }
   239  }`,
   240  		},
   241  		"simple_resource_with_aliased_provider": {
   242  			schema: &configschema.Block{
   243  				BlockTypes: map[string]*configschema.NestedBlock{
   244  					"list_block": {
   245  						Block: configschema.Block{
   246  							Attributes: map[string]*configschema.Attribute{
   247  								"nested_value": {
   248  									Type:     cty.String,
   249  									Optional: true,
   250  								},
   251  							},
   252  						},
   253  						Nesting: configschema.NestingSingle,
   254  					},
   255  				},
   256  				Attributes: map[string]*configschema.Attribute{
   257  					"id": {
   258  						Type:     cty.String,
   259  						Computed: true,
   260  					},
   261  					"value": {
   262  						Type:     cty.String,
   263  						Optional: true,
   264  					},
   265  				},
   266  			},
   267  			addr: addrs.AbsResourceInstance{
   268  				Module: nil,
   269  				Resource: addrs.ResourceInstance{
   270  					Resource: addrs.Resource{
   271  						Mode: addrs.ManagedResourceMode,
   272  						Type: "tfcoremock_simple_resource",
   273  						Name: "empty",
   274  					},
   275  					Key: nil,
   276  				},
   277  			},
   278  			provider: addrs.LocalProviderConfig{
   279  				LocalName: "tfcoremock",
   280  				Alias:     "alternate",
   281  			},
   282  			value: cty.ObjectVal(map[string]cty.Value{
   283  				"id":    cty.StringVal("D2320658"),
   284  				"value": cty.StringVal("Hello, world!"),
   285  				"list_block": cty.ObjectVal(map[string]cty.Value{
   286  					"nested_value": cty.StringVal("Hello, solar system!"),
   287  				}),
   288  			}),
   289  			expected: `
   290  resource "tfcoremock_simple_resource" "empty" {
   291    provider = tfcoremock.alternate
   292    value    = "Hello, world!"
   293    list_block {
   294      nested_value = "Hello, solar system!"
   295    }
   296  }`,
   297  		},
   298  		"resource_with_nulls": {
   299  			schema: &configschema.Block{
   300  				Attributes: map[string]*configschema.Attribute{
   301  					"id": {
   302  						Type:     cty.String,
   303  						Computed: true,
   304  					},
   305  					"single": {
   306  						NestedType: &configschema.Object{
   307  							Attributes: map[string]*configschema.Attribute{},
   308  							Nesting:    configschema.NestingSingle,
   309  						},
   310  						Required: true,
   311  					},
   312  					"list": {
   313  						NestedType: &configschema.Object{
   314  							Attributes: map[string]*configschema.Attribute{
   315  								"nested_id": {
   316  									Type:     cty.String,
   317  									Optional: true,
   318  								},
   319  							},
   320  							Nesting: configschema.NestingList,
   321  						},
   322  						Required: true,
   323  					},
   324  					"map": {
   325  						NestedType: &configschema.Object{
   326  							Attributes: map[string]*configschema.Attribute{
   327  								"nested_id": {
   328  									Type:     cty.String,
   329  									Optional: true,
   330  								},
   331  							},
   332  							Nesting: configschema.NestingMap,
   333  						},
   334  						Required: true,
   335  					},
   336  				},
   337  				BlockTypes: map[string]*configschema.NestedBlock{
   338  					"nested_single": {
   339  						Nesting: configschema.NestingSingle,
   340  						Block: configschema.Block{
   341  							Attributes: map[string]*configschema.Attribute{
   342  								"nested_id": {
   343  									Type:     cty.String,
   344  									Optional: true,
   345  								},
   346  							},
   347  						},
   348  					},
   349  					// No configschema.NestingGroup example for this test, because this block type can never be null in state.
   350  					"nested_list": {
   351  						Nesting: configschema.NestingList,
   352  						Block: configschema.Block{
   353  							Attributes: map[string]*configschema.Attribute{
   354  								"nested_id": {
   355  									Type:     cty.String,
   356  									Optional: true,
   357  								},
   358  							},
   359  						},
   360  					},
   361  					"nested_set": {
   362  						Nesting: configschema.NestingSet,
   363  						Block: configschema.Block{
   364  							Attributes: map[string]*configschema.Attribute{
   365  								"nested_id": {
   366  									Type:     cty.String,
   367  									Optional: true,
   368  								},
   369  							},
   370  						},
   371  					},
   372  					"nested_map": {
   373  						Nesting: configschema.NestingMap,
   374  						Block: configschema.Block{
   375  							Attributes: map[string]*configschema.Attribute{
   376  								"nested_id": {
   377  									Type:     cty.String,
   378  									Optional: true,
   379  								},
   380  							},
   381  						},
   382  					},
   383  				},
   384  			},
   385  			addr: addrs.AbsResourceInstance{
   386  				Module: nil,
   387  				Resource: addrs.ResourceInstance{
   388  					Resource: addrs.Resource{
   389  						Mode: addrs.ManagedResourceMode,
   390  						Type: "tfcoremock_simple_resource",
   391  						Name: "empty",
   392  					},
   393  					Key: nil,
   394  				},
   395  			},
   396  			provider: addrs.LocalProviderConfig{
   397  				LocalName: "tfcoremock",
   398  			},
   399  			value: cty.ObjectVal(map[string]cty.Value{
   400  				"id":     cty.StringVal("D2320658"),
   401  				"single": cty.NullVal(cty.Object(map[string]cty.Type{})),
   402  				"list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   403  					"nested_id": cty.String,
   404  				}))),
   405  				"map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
   406  					"nested_id": cty.String,
   407  				}))),
   408  				"nested_single": cty.NullVal(cty.Object(map[string]cty.Type{
   409  					"nested_id": cty.String,
   410  				})),
   411  				"nested_list": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   412  					"nested_id": cty.String,
   413  				})),
   414  				"nested_set": cty.SetValEmpty(cty.Object(map[string]cty.Type{
   415  					"nested_id": cty.String,
   416  				})),
   417  				"nested_map": cty.MapValEmpty(cty.Object(map[string]cty.Type{
   418  					"nested_id": cty.String,
   419  				})),
   420  			}),
   421  			expected: `
   422  resource "tfcoremock_simple_resource" "empty" {
   423    list   = null
   424    map    = null
   425    single = null
   426  }`,
   427  		},
   428  	}
   429  	for name, tc := range tcs {
   430  		t.Run(name, func(t *testing.T) {
   431  			err := tc.schema.InternalValidate()
   432  			if err != nil {
   433  				t.Fatalf("schema failed InternalValidate: %s", err)
   434  			}
   435  			contents, diags := GenerateResourceContents(tc.addr, tc.schema, tc.provider, tc.value)
   436  			if len(diags) > 0 {
   437  				t.Errorf("expected no diagnostics but found %s", diags)
   438  			}
   439  
   440  			got := WrapResourceContents(tc.addr, contents)
   441  			want := strings.TrimSpace(tc.expected)
   442  			if diff := cmp.Diff(got, want); len(diff) > 0 {
   443  				t.Errorf("got:\n%s\nwant:\n%s\ndiff:\n%s", got, want, diff)
   444  			}
   445  		})
   446  	}
   447  }