github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/configs/hcl2shim/paths_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hcl2shim
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"strconv"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/google/go-cmp/cmp/cmpopts"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  
    17  	"github.com/zclconf/go-cty/cty"
    18  )
    19  
    20  var (
    21  	ignoreUnexported = cmpopts.IgnoreUnexported(cty.GetAttrStep{}, cty.IndexStep{})
    22  	valueComparer    = cmp.Comparer(cty.Value.RawEquals)
    23  )
    24  
    25  func TestPathFromFlatmap(t *testing.T) {
    26  	tests := []struct {
    27  		Flatmap string
    28  		Type    cty.Type
    29  		Want    cty.Path
    30  		WantErr string
    31  	}{
    32  		{
    33  			Flatmap: "",
    34  			Type:    cty.EmptyObject,
    35  			Want:    nil,
    36  		},
    37  		{
    38  			Flatmap: "attr",
    39  			Type:    cty.EmptyObject,
    40  			Want:    nil,
    41  			WantErr: `attribute "attr" not found`,
    42  		},
    43  		{
    44  			Flatmap: "foo",
    45  			Type: cty.Object(map[string]cty.Type{
    46  				"foo": cty.String,
    47  			}),
    48  			Want: cty.Path{
    49  				cty.GetAttrStep{Name: "foo"},
    50  			},
    51  		},
    52  		{
    53  			Flatmap: "foo.#",
    54  			Type: cty.Object(map[string]cty.Type{
    55  				"foo": cty.List(cty.String),
    56  			}),
    57  			Want: cty.Path{
    58  				cty.GetAttrStep{Name: "foo"},
    59  			},
    60  		},
    61  		{
    62  			Flatmap: "foo.1",
    63  			Type: cty.Object(map[string]cty.Type{
    64  				"foo": cty.List(cty.String),
    65  			}),
    66  			Want: cty.Path{
    67  				cty.GetAttrStep{Name: "foo"},
    68  				cty.IndexStep{Key: cty.NumberIntVal(1)},
    69  			},
    70  		},
    71  		{
    72  			Flatmap: "foo.1",
    73  			Type: cty.Object(map[string]cty.Type{
    74  				"foo": cty.Tuple([]cty.Type{
    75  					cty.String,
    76  					cty.Bool,
    77  				}),
    78  			}),
    79  			Want: cty.Path{
    80  				cty.GetAttrStep{Name: "foo"},
    81  				cty.IndexStep{Key: cty.NumberIntVal(1)},
    82  			},
    83  		},
    84  		{
    85  			// a set index returns the set itself, since this being applied to
    86  			// a diff and the set is changing.
    87  			Flatmap: "foo.24534534",
    88  			Type: cty.Object(map[string]cty.Type{
    89  				"foo": cty.Set(cty.String),
    90  			}),
    91  			Want: cty.Path{
    92  				cty.GetAttrStep{Name: "foo"},
    93  			},
    94  		},
    95  		{
    96  			Flatmap: "foo.%",
    97  			Type: cty.Object(map[string]cty.Type{
    98  				"foo": cty.Map(cty.String),
    99  			}),
   100  			Want: cty.Path{
   101  				cty.GetAttrStep{Name: "foo"},
   102  			},
   103  		},
   104  		{
   105  			Flatmap: "foo.baz",
   106  			Type: cty.Object(map[string]cty.Type{
   107  				"foo": cty.Map(cty.Bool),
   108  			}),
   109  			Want: cty.Path{
   110  				cty.GetAttrStep{Name: "foo"},
   111  				cty.IndexStep{Key: cty.StringVal("baz")},
   112  			},
   113  		},
   114  		{
   115  			Flatmap: "foo.bar.baz",
   116  			Type: cty.Object(map[string]cty.Type{
   117  				"foo": cty.Map(
   118  					cty.Map(cty.Bool),
   119  				),
   120  			}),
   121  			Want: cty.Path{
   122  				cty.GetAttrStep{Name: "foo"},
   123  				cty.IndexStep{Key: cty.StringVal("bar")},
   124  				cty.IndexStep{Key: cty.StringVal("baz")},
   125  			},
   126  		},
   127  		{
   128  			Flatmap: "foo.bar.baz",
   129  			Type: cty.Object(map[string]cty.Type{
   130  				"foo": cty.Map(
   131  					cty.Object(map[string]cty.Type{
   132  						"baz": cty.String,
   133  					}),
   134  				),
   135  			}),
   136  			Want: cty.Path{
   137  				cty.GetAttrStep{Name: "foo"},
   138  				cty.IndexStep{Key: cty.StringVal("bar")},
   139  				cty.GetAttrStep{Name: "baz"},
   140  			},
   141  		},
   142  		{
   143  			Flatmap: "foo.0.bar",
   144  			Type: cty.Object(map[string]cty.Type{
   145  				"foo": cty.List(cty.Object(map[string]cty.Type{
   146  					"bar": cty.String,
   147  					"baz": cty.Bool,
   148  				})),
   149  			}),
   150  			Want: cty.Path{
   151  				cty.GetAttrStep{Name: "foo"},
   152  				cty.IndexStep{Key: cty.NumberIntVal(0)},
   153  				cty.GetAttrStep{Name: "bar"},
   154  			},
   155  		},
   156  		{
   157  			Flatmap: "foo.34534534.baz",
   158  			Type: cty.Object(map[string]cty.Type{
   159  				"foo": cty.Set(cty.Object(map[string]cty.Type{
   160  					"bar": cty.String,
   161  					"baz": cty.Bool,
   162  				})),
   163  			}),
   164  			Want: cty.Path{
   165  				cty.GetAttrStep{Name: "foo"},
   166  			},
   167  		},
   168  		{
   169  			Flatmap: "foo.bar.bang",
   170  			Type: cty.Object(map[string]cty.Type{
   171  				"foo": cty.String,
   172  			}),
   173  			WantErr: `invalid step "bar.bang"`,
   174  		},
   175  		{
   176  			// there should not be any attribute names with dots
   177  			Flatmap: "foo.bar.bang",
   178  			Type: cty.Object(map[string]cty.Type{
   179  				"foo.bar": cty.Map(cty.String),
   180  			}),
   181  			WantErr: `attribute "foo" not found`,
   182  		},
   183  		{
   184  			// We can only handle key names with dots if the map elements are a
   185  			// primitive type.
   186  			Flatmap: "foo.bar.bop",
   187  			Type: cty.Object(map[string]cty.Type{
   188  				"foo": cty.Map(cty.String),
   189  			}),
   190  			Want: cty.Path{
   191  				cty.GetAttrStep{Name: "foo"},
   192  				cty.IndexStep{Key: cty.StringVal("bar.bop")},
   193  			},
   194  		},
   195  		{
   196  			Flatmap: "foo.bar.0.baz",
   197  			Type: cty.Object(map[string]cty.Type{
   198  				"foo": cty.Map(
   199  					cty.List(
   200  						cty.Map(cty.String),
   201  					),
   202  				),
   203  			}),
   204  			Want: cty.Path{
   205  				cty.GetAttrStep{Name: "foo"},
   206  				cty.IndexStep{Key: cty.StringVal("bar")},
   207  				cty.IndexStep{Key: cty.NumberIntVal(0)},
   208  				cty.IndexStep{Key: cty.StringVal("baz")},
   209  			},
   210  		},
   211  	}
   212  
   213  	for _, test := range tests {
   214  		t.Run(fmt.Sprintf("%s as %#v", test.Flatmap, test.Type), func(t *testing.T) {
   215  			got, err := requiresReplacePath(test.Flatmap, test.Type)
   216  
   217  			if test.WantErr != "" {
   218  				if err == nil {
   219  					t.Fatalf("succeeded; want error: %s", test.WantErr)
   220  				}
   221  				if got, want := err.Error(), test.WantErr; !strings.Contains(got, want) {
   222  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
   223  				}
   224  				return
   225  			} else {
   226  				if err != nil {
   227  					t.Fatalf("unexpected error: %s", err.Error())
   228  				}
   229  			}
   230  
   231  			if !reflect.DeepEqual(got, test.Want) {
   232  				t.Fatalf("incorrect path\ngot:  %#v\nwant: %#v\n", got, test.Want)
   233  			}
   234  		})
   235  	}
   236  }
   237  
   238  func TestRequiresReplace(t *testing.T) {
   239  	for _, tc := range []struct {
   240  		name     string
   241  		attrs    []string
   242  		expected []cty.Path
   243  		ty       cty.Type
   244  	}{
   245  		{
   246  			name: "basic",
   247  			attrs: []string{
   248  				"foo",
   249  			},
   250  			ty: cty.Object(map[string]cty.Type{
   251  				"foo": cty.String,
   252  			}),
   253  			expected: []cty.Path{
   254  				cty.Path{cty.GetAttrStep{Name: "foo"}},
   255  			},
   256  		},
   257  		{
   258  			name: "two",
   259  			attrs: []string{
   260  				"foo",
   261  				"bar",
   262  			},
   263  			ty: cty.Object(map[string]cty.Type{
   264  				"foo": cty.String,
   265  				"bar": cty.String,
   266  			}),
   267  			expected: []cty.Path{
   268  				cty.Path{cty.GetAttrStep{Name: "foo"}},
   269  				cty.Path{cty.GetAttrStep{Name: "bar"}},
   270  			},
   271  		},
   272  		{
   273  			name: "nested object",
   274  			attrs: []string{
   275  				"foo.bar",
   276  			},
   277  			ty: cty.Object(map[string]cty.Type{
   278  				"foo": cty.Object(map[string]cty.Type{
   279  					"bar": cty.String,
   280  				}),
   281  			}),
   282  			expected: []cty.Path{
   283  				cty.Path{cty.GetAttrStep{Name: "foo"}, cty.GetAttrStep{Name: "bar"}},
   284  			},
   285  		},
   286  		{
   287  			name: "nested objects",
   288  			attrs: []string{
   289  				"foo.bar.baz",
   290  			},
   291  			ty: cty.Object(map[string]cty.Type{
   292  				"foo": cty.Object(map[string]cty.Type{
   293  					"bar": cty.Object(map[string]cty.Type{
   294  						"baz": cty.String,
   295  					}),
   296  				}),
   297  			}),
   298  			expected: []cty.Path{
   299  				cty.Path{cty.GetAttrStep{Name: "foo"}, cty.GetAttrStep{Name: "bar"}, cty.GetAttrStep{Name: "baz"}},
   300  			},
   301  		},
   302  		{
   303  			name: "nested map",
   304  			attrs: []string{
   305  				"foo.%",
   306  				"foo.bar",
   307  			},
   308  			ty: cty.Object(map[string]cty.Type{
   309  				"foo": cty.Map(cty.String),
   310  			}),
   311  			expected: []cty.Path{
   312  				cty.Path{cty.GetAttrStep{Name: "foo"}},
   313  			},
   314  		},
   315  		{
   316  			name: "nested list",
   317  			attrs: []string{
   318  				"foo.#",
   319  				"foo.1",
   320  			},
   321  			ty: cty.Object(map[string]cty.Type{
   322  				"foo": cty.Map(cty.String),
   323  			}),
   324  			expected: []cty.Path{
   325  				cty.Path{cty.GetAttrStep{Name: "foo"}},
   326  			},
   327  		},
   328  		{
   329  			name: "object in map",
   330  			attrs: []string{
   331  				"foo.bar.baz",
   332  			},
   333  			ty: cty.Object(map[string]cty.Type{
   334  				"foo": cty.Map(cty.Object(
   335  					map[string]cty.Type{
   336  						"baz": cty.String,
   337  					},
   338  				)),
   339  			}),
   340  			expected: []cty.Path{
   341  				cty.Path{cty.GetAttrStep{Name: "foo"}, cty.IndexStep{Key: cty.StringVal("bar")}, cty.GetAttrStep{Name: "baz"}},
   342  			},
   343  		},
   344  		{
   345  			name: "object in list",
   346  			attrs: []string{
   347  				"foo.1.baz",
   348  			},
   349  			ty: cty.Object(map[string]cty.Type{
   350  				"foo": cty.List(cty.Object(
   351  					map[string]cty.Type{
   352  						"baz": cty.String,
   353  					},
   354  				)),
   355  			}),
   356  			expected: []cty.Path{
   357  				cty.Path{cty.GetAttrStep{Name: "foo"}, cty.IndexStep{Key: cty.NumberIntVal(1)}, cty.GetAttrStep{Name: "baz"}},
   358  			},
   359  		},
   360  	} {
   361  		t.Run(tc.name, func(t *testing.T) {
   362  			rp, err := RequiresReplace(tc.attrs, tc.ty)
   363  			if err != nil {
   364  				t.Fatal(err)
   365  			}
   366  			if !cmp.Equal(tc.expected, rp, ignoreUnexported, valueComparer) {
   367  				t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.expected, rp)
   368  			}
   369  		})
   370  
   371  	}
   372  }
   373  
   374  func TestFlatmapKeyFromPath(t *testing.T) {
   375  	for i, tc := range []struct {
   376  		path cty.Path
   377  		attr string
   378  	}{
   379  		{
   380  			path: cty.Path{
   381  				cty.GetAttrStep{Name: "force_new"},
   382  			},
   383  			attr: "force_new",
   384  		},
   385  		{
   386  			path: cty.Path{
   387  				cty.GetAttrStep{Name: "attr"},
   388  				cty.IndexStep{Key: cty.NumberIntVal(0)},
   389  				cty.GetAttrStep{Name: "force_new"},
   390  			},
   391  			attr: "attr.0.force_new",
   392  		},
   393  		{
   394  			path: cty.Path{
   395  				cty.GetAttrStep{Name: "attr"},
   396  				cty.IndexStep{Key: cty.StringVal("key")},
   397  				cty.GetAttrStep{Name: "obj_attr"},
   398  				cty.IndexStep{Key: cty.NumberIntVal(0)},
   399  				cty.GetAttrStep{Name: "force_new"},
   400  			},
   401  			attr: "attr.key.obj_attr.0.force_new",
   402  		},
   403  	} {
   404  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   405  			attr := FlatmapKeyFromPath(tc.path)
   406  			if attr != tc.attr {
   407  				t.Fatalf("expected:%q got:%q", tc.attr, attr)
   408  			}
   409  		})
   410  	}
   411  }