github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/eval_for_each_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/davecgh/go-spew/spew"
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/hashicorp/hcl/v2/hcltest"
    11  	"github.com/hashicorp/terraform/internal/lang/marks"
    12  	"github.com/hashicorp/terraform/internal/tfdiags"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  func TestEvaluateForEachExpression_valid(t *testing.T) {
    17  	tests := map[string]struct {
    18  		Expr       hcl.Expression
    19  		ForEachMap map[string]cty.Value
    20  	}{
    21  		"empty set": {
    22  			hcltest.MockExprLiteral(cty.SetValEmpty(cty.String)),
    23  			map[string]cty.Value{},
    24  		},
    25  		"multi-value string set": {
    26  			hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")})),
    27  			map[string]cty.Value{
    28  				"a": cty.StringVal("a"),
    29  				"b": cty.StringVal("b"),
    30  			},
    31  		},
    32  		"empty map": {
    33  			hcltest.MockExprLiteral(cty.MapValEmpty(cty.Bool)),
    34  			map[string]cty.Value{},
    35  		},
    36  		"map": {
    37  			hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
    38  				"a": cty.BoolVal(true),
    39  				"b": cty.BoolVal(false),
    40  			})),
    41  			map[string]cty.Value{
    42  				"a": cty.BoolVal(true),
    43  				"b": cty.BoolVal(false),
    44  			},
    45  		},
    46  		"map containing unknown values": {
    47  			hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
    48  				"a": cty.UnknownVal(cty.Bool),
    49  				"b": cty.UnknownVal(cty.Bool),
    50  			})),
    51  			map[string]cty.Value{
    52  				"a": cty.UnknownVal(cty.Bool),
    53  				"b": cty.UnknownVal(cty.Bool),
    54  			},
    55  		},
    56  		"map containing sensitive values, but strings are literal": {
    57  			hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
    58  				"a": cty.BoolVal(true).Mark(marks.Sensitive),
    59  				"b": cty.BoolVal(false),
    60  			})),
    61  			map[string]cty.Value{
    62  				"a": cty.BoolVal(true).Mark(marks.Sensitive),
    63  				"b": cty.BoolVal(false),
    64  			},
    65  		},
    66  	}
    67  
    68  	for name, test := range tests {
    69  		t.Run(name, func(t *testing.T) {
    70  			ctx := &MockEvalContext{}
    71  			ctx.installSimpleEval()
    72  			forEachMap, diags := evaluateForEachExpression(test.Expr, ctx)
    73  
    74  			if len(diags) != 0 {
    75  				t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
    76  			}
    77  
    78  			if !reflect.DeepEqual(forEachMap, test.ForEachMap) {
    79  				t.Errorf(
    80  					"wrong map value\ngot:  %swant: %s",
    81  					spew.Sdump(forEachMap), spew.Sdump(test.ForEachMap),
    82  				)
    83  			}
    84  
    85  		})
    86  	}
    87  }
    88  
    89  func TestEvaluateForEachExpression_errors(t *testing.T) {
    90  	tests := map[string]struct {
    91  		Expr                               hcl.Expression
    92  		Summary, DetailSubstring           string
    93  		CausedByUnknown, CausedBySensitive bool
    94  	}{
    95  		"null set": {
    96  			hcltest.MockExprLiteral(cty.NullVal(cty.Set(cty.String))),
    97  			"Invalid for_each argument",
    98  			`the given "for_each" argument value is null`,
    99  			false, false,
   100  		},
   101  		"string": {
   102  			hcltest.MockExprLiteral(cty.StringVal("i am definitely a set")),
   103  			"Invalid for_each argument",
   104  			"must be a map, or set of strings, and you have provided a value of type string",
   105  			false, false,
   106  		},
   107  		"list": {
   108  			hcltest.MockExprLiteral(cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("a")})),
   109  			"Invalid for_each argument",
   110  			"must be a map, or set of strings, and you have provided a value of type list",
   111  			false, false,
   112  		},
   113  		"tuple": {
   114  			hcltest.MockExprLiteral(cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")})),
   115  			"Invalid for_each argument",
   116  			"must be a map, or set of strings, and you have provided a value of type tuple",
   117  			false, false,
   118  		},
   119  		"unknown string set": {
   120  			hcltest.MockExprLiteral(cty.UnknownVal(cty.Set(cty.String))),
   121  			"Invalid for_each argument",
   122  			"set includes values derived from resource attributes that cannot be determined until apply",
   123  			true, false,
   124  		},
   125  		"unknown map": {
   126  			hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.Bool))),
   127  			"Invalid for_each argument",
   128  			"map includes keys derived from resource attributes that cannot be determined until apply",
   129  			true, false,
   130  		},
   131  		"marked map": {
   132  			hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
   133  				"a": cty.BoolVal(true),
   134  				"b": cty.BoolVal(false),
   135  			}).Mark(marks.Sensitive)),
   136  			"Invalid for_each argument",
   137  			"Sensitive values, or values derived from sensitive values, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
   138  			false, true,
   139  		},
   140  		"set containing booleans": {
   141  			hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.BoolVal(true)})),
   142  			"Invalid for_each set argument",
   143  			"supports maps and sets of strings, but you have provided a set containing type bool",
   144  			false, false,
   145  		},
   146  		"set containing null": {
   147  			hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.NullVal(cty.String)})),
   148  			"Invalid for_each set argument",
   149  			"must not contain null values",
   150  			false, false,
   151  		},
   152  		"set containing unknown value": {
   153  			hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.UnknownVal(cty.String)})),
   154  			"Invalid for_each argument",
   155  			"set includes values derived from resource attributes that cannot be determined until apply",
   156  			true, false,
   157  		},
   158  		"set containing dynamic unknown value": {
   159  			hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.UnknownVal(cty.DynamicPseudoType)})),
   160  			"Invalid for_each argument",
   161  			"set includes values derived from resource attributes that cannot be determined until apply",
   162  			true, false,
   163  		},
   164  		"set containing marked values": {
   165  			hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.StringVal("beep").Mark(marks.Sensitive), cty.StringVal("boop")})),
   166  			"Invalid for_each argument",
   167  			"Sensitive values, or values derived from sensitive values, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
   168  			false, true,
   169  		},
   170  	}
   171  
   172  	for name, test := range tests {
   173  		t.Run(name, func(t *testing.T) {
   174  			ctx := &MockEvalContext{}
   175  			ctx.installSimpleEval()
   176  			_, diags := evaluateForEachExpression(test.Expr, ctx)
   177  
   178  			if len(diags) != 1 {
   179  				t.Fatalf("got %d diagnostics; want 1", diags)
   180  			}
   181  			if got, want := diags[0].Severity(), tfdiags.Error; got != want {
   182  				t.Errorf("wrong diagnostic severity %#v; want %#v", got, want)
   183  			}
   184  			if got, want := diags[0].Description().Summary, test.Summary; got != want {
   185  				t.Errorf("wrong diagnostic summary\ngot:  %s\nwant: %s", got, want)
   186  			}
   187  			if got, want := diags[0].Description().Detail, test.DetailSubstring; !strings.Contains(got, want) {
   188  				t.Errorf("wrong diagnostic detail\ngot: %s\nwant substring: %s", got, want)
   189  			}
   190  			if fromExpr := diags[0].FromExpr(); fromExpr != nil {
   191  				if fromExpr.Expression == nil {
   192  					t.Errorf("diagnostic does not refer to an expression")
   193  				}
   194  				if fromExpr.EvalContext == nil {
   195  					t.Errorf("diagnostic does not refer to an EvalContext")
   196  				}
   197  			} else {
   198  				t.Errorf("diagnostic does not support FromExpr\ngot: %s", spew.Sdump(diags[0]))
   199  			}
   200  
   201  			if got, want := tfdiags.DiagnosticCausedByUnknown(diags[0]), test.CausedByUnknown; got != want {
   202  				t.Errorf("wrong result from tfdiags.DiagnosticCausedByUnknown\ngot:  %#v\nwant: %#v", got, want)
   203  			}
   204  			if got, want := tfdiags.DiagnosticCausedBySensitive(diags[0]), test.CausedBySensitive; got != want {
   205  				t.Errorf("wrong result from tfdiags.DiagnosticCausedBySensitive\ngot:  %#v\nwant: %#v", got, want)
   206  			}
   207  		})
   208  	}
   209  }
   210  
   211  func TestEvaluateForEachExpressionKnown(t *testing.T) {
   212  	tests := map[string]hcl.Expression{
   213  		"unknown string set": hcltest.MockExprLiteral(cty.UnknownVal(cty.Set(cty.String))),
   214  		"unknown map":        hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.Bool))),
   215  	}
   216  
   217  	for name, expr := range tests {
   218  		t.Run(name, func(t *testing.T) {
   219  			ctx := &MockEvalContext{}
   220  			ctx.installSimpleEval()
   221  			forEachVal, diags := evaluateForEachExpressionValue(expr, ctx, true)
   222  
   223  			if len(diags) != 0 {
   224  				t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   225  			}
   226  
   227  			if forEachVal.IsKnown() {
   228  				t.Error("got known, want unknown")
   229  			}
   230  		})
   231  	}
   232  }