package blocktoattr import ( "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" hcljson "github.com/hashicorp/hcl/v2/json" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/zclconf/go-cty/cty" ) func TestExpandedVariables(t *testing.T) { fooSchema := &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "foo": { Type: cty.List(cty.Object(map[string]cty.Type{ "bar": cty.String, })), Optional: true, }, "bar": { Type: cty.Map(cty.String), Optional: true, }, }, } tests := map[string]struct { src string json bool schema *configschema.Block want []hcl.Traversal }{ "empty": { src: ``, schema: &configschema.Block{}, want: nil, }, "attribute syntax": { src: ` foo = [ { bar = baz }, ] `, schema: fooSchema, want: []hcl.Traversal{ { hcl.TraverseRoot{ Name: "baz", SrcRange: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{Line: 4, Column: 11, Byte: 23}, End: hcl.Pos{Line: 4, Column: 14, Byte: 26}, }, }, }, }, }, "block syntax": { src: ` foo { bar = baz } `, schema: fooSchema, want: []hcl.Traversal{ { hcl.TraverseRoot{ Name: "baz", SrcRange: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{Line: 3, Column: 9, Byte: 15}, End: hcl.Pos{Line: 3, Column: 12, Byte: 18}, }, }, }, }, }, "block syntax with nested blocks": { src: ` foo { bar { boop = baz } } `, schema: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "foo": { Type: cty.List(cty.Object(map[string]cty.Type{ "bar": cty.List(cty.Object(map[string]cty.Type{ "boop": cty.String, })), })), Optional: true, }, }, }, want: []hcl.Traversal{ { hcl.TraverseRoot{ Name: "baz", SrcRange: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{Line: 4, Column: 12, Byte: 26}, End: hcl.Pos{Line: 4, Column: 15, Byte: 29}, }, }, }, }, }, "dynamic block syntax": { src: ` dynamic "foo" { for_each = beep content { bar = baz } } `, schema: fooSchema, want: []hcl.Traversal{ { hcl.TraverseRoot{ Name: "beep", SrcRange: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{Line: 3, Column: 14, Byte: 30}, End: hcl.Pos{Line: 3, Column: 18, Byte: 34}, }, }, }, { hcl.TraverseRoot{ Name: "baz", SrcRange: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{Line: 5, Column: 11, Byte: 57}, End: hcl.Pos{Line: 5, Column: 14, Byte: 60}, }, }, }, }, }, "misplaced dynamic block": { src: ` dynamic "bar" { for_each = beep content { key = val } } `, schema: fooSchema, want: []hcl.Traversal{ { hcl.TraverseRoot{ Name: "beep", SrcRange: hcl.Range{ Filename: "test.tf", Start: hcl.Pos{Line: 3, Column: 14, Byte: 30}, End: hcl.Pos{Line: 3, Column: 18, Byte: 34}, }, }, }, }, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { var f *hcl.File var diags hcl.Diagnostics if test.json { f, diags = hcljson.Parse([]byte(test.src), "test.tf.json") } else { f, diags = hclsyntax.ParseConfig([]byte(test.src), "test.tf", hcl.Pos{Line: 1, Column: 1}) } if diags.HasErrors() { for _, diag := range diags { t.Errorf("unexpected diagnostic: %s", diag) } t.FailNow() } got := ExpandedVariables(f.Body, test.schema) co := cmpopts.IgnoreUnexported(hcl.TraverseRoot{}) if !cmp.Equal(got, test.want, co) { t.Errorf("wrong result\n%s", cmp.Diff(test.want, got, co)) } }) } }