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 }