github.com/hashicorp/hcl/v2@v2.20.0/hclsyntax/structure_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclsyntax
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/hcl/v2"
    12  	"github.com/kylelemons/godebug/pretty"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  func TestBodyContent(t *testing.T) {
    17  	tests := []struct {
    18  		body      *Body
    19  		schema    *hcl.BodySchema
    20  		partial   bool
    21  		want      *hcl.BodyContent
    22  		diagCount int
    23  	}{
    24  		{
    25  			&Body{},
    26  			&hcl.BodySchema{},
    27  			false,
    28  			&hcl.BodyContent{
    29  				Attributes: hcl.Attributes{},
    30  			},
    31  			0,
    32  		},
    33  
    34  		// Attributes
    35  		{
    36  			&Body{
    37  				Attributes: Attributes{
    38  					"foo": &Attribute{
    39  						Name: "foo",
    40  					},
    41  				},
    42  			},
    43  			&hcl.BodySchema{
    44  				Attributes: []hcl.AttributeSchema{
    45  					{
    46  						Name: "foo",
    47  					},
    48  				},
    49  			},
    50  			false,
    51  			&hcl.BodyContent{
    52  				Attributes: hcl.Attributes{
    53  					"foo": &hcl.Attribute{
    54  						Name: "foo",
    55  					},
    56  				},
    57  			},
    58  			0,
    59  		},
    60  		{
    61  			&Body{
    62  				Attributes: Attributes{
    63  					"foo": &Attribute{
    64  						Name: "foo",
    65  					},
    66  				},
    67  			},
    68  			&hcl.BodySchema{},
    69  			false,
    70  			&hcl.BodyContent{
    71  				Attributes: hcl.Attributes{},
    72  			},
    73  			1, // attribute "foo" is not expected
    74  		},
    75  		{
    76  			&Body{
    77  				Attributes: Attributes{
    78  					"foo": &Attribute{
    79  						Name: "foo",
    80  					},
    81  				},
    82  			},
    83  			&hcl.BodySchema{},
    84  			true,
    85  			&hcl.BodyContent{
    86  				Attributes: hcl.Attributes{},
    87  			},
    88  			0, // in partial mode, so extra "foo" is acceptable
    89  		},
    90  		{
    91  			&Body{
    92  				Attributes: Attributes{},
    93  			},
    94  			&hcl.BodySchema{
    95  				Attributes: []hcl.AttributeSchema{
    96  					{
    97  						Name: "foo",
    98  					},
    99  				},
   100  			},
   101  			false,
   102  			&hcl.BodyContent{
   103  				Attributes: hcl.Attributes{},
   104  			},
   105  			0, // "foo" not required, so no error
   106  		},
   107  		{
   108  			&Body{
   109  				Attributes: Attributes{},
   110  			},
   111  			&hcl.BodySchema{
   112  				Attributes: []hcl.AttributeSchema{
   113  					{
   114  						Name:     "foo",
   115  						Required: true,
   116  					},
   117  				},
   118  			},
   119  			false,
   120  			&hcl.BodyContent{
   121  				Attributes: hcl.Attributes{},
   122  			},
   123  			1, // "foo" is required
   124  		},
   125  		{
   126  			&Body{
   127  				Attributes: Attributes{
   128  					"foo": &Attribute{
   129  						Name: "foo",
   130  					},
   131  				},
   132  			},
   133  			&hcl.BodySchema{
   134  				Blocks: []hcl.BlockHeaderSchema{
   135  					{
   136  						Type: "foo",
   137  					},
   138  				},
   139  			},
   140  			false,
   141  			&hcl.BodyContent{
   142  				Attributes: hcl.Attributes{},
   143  			},
   144  			1, // attribute "foo" not expected (it's defined as a block)
   145  		},
   146  
   147  		// Blocks
   148  		{
   149  			&Body{
   150  				Blocks: Blocks{
   151  					&Block{
   152  						Type: "foo",
   153  					},
   154  				},
   155  			},
   156  			&hcl.BodySchema{
   157  				Blocks: []hcl.BlockHeaderSchema{
   158  					{
   159  						Type: "foo",
   160  					},
   161  				},
   162  			},
   163  			false,
   164  			&hcl.BodyContent{
   165  				Attributes: hcl.Attributes{},
   166  				Blocks: hcl.Blocks{
   167  					{
   168  						Type: "foo",
   169  						Body: (*Body)(nil),
   170  					},
   171  				},
   172  			},
   173  			0,
   174  		},
   175  		{
   176  			&Body{
   177  				Blocks: Blocks{
   178  					&Block{
   179  						Type: "foo",
   180  					},
   181  					&Block{
   182  						Type: "foo",
   183  					},
   184  				},
   185  			},
   186  			&hcl.BodySchema{
   187  				Blocks: []hcl.BlockHeaderSchema{
   188  					{
   189  						Type: "foo",
   190  					},
   191  				},
   192  			},
   193  			false,
   194  			&hcl.BodyContent{
   195  				Attributes: hcl.Attributes{},
   196  				Blocks: hcl.Blocks{
   197  					{
   198  						Type: "foo",
   199  						Body: (*Body)(nil),
   200  					},
   201  					{
   202  						Type: "foo",
   203  						Body: (*Body)(nil),
   204  					},
   205  				},
   206  			},
   207  			0,
   208  		},
   209  		{
   210  			&Body{
   211  				Blocks: Blocks{
   212  					&Block{
   213  						Type: "foo",
   214  					},
   215  					&Block{
   216  						Type: "bar",
   217  					},
   218  				},
   219  			},
   220  			&hcl.BodySchema{
   221  				Blocks: []hcl.BlockHeaderSchema{
   222  					{
   223  						Type: "foo",
   224  					},
   225  				},
   226  			},
   227  			false,
   228  			&hcl.BodyContent{
   229  				Attributes: hcl.Attributes{},
   230  				Blocks: hcl.Blocks{
   231  					{
   232  						Type: "foo",
   233  						Body: (*Body)(nil),
   234  					},
   235  				},
   236  			},
   237  			1, // blocks of type "bar" not expected
   238  		},
   239  		{
   240  			&Body{
   241  				Blocks: Blocks{
   242  					&Block{
   243  						Type: "foo",
   244  					},
   245  					&Block{
   246  						Type: "bar",
   247  					},
   248  				},
   249  			},
   250  			&hcl.BodySchema{
   251  				Blocks: []hcl.BlockHeaderSchema{
   252  					{
   253  						Type: "foo",
   254  					},
   255  				},
   256  			},
   257  			true,
   258  			&hcl.BodyContent{
   259  				Attributes: hcl.Attributes{},
   260  				Blocks: hcl.Blocks{
   261  					{
   262  						Type: "foo",
   263  						Body: (*Body)(nil),
   264  					},
   265  				},
   266  			},
   267  			0, // extra "bar" allowed because we're in partial mode
   268  		},
   269  		{
   270  			&Body{
   271  				Blocks: Blocks{
   272  					&Block{
   273  						Type:   "foo",
   274  						Labels: []string{"bar"},
   275  					},
   276  				},
   277  			},
   278  			&hcl.BodySchema{
   279  				Blocks: []hcl.BlockHeaderSchema{
   280  					{
   281  						Type:       "foo",
   282  						LabelNames: []string{"name"},
   283  					},
   284  				},
   285  			},
   286  			false,
   287  			&hcl.BodyContent{
   288  				Attributes: hcl.Attributes{},
   289  				Blocks: hcl.Blocks{
   290  					{
   291  						Type:   "foo",
   292  						Labels: []string{"bar"},
   293  						Body:   (*Body)(nil),
   294  					},
   295  				},
   296  			},
   297  			0,
   298  		},
   299  		{
   300  			&Body{
   301  				Blocks: Blocks{
   302  					&Block{
   303  						Type: "foo",
   304  					},
   305  				},
   306  			},
   307  			&hcl.BodySchema{
   308  				Blocks: []hcl.BlockHeaderSchema{
   309  					{
   310  						Type:       "foo",
   311  						LabelNames: []string{"name"},
   312  					},
   313  				},
   314  			},
   315  			false,
   316  			&hcl.BodyContent{
   317  				Attributes: hcl.Attributes{},
   318  			},
   319  			1, // missing label "name"
   320  		},
   321  		{
   322  			&Body{
   323  				Blocks: Blocks{
   324  					&Block{
   325  						Type:   "foo",
   326  						Labels: []string{"bar"},
   327  
   328  						LabelRanges: []hcl.Range{{}},
   329  					},
   330  				},
   331  			},
   332  			&hcl.BodySchema{
   333  				Blocks: []hcl.BlockHeaderSchema{
   334  					{
   335  						Type: "foo",
   336  					},
   337  				},
   338  			},
   339  			false,
   340  			&hcl.BodyContent{
   341  				Attributes: hcl.Attributes{},
   342  			},
   343  			1, // no labels expected
   344  		},
   345  		{
   346  			&Body{
   347  				Blocks: Blocks{
   348  					&Block{
   349  						Type:   "foo",
   350  						Labels: []string{"bar", "baz"},
   351  
   352  						LabelRanges: []hcl.Range{{}, {}},
   353  					},
   354  				},
   355  			},
   356  			&hcl.BodySchema{
   357  				Blocks: []hcl.BlockHeaderSchema{
   358  					{
   359  						Type:       "foo",
   360  						LabelNames: []string{"name"},
   361  					},
   362  				},
   363  			},
   364  			false,
   365  			&hcl.BodyContent{
   366  				Attributes: hcl.Attributes{},
   367  			},
   368  			1, // too many labels
   369  		},
   370  		{
   371  			&Body{
   372  				Attributes: Attributes{
   373  					"foo": &Attribute{
   374  						Name: "foo",
   375  					},
   376  				},
   377  			},
   378  			&hcl.BodySchema{
   379  				Blocks: []hcl.BlockHeaderSchema{
   380  					{
   381  						Type: "foo",
   382  					},
   383  				},
   384  			},
   385  			false,
   386  			&hcl.BodyContent{
   387  				Attributes: hcl.Attributes{},
   388  			},
   389  			1, // should've been a block, not an attribute
   390  		},
   391  	}
   392  
   393  	prettyConfig := &pretty.Config{
   394  		Diffable:          true,
   395  		IncludeUnexported: true,
   396  		PrintStringers:    true,
   397  	}
   398  
   399  	for i, test := range tests {
   400  		t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
   401  			var got *hcl.BodyContent
   402  			var diags hcl.Diagnostics
   403  			if test.partial {
   404  				got, _, diags = test.body.PartialContent(test.schema)
   405  			} else {
   406  				got, diags = test.body.Content(test.schema)
   407  			}
   408  
   409  			if len(diags) != test.diagCount {
   410  				t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
   411  				for _, diag := range diags {
   412  					t.Logf(" - %s", diag.Error())
   413  				}
   414  			}
   415  
   416  			if !reflect.DeepEqual(got, test.want) {
   417  				t.Errorf(
   418  					"wrong result\ndiff: %s",
   419  					prettyConfig.Compare(test.want, got),
   420  				)
   421  			}
   422  		})
   423  	}
   424  }
   425  
   426  func TestBodyJustAttributes(t *testing.T) {
   427  	tests := []struct {
   428  		body      *Body
   429  		want      hcl.Attributes
   430  		diagCount int
   431  	}{
   432  		{
   433  			&Body{},
   434  			hcl.Attributes{},
   435  			0,
   436  		},
   437  		{
   438  			&Body{
   439  				Attributes: Attributes{},
   440  			},
   441  			hcl.Attributes{},
   442  			0,
   443  		},
   444  		{
   445  			&Body{
   446  				Attributes: Attributes{
   447  					"foo": &Attribute{
   448  						Name: "foo",
   449  						Expr: &LiteralValueExpr{
   450  							Val: cty.StringVal("bar"),
   451  						},
   452  					},
   453  				},
   454  			},
   455  			hcl.Attributes{
   456  				"foo": &hcl.Attribute{
   457  					Name: "foo",
   458  					Expr: &LiteralValueExpr{
   459  						Val: cty.StringVal("bar"),
   460  					},
   461  				},
   462  			},
   463  			0,
   464  		},
   465  		{
   466  			&Body{
   467  				Attributes: Attributes{
   468  					"foo": &Attribute{
   469  						Name: "foo",
   470  						Expr: &LiteralValueExpr{
   471  							Val: cty.StringVal("bar"),
   472  						},
   473  					},
   474  				},
   475  				Blocks: Blocks{
   476  					{
   477  						Type: "foo",
   478  					},
   479  				},
   480  			},
   481  			hcl.Attributes{
   482  				"foo": &hcl.Attribute{
   483  					Name: "foo",
   484  					Expr: &LiteralValueExpr{
   485  						Val: cty.StringVal("bar"),
   486  					},
   487  				},
   488  			},
   489  			1, // blocks are not allowed here
   490  		},
   491  		{
   492  			&Body{
   493  				Attributes: Attributes{
   494  					"foo": &Attribute{
   495  						Name: "foo",
   496  						Expr: &LiteralValueExpr{
   497  							Val: cty.StringVal("bar"),
   498  						},
   499  					},
   500  				},
   501  				hiddenAttrs: map[string]struct{}{
   502  					"foo": struct{}{},
   503  				},
   504  			},
   505  			hcl.Attributes{},
   506  			0,
   507  		},
   508  	}
   509  
   510  	prettyConfig := &pretty.Config{
   511  		Diffable:          true,
   512  		IncludeUnexported: true,
   513  		PrintStringers:    true,
   514  	}
   515  
   516  	for i, test := range tests {
   517  		t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
   518  			got, diags := test.body.JustAttributes()
   519  
   520  			if len(diags) != test.diagCount {
   521  				t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
   522  				for _, diag := range diags {
   523  					t.Logf(" - %s", diag.Error())
   524  				}
   525  			}
   526  
   527  			if !reflect.DeepEqual(got, test.want) {
   528  				t.Errorf(
   529  					"wrong result\nbody: %s\ndiff: %s",
   530  					prettyConfig.Sprint(test.body),
   531  					prettyConfig.Compare(test.want, got),
   532  				)
   533  			}
   534  		})
   535  	}
   536  }