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 }