package statefile import ( "sort" "strings" "testing" "github.com/hashicorp/terraform/internal/tfdiags" "github.com/zclconf/go-cty/cty" ) // This test verifies that modules are sorted before resources: // https://github.com/hashicorp/terraform/issues/21552 func TestVersion4_sort(t *testing.T) { resources := sortResourcesV4{ { Module: "module.child", Type: "test_instance", Name: "foo", }, { Type: "test_instance", Name: "foo", }, { Module: "module.kinder", Type: "test_instance", Name: "foo", }, { Module: "module.child.grandchild", Type: "test_instance", Name: "foo", }, } sort.Stable(resources) moduleOrder := []string{"", "module.child", "module.child.grandchild", "module.kinder"} for i, resource := range resources { if resource.Module != moduleOrder[i] { t.Errorf("wrong sort order: expected %q, got %q\n", moduleOrder[i], resource.Module) } } } func TestVersion4_unmarshalPaths(t *testing.T) { testCases := map[string]struct { json string paths []cty.Path diags []string }{ "no paths": { json: `[]`, paths: []cty.Path{}, }, "attribute path": { json: `[ [ { "type": "get_attr", "value": "password" } ] ]`, paths: []cty.Path{cty.GetAttrPath("password")}, }, "attribute and string index": { json: `[ [ { "type": "get_attr", "value": "triggers" }, { "type": "index", "value": { "value": "secret", "type": "string" } } ] ]`, paths: []cty.Path{cty.GetAttrPath("triggers").IndexString("secret")}, }, "attribute, number index, attribute": { json: `[ [ { "type": "get_attr", "value": "identities" }, { "type": "index", "value": { "value": 2, "type": "number" } }, { "type": "get_attr", "value": "private_key" } ] ]`, paths: []cty.Path{cty.GetAttrPath("identities").IndexInt(2).GetAttr("private_key")}, }, "multiple paths": { json: `[ [ { "type": "get_attr", "value": "alpha" } ], [ { "type": "get_attr", "value": "beta" } ], [ { "type": "get_attr", "value": "gamma" } ] ]`, paths: []cty.Path{cty.GetAttrPath("alpha"), cty.GetAttrPath("beta"), cty.GetAttrPath("gamma")}, }, "errors": { json: `[ [ { "type": "get_attr", "value": 5 } ], [ { "type": "index", "value": "test" } ], [ { "type": "invalid_type", "value": ["this is invalid too"] } ] ]`, paths: []cty.Path{}, diags: []string{ "Failed to unmarshal get attr step name", "Failed to unmarshal index step key", "Unsupported path step", }, }, "one invalid path, others valid": { json: `[ [ { "type": "get_attr", "value": "alpha" } ], [ { "type": "invalid_type", "value": ["this is invalid too"] } ], [ { "type": "get_attr", "value": "gamma" } ] ]`, paths: []cty.Path{cty.GetAttrPath("alpha"), cty.GetAttrPath("gamma")}, diags: []string{"Unsupported path step"}, }, "invalid structure": { json: `{}`, paths: []cty.Path{}, diags: []string{"Error unmarshaling path steps"}, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { paths, diags := unmarshalPaths([]byte(tc.json)) if len(tc.diags) == 0 { if len(diags) != 0 { t.Errorf("expected no diags, got: %#v", diags) } } else { if got, want := len(diags), len(tc.diags); got != want { t.Fatalf("got %d diags, want %d\n%s", got, want, diags.Err()) } for i := range tc.diags { got := tfdiags.Diagnostics{diags[i]}.Err().Error() if !strings.Contains(got, tc.diags[i]) { t.Errorf("expected diag %d to contain %q, but was:\n%s", i, tc.diags[i], got) } } } if len(paths) != len(tc.paths) { t.Fatalf("got %d paths, want %d", len(paths), len(tc.paths)) } for i, path := range paths { if !path.Equals(tc.paths[i]) { t.Errorf("wrong paths\n got: %#v\nwant: %#v", path, tc.paths[i]) } } }) } } func TestVersion4_marshalPaths(t *testing.T) { testCases := map[string]struct { paths []cty.Path json string }{ "no paths": { paths: []cty.Path{}, json: `[]`, }, "attribute path": { paths: []cty.Path{cty.GetAttrPath("password")}, json: `[[{"type":"get_attr","value":"password"}]]`, }, "attribute, number index, attribute": { paths: []cty.Path{cty.GetAttrPath("identities").IndexInt(2).GetAttr("private_key")}, json: `[[{"type":"get_attr","value":"identities"},{"type":"index","value":{"value":2,"type":"number"}},{"type":"get_attr","value":"private_key"}]]`, }, "multiple paths": { paths: []cty.Path{cty.GetAttrPath("a"), cty.GetAttrPath("b"), cty.GetAttrPath("c")}, json: `[[{"type":"get_attr","value":"a"}],[{"type":"get_attr","value":"b"}],[{"type":"get_attr","value":"c"}]]`, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { json, diags := marshalPaths(tc.paths) if len(diags) != 0 { t.Fatalf("expected no diags, got: %#v", diags) } if got, want := string(json), tc.json; got != want { t.Fatalf("wrong JSON output\n got: %s\nwant: %s\n", got, want) } }) } }