github.com/opentofu/opentofu@v1.7.1/internal/tofu/evaluate_valid_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 tofu
     7  
     8  import (
     9  	"testing"
    10  
    11  	"github.com/hashicorp/hcl/v2"
    12  	"github.com/hashicorp/hcl/v2/hclsyntax"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/opentofu/opentofu/internal/addrs"
    16  	"github.com/opentofu/opentofu/internal/configs/configschema"
    17  	"github.com/opentofu/opentofu/internal/lang"
    18  	"github.com/opentofu/opentofu/internal/providers"
    19  )
    20  
    21  func TestStaticValidateReferences(t *testing.T) {
    22  	tests := []struct {
    23  		Ref     string
    24  		Src     addrs.Referenceable
    25  		WantErr string
    26  	}{
    27  		{
    28  			Ref:     "aws_instance.no_count",
    29  			WantErr: ``,
    30  		},
    31  		{
    32  			Ref:     "aws_instance.count",
    33  			WantErr: ``,
    34  		},
    35  		{
    36  			Ref:     "aws_instance.count[0]",
    37  			WantErr: ``,
    38  		},
    39  		{
    40  			Ref:     "aws_instance.nonexist",
    41  			WantErr: `Reference to undeclared resource: A managed resource "aws_instance" "nonexist" has not been declared in the root module.`,
    42  		},
    43  		{
    44  			Ref: "beep.boop",
    45  			WantErr: `Reference to undeclared resource: A managed resource "beep" "boop" has not been declared in the root module.
    46  
    47  Did you mean the data resource data.beep.boop?`,
    48  		},
    49  		{
    50  			Ref:     "aws_instance.no_count[0]",
    51  			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.`,
    52  		},
    53  		{
    54  			Ref: "aws_instance.count.foo",
    55  			// In this case we return two errors that are somewhat redundant with
    56  			// one another, but we'll accept that because they both report the
    57  			// problem from different perspectives and so give the user more
    58  			// opportunity to understand what's going on here.
    59  			WantErr: `2 problems:
    60  
    61  - Missing resource instance key: Because aws_instance.count has "count" set, its attributes must be accessed on specific instances.
    62  
    63  For example, to correlate with indices of a referring resource, use:
    64      aws_instance.count[count.index]
    65  - Unsupported attribute: This object has no argument, nested block, or exported attribute named "foo".`,
    66  		},
    67  		{
    68  			Ref:     "boop_instance.yep",
    69  			WantErr: ``,
    70  		},
    71  		{
    72  			Ref:     "boop_whatever.nope",
    73  			WantErr: `Invalid resource type: A managed resource type "boop_whatever" is not supported by provider "registry.opentofu.org/foobar/beep".`,
    74  		},
    75  		{
    76  			Ref:     "data.boop_data.boop_nested",
    77  			WantErr: `Reference to scoped resource: The referenced data resource "boop_data" "boop_nested" is not available from this context.`,
    78  		},
    79  		{
    80  			Ref:     "data.boop_data.boop_nested",
    81  			WantErr: ``,
    82  			Src:     addrs.Check{Name: "foo"},
    83  		},
    84  	}
    85  
    86  	cfg := testModule(t, "static-validate-refs")
    87  	evaluator := &Evaluator{
    88  		Config: cfg,
    89  		Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]providers.ProviderSchema{
    90  			addrs.NewDefaultProvider("aws"): {
    91  				ResourceTypes: map[string]providers.Schema{
    92  					"aws_instance": {
    93  						Block: &configschema.Block{},
    94  					},
    95  				},
    96  			},
    97  			addrs.MustParseProviderSourceString("foobar/beep"): {
    98  				ResourceTypes: map[string]providers.Schema{
    99  					// intentional mismatch between resource type prefix and provider type
   100  					"boop_instance": {
   101  						Block: &configschema.Block{},
   102  					},
   103  				},
   104  				DataSources: map[string]providers.Schema{
   105  					"boop_data": {
   106  						Block: &configschema.Block{
   107  							Attributes: map[string]*configschema.Attribute{
   108  								"id": {
   109  									Type:     cty.String,
   110  									Optional: true,
   111  								},
   112  							},
   113  						},
   114  					},
   115  				},
   116  			},
   117  		}, t),
   118  	}
   119  
   120  	for _, test := range tests {
   121  		t.Run(test.Ref, func(t *testing.T) {
   122  			traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(test.Ref), "", hcl.Pos{Line: 1, Column: 1})
   123  			if hclDiags.HasErrors() {
   124  				t.Fatal(hclDiags.Error())
   125  			}
   126  
   127  			refs, diags := lang.References(addrs.ParseRef, []hcl.Traversal{traversal})
   128  			if diags.HasErrors() {
   129  				t.Fatal(diags.Err())
   130  			}
   131  
   132  			data := &evaluationStateData{
   133  				Evaluator: evaluator,
   134  			}
   135  
   136  			diags = data.StaticValidateReferences(refs, nil, test.Src)
   137  			if diags.HasErrors() {
   138  				if test.WantErr == "" {
   139  					t.Fatalf("Unexpected diagnostics: %s", diags.Err())
   140  				}
   141  
   142  				gotErr := diags.Err().Error()
   143  				if gotErr != test.WantErr {
   144  					t.Fatalf("Wrong diagnostics\ngot:  %s\nwant: %s", gotErr, test.WantErr)
   145  				}
   146  				return
   147  			}
   148  
   149  			if test.WantErr != "" {
   150  				t.Fatalf("Expected diagnostics, but got none\nwant: %s", test.WantErr)
   151  			}
   152  		})
   153  	}
   154  }