github.com/opentofu/opentofu@v1.7.1/internal/lang/blocktoattr/variables_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 blocktoattr
     7  
     8  import (
     9  	"testing"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/google/go-cmp/cmp/cmpopts"
    13  	"github.com/hashicorp/hcl/v2"
    14  	"github.com/hashicorp/hcl/v2/hclsyntax"
    15  	hcljson "github.com/hashicorp/hcl/v2/json"
    16  	"github.com/opentofu/opentofu/internal/configs/configschema"
    17  	"github.com/zclconf/go-cty/cty"
    18  )
    19  
    20  func TestExpandedVariables(t *testing.T) {
    21  	fooSchema := &configschema.Block{
    22  		Attributes: map[string]*configschema.Attribute{
    23  			"foo": {
    24  				Type: cty.List(cty.Object(map[string]cty.Type{
    25  					"bar": cty.String,
    26  				})),
    27  				Optional: true,
    28  			},
    29  			"bar": {
    30  				Type:     cty.Map(cty.String),
    31  				Optional: true,
    32  			},
    33  		},
    34  	}
    35  
    36  	tests := map[string]struct {
    37  		src    string
    38  		json   bool
    39  		schema *configschema.Block
    40  		want   []hcl.Traversal
    41  	}{
    42  		"empty": {
    43  			src:    ``,
    44  			schema: &configschema.Block{},
    45  			want:   nil,
    46  		},
    47  		"attribute syntax": {
    48  			src: `
    49  foo = [
    50    {
    51      bar = baz
    52    },
    53  ]
    54  `,
    55  			schema: fooSchema,
    56  			want: []hcl.Traversal{
    57  				{
    58  					hcl.TraverseRoot{
    59  						Name: "baz",
    60  						SrcRange: hcl.Range{
    61  							Filename: "test.tf",
    62  							Start:    hcl.Pos{Line: 4, Column: 11, Byte: 23},
    63  							End:      hcl.Pos{Line: 4, Column: 14, Byte: 26},
    64  						},
    65  					},
    66  				},
    67  			},
    68  		},
    69  		"block syntax": {
    70  			src: `
    71  foo {
    72    bar = baz
    73  }
    74  `,
    75  			schema: fooSchema,
    76  			want: []hcl.Traversal{
    77  				{
    78  					hcl.TraverseRoot{
    79  						Name: "baz",
    80  						SrcRange: hcl.Range{
    81  							Filename: "test.tf",
    82  							Start:    hcl.Pos{Line: 3, Column: 9, Byte: 15},
    83  							End:      hcl.Pos{Line: 3, Column: 12, Byte: 18},
    84  						},
    85  					},
    86  				},
    87  			},
    88  		},
    89  		"block syntax with nested blocks": {
    90  			src: `
    91  foo {
    92    bar {
    93      boop = baz
    94    }
    95  }
    96  `,
    97  			schema: &configschema.Block{
    98  				Attributes: map[string]*configschema.Attribute{
    99  					"foo": {
   100  						Type: cty.List(cty.Object(map[string]cty.Type{
   101  							"bar": cty.List(cty.Object(map[string]cty.Type{
   102  								"boop": cty.String,
   103  							})),
   104  						})),
   105  						Optional: true,
   106  					},
   107  				},
   108  			},
   109  			want: []hcl.Traversal{
   110  				{
   111  					hcl.TraverseRoot{
   112  						Name: "baz",
   113  						SrcRange: hcl.Range{
   114  							Filename: "test.tf",
   115  							Start:    hcl.Pos{Line: 4, Column: 12, Byte: 26},
   116  							End:      hcl.Pos{Line: 4, Column: 15, Byte: 29},
   117  						},
   118  					},
   119  				},
   120  			},
   121  		},
   122  		"dynamic block syntax": {
   123  			src: `
   124  dynamic "foo" {
   125    for_each = beep
   126    content {
   127      bar = baz
   128    }
   129  }
   130  `,
   131  			schema: fooSchema,
   132  			want: []hcl.Traversal{
   133  				{
   134  					hcl.TraverseRoot{
   135  						Name: "beep",
   136  						SrcRange: hcl.Range{
   137  							Filename: "test.tf",
   138  							Start:    hcl.Pos{Line: 3, Column: 14, Byte: 30},
   139  							End:      hcl.Pos{Line: 3, Column: 18, Byte: 34},
   140  						},
   141  					},
   142  				},
   143  				{
   144  					hcl.TraverseRoot{
   145  						Name: "baz",
   146  						SrcRange: hcl.Range{
   147  							Filename: "test.tf",
   148  							Start:    hcl.Pos{Line: 5, Column: 11, Byte: 57},
   149  							End:      hcl.Pos{Line: 5, Column: 14, Byte: 60},
   150  						},
   151  					},
   152  				},
   153  			},
   154  		},
   155  		"misplaced dynamic block": {
   156  			src: `
   157  dynamic "bar" {
   158    for_each = beep
   159    content {
   160      key = val
   161    }
   162  }
   163  `,
   164  			schema: fooSchema,
   165  			want: []hcl.Traversal{
   166  				{
   167  					hcl.TraverseRoot{
   168  						Name: "beep",
   169  						SrcRange: hcl.Range{
   170  							Filename: "test.tf",
   171  							Start:    hcl.Pos{Line: 3, Column: 14, Byte: 30},
   172  							End:      hcl.Pos{Line: 3, Column: 18, Byte: 34},
   173  						},
   174  					},
   175  				},
   176  			},
   177  		},
   178  	}
   179  
   180  	for name, test := range tests {
   181  		t.Run(name, func(t *testing.T) {
   182  			var f *hcl.File
   183  			var diags hcl.Diagnostics
   184  			if test.json {
   185  				f, diags = hcljson.Parse([]byte(test.src), "test.tf.json")
   186  			} else {
   187  				f, diags = hclsyntax.ParseConfig([]byte(test.src), "test.tf", hcl.Pos{Line: 1, Column: 1})
   188  			}
   189  			if diags.HasErrors() {
   190  				for _, diag := range diags {
   191  					t.Errorf("unexpected diagnostic: %s", diag)
   192  				}
   193  				t.FailNow()
   194  			}
   195  
   196  			got := ExpandedVariables(f.Body, test.schema)
   197  
   198  			co := cmpopts.IgnoreUnexported(hcl.TraverseRoot{})
   199  			if !cmp.Equal(got, test.want, co) {
   200  				t.Errorf("wrong result\n%s", cmp.Diff(test.want, got, co))
   201  			}
   202  		})
   203  	}
   204  
   205  }