github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/states/statefile/version4_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package statefile
     5  
     6  import (
     7  	"sort"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/terramate-io/tf/tfdiags"
    12  	"github.com/zclconf/go-cty/cty"
    13  )
    14  
    15  // This test verifies that modules are sorted before resources:
    16  // https://github.com/terramate-io/tf/issues/21552
    17  func TestVersion4_sort(t *testing.T) {
    18  	resources := sortResourcesV4{
    19  		{
    20  			Module: "module.child",
    21  			Type:   "test_instance",
    22  			Name:   "foo",
    23  		},
    24  		{
    25  			Type: "test_instance",
    26  			Name: "foo",
    27  		},
    28  		{
    29  			Module: "module.kinder",
    30  			Type:   "test_instance",
    31  			Name:   "foo",
    32  		},
    33  		{
    34  			Module: "module.child.grandchild",
    35  			Type:   "test_instance",
    36  			Name:   "foo",
    37  		},
    38  	}
    39  	sort.Stable(resources)
    40  
    41  	moduleOrder := []string{"", "module.child", "module.child.grandchild", "module.kinder"}
    42  
    43  	for i, resource := range resources {
    44  		if resource.Module != moduleOrder[i] {
    45  			t.Errorf("wrong sort order: expected %q, got %q\n", moduleOrder[i], resource.Module)
    46  		}
    47  	}
    48  }
    49  
    50  func TestVersion4_unmarshalPaths(t *testing.T) {
    51  	testCases := map[string]struct {
    52  		json  string
    53  		paths []cty.Path
    54  		diags []string
    55  	}{
    56  		"no paths": {
    57  			json:  `[]`,
    58  			paths: []cty.Path{},
    59  		},
    60  		"attribute path": {
    61  			json: `[
    62    [
    63      {
    64        "type": "get_attr",
    65  	  "value": "password"
    66      }
    67    ]
    68  ]`,
    69  			paths: []cty.Path{cty.GetAttrPath("password")},
    70  		},
    71  		"attribute and string index": {
    72  			json: `[
    73    [
    74      {
    75        "type": "get_attr",
    76  	  "value": "triggers"
    77      },
    78      {
    79        "type": "index",
    80        "value": {
    81          "value": "secret",
    82  		"type": "string"
    83        }
    84      }
    85    ]
    86  ]`,
    87  			paths: []cty.Path{cty.GetAttrPath("triggers").IndexString("secret")},
    88  		},
    89  		"attribute, number index, attribute": {
    90  			json: `[
    91    [
    92      {
    93        "type": "get_attr",
    94  	  "value": "identities"
    95      },
    96      {
    97        "type": "index",
    98        "value": {
    99          "value": 2,
   100  		"type": "number"
   101        }
   102      },
   103      {
   104        "type": "get_attr",
   105        "value": "private_key"
   106      }
   107    ]
   108  ]`,
   109  			paths: []cty.Path{cty.GetAttrPath("identities").IndexInt(2).GetAttr("private_key")},
   110  		},
   111  		"multiple paths": {
   112  			json: `[
   113    [
   114      {
   115        "type": "get_attr",
   116  	  "value": "alpha"
   117      }
   118    ],
   119    [
   120      {
   121        "type": "get_attr",
   122  	  "value": "beta"
   123      }
   124    ],
   125    [
   126      {
   127        "type": "get_attr",
   128  	  "value": "gamma"
   129      }
   130    ]
   131  ]`,
   132  			paths: []cty.Path{cty.GetAttrPath("alpha"), cty.GetAttrPath("beta"), cty.GetAttrPath("gamma")},
   133  		},
   134  		"errors": {
   135  			json: `[
   136    [
   137      {
   138        "type": "get_attr",
   139  	  "value": 5
   140      }
   141    ],
   142    [
   143      {
   144        "type": "index",
   145  	  "value": "test"
   146      }
   147    ],
   148    [
   149      {
   150        "type": "invalid_type",
   151  	  "value": ["this is invalid too"]
   152      }
   153    ]
   154  ]`,
   155  			paths: []cty.Path{},
   156  			diags: []string{
   157  				"Failed to unmarshal get attr step name",
   158  				"Failed to unmarshal index step key",
   159  				"Unsupported path step",
   160  			},
   161  		},
   162  		"one invalid path, others valid": {
   163  			json: `[
   164    [
   165      {
   166        "type": "get_attr",
   167  	  "value": "alpha"
   168      }
   169    ],
   170    [
   171      {
   172        "type": "invalid_type",
   173  	  "value": ["this is invalid too"]
   174      }
   175    ],
   176    [
   177      {
   178        "type": "get_attr",
   179  	  "value": "gamma"
   180      }
   181    ]
   182  ]`,
   183  			paths: []cty.Path{cty.GetAttrPath("alpha"), cty.GetAttrPath("gamma")},
   184  			diags: []string{"Unsupported path step"},
   185  		},
   186  		"invalid structure": {
   187  			json:  `{}`,
   188  			paths: []cty.Path{},
   189  			diags: []string{"Error unmarshaling path steps"},
   190  		},
   191  	}
   192  
   193  	for name, tc := range testCases {
   194  		t.Run(name, func(t *testing.T) {
   195  			paths, diags := unmarshalPaths([]byte(tc.json))
   196  
   197  			if len(tc.diags) == 0 {
   198  				if len(diags) != 0 {
   199  					t.Errorf("expected no diags, got: %#v", diags)
   200  				}
   201  			} else {
   202  				if got, want := len(diags), len(tc.diags); got != want {
   203  					t.Fatalf("got %d diags, want %d\n%s", got, want, diags.Err())
   204  				}
   205  				for i := range tc.diags {
   206  					got := tfdiags.Diagnostics{diags[i]}.Err().Error()
   207  					if !strings.Contains(got, tc.diags[i]) {
   208  						t.Errorf("expected diag %d to contain %q, but was:\n%s", i, tc.diags[i], got)
   209  					}
   210  				}
   211  			}
   212  
   213  			if len(paths) != len(tc.paths) {
   214  				t.Fatalf("got %d paths, want %d", len(paths), len(tc.paths))
   215  			}
   216  			for i, path := range paths {
   217  				if !path.Equals(tc.paths[i]) {
   218  					t.Errorf("wrong paths\n got: %#v\nwant: %#v", path, tc.paths[i])
   219  				}
   220  			}
   221  		})
   222  	}
   223  }
   224  
   225  func TestVersion4_marshalPaths(t *testing.T) {
   226  	testCases := map[string]struct {
   227  		paths []cty.Path
   228  		json  string
   229  	}{
   230  		"no paths": {
   231  			paths: []cty.Path{},
   232  			json:  `[]`,
   233  		},
   234  		"attribute path": {
   235  			paths: []cty.Path{cty.GetAttrPath("password")},
   236  			json:  `[[{"type":"get_attr","value":"password"}]]`,
   237  		},
   238  		"attribute, number index, attribute": {
   239  			paths: []cty.Path{cty.GetAttrPath("identities").IndexInt(2).GetAttr("private_key")},
   240  			json:  `[[{"type":"get_attr","value":"identities"},{"type":"index","value":{"value":2,"type":"number"}},{"type":"get_attr","value":"private_key"}]]`,
   241  		},
   242  		"multiple paths": {
   243  			paths: []cty.Path{cty.GetAttrPath("a"), cty.GetAttrPath("b"), cty.GetAttrPath("c")},
   244  			json:  `[[{"type":"get_attr","value":"a"}],[{"type":"get_attr","value":"b"}],[{"type":"get_attr","value":"c"}]]`,
   245  		},
   246  	}
   247  
   248  	for name, tc := range testCases {
   249  		t.Run(name, func(t *testing.T) {
   250  			json, diags := marshalPaths(tc.paths)
   251  
   252  			if len(diags) != 0 {
   253  				t.Fatalf("expected no diags, got: %#v", diags)
   254  			}
   255  
   256  			if got, want := string(json), tc.json; got != want {
   257  				t.Fatalf("wrong JSON output\n got: %s\nwant: %s\n", got, want)
   258  			}
   259  		})
   260  	}
   261  }