// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package tofu import ( "testing" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/zclconf/go-cty/cty" "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/configs/configschema" "github.com/opentofu/opentofu/internal/lang" "github.com/opentofu/opentofu/internal/providers" ) func TestStaticValidateReferences(t *testing.T) { tests := []struct { Ref string Src addrs.Referenceable WantErr string }{ { Ref: "aws_instance.no_count", WantErr: ``, }, { Ref: "aws_instance.count", WantErr: ``, }, { Ref: "aws_instance.count[0]", WantErr: ``, }, { Ref: "aws_instance.nonexist", WantErr: `Reference to undeclared resource: A managed resource "aws_instance" "nonexist" has not been declared in the root module.`, }, { Ref: "beep.boop", WantErr: `Reference to undeclared resource: A managed resource "beep" "boop" has not been declared in the root module. Did you mean the data resource data.beep.boop?`, }, { Ref: "aws_instance.no_count[0]", WantErr: `Unexpected resource instance key: Because aws_instance.no_count does not have "count" or "for_each" set, references to it must not include an index key. Remove the bracketed index to refer to the single instance of this resource.`, }, { Ref: "aws_instance.count.foo", // In this case we return two errors that are somewhat redundant with // one another, but we'll accept that because they both report the // problem from different perspectives and so give the user more // opportunity to understand what's going on here. WantErr: `2 problems: - Missing resource instance key: Because aws_instance.count has "count" set, its attributes must be accessed on specific instances. For example, to correlate with indices of a referring resource, use: aws_instance.count[count.index] - Unsupported attribute: This object has no argument, nested block, or exported attribute named "foo".`, }, { Ref: "boop_instance.yep", WantErr: ``, }, { Ref: "boop_whatever.nope", WantErr: `Invalid resource type: A managed resource type "boop_whatever" is not supported by provider "registry.opentofu.org/foobar/beep".`, }, { Ref: "data.boop_data.boop_nested", WantErr: `Reference to scoped resource: The referenced data resource "boop_data" "boop_nested" is not available from this context.`, }, { Ref: "data.boop_data.boop_nested", WantErr: ``, Src: addrs.Check{Name: "foo"}, }, } cfg := testModule(t, "static-validate-refs") evaluator := &Evaluator{ Config: cfg, Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]providers.ProviderSchema{ addrs.NewDefaultProvider("aws"): { ResourceTypes: map[string]providers.Schema{ "aws_instance": { Block: &configschema.Block{}, }, }, }, addrs.MustParseProviderSourceString("foobar/beep"): { ResourceTypes: map[string]providers.Schema{ // intentional mismatch between resource type prefix and provider type "boop_instance": { Block: &configschema.Block{}, }, }, DataSources: map[string]providers.Schema{ "boop_data": { Block: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "id": { Type: cty.String, Optional: true, }, }, }, }, }, }, }, t), } for _, test := range tests { t.Run(test.Ref, func(t *testing.T) { traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(test.Ref), "", hcl.Pos{Line: 1, Column: 1}) if hclDiags.HasErrors() { t.Fatal(hclDiags.Error()) } refs, diags := lang.References(addrs.ParseRef, []hcl.Traversal{traversal}) if diags.HasErrors() { t.Fatal(diags.Err()) } data := &evaluationStateData{ Evaluator: evaluator, } diags = data.StaticValidateReferences(refs, nil, test.Src) if diags.HasErrors() { if test.WantErr == "" { t.Fatalf("Unexpected diagnostics: %s", diags.Err()) } gotErr := diags.Err().Error() if gotErr != test.WantErr { t.Fatalf("Wrong diagnostics\ngot: %s\nwant: %s", gotErr, test.WantErr) } return } if test.WantErr != "" { t.Fatalf("Expected diagnostics, but got none\nwant: %s", test.WantErr) } }) } }