github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_reader_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 schema 7 8 import ( 9 "reflect" 10 "testing" 11 ) 12 13 func TestAddrToSchema(t *testing.T) { 14 cases := map[string]struct { 15 Addr []string 16 Schema map[string]*Schema 17 Result []ValueType 18 }{ 19 "full object": { 20 []string{}, 21 map[string]*Schema{ 22 "list": &Schema{ 23 Type: TypeList, 24 Elem: &Schema{Type: TypeInt}, 25 }, 26 }, 27 []ValueType{typeObject}, 28 }, 29 30 "list": { 31 []string{"list"}, 32 map[string]*Schema{ 33 "list": &Schema{ 34 Type: TypeList, 35 Elem: &Schema{Type: TypeInt}, 36 }, 37 }, 38 []ValueType{TypeList}, 39 }, 40 41 "list.#": { 42 []string{"list", "#"}, 43 map[string]*Schema{ 44 "list": &Schema{ 45 Type: TypeList, 46 Elem: &Schema{Type: TypeInt}, 47 }, 48 }, 49 []ValueType{TypeList, TypeInt}, 50 }, 51 52 "list.0": { 53 []string{"list", "0"}, 54 map[string]*Schema{ 55 "list": &Schema{ 56 Type: TypeList, 57 Elem: &Schema{Type: TypeInt}, 58 }, 59 }, 60 []ValueType{TypeList, TypeInt}, 61 }, 62 63 "list.0 with resource": { 64 []string{"list", "0"}, 65 map[string]*Schema{ 66 "list": &Schema{ 67 Type: TypeList, 68 Elem: &Resource{ 69 Schema: map[string]*Schema{ 70 "field": &Schema{Type: TypeString}, 71 }, 72 }, 73 }, 74 }, 75 []ValueType{TypeList, typeObject}, 76 }, 77 78 "list.0.field": { 79 []string{"list", "0", "field"}, 80 map[string]*Schema{ 81 "list": &Schema{ 82 Type: TypeList, 83 Elem: &Resource{ 84 Schema: map[string]*Schema{ 85 "field": &Schema{Type: TypeString}, 86 }, 87 }, 88 }, 89 }, 90 []ValueType{TypeList, typeObject, TypeString}, 91 }, 92 93 "set": { 94 []string{"set"}, 95 map[string]*Schema{ 96 "set": &Schema{ 97 Type: TypeSet, 98 Elem: &Schema{Type: TypeInt}, 99 Set: func(a interface{}) int { 100 return a.(int) 101 }, 102 }, 103 }, 104 []ValueType{TypeSet}, 105 }, 106 107 "set.#": { 108 []string{"set", "#"}, 109 map[string]*Schema{ 110 "set": &Schema{ 111 Type: TypeSet, 112 Elem: &Schema{Type: TypeInt}, 113 Set: func(a interface{}) int { 114 return a.(int) 115 }, 116 }, 117 }, 118 []ValueType{TypeSet, TypeInt}, 119 }, 120 121 "set.0": { 122 []string{"set", "0"}, 123 map[string]*Schema{ 124 "set": &Schema{ 125 Type: TypeSet, 126 Elem: &Schema{Type: TypeInt}, 127 Set: func(a interface{}) int { 128 return a.(int) 129 }, 130 }, 131 }, 132 []ValueType{TypeSet, TypeInt}, 133 }, 134 135 "set.0 with resource": { 136 []string{"set", "0"}, 137 map[string]*Schema{ 138 "set": &Schema{ 139 Type: TypeSet, 140 Elem: &Resource{ 141 Schema: map[string]*Schema{ 142 "field": &Schema{Type: TypeString}, 143 }, 144 }, 145 }, 146 }, 147 []ValueType{TypeSet, typeObject}, 148 }, 149 150 "mapElem": { 151 []string{"map", "foo"}, 152 map[string]*Schema{ 153 "map": &Schema{Type: TypeMap}, 154 }, 155 []ValueType{TypeMap, TypeString}, 156 }, 157 158 "setDeep": { 159 []string{"set", "50", "index"}, 160 map[string]*Schema{ 161 "set": &Schema{ 162 Type: TypeSet, 163 Elem: &Resource{ 164 Schema: map[string]*Schema{ 165 "index": &Schema{Type: TypeInt}, 166 "value": &Schema{Type: TypeString}, 167 }, 168 }, 169 Set: func(a interface{}) int { 170 return a.(map[string]interface{})["index"].(int) 171 }, 172 }, 173 }, 174 []ValueType{TypeSet, typeObject, TypeInt}, 175 }, 176 } 177 178 for name, tc := range cases { 179 result := addrToSchema(tc.Addr, tc.Schema) 180 types := make([]ValueType, len(result)) 181 for i, v := range result { 182 types[i] = v.Type 183 } 184 185 if !reflect.DeepEqual(types, tc.Result) { 186 t.Fatalf("%s: %#v", name, types) 187 } 188 } 189 } 190 191 // testFieldReader is a helper that should be used to verify that 192 // a FieldReader behaves properly in all the common cases. 193 func testFieldReader(t *testing.T, f func(map[string]*Schema) FieldReader) { 194 schema := map[string]*Schema{ 195 // Primitives 196 "bool": &Schema{Type: TypeBool}, 197 "float": &Schema{Type: TypeFloat}, 198 "int": &Schema{Type: TypeInt}, 199 "string": &Schema{Type: TypeString}, 200 201 // Lists 202 "list": &Schema{ 203 Type: TypeList, 204 Elem: &Schema{Type: TypeString}, 205 }, 206 "listInt": &Schema{ 207 Type: TypeList, 208 Elem: &Schema{Type: TypeInt}, 209 }, 210 "listMap": &Schema{ 211 Type: TypeList, 212 Elem: &Schema{ 213 Type: TypeMap, 214 }, 215 }, 216 217 // Maps 218 "map": &Schema{Type: TypeMap}, 219 "mapInt": &Schema{ 220 Type: TypeMap, 221 Elem: TypeInt, 222 }, 223 224 // This is used to verify that the type of a Map can be specified using the 225 // same syntax as for lists (as a nested *Schema passed to Elem) 226 "mapIntNestedSchema": &Schema{ 227 Type: TypeMap, 228 Elem: &Schema{Type: TypeInt}, 229 }, 230 "mapFloat": &Schema{ 231 Type: TypeMap, 232 Elem: TypeFloat, 233 }, 234 "mapBool": &Schema{ 235 Type: TypeMap, 236 Elem: TypeBool, 237 }, 238 239 // Sets 240 "set": &Schema{ 241 Type: TypeSet, 242 Elem: &Schema{Type: TypeInt}, 243 Set: func(a interface{}) int { 244 return a.(int) 245 }, 246 }, 247 "setDeep": &Schema{ 248 Type: TypeSet, 249 Elem: &Resource{ 250 Schema: map[string]*Schema{ 251 "index": &Schema{Type: TypeInt}, 252 "value": &Schema{Type: TypeString}, 253 }, 254 }, 255 Set: func(a interface{}) int { 256 return a.(map[string]interface{})["index"].(int) 257 }, 258 }, 259 "setEmpty": &Schema{ 260 Type: TypeSet, 261 Elem: &Schema{Type: TypeInt}, 262 Set: func(a interface{}) int { 263 return a.(int) 264 }, 265 }, 266 } 267 268 cases := map[string]struct { 269 Addr []string 270 Result FieldReadResult 271 Err bool 272 }{ 273 "noexist": { 274 []string{"boolNOPE"}, 275 FieldReadResult{ 276 Value: nil, 277 Exists: false, 278 Computed: false, 279 }, 280 false, 281 }, 282 283 "bool": { 284 []string{"bool"}, 285 FieldReadResult{ 286 Value: true, 287 Exists: true, 288 Computed: false, 289 }, 290 false, 291 }, 292 293 "float": { 294 []string{"float"}, 295 FieldReadResult{ 296 Value: 3.1415, 297 Exists: true, 298 Computed: false, 299 }, 300 false, 301 }, 302 303 "int": { 304 []string{"int"}, 305 FieldReadResult{ 306 Value: 42, 307 Exists: true, 308 Computed: false, 309 }, 310 false, 311 }, 312 313 "string": { 314 []string{"string"}, 315 FieldReadResult{ 316 Value: "string", 317 Exists: true, 318 Computed: false, 319 }, 320 false, 321 }, 322 323 "list": { 324 []string{"list"}, 325 FieldReadResult{ 326 Value: []interface{}{ 327 "foo", 328 "bar", 329 }, 330 Exists: true, 331 Computed: false, 332 }, 333 false, 334 }, 335 336 "listInt": { 337 []string{"listInt"}, 338 FieldReadResult{ 339 Value: []interface{}{ 340 21, 341 42, 342 }, 343 Exists: true, 344 Computed: false, 345 }, 346 false, 347 }, 348 349 "map": { 350 []string{"map"}, 351 FieldReadResult{ 352 Value: map[string]interface{}{ 353 "foo": "bar", 354 "bar": "baz", 355 }, 356 Exists: true, 357 Computed: false, 358 }, 359 false, 360 }, 361 362 "mapInt": { 363 []string{"mapInt"}, 364 FieldReadResult{ 365 Value: map[string]interface{}{ 366 "one": 1, 367 "two": 2, 368 }, 369 Exists: true, 370 Computed: false, 371 }, 372 false, 373 }, 374 375 "mapIntNestedSchema": { 376 []string{"mapIntNestedSchema"}, 377 FieldReadResult{ 378 Value: map[string]interface{}{ 379 "one": 1, 380 "two": 2, 381 }, 382 Exists: true, 383 Computed: false, 384 }, 385 false, 386 }, 387 388 "mapFloat": { 389 []string{"mapFloat"}, 390 FieldReadResult{ 391 Value: map[string]interface{}{ 392 "oneDotTwo": 1.2, 393 }, 394 Exists: true, 395 Computed: false, 396 }, 397 false, 398 }, 399 400 "mapBool": { 401 []string{"mapBool"}, 402 FieldReadResult{ 403 Value: map[string]interface{}{ 404 "True": true, 405 "False": false, 406 }, 407 Exists: true, 408 Computed: false, 409 }, 410 false, 411 }, 412 413 "mapelem": { 414 []string{"map", "foo"}, 415 FieldReadResult{ 416 Value: "bar", 417 Exists: true, 418 Computed: false, 419 }, 420 false, 421 }, 422 423 "set": { 424 []string{"set"}, 425 FieldReadResult{ 426 Value: []interface{}{10, 50}, 427 Exists: true, 428 Computed: false, 429 }, 430 false, 431 }, 432 433 "setDeep": { 434 []string{"setDeep"}, 435 FieldReadResult{ 436 Value: []interface{}{ 437 map[string]interface{}{ 438 "index": 10, 439 "value": "foo", 440 }, 441 map[string]interface{}{ 442 "index": 50, 443 "value": "bar", 444 }, 445 }, 446 Exists: true, 447 Computed: false, 448 }, 449 false, 450 }, 451 452 "setEmpty": { 453 []string{"setEmpty"}, 454 FieldReadResult{ 455 Value: []interface{}{}, 456 Exists: false, 457 }, 458 false, 459 }, 460 } 461 462 for name, tc := range cases { 463 r := f(schema) 464 out, err := r.ReadField(tc.Addr) 465 if err != nil != tc.Err { 466 t.Fatalf("%s: err: %s", name, err) 467 } 468 if s, ok := out.Value.(*Set); ok { 469 // If it is a set, convert to a list so its more easily checked. 470 out.Value = s.List() 471 } 472 if !reflect.DeepEqual(tc.Result, out) { 473 t.Fatalf("%s: bad: %#v", name, out) 474 } 475 } 476 }