package hclsyntax import ( "fmt" "reflect" "testing" "github.com/hashicorp/hcl/v2" "github.com/kylelemons/godebug/pretty" "github.com/zclconf/go-cty/cty" ) func TestBodyContent(t *testing.T) { tests := []struct { body *Body schema *hcl.BodySchema partial bool want *hcl.BodyContent diagCount int }{ { &Body{}, &hcl.BodySchema{}, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 0, }, // Attributes { &Body{ Attributes: Attributes{ "foo": &Attribute{ Name: "foo", }, }, }, &hcl.BodySchema{ Attributes: []hcl.AttributeSchema{ { Name: "foo", }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{ "foo": &hcl.Attribute{ Name: "foo", }, }, }, 0, }, { &Body{ Attributes: Attributes{ "foo": &Attribute{ Name: "foo", }, }, }, &hcl.BodySchema{}, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 1, // attribute "foo" is not expected }, { &Body{ Attributes: Attributes{ "foo": &Attribute{ Name: "foo", }, }, }, &hcl.BodySchema{}, true, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 0, // in partial mode, so extra "foo" is acceptable }, { &Body{ Attributes: Attributes{}, }, &hcl.BodySchema{ Attributes: []hcl.AttributeSchema{ { Name: "foo", }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 0, // "foo" not required, so no error }, { &Body{ Attributes: Attributes{}, }, &hcl.BodySchema{ Attributes: []hcl.AttributeSchema{ { Name: "foo", Required: true, }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 1, // "foo" is required }, { &Body{ Attributes: Attributes{ "foo": &Attribute{ Name: "foo", }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 1, // attribute "foo" not expected (it's defined as a block) }, // Blocks { &Body{ Blocks: Blocks{ &Block{ Type: "foo", }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, Blocks: hcl.Blocks{ { Type: "foo", Body: (*Body)(nil), }, }, }, 0, }, { &Body{ Blocks: Blocks{ &Block{ Type: "foo", }, &Block{ Type: "foo", }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, Blocks: hcl.Blocks{ { Type: "foo", Body: (*Body)(nil), }, { Type: "foo", Body: (*Body)(nil), }, }, }, 0, }, { &Body{ Blocks: Blocks{ &Block{ Type: "foo", }, &Block{ Type: "bar", }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, Blocks: hcl.Blocks{ { Type: "foo", Body: (*Body)(nil), }, }, }, 1, // blocks of type "bar" not expected }, { &Body{ Blocks: Blocks{ &Block{ Type: "foo", }, &Block{ Type: "bar", }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", }, }, }, true, &hcl.BodyContent{ Attributes: hcl.Attributes{}, Blocks: hcl.Blocks{ { Type: "foo", Body: (*Body)(nil), }, }, }, 0, // extra "bar" allowed because we're in partial mode }, { &Body{ Blocks: Blocks{ &Block{ Type: "foo", Labels: []string{"bar"}, }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", LabelNames: []string{"name"}, }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, Blocks: hcl.Blocks{ { Type: "foo", Labels: []string{"bar"}, Body: (*Body)(nil), }, }, }, 0, }, { &Body{ Blocks: Blocks{ &Block{ Type: "foo", }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", LabelNames: []string{"name"}, }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 1, // missing label "name" }, { &Body{ Blocks: Blocks{ &Block{ Type: "foo", Labels: []string{"bar"}, LabelRanges: []hcl.Range{{}}, }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 1, // no labels expected }, { &Body{ Blocks: Blocks{ &Block{ Type: "foo", Labels: []string{"bar", "baz"}, LabelRanges: []hcl.Range{{}, {}}, }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", LabelNames: []string{"name"}, }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 1, // too many labels }, { &Body{ Attributes: Attributes{ "foo": &Attribute{ Name: "foo", }, }, }, &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ { Type: "foo", }, }, }, false, &hcl.BodyContent{ Attributes: hcl.Attributes{}, }, 1, // should've been a block, not an attribute }, } prettyConfig := &pretty.Config{ Diffable: true, IncludeUnexported: true, PrintStringers: true, } for i, test := range tests { t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { var got *hcl.BodyContent var diags hcl.Diagnostics if test.partial { got, _, diags = test.body.PartialContent(test.schema) } else { got, diags = test.body.Content(test.schema) } if len(diags) != test.diagCount { t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount) for _, diag := range diags { t.Logf(" - %s", diag.Error()) } } if !reflect.DeepEqual(got, test.want) { t.Errorf( "wrong result\ndiff: %s", prettyConfig.Compare(test.want, got), ) } }) } } func TestBodyJustAttributes(t *testing.T) { tests := []struct { body *Body want hcl.Attributes diagCount int }{ { &Body{}, hcl.Attributes{}, 0, }, { &Body{ Attributes: Attributes{}, }, hcl.Attributes{}, 0, }, { &Body{ Attributes: Attributes{ "foo": &Attribute{ Name: "foo", Expr: &LiteralValueExpr{ Val: cty.StringVal("bar"), }, }, }, }, hcl.Attributes{ "foo": &hcl.Attribute{ Name: "foo", Expr: &LiteralValueExpr{ Val: cty.StringVal("bar"), }, }, }, 0, }, { &Body{ Attributes: Attributes{ "foo": &Attribute{ Name: "foo", Expr: &LiteralValueExpr{ Val: cty.StringVal("bar"), }, }, }, Blocks: Blocks{ { Type: "foo", }, }, }, hcl.Attributes{ "foo": &hcl.Attribute{ Name: "foo", Expr: &LiteralValueExpr{ Val: cty.StringVal("bar"), }, }, }, 1, // blocks are not allowed here }, { &Body{ Attributes: Attributes{ "foo": &Attribute{ Name: "foo", Expr: &LiteralValueExpr{ Val: cty.StringVal("bar"), }, }, }, hiddenAttrs: map[string]struct{}{ "foo": struct{}{}, }, }, hcl.Attributes{}, 0, }, } prettyConfig := &pretty.Config{ Diffable: true, IncludeUnexported: true, PrintStringers: true, } for i, test := range tests { t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { got, diags := test.body.JustAttributes() if len(diags) != test.diagCount { t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount) for _, diag := range diags { t.Logf(" - %s", diag.Error()) } } if !reflect.DeepEqual(got, test.want) { t.Errorf( "wrong result\nbody: %s\ndiff: %s", prettyConfig.Sprint(test.body), prettyConfig.Compare(test.want, got), ) } }) } }