github.com/opentofu/opentofu@v1.7.1/internal/states/statefile/version4_test.go (about)

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