github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/input_value_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/google/go-cmp/cmp"
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/zclconf/go-cty/cty"
     9  )
    10  
    11  func TestDefaultVariableValues(t *testing.T) {
    12  	tests := []struct {
    13  		name      string
    14  		variables map[string]*Variable
    15  		want      InputValues
    16  	}{
    17  		{
    18  			name: "basic",
    19  			variables: map[string]*Variable{
    20  				"default":      {Name: "default", Type: cty.String, Default: cty.StringVal("default")},
    21  				"no_default":   {Name: "no_default", Type: cty.String},
    22  				"null_default": {Name: "null_default", Type: cty.String, Default: cty.NullVal(cty.String)},
    23  			},
    24  			want: InputValues{
    25  				"default":      {Value: cty.StringVal("default")},
    26  				"no_default":   {Value: cty.UnknownVal(cty.String)},
    27  				"null_default": {Value: cty.NullVal(cty.String)},
    28  			},
    29  		},
    30  	}
    31  
    32  	for _, test := range tests {
    33  		t.Run(test.name, func(t *testing.T) {
    34  			got := DefaultVariableValues(test.variables)
    35  
    36  			opt := cmp.Comparer(func(x, y cty.Value) bool {
    37  				return x.RawEquals(y)
    38  			})
    39  			if diff := cmp.Diff(test.want, got, opt); diff != "" {
    40  				t.Error(diff)
    41  			}
    42  		})
    43  	}
    44  }
    45  
    46  func TestEnvironmentVariableValues(t *testing.T) {
    47  	neverHappend := func(diags hcl.Diagnostics) bool { return diags.HasErrors() }
    48  
    49  	tests := []struct {
    50  		name     string
    51  		declared map[string]*Variable
    52  		env      map[string]string
    53  		want     InputValues
    54  		errCheck func(hcl.Diagnostics) bool
    55  	}{
    56  		{
    57  			name:     "undeclared",
    58  			declared: map[string]*Variable{},
    59  			env: map[string]string{
    60  				"TF_VAR_instance_type": "t2.micro",
    61  				"TF_VAR_count":         "5",
    62  				"TF_VAR_list":          "[\"foo\"]",
    63  				"TF_VAR_map":           "{foo=\"bar\"}",
    64  			},
    65  			want: InputValues{
    66  				"instance_type": &InputValue{
    67  					Value: cty.StringVal("t2.micro"),
    68  				},
    69  				"count": &InputValue{
    70  					Value: cty.StringVal("5"),
    71  				},
    72  				"list": &InputValue{
    73  					Value: cty.StringVal("[\"foo\"]"),
    74  				},
    75  				"map": &InputValue{
    76  					Value: cty.StringVal("{foo=\"bar\"}"),
    77  				},
    78  			},
    79  			errCheck: neverHappend,
    80  		},
    81  		{
    82  			name: "declared",
    83  			declared: map[string]*Variable{
    84  				"instance_type": {ParsingMode: VariableParseLiteral},
    85  				"count":         {ParsingMode: VariableParseHCL},
    86  				"list":          {ParsingMode: VariableParseHCL},
    87  				"map":           {ParsingMode: VariableParseHCL},
    88  			},
    89  			env: map[string]string{
    90  				"TF_VAR_instance_type": "t2.micro",
    91  				"TF_VAR_count":         "5",
    92  				"TF_VAR_list":          "[\"foo\"]",
    93  				"TF_VAR_map":           "{foo=\"bar\"}",
    94  			},
    95  			want: InputValues{
    96  				"instance_type": &InputValue{
    97  					Value: cty.StringVal("t2.micro"),
    98  				},
    99  				"count": &InputValue{
   100  					Value: cty.NumberIntVal(5),
   101  				},
   102  				"list": &InputValue{
   103  					Value: cty.TupleVal([]cty.Value{cty.StringVal("foo")}),
   104  				},
   105  				"map": &InputValue{
   106  					Value: cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("bar")}),
   107  				},
   108  			},
   109  			errCheck: neverHappend,
   110  		},
   111  		{
   112  			name: "invalid parsing mode",
   113  			declared: map[string]*Variable{
   114  				"foo": {ParsingMode: VariableParseHCL},
   115  			},
   116  			env: map[string]string{
   117  				"TF_VAR_foo": "bar",
   118  			},
   119  			want: InputValues{},
   120  			errCheck: func(diags hcl.Diagnostics) bool {
   121  				return diags.Error() != "<value for var.foo>:1,1-4: Variables not allowed; Variables may not be used here."
   122  			},
   123  		},
   124  		{
   125  			name: "invalid expression",
   126  			declared: map[string]*Variable{
   127  				"foo": {ParsingMode: VariableParseHCL},
   128  			},
   129  			env: map[string]string{
   130  				"TF_VAR_foo": `{"bar": "baz"`,
   131  			},
   132  			want: InputValues{},
   133  			errCheck: func(diags hcl.Diagnostics) bool {
   134  				return diags.Error() != "<value for var.foo>:1,1-2: Unterminated object constructor expression; There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file."
   135  			},
   136  		},
   137  	}
   138  
   139  	for _, test := range tests {
   140  		t.Run(test.name, func(t *testing.T) {
   141  			for k, v := range test.env {
   142  				t.Setenv(k, v)
   143  			}
   144  
   145  			got, diags := EnvironmentVariableValues(test.declared)
   146  			if test.errCheck(diags) {
   147  				t.Fatal(diags)
   148  			}
   149  
   150  			opt := cmp.Comparer(func(x, y cty.Value) bool {
   151  				return x.RawEquals(y)
   152  			})
   153  			if diff := cmp.Diff(test.want, got, opt); diff != "" {
   154  				t.Error(diff)
   155  			}
   156  		})
   157  	}
   158  }
   159  
   160  func TestParseVariableValues(t *testing.T) {
   161  	neverHappend := func(diags hcl.Diagnostics) bool { return diags.HasErrors() }
   162  
   163  	tests := []struct {
   164  		name     string
   165  		declared map[string]*Variable
   166  		vars     []string
   167  		want     InputValues
   168  		errCheck func(hcl.Diagnostics) bool
   169  	}{
   170  		{
   171  			name:     "undeclared",
   172  			declared: map[string]*Variable{},
   173  			vars: []string{
   174  				"foo=bar",
   175  			},
   176  			want: InputValues{},
   177  			errCheck: func(diags hcl.Diagnostics) bool {
   178  				return diags.Error() != `<value for var.foo>:1,1-1: Value for undeclared variable; A variable named "foo" was assigned, but the root module does not declare a variable of that name.`
   179  			},
   180  		},
   181  		{
   182  			name: "declared",
   183  			declared: map[string]*Variable{
   184  				"foo": {ParsingMode: VariableParseLiteral},
   185  				"bar": {ParsingMode: VariableParseHCL},
   186  				"baz": {ParsingMode: VariableParseHCL},
   187  			},
   188  			vars: []string{
   189  				"foo=bar",
   190  				"bar=[\"foo\"]",
   191  				"baz={ foo=\"bar\" }",
   192  			},
   193  			want: InputValues{
   194  				"foo": &InputValue{
   195  					Value: cty.StringVal("bar"),
   196  				},
   197  				"bar": &InputValue{
   198  					Value: cty.TupleVal([]cty.Value{cty.StringVal("foo")}),
   199  				},
   200  				"baz": &InputValue{
   201  					Value: cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("bar")}),
   202  				},
   203  			},
   204  			errCheck: neverHappend,
   205  		},
   206  		{
   207  			name:     "invalid format",
   208  			declared: map[string]*Variable{},
   209  			vars:     []string{"foo"},
   210  			want:     InputValues{},
   211  			errCheck: func(diags hcl.Diagnostics) bool {
   212  				return diags.Error() != `<input-value>:1,1-1: invalid variable value format; "foo" is invalid. Variables must be "key=value" format`
   213  			},
   214  		},
   215  		{
   216  			name: "invalid parsing mode",
   217  			declared: map[string]*Variable{
   218  				"foo": {ParsingMode: VariableParseHCL},
   219  			},
   220  			vars: []string{"foo=bar"},
   221  			want: InputValues{},
   222  			errCheck: func(diags hcl.Diagnostics) bool {
   223  				return diags.Error() != "<value for var.foo>:1,1-4: Variables not allowed; Variables may not be used here."
   224  			},
   225  		},
   226  		{
   227  			name: "invalid expression",
   228  			declared: map[string]*Variable{
   229  				"foo": {ParsingMode: VariableParseHCL},
   230  			},
   231  			vars: []string{"foo="},
   232  			want: InputValues{},
   233  			errCheck: func(diags hcl.Diagnostics) bool {
   234  				return diags.Error() != "<value for var.foo>:1,1-1: Missing expression; Expected the start of an expression, but found the end of the file."
   235  			},
   236  		},
   237  	}
   238  
   239  	for _, test := range tests {
   240  		t.Run(test.name, func(t *testing.T) {
   241  			got, diags := ParseVariableValues(test.vars, test.declared)
   242  			if test.errCheck(diags) {
   243  				t.Fatal(diags)
   244  			}
   245  
   246  			opt := cmp.Comparer(func(x, y cty.Value) bool {
   247  				return x.RawEquals(y)
   248  			})
   249  			if diff := cmp.Diff(test.want, got, opt); diff != "" {
   250  				t.Error(diff)
   251  			}
   252  		})
   253  	}
   254  }
   255  
   256  func TestVariableValues(t *testing.T) {
   257  	tests := []struct {
   258  		name   string
   259  		config *Config
   260  		env    map[string]string
   261  		inputs []InputValues
   262  		want   map[string]map[string]cty.Value
   263  	}{
   264  		{
   265  			name: "basic",
   266  			config: &Config{
   267  				Path: []string{"child1", "child2"},
   268  				Module: &Module{
   269  					Variables: map[string]*Variable{
   270  						"a": {Name: "a", Type: cty.String, ParsingMode: VariableParseLiteral, Default: cty.StringVal("config")},
   271  						"b": {Name: "b", Type: cty.String, ParsingMode: VariableParseLiteral, Default: cty.StringVal("config")},
   272  						"c": {Name: "c", Type: cty.String, ParsingMode: VariableParseLiteral, Default: cty.StringVal("config")},
   273  						"d": {Name: "d", Type: cty.String, ParsingMode: VariableParseLiteral, Default: cty.StringVal("config")},
   274  					},
   275  				},
   276  			},
   277  			env: map[string]string{
   278  				"TF_VAR_a": "env",
   279  				"TF_VAR_b": "env",
   280  				"TF_VAR_c": "env",
   281  			},
   282  			inputs: []InputValues{
   283  				{
   284  					"a": {Value: cty.StringVal("input1")},
   285  					"b": {Value: cty.StringVal("input1")},
   286  				},
   287  				{
   288  					"a": {Value: cty.StringVal("input2")},
   289  				},
   290  			},
   291  			want: map[string]map[string]cty.Value{
   292  				"module.child1.module.child2": {
   293  					"a": cty.StringVal("input2"),
   294  					"b": cty.StringVal("input1"),
   295  					"c": cty.StringVal("env"),
   296  					"d": cty.StringVal("config"),
   297  				},
   298  			},
   299  		},
   300  	}
   301  
   302  	for _, test := range tests {
   303  		t.Run(test.name, func(t *testing.T) {
   304  			for k, v := range test.env {
   305  				t.Setenv(k, v)
   306  			}
   307  
   308  			got, diags := VariableValues(test.config, test.inputs...)
   309  			if diags.HasErrors() {
   310  				t.Fatal(diags)
   311  			}
   312  
   313  			opt := cmp.Comparer(func(x, y cty.Value) bool {
   314  				return x.RawEquals(y)
   315  			})
   316  			if diff := cmp.Diff(test.want, got, opt); diff != "" {
   317  				t.Error(diff)
   318  			}
   319  		})
   320  	}
   321  }