github.com/opentofu/opentofu@v1.7.1/internal/tofu/variables_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/opentofu/opentofu/internal/configs"
    12  	"github.com/zclconf/go-cty/cty"
    13  )
    14  
    15  func TestCheckInputVariables(t *testing.T) {
    16  	c := testModule(t, "input-variables")
    17  
    18  	t.Run("No variables set", func(t *testing.T) {
    19  		// No variables set
    20  		diags := checkInputVariables(c.Module.Variables, nil)
    21  		if !diags.HasErrors() {
    22  			t.Fatal("check succeeded, but want errors")
    23  		}
    24  
    25  		// Required variables set, optional variables unset
    26  		// This is still an error at this layer, since it's the caller's
    27  		// responsibility to have already merged in any default values.
    28  		diags = checkInputVariables(c.Module.Variables, InputValues{
    29  			"foo": &InputValue{
    30  				Value:      cty.StringVal("bar"),
    31  				SourceType: ValueFromCLIArg,
    32  			},
    33  		})
    34  		if !diags.HasErrors() {
    35  			t.Fatal("check succeeded, but want errors")
    36  		}
    37  	})
    38  
    39  	t.Run("All variables set", func(t *testing.T) {
    40  		diags := checkInputVariables(c.Module.Variables, InputValues{
    41  			"foo": &InputValue{
    42  				Value:      cty.StringVal("bar"),
    43  				SourceType: ValueFromCLIArg,
    44  			},
    45  			"bar": &InputValue{
    46  				Value:      cty.StringVal("baz"),
    47  				SourceType: ValueFromCLIArg,
    48  			},
    49  			"map": &InputValue{
    50  				Value:      cty.StringVal("baz"), // okay because config has no type constraint
    51  				SourceType: ValueFromCLIArg,
    52  			},
    53  			"object_map": &InputValue{
    54  				Value: cty.MapVal(map[string]cty.Value{
    55  					"uno": cty.ObjectVal(map[string]cty.Value{
    56  						"foo": cty.StringVal("baz"),
    57  						"bar": cty.NumberIntVal(2), // type = any
    58  					}),
    59  					"dos": cty.ObjectVal(map[string]cty.Value{
    60  						"foo": cty.StringVal("bat"),
    61  						"bar": cty.NumberIntVal(99), // type = any
    62  					}),
    63  				}),
    64  				SourceType: ValueFromCLIArg,
    65  			},
    66  			"object_list": &InputValue{
    67  				Value: cty.ListVal([]cty.Value{
    68  					cty.ObjectVal(map[string]cty.Value{
    69  						"foo": cty.StringVal("baz"),
    70  						"bar": cty.NumberIntVal(2), // type = any
    71  					}),
    72  					cty.ObjectVal(map[string]cty.Value{
    73  						"foo": cty.StringVal("bang"),
    74  						"bar": cty.NumberIntVal(42), // type = any
    75  					}),
    76  				}),
    77  				SourceType: ValueFromCLIArg,
    78  			},
    79  		})
    80  		if diags.HasErrors() {
    81  			t.Fatalf("unexpected errors: %s", diags.Err())
    82  		}
    83  	})
    84  
    85  	t.Run("Invalid Complex Types", func(t *testing.T) {
    86  		diags := checkInputVariables(c.Module.Variables, InputValues{
    87  			"foo": &InputValue{
    88  				Value:      cty.StringVal("bar"),
    89  				SourceType: ValueFromCLIArg,
    90  			},
    91  			"bar": &InputValue{
    92  				Value:      cty.StringVal("baz"),
    93  				SourceType: ValueFromCLIArg,
    94  			},
    95  			"map": &InputValue{
    96  				Value:      cty.StringVal("baz"), // okay because config has no type constraint
    97  				SourceType: ValueFromCLIArg,
    98  			},
    99  			"object_map": &InputValue{
   100  				Value: cty.MapVal(map[string]cty.Value{
   101  					"uno": cty.ObjectVal(map[string]cty.Value{
   102  						"foo": cty.StringVal("baz"),
   103  						"bar": cty.NumberIntVal(2), // type = any
   104  					}),
   105  					"dos": cty.ObjectVal(map[string]cty.Value{
   106  						"foo": cty.StringVal("bat"),
   107  						"bar": cty.NumberIntVal(99), // type = any
   108  					}),
   109  				}),
   110  				SourceType: ValueFromCLIArg,
   111  			},
   112  			"object_list": &InputValue{
   113  				Value: cty.TupleVal([]cty.Value{
   114  					cty.ObjectVal(map[string]cty.Value{
   115  						"foo": cty.StringVal("baz"),
   116  						"bar": cty.NumberIntVal(2), // type = any
   117  					}),
   118  					cty.ObjectVal(map[string]cty.Value{
   119  						"foo": cty.StringVal("bang"),
   120  						"bar": cty.StringVal("42"), // type = any, but mismatch with the first list item
   121  					}),
   122  				}),
   123  				SourceType: ValueFromCLIArg,
   124  			},
   125  		})
   126  
   127  		if diags.HasErrors() {
   128  			t.Fatalf("unexpected errors: %s", diags.Err())
   129  		}
   130  	})
   131  }
   132  
   133  // testInputValuesUnset is a helper for constructing InputValues values for
   134  // situations where all of the root module variables are optional and a
   135  // test case intends to just use those default values and not override them
   136  // at all.
   137  //
   138  // In other words, this constructs an InputValues with one entry per given
   139  // input variable declaration where all of them are declared as unset.
   140  func testInputValuesUnset(decls map[string]*configs.Variable) InputValues {
   141  	if len(decls) == 0 {
   142  		return nil
   143  	}
   144  
   145  	ret := make(InputValues, len(decls))
   146  	for name := range decls {
   147  		ret[name] = &InputValue{
   148  			Value:      cty.NilVal,
   149  			SourceType: ValueFromUnknown,
   150  		}
   151  	}
   152  	return ret
   153  }