github.com/opentofu/opentofu@v1.7.1/internal/backend/unparsed_value_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 backend
     7  
     8  import (
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/hashicorp/hcl/v2"
    14  	"github.com/zclconf/go-cty/cty"
    15  
    16  	"github.com/opentofu/opentofu/internal/configs"
    17  	"github.com/opentofu/opentofu/internal/tfdiags"
    18  	"github.com/opentofu/opentofu/internal/tofu"
    19  )
    20  
    21  func TestUnparsedValue(t *testing.T) {
    22  	vv := map[string]UnparsedVariableValue{
    23  		"undeclared0": testUnparsedVariableValue("0"),
    24  		"undeclared1": testUnparsedVariableValue("1"),
    25  		"undeclared2": testUnparsedVariableValue("2"),
    26  		"undeclared3": testUnparsedVariableValue("3"),
    27  		"undeclared4": testUnparsedVariableValue("4"),
    28  		"declared1":   testUnparsedVariableValue("5"),
    29  	}
    30  	decls := map[string]*configs.Variable{
    31  		"declared1": {
    32  			Name:           "declared1",
    33  			Type:           cty.String,
    34  			ConstraintType: cty.String,
    35  			ParsingMode:    configs.VariableParseLiteral,
    36  			DeclRange: hcl.Range{
    37  				Filename: "fake.tf",
    38  				Start:    hcl.Pos{Line: 2, Column: 1, Byte: 0},
    39  				End:      hcl.Pos{Line: 2, Column: 1, Byte: 0},
    40  			},
    41  		},
    42  		"missing1": {
    43  			Name:           "missing1",
    44  			Type:           cty.String,
    45  			ConstraintType: cty.String,
    46  			ParsingMode:    configs.VariableParseLiteral,
    47  			DeclRange: hcl.Range{
    48  				Filename: "fake.tf",
    49  				Start:    hcl.Pos{Line: 3, Column: 1, Byte: 0},
    50  				End:      hcl.Pos{Line: 3, Column: 1, Byte: 0},
    51  			},
    52  		},
    53  		"missing2": {
    54  			Name:           "missing1",
    55  			Type:           cty.String,
    56  			ConstraintType: cty.String,
    57  			ParsingMode:    configs.VariableParseLiteral,
    58  			Default:        cty.StringVal("default for missing2"),
    59  			DeclRange: hcl.Range{
    60  				Filename: "fake.tf",
    61  				Start:    hcl.Pos{Line: 4, Column: 1, Byte: 0},
    62  				End:      hcl.Pos{Line: 4, Column: 1, Byte: 0},
    63  			},
    64  		},
    65  	}
    66  
    67  	const undeclSingular = `Value for undeclared variable`
    68  	const undeclPlural = `Values for undeclared variables`
    69  
    70  	t.Run("ParseDeclaredVariableValues", func(t *testing.T) {
    71  		gotVals, diags := ParseDeclaredVariableValues(vv, decls)
    72  
    73  		if got, want := len(diags), 0; got != want {
    74  			t.Fatalf("wrong number of diagnostics %d; want %d", got, want)
    75  		}
    76  
    77  		wantVals := tofu.InputValues{
    78  			"declared1": {
    79  				Value:      cty.StringVal("5"),
    80  				SourceType: tofu.ValueFromNamedFile,
    81  				SourceRange: tfdiags.SourceRange{
    82  					Filename: "fake.tfvars",
    83  					Start:    tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    84  					End:      tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    85  				},
    86  			},
    87  		}
    88  
    89  		if diff := cmp.Diff(wantVals, gotVals, cmp.Comparer(cty.Value.RawEquals)); diff != "" {
    90  			t.Errorf("wrong result\n%s", diff)
    91  		}
    92  	})
    93  
    94  	t.Run("ParseUndeclaredVariableValues", func(t *testing.T) {
    95  		gotVals, diags := ParseUndeclaredVariableValues(vv, decls)
    96  
    97  		if got, want := len(diags), 3; got != want {
    98  			t.Fatalf("wrong number of diagnostics %d; want %d", got, want)
    99  		}
   100  
   101  		if got, want := diags[0].Description().Summary, undeclSingular; got != want {
   102  			t.Errorf("wrong summary for diagnostic 0\ngot:  %s\nwant: %s", got, want)
   103  		}
   104  
   105  		if got, want := diags[1].Description().Summary, undeclSingular; got != want {
   106  			t.Errorf("wrong summary for diagnostic 1\ngot:  %s\nwant: %s", got, want)
   107  		}
   108  
   109  		if got, want := diags[2].Description().Summary, undeclPlural; got != want {
   110  			t.Errorf("wrong summary for diagnostic 2\ngot:  %s\nwant: %s", got, want)
   111  		}
   112  
   113  		wantVals := tofu.InputValues{
   114  			"undeclared0": {
   115  				Value:      cty.StringVal("0"),
   116  				SourceType: tofu.ValueFromNamedFile,
   117  				SourceRange: tfdiags.SourceRange{
   118  					Filename: "fake.tfvars",
   119  					Start:    tfdiags.SourcePos{Line: 1, Column: 1},
   120  					End:      tfdiags.SourcePos{Line: 1, Column: 1},
   121  				},
   122  			},
   123  			"undeclared1": {
   124  				Value:      cty.StringVal("1"),
   125  				SourceType: tofu.ValueFromNamedFile,
   126  				SourceRange: tfdiags.SourceRange{
   127  					Filename: "fake.tfvars",
   128  					Start:    tfdiags.SourcePos{Line: 1, Column: 1},
   129  					End:      tfdiags.SourcePos{Line: 1, Column: 1},
   130  				},
   131  			},
   132  			"undeclared2": {
   133  				Value:      cty.StringVal("2"),
   134  				SourceType: tofu.ValueFromNamedFile,
   135  				SourceRange: tfdiags.SourceRange{
   136  					Filename: "fake.tfvars",
   137  					Start:    tfdiags.SourcePos{Line: 1, Column: 1},
   138  					End:      tfdiags.SourcePos{Line: 1, Column: 1},
   139  				},
   140  			},
   141  			"undeclared3": {
   142  				Value:      cty.StringVal("3"),
   143  				SourceType: tofu.ValueFromNamedFile,
   144  				SourceRange: tfdiags.SourceRange{
   145  					Filename: "fake.tfvars",
   146  					Start:    tfdiags.SourcePos{Line: 1, Column: 1},
   147  					End:      tfdiags.SourcePos{Line: 1, Column: 1},
   148  				},
   149  			},
   150  			"undeclared4": {
   151  				Value:      cty.StringVal("4"),
   152  				SourceType: tofu.ValueFromNamedFile,
   153  				SourceRange: tfdiags.SourceRange{
   154  					Filename: "fake.tfvars",
   155  					Start:    tfdiags.SourcePos{Line: 1, Column: 1},
   156  					End:      tfdiags.SourcePos{Line: 1, Column: 1},
   157  				},
   158  			},
   159  		}
   160  		if diff := cmp.Diff(wantVals, gotVals, cmp.Comparer(cty.Value.RawEquals)); diff != "" {
   161  			t.Errorf("wrong result\n%s", diff)
   162  		}
   163  	})
   164  
   165  	t.Run("ParseVariableValues", func(t *testing.T) {
   166  		gotVals, diags := ParseVariableValues(vv, decls)
   167  		for _, diag := range diags {
   168  			t.Logf("%s: %s", diag.Description().Summary, diag.Description().Detail)
   169  		}
   170  		if got, want := len(diags), 4; got != want {
   171  			t.Fatalf("wrong number of diagnostics %d; want %d", got, want)
   172  		}
   173  
   174  		const missingRequired = `No value for required variable`
   175  
   176  		if got, want := diags[0].Description().Summary, undeclSingular; got != want {
   177  			t.Errorf("wrong summary for diagnostic 0\ngot:  %s\nwant: %s", got, want)
   178  		}
   179  		if got, want := diags[1].Description().Summary, undeclSingular; got != want {
   180  			t.Errorf("wrong summary for diagnostic 1\ngot:  %s\nwant: %s", got, want)
   181  		}
   182  		if got, want := diags[2].Description().Summary, undeclPlural; got != want {
   183  			t.Errorf("wrong summary for diagnostic 2\ngot:  %s\nwant: %s", got, want)
   184  		}
   185  		if got, want := diags[2].Description().Detail, "3 other variable(s)"; !strings.Contains(got, want) {
   186  			t.Errorf("wrong detail for diagnostic 2\ngot:  %s\nmust contain: %s", got, want)
   187  		}
   188  		if got, want := diags[3].Description().Summary, missingRequired; got != want {
   189  			t.Errorf("wrong summary for diagnostic 3\ngot:  %s\nwant: %s", got, want)
   190  		}
   191  
   192  		wantVals := tofu.InputValues{
   193  			"declared1": {
   194  				Value:      cty.StringVal("5"),
   195  				SourceType: tofu.ValueFromNamedFile,
   196  				SourceRange: tfdiags.SourceRange{
   197  					Filename: "fake.tfvars",
   198  					Start:    tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   199  					End:      tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   200  				},
   201  			},
   202  			"missing1": {
   203  				Value:      cty.DynamicVal,
   204  				SourceType: tofu.ValueFromConfig,
   205  				SourceRange: tfdiags.SourceRange{
   206  					Filename: "fake.tf",
   207  					Start:    tfdiags.SourcePos{Line: 3, Column: 1, Byte: 0},
   208  					End:      tfdiags.SourcePos{Line: 3, Column: 1, Byte: 0},
   209  				},
   210  			},
   211  			"missing2": {
   212  				Value:      cty.NilVal, // OpenTofu Core handles substituting the default
   213  				SourceType: tofu.ValueFromConfig,
   214  				SourceRange: tfdiags.SourceRange{
   215  					Filename: "fake.tf",
   216  					Start:    tfdiags.SourcePos{Line: 4, Column: 1, Byte: 0},
   217  					End:      tfdiags.SourcePos{Line: 4, Column: 1, Byte: 0},
   218  				},
   219  			},
   220  		}
   221  		if diff := cmp.Diff(wantVals, gotVals, cmp.Comparer(cty.Value.RawEquals)); diff != "" {
   222  			t.Errorf("wrong result\n%s", diff)
   223  		}
   224  	})
   225  }
   226  
   227  type testUnparsedVariableValue string
   228  
   229  func (v testUnparsedVariableValue) ParseVariableValue(mode configs.VariableParsingMode) (*tofu.InputValue, tfdiags.Diagnostics) {
   230  	return &tofu.InputValue{
   231  		Value:      cty.StringVal(string(v)),
   232  		SourceType: tofu.ValueFromNamedFile,
   233  		SourceRange: tfdiags.SourceRange{
   234  			Filename: "fake.tfvars",
   235  			Start:    tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   236  			End:      tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   237  		},
   238  	}, nil
   239  }