github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/tfdiags/contextual_test.go (about)

     1  package tfdiags
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"testing"
     7  
     8  	"github.com/go-test/deep"
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/hashicorp/hcl/v2/hclsyntax"
    11  	"github.com/zclconf/go-cty/cty"
    12  )
    13  
    14  func TestAttributeValue(t *testing.T) {
    15  	testConfig := `
    16  foo {
    17    bar = "hi"
    18  }
    19  foo {
    20    bar = "bar"
    21  }
    22  bar {
    23    bar = "woot"
    24  }
    25  baz "a" {
    26    bar = "beep"
    27  }
    28  baz "b" {
    29    bar = "boop"
    30  }
    31  parent {
    32    nested_str = "hello"
    33    nested_str_tuple = ["aa", "bbb", "cccc"]
    34    nested_num_tuple = [1, 9863, 22]
    35    nested_map = {
    36      first_key  = "first_value"
    37      second_key = "2nd value"
    38    }
    39  }
    40  tuple_of_one = ["one"]
    41  tuple_of_two = ["first", "22222"]
    42  root_map = {
    43    first  = "1st"
    44    second = "2nd"
    45  }
    46  simple_attr = "val"
    47  `
    48  	// TODO: Test ConditionalExpr
    49  	// TODO: Test ForExpr
    50  	// TODO: Test FunctionCallExpr
    51  	// TODO: Test IndexExpr
    52  	// TODO: Test interpolation
    53  	// TODO: Test SplatExpr
    54  
    55  	f, parseDiags := hclsyntax.ParseConfig([]byte(testConfig), "test.tf", hcl.Pos{Line: 1, Column: 1})
    56  	if len(parseDiags) != 0 {
    57  		t.Fatal(parseDiags)
    58  	}
    59  	emptySrcRng := &SourceRange{
    60  		Filename: "test.tf",
    61  		Start:    SourcePos{Line: 1, Column: 1, Byte: 0},
    62  		End:      SourcePos{Line: 1, Column: 1, Byte: 0},
    63  	}
    64  
    65  	testCases := []struct {
    66  		Diag          Diagnostic
    67  		ExpectedRange *SourceRange
    68  	}{
    69  		{
    70  			AttributeValue(
    71  				Error,
    72  				"foo[0].bar",
    73  				"detail",
    74  				cty.Path{
    75  					cty.GetAttrStep{Name: "foo"},
    76  					cty.IndexStep{Key: cty.NumberIntVal(0)},
    77  					cty.GetAttrStep{Name: "bar"},
    78  				},
    79  			),
    80  			&SourceRange{
    81  				Filename: "test.tf",
    82  				Start:    SourcePos{Line: 3, Column: 9, Byte: 15},
    83  				End:      SourcePos{Line: 3, Column: 13, Byte: 19},
    84  			},
    85  		},
    86  		{
    87  			AttributeValue(
    88  				Error,
    89  				"foo[1].bar",
    90  				"detail",
    91  				cty.Path{
    92  					cty.GetAttrStep{Name: "foo"},
    93  					cty.IndexStep{Key: cty.NumberIntVal(1)},
    94  					cty.GetAttrStep{Name: "bar"},
    95  				},
    96  			),
    97  			&SourceRange{
    98  				Filename: "test.tf",
    99  				Start:    SourcePos{Line: 6, Column: 9, Byte: 36},
   100  				End:      SourcePos{Line: 6, Column: 14, Byte: 41},
   101  			},
   102  		},
   103  		{
   104  			AttributeValue(
   105  				Error,
   106  				"foo[99].bar",
   107  				"detail",
   108  				cty.Path{
   109  					cty.GetAttrStep{Name: "foo"},
   110  					cty.IndexStep{Key: cty.NumberIntVal(99)},
   111  					cty.GetAttrStep{Name: "bar"},
   112  				},
   113  			),
   114  			emptySrcRng,
   115  		},
   116  		{
   117  			AttributeValue(
   118  				Error,
   119  				"bar.bar",
   120  				"detail",
   121  				cty.Path{
   122  					cty.GetAttrStep{Name: "bar"},
   123  					cty.GetAttrStep{Name: "bar"},
   124  				},
   125  			),
   126  			&SourceRange{
   127  				Filename: "test.tf",
   128  				Start:    SourcePos{Line: 9, Column: 9, Byte: 58},
   129  				End:      SourcePos{Line: 9, Column: 15, Byte: 64},
   130  			},
   131  		},
   132  		{
   133  			AttributeValue(
   134  				Error,
   135  				`baz["a"].bar`,
   136  				"detail",
   137  				cty.Path{
   138  					cty.GetAttrStep{Name: "baz"},
   139  					cty.IndexStep{Key: cty.StringVal("a")},
   140  					cty.GetAttrStep{Name: "bar"},
   141  				},
   142  			),
   143  			&SourceRange{
   144  				Filename: "test.tf",
   145  				Start:    SourcePos{Line: 12, Column: 9, Byte: 85},
   146  				End:      SourcePos{Line: 12, Column: 15, Byte: 91},
   147  			},
   148  		},
   149  		{
   150  			AttributeValue(
   151  				Error,
   152  				`baz["b"].bar`,
   153  				"detail",
   154  				cty.Path{
   155  					cty.GetAttrStep{Name: "baz"},
   156  					cty.IndexStep{Key: cty.StringVal("b")},
   157  					cty.GetAttrStep{Name: "bar"},
   158  				},
   159  			),
   160  			&SourceRange{
   161  				Filename: "test.tf",
   162  				Start:    SourcePos{Line: 15, Column: 9, Byte: 112},
   163  				End:      SourcePos{Line: 15, Column: 15, Byte: 118},
   164  			},
   165  		},
   166  		{
   167  			AttributeValue(
   168  				Error,
   169  				`baz["not_exists"].bar`,
   170  				"detail",
   171  				cty.Path{
   172  					cty.GetAttrStep{Name: "baz"},
   173  					cty.IndexStep{Key: cty.StringVal("not_exists")},
   174  					cty.GetAttrStep{Name: "bar"},
   175  				},
   176  			),
   177  			emptySrcRng,
   178  		},
   179  		{
   180  			// Attribute value with subject already populated should not be disturbed.
   181  			// (in a real case, this might've been passed through from a deeper function
   182  			// in the call stack, for example.)
   183  			&attributeDiagnostic{
   184  				attrPath: cty.Path{cty.GetAttrStep{Name: "foo"}},
   185  				diagnosticBase: diagnosticBase{
   186  					summary: "preexisting",
   187  					detail:  "detail",
   188  				},
   189  				subject: &SourceRange{
   190  					Filename: "somewhere_else.tf",
   191  				},
   192  			},
   193  			&SourceRange{
   194  				Filename: "somewhere_else.tf",
   195  			},
   196  		},
   197  		{
   198  			// Missing path
   199  			&attributeDiagnostic{
   200  				diagnosticBase: diagnosticBase{
   201  					summary: "missing path",
   202  				},
   203  			},
   204  			nil,
   205  		},
   206  
   207  		// Nested attributes
   208  		{
   209  			AttributeValue(
   210  				Error,
   211  				"parent.nested_str",
   212  				"detail",
   213  				cty.Path{
   214  					cty.GetAttrStep{Name: "parent"},
   215  					cty.GetAttrStep{Name: "nested_str"},
   216  				},
   217  			),
   218  			&SourceRange{
   219  				Filename: "test.tf",
   220  				Start:    SourcePos{Line: 18, Column: 16, Byte: 145},
   221  				End:      SourcePos{Line: 18, Column: 23, Byte: 152},
   222  			},
   223  		},
   224  		{
   225  			AttributeValue(
   226  				Error,
   227  				"parent.nested_str_tuple[99]",
   228  				"detail",
   229  				cty.Path{
   230  					cty.GetAttrStep{Name: "parent"},
   231  					cty.GetAttrStep{Name: "nested_str_tuple"},
   232  					cty.IndexStep{Key: cty.NumberIntVal(99)},
   233  				},
   234  			),
   235  			&SourceRange{
   236  				Filename: "test.tf",
   237  				Start:    SourcePos{Line: 19, Column: 3, Byte: 155},
   238  				End:      SourcePos{Line: 19, Column: 19, Byte: 171},
   239  			},
   240  		},
   241  		{
   242  			AttributeValue(
   243  				Error,
   244  				"parent.nested_str_tuple[0]",
   245  				"detail",
   246  				cty.Path{
   247  					cty.GetAttrStep{Name: "parent"},
   248  					cty.GetAttrStep{Name: "nested_str_tuple"},
   249  					cty.IndexStep{Key: cty.NumberIntVal(0)},
   250  				},
   251  			),
   252  			&SourceRange{
   253  				Filename: "test.tf",
   254  				Start:    SourcePos{Line: 19, Column: 23, Byte: 175},
   255  				End:      SourcePos{Line: 19, Column: 27, Byte: 179},
   256  			},
   257  		},
   258  		{
   259  			AttributeValue(
   260  				Error,
   261  				"parent.nested_str_tuple[2]",
   262  				"detail",
   263  				cty.Path{
   264  					cty.GetAttrStep{Name: "parent"},
   265  					cty.GetAttrStep{Name: "nested_str_tuple"},
   266  					cty.IndexStep{Key: cty.NumberIntVal(2)},
   267  				},
   268  			),
   269  			&SourceRange{
   270  				Filename: "test.tf",
   271  				Start:    SourcePos{Line: 19, Column: 36, Byte: 188},
   272  				End:      SourcePos{Line: 19, Column: 42, Byte: 194},
   273  			},
   274  		},
   275  		{
   276  			AttributeValue(
   277  				Error,
   278  				"parent.nested_num_tuple[0]",
   279  				"detail",
   280  				cty.Path{
   281  					cty.GetAttrStep{Name: "parent"},
   282  					cty.GetAttrStep{Name: "nested_num_tuple"},
   283  					cty.IndexStep{Key: cty.NumberIntVal(0)},
   284  				},
   285  			),
   286  			&SourceRange{
   287  				Filename: "test.tf",
   288  				Start:    SourcePos{Line: 20, Column: 23, Byte: 218},
   289  				End:      SourcePos{Line: 20, Column: 24, Byte: 219},
   290  			},
   291  		},
   292  		{
   293  			AttributeValue(
   294  				Error,
   295  				"parent.nested_num_tuple[1]",
   296  				"detail",
   297  				cty.Path{
   298  					cty.GetAttrStep{Name: "parent"},
   299  					cty.GetAttrStep{Name: "nested_num_tuple"},
   300  					cty.IndexStep{Key: cty.NumberIntVal(1)},
   301  				},
   302  			),
   303  			&SourceRange{
   304  				Filename: "test.tf",
   305  				Start:    SourcePos{Line: 20, Column: 26, Byte: 221},
   306  				End:      SourcePos{Line: 20, Column: 30, Byte: 225},
   307  			},
   308  		},
   309  		{
   310  			AttributeValue(
   311  				Error,
   312  				"parent.nested_map.first_key",
   313  				"detail",
   314  				cty.Path{
   315  					cty.GetAttrStep{Name: "parent"},
   316  					cty.GetAttrStep{Name: "nested_map"},
   317  					cty.IndexStep{Key: cty.StringVal("first_key")},
   318  				},
   319  			),
   320  			&SourceRange{
   321  				Filename: "test.tf",
   322  				Start:    SourcePos{Line: 22, Column: 19, Byte: 266},
   323  				End:      SourcePos{Line: 22, Column: 30, Byte: 277},
   324  			},
   325  		},
   326  		{
   327  			AttributeValue(
   328  				Error,
   329  				"parent.nested_map.second_key",
   330  				"detail",
   331  				cty.Path{
   332  					cty.GetAttrStep{Name: "parent"},
   333  					cty.GetAttrStep{Name: "nested_map"},
   334  					cty.IndexStep{Key: cty.StringVal("second_key")},
   335  				},
   336  			),
   337  			&SourceRange{
   338  				Filename: "test.tf",
   339  				Start:    SourcePos{Line: 23, Column: 19, Byte: 297},
   340  				End:      SourcePos{Line: 23, Column: 28, Byte: 306},
   341  			},
   342  		},
   343  		{
   344  			AttributeValue(
   345  				Error,
   346  				"parent.nested_map.undefined_key",
   347  				"detail",
   348  				cty.Path{
   349  					cty.GetAttrStep{Name: "parent"},
   350  					cty.GetAttrStep{Name: "nested_map"},
   351  					cty.IndexStep{Key: cty.StringVal("undefined_key")},
   352  				},
   353  			),
   354  			&SourceRange{
   355  				Filename: "test.tf",
   356  				Start:    SourcePos{Line: 21, Column: 3, Byte: 233},
   357  				End:      SourcePos{Line: 21, Column: 13, Byte: 243},
   358  			},
   359  		},
   360  
   361  		// Root attributes of complex types
   362  		{
   363  			AttributeValue(
   364  				Error,
   365  				"tuple_of_one[0]",
   366  				"detail",
   367  				cty.Path{
   368  					cty.GetAttrStep{Name: "tuple_of_one"},
   369  					cty.IndexStep{Key: cty.NumberIntVal(0)},
   370  				},
   371  			),
   372  			&SourceRange{
   373  				Filename: "test.tf",
   374  				Start:    SourcePos{Line: 26, Column: 17, Byte: 330},
   375  				End:      SourcePos{Line: 26, Column: 22, Byte: 335},
   376  			},
   377  		},
   378  		{
   379  			AttributeValue(
   380  				Error,
   381  				"tuple_of_two[0]",
   382  				"detail",
   383  				cty.Path{
   384  					cty.GetAttrStep{Name: "tuple_of_two"},
   385  					cty.IndexStep{Key: cty.NumberIntVal(0)},
   386  				},
   387  			),
   388  			&SourceRange{
   389  				Filename: "test.tf",
   390  				Start:    SourcePos{Line: 27, Column: 17, Byte: 353},
   391  				End:      SourcePos{Line: 27, Column: 24, Byte: 360},
   392  			},
   393  		},
   394  		{
   395  			AttributeValue(
   396  				Error,
   397  				"tuple_of_two[1]",
   398  				"detail",
   399  				cty.Path{
   400  					cty.GetAttrStep{Name: "tuple_of_two"},
   401  					cty.IndexStep{Key: cty.NumberIntVal(1)},
   402  				},
   403  			),
   404  			&SourceRange{
   405  				Filename: "test.tf",
   406  				Start:    SourcePos{Line: 27, Column: 26, Byte: 362},
   407  				End:      SourcePos{Line: 27, Column: 33, Byte: 369},
   408  			},
   409  		},
   410  		{
   411  			AttributeValue(
   412  				Error,
   413  				"tuple_of_one[null]",
   414  				"detail",
   415  				cty.Path{
   416  					cty.GetAttrStep{Name: "tuple_of_one"},
   417  					cty.IndexStep{Key: cty.NullVal(cty.Number)},
   418  				},
   419  			),
   420  			&SourceRange{
   421  				Filename: "test.tf",
   422  				Start:    SourcePos{Line: 26, Column: 1, Byte: 314},
   423  				End:      SourcePos{Line: 26, Column: 13, Byte: 326},
   424  			},
   425  		},
   426  		{
   427  			// index out of range
   428  			AttributeValue(
   429  				Error,
   430  				"tuple_of_two[99]",
   431  				"detail",
   432  				cty.Path{
   433  					cty.GetAttrStep{Name: "tuple_of_two"},
   434  					cty.IndexStep{Key: cty.NumberIntVal(99)},
   435  				},
   436  			),
   437  			&SourceRange{
   438  				Filename: "test.tf",
   439  				Start:    SourcePos{Line: 27, Column: 1, Byte: 337},
   440  				End:      SourcePos{Line: 27, Column: 13, Byte: 349},
   441  			},
   442  		},
   443  		{
   444  			AttributeValue(
   445  				Error,
   446  				"root_map.first",
   447  				"detail",
   448  				cty.Path{
   449  					cty.GetAttrStep{Name: "root_map"},
   450  					cty.IndexStep{Key: cty.StringVal("first")},
   451  				},
   452  			),
   453  			&SourceRange{
   454  				Filename: "test.tf",
   455  				Start:    SourcePos{Line: 29, Column: 13, Byte: 396},
   456  				End:      SourcePos{Line: 29, Column: 16, Byte: 399},
   457  			},
   458  		},
   459  		{
   460  			AttributeValue(
   461  				Error,
   462  				"root_map.second",
   463  				"detail",
   464  				cty.Path{
   465  					cty.GetAttrStep{Name: "root_map"},
   466  					cty.IndexStep{Key: cty.StringVal("second")},
   467  				},
   468  			),
   469  			&SourceRange{
   470  				Filename: "test.tf",
   471  				Start:    SourcePos{Line: 30, Column: 13, Byte: 413},
   472  				End:      SourcePos{Line: 30, Column: 16, Byte: 416},
   473  			},
   474  		},
   475  		{
   476  			AttributeValue(
   477  				Error,
   478  				"root_map.undefined_key",
   479  				"detail",
   480  				cty.Path{
   481  					cty.GetAttrStep{Name: "root_map"},
   482  					cty.IndexStep{Key: cty.StringVal("undefined_key")},
   483  				},
   484  			),
   485  			&SourceRange{
   486  				Filename: "test.tf",
   487  				Start:    SourcePos{Line: 28, Column: 1, Byte: 371},
   488  				End:      SourcePos{Line: 28, Column: 9, Byte: 379},
   489  			},
   490  		},
   491  		{
   492  			AttributeValue(
   493  				Error,
   494  				"simple_attr",
   495  				"detail",
   496  				cty.Path{
   497  					cty.GetAttrStep{Name: "simple_attr"},
   498  				},
   499  			),
   500  			&SourceRange{
   501  				Filename: "test.tf",
   502  				Start:    SourcePos{Line: 32, Column: 15, Byte: 434},
   503  				End:      SourcePos{Line: 32, Column: 20, Byte: 439},
   504  			},
   505  		},
   506  		{
   507  			// This should never happen as error should always point to an attribute
   508  			// or index of an attribute, but we should not crash if it does
   509  			AttributeValue(
   510  				Error,
   511  				"key",
   512  				"index_step",
   513  				cty.Path{
   514  					cty.IndexStep{Key: cty.StringVal("key")},
   515  				},
   516  			),
   517  			emptySrcRng,
   518  		},
   519  		{
   520  			// This should never happen as error should always point to an attribute
   521  			// or index of an attribute, but we should not crash if it does
   522  			AttributeValue(
   523  				Error,
   524  				"key.another",
   525  				"index_step",
   526  				cty.Path{
   527  					cty.IndexStep{Key: cty.StringVal("key")},
   528  					cty.IndexStep{Key: cty.StringVal("another")},
   529  				},
   530  			),
   531  			emptySrcRng,
   532  		},
   533  	}
   534  
   535  	for i, tc := range testCases {
   536  		t.Run(fmt.Sprintf("%d:%s", i, tc.Diag.Description()), func(t *testing.T) {
   537  			var diags Diagnostics
   538  			diags = diags.Append(tc.Diag)
   539  			gotDiags := diags.InConfigBody(f.Body)
   540  			gotRange := gotDiags[0].Source().Subject
   541  
   542  			for _, problem := range deep.Equal(gotRange, tc.ExpectedRange) {
   543  				t.Error(problem)
   544  			}
   545  		})
   546  	}
   547  }
   548  
   549  func TestGetAttribute(t *testing.T) {
   550  	path := cty.Path{
   551  		cty.GetAttrStep{Name: "foo"},
   552  		cty.IndexStep{Key: cty.NumberIntVal(0)},
   553  		cty.GetAttrStep{Name: "bar"},
   554  	}
   555  
   556  	d := AttributeValue(
   557  		Error,
   558  		"foo[0].bar",
   559  		"detail",
   560  		path,
   561  	)
   562  
   563  	p := GetAttribute(d)
   564  	if !reflect.DeepEqual(path, p) {
   565  		t.Fatalf("paths don't match:\nexpected: %#v\ngot: %#v", path, p)
   566  	}
   567  }