kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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 "kubeform.dev/terraform-backend-sdk/lang/marks" 12 "kubeform.dev/terraform-backend-sdk/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 }{ 94 "null set": { 95 hcltest.MockExprLiteral(cty.NullVal(cty.Set(cty.String))), 96 "Invalid for_each argument", 97 `the given "for_each" argument value is null`, 98 }, 99 "string": { 100 hcltest.MockExprLiteral(cty.StringVal("i am definitely a set")), 101 "Invalid for_each argument", 102 "must be a map, or set of strings, and you have provided a value of type string", 103 }, 104 "list": { 105 hcltest.MockExprLiteral(cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("a")})), 106 "Invalid for_each argument", 107 "must be a map, or set of strings, and you have provided a value of type list", 108 }, 109 "tuple": { 110 hcltest.MockExprLiteral(cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")})), 111 "Invalid for_each argument", 112 "must be a map, or set of strings, and you have provided a value of type tuple", 113 }, 114 "unknown string set": { 115 hcltest.MockExprLiteral(cty.UnknownVal(cty.Set(cty.String))), 116 "Invalid for_each argument", 117 "depends on resource attributes that cannot be determined until apply", 118 }, 119 "unknown map": { 120 hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.Bool))), 121 "Invalid for_each argument", 122 "depends on resource attributes that cannot be determined until apply", 123 }, 124 "marked map": { 125 hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{ 126 "a": cty.BoolVal(true), 127 "b": cty.BoolVal(false), 128 }).Mark(marks.Sensitive)), 129 "Invalid for_each argument", 130 "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.", 131 }, 132 "set containing booleans": { 133 hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.BoolVal(true)})), 134 "Invalid for_each set argument", 135 "supports maps and sets of strings, but you have provided a set containing type bool", 136 }, 137 "set containing null": { 138 hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.NullVal(cty.String)})), 139 "Invalid for_each set argument", 140 "must not contain null values", 141 }, 142 "set containing unknown value": { 143 hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.UnknownVal(cty.String)})), 144 "Invalid for_each argument", 145 "depends on resource attributes that cannot be determined until apply", 146 }, 147 "set containing dynamic unknown value": { 148 hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.UnknownVal(cty.DynamicPseudoType)})), 149 "Invalid for_each argument", 150 "depends on resource attributes that cannot be determined until apply", 151 }, 152 "set containing marked values": { 153 hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.StringVal("beep").Mark(marks.Sensitive), cty.StringVal("boop")})), 154 "Invalid for_each argument", 155 "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.", 156 }, 157 } 158 159 for name, test := range tests { 160 t.Run(name, func(t *testing.T) { 161 ctx := &MockEvalContext{} 162 ctx.installSimpleEval() 163 _, diags := evaluateForEachExpression(test.Expr, ctx) 164 165 if len(diags) != 1 { 166 t.Fatalf("got %d diagnostics; want 1", diags) 167 } 168 if got, want := diags[0].Severity(), tfdiags.Error; got != want { 169 t.Errorf("wrong diagnostic severity %#v; want %#v", got, want) 170 } 171 if got, want := diags[0].Description().Summary, test.Summary; got != want { 172 t.Errorf("wrong diagnostic summary %#v; want %#v", got, want) 173 } 174 if got, want := diags[0].Description().Detail, test.DetailSubstring; !strings.Contains(got, want) { 175 t.Errorf("wrong diagnostic detail %#v; want %#v", got, want) 176 } 177 if fromExpr := diags[0].FromExpr(); fromExpr != nil { 178 if fromExpr.Expression == nil { 179 t.Errorf("diagnostic does not refer to an expression") 180 } 181 if fromExpr.EvalContext == nil { 182 t.Errorf("diagnostic does not refer to an EvalContext") 183 } 184 } else { 185 t.Errorf("diagnostic does not support FromExpr\ngot: %s", spew.Sdump(diags[0])) 186 } 187 }) 188 } 189 } 190 191 func TestEvaluateForEachExpressionKnown(t *testing.T) { 192 tests := map[string]hcl.Expression{ 193 "unknown string set": hcltest.MockExprLiteral(cty.UnknownVal(cty.Set(cty.String))), 194 "unknown map": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.Bool))), 195 } 196 197 for name, expr := range tests { 198 t.Run(name, func(t *testing.T) { 199 ctx := &MockEvalContext{} 200 ctx.installSimpleEval() 201 forEachVal, diags := evaluateForEachExpressionValue(expr, ctx, true) 202 203 if len(diags) != 0 { 204 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 205 } 206 207 if forEachVal.IsKnown() { 208 t.Error("got known, want unknown") 209 } 210 }) 211 } 212 }