github.com/opentofu/opentofu@v1.7.1/internal/lang/funcs/conversion_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 funcs 7 8 import ( 9 "fmt" 10 "testing" 11 12 "github.com/opentofu/opentofu/internal/lang/marks" 13 "github.com/zclconf/go-cty/cty" 14 ) 15 16 func TestTo(t *testing.T) { 17 tests := []struct { 18 Value cty.Value 19 TargetTy cty.Type 20 Want cty.Value 21 Err string 22 }{ 23 { 24 cty.StringVal("a"), 25 cty.String, 26 cty.StringVal("a"), 27 ``, 28 }, 29 { 30 cty.UnknownVal(cty.String), 31 cty.String, 32 cty.UnknownVal(cty.String), 33 ``, 34 }, 35 { 36 cty.NullVal(cty.String), 37 cty.String, 38 cty.NullVal(cty.String), 39 ``, 40 }, 41 { 42 // This test case represents evaluating the expression tostring(null) 43 // from HCL, since null in HCL is cty.NullVal(cty.DynamicPseudoType). 44 // The result in that case should still be null, but a null specifically 45 // of type string. 46 cty.NullVal(cty.DynamicPseudoType), 47 cty.String, 48 cty.NullVal(cty.String), 49 ``, 50 }, 51 { 52 cty.StringVal("a").Mark("boop"), 53 cty.String, 54 cty.StringVal("a").Mark("boop"), 55 ``, 56 }, 57 { 58 cty.NullVal(cty.String).Mark("boop"), 59 cty.String, 60 cty.NullVal(cty.String).Mark("boop"), 61 ``, 62 }, 63 { 64 cty.True, 65 cty.String, 66 cty.StringVal("true"), 67 ``, 68 }, 69 { 70 cty.StringVal("a"), 71 cty.Bool, 72 cty.DynamicVal, 73 `cannot convert "a" to bool; only the strings "true" or "false" are allowed`, 74 }, 75 { 76 cty.StringVal("a").Mark("boop"), 77 cty.Bool, 78 cty.DynamicVal, 79 `cannot convert "a" to bool; only the strings "true" or "false" are allowed`, 80 }, 81 { 82 cty.StringVal("a").Mark(marks.Sensitive), 83 cty.Bool, 84 cty.DynamicVal, 85 `cannot convert this sensitive string to bool`, 86 }, 87 { 88 cty.StringVal("a"), 89 cty.Number, 90 cty.DynamicVal, 91 `cannot convert "a" to number; given string must be a decimal representation of a number`, 92 }, 93 { 94 cty.StringVal("a").Mark("boop"), 95 cty.Number, 96 cty.DynamicVal, 97 `cannot convert "a" to number; given string must be a decimal representation of a number`, 98 }, 99 { 100 cty.StringVal("a").Mark(marks.Sensitive), 101 cty.Number, 102 cty.DynamicVal, 103 `cannot convert this sensitive string to number`, 104 }, 105 { 106 cty.NullVal(cty.String), 107 cty.Number, 108 cty.NullVal(cty.Number), 109 ``, 110 }, 111 { 112 cty.UnknownVal(cty.Bool), 113 cty.String, 114 cty.UnknownVal(cty.String), 115 ``, 116 }, 117 { 118 cty.UnknownVal(cty.String), 119 cty.Bool, 120 cty.UnknownVal(cty.Bool), // conversion is optimistic 121 ``, 122 }, 123 { 124 cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.True}), 125 cty.List(cty.String), 126 cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("true")}), 127 ``, 128 }, 129 { 130 cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.True}), 131 cty.Set(cty.String), 132 cty.SetVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("true")}), 133 ``, 134 }, 135 { 136 cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.True}), 137 cty.Map(cty.String), 138 cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("true")}), 139 ``, 140 }, 141 { 142 cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("world").Mark("boop")}), 143 cty.Map(cty.String), 144 cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("world").Mark("boop")}), 145 ``, 146 }, 147 { 148 cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("world")}).Mark("boop"), 149 cty.Map(cty.String), 150 cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("world")}).Mark("boop"), 151 ``, 152 }, 153 { 154 cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world").Mark("boop")}), 155 cty.List(cty.String), 156 cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world").Mark("boop")}), 157 ``, 158 }, 159 { 160 cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world")}).Mark("boop"), 161 cty.List(cty.String), 162 cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world")}).Mark("boop"), 163 ``, 164 }, 165 { 166 cty.EmptyTupleVal, 167 cty.String, 168 cty.DynamicVal, 169 `cannot convert tuple to string`, 170 }, 171 { 172 cty.UnknownVal(cty.EmptyTuple), 173 cty.String, 174 cty.DynamicVal, 175 `cannot convert tuple to string`, 176 }, 177 { 178 cty.EmptyObjectVal, 179 cty.Object(map[string]cty.Type{"foo": cty.String}), 180 cty.DynamicVal, 181 `incompatible object type for conversion: attribute "foo" is required`, 182 }, 183 } 184 185 for _, test := range tests { 186 t.Run(fmt.Sprintf("to %s(%#v)", test.TargetTy.FriendlyNameForConstraint(), test.Value), func(t *testing.T) { 187 f := MakeToFunc(test.TargetTy) 188 got, err := f.Call([]cty.Value{test.Value}) 189 190 if test.Err != "" { 191 if err == nil { 192 t.Fatal("succeeded; want error") 193 } 194 if got, want := err.Error(), test.Err; got != want { 195 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 196 } 197 return 198 } else if err != nil { 199 t.Fatalf("unexpected error: %s", err) 200 } 201 202 if !got.RawEquals(test.Want) { 203 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 204 } 205 }) 206 } 207 }