github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_writer_map_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 TestMapFieldWriter_impl(t *testing.T) { 14 var _ FieldWriter = new(MapFieldWriter) 15 } 16 17 func TestMapFieldWriter(t *testing.T) { 18 schema := map[string]*Schema{ 19 "bool": &Schema{Type: TypeBool}, 20 "int": &Schema{Type: TypeInt}, 21 "string": &Schema{Type: TypeString}, 22 "list": &Schema{ 23 Type: TypeList, 24 Elem: &Schema{Type: TypeString}, 25 }, 26 "listInt": &Schema{ 27 Type: TypeList, 28 Elem: &Schema{Type: TypeInt}, 29 }, 30 "listResource": &Schema{ 31 Type: TypeList, 32 Optional: true, 33 Computed: true, 34 Elem: &Resource{ 35 Schema: map[string]*Schema{ 36 "value": &Schema{ 37 Type: TypeInt, 38 Optional: true, 39 }, 40 }, 41 }, 42 }, 43 "map": &Schema{Type: TypeMap}, 44 "set": &Schema{ 45 Type: TypeSet, 46 Elem: &Schema{Type: TypeInt}, 47 Set: func(a interface{}) int { 48 return a.(int) 49 }, 50 }, 51 "setDeep": &Schema{ 52 Type: TypeSet, 53 Elem: &Resource{ 54 Schema: map[string]*Schema{ 55 "index": &Schema{Type: TypeInt}, 56 "value": &Schema{Type: TypeString}, 57 }, 58 }, 59 Set: func(a interface{}) int { 60 return a.(map[string]interface{})["index"].(int) 61 }, 62 }, 63 } 64 65 cases := map[string]struct { 66 Addr []string 67 Value interface{} 68 Err bool 69 Out map[string]string 70 }{ 71 "noexist": { 72 []string{"noexist"}, 73 42, 74 true, 75 map[string]string{}, 76 }, 77 78 "bool": { 79 []string{"bool"}, 80 false, 81 false, 82 map[string]string{ 83 "bool": "false", 84 }, 85 }, 86 87 "int": { 88 []string{"int"}, 89 42, 90 false, 91 map[string]string{ 92 "int": "42", 93 }, 94 }, 95 96 "string": { 97 []string{"string"}, 98 "42", 99 false, 100 map[string]string{ 101 "string": "42", 102 }, 103 }, 104 105 "string nil": { 106 []string{"string"}, 107 nil, 108 false, 109 map[string]string{ 110 "string": "", 111 }, 112 }, 113 114 "list of resources": { 115 []string{"listResource"}, 116 []interface{}{ 117 map[string]interface{}{ 118 "value": 80, 119 }, 120 }, 121 false, 122 map[string]string{ 123 "listResource.#": "1", 124 "listResource.0.value": "80", 125 }, 126 }, 127 128 "list of resources empty": { 129 []string{"listResource"}, 130 []interface{}{}, 131 false, 132 map[string]string{ 133 "listResource.#": "0", 134 }, 135 }, 136 137 "list of resources nil": { 138 []string{"listResource"}, 139 nil, 140 false, 141 map[string]string{ 142 "listResource.#": "0", 143 }, 144 }, 145 146 "list of strings": { 147 []string{"list"}, 148 []interface{}{"foo", "bar"}, 149 false, 150 map[string]string{ 151 "list.#": "2", 152 "list.0": "foo", 153 "list.1": "bar", 154 }, 155 }, 156 157 "list element": { 158 []string{"list", "0"}, 159 "string", 160 true, 161 map[string]string{}, 162 }, 163 164 "map": { 165 []string{"map"}, 166 map[string]interface{}{"foo": "bar"}, 167 false, 168 map[string]string{ 169 "map.%": "1", 170 "map.foo": "bar", 171 }, 172 }, 173 174 "map delete": { 175 []string{"map"}, 176 nil, 177 false, 178 map[string]string{ 179 "map": "", 180 }, 181 }, 182 183 "map element": { 184 []string{"map", "foo"}, 185 "bar", 186 true, 187 map[string]string{}, 188 }, 189 190 "set": { 191 []string{"set"}, 192 []interface{}{1, 2, 5}, 193 false, 194 map[string]string{ 195 "set.#": "3", 196 "set.1": "1", 197 "set.2": "2", 198 "set.5": "5", 199 }, 200 }, 201 202 "set nil": { 203 []string{"set"}, 204 nil, 205 false, 206 map[string]string{ 207 "set.#": "0", 208 }, 209 }, 210 211 "set typed nil": { 212 []string{"set"}, 213 func() *Set { return nil }(), 214 false, 215 map[string]string{ 216 "set.#": "0", 217 }, 218 }, 219 220 "set resource": { 221 []string{"setDeep"}, 222 []interface{}{ 223 map[string]interface{}{ 224 "index": 10, 225 "value": "foo", 226 }, 227 map[string]interface{}{ 228 "index": 50, 229 "value": "bar", 230 }, 231 }, 232 false, 233 map[string]string{ 234 "setDeep.#": "2", 235 "setDeep.10.index": "10", 236 "setDeep.10.value": "foo", 237 "setDeep.50.index": "50", 238 "setDeep.50.value": "bar", 239 }, 240 }, 241 242 "set element": { 243 []string{"set", "5"}, 244 5, 245 true, 246 map[string]string{}, 247 }, 248 249 "full object": { 250 nil, 251 map[string]interface{}{ 252 "string": "foo", 253 "list": []interface{}{"foo", "bar"}, 254 }, 255 false, 256 map[string]string{ 257 "string": "foo", 258 "list.#": "2", 259 "list.0": "foo", 260 "list.1": "bar", 261 }, 262 }, 263 } 264 265 for name, tc := range cases { 266 w := &MapFieldWriter{Schema: schema} 267 err := w.WriteField(tc.Addr, tc.Value) 268 if err != nil != tc.Err { 269 t.Fatalf("%s: err: %s", name, err) 270 } 271 272 actual := w.Map() 273 if !reflect.DeepEqual(actual, tc.Out) { 274 t.Fatalf("%s: bad: %#v", name, actual) 275 } 276 } 277 } 278 279 func TestMapFieldWriterCleanSet(t *testing.T) { 280 schema := map[string]*Schema{ 281 "setDeep": &Schema{ 282 Type: TypeSet, 283 Elem: &Resource{ 284 Schema: map[string]*Schema{ 285 "index": &Schema{Type: TypeInt}, 286 "value": &Schema{Type: TypeString}, 287 }, 288 }, 289 Set: func(a interface{}) int { 290 return a.(map[string]interface{})["index"].(int) 291 }, 292 }, 293 } 294 295 values := []struct { 296 Addr []string 297 Value interface{} 298 Out map[string]string 299 }{ 300 { 301 []string{"setDeep"}, 302 []interface{}{ 303 map[string]interface{}{ 304 "index": 10, 305 "value": "foo", 306 }, 307 map[string]interface{}{ 308 "index": 50, 309 "value": "bar", 310 }, 311 }, 312 map[string]string{ 313 "setDeep.#": "2", 314 "setDeep.10.index": "10", 315 "setDeep.10.value": "foo", 316 "setDeep.50.index": "50", 317 "setDeep.50.value": "bar", 318 }, 319 }, 320 { 321 []string{"setDeep"}, 322 []interface{}{ 323 map[string]interface{}{ 324 "index": 20, 325 "value": "baz", 326 }, 327 map[string]interface{}{ 328 "index": 60, 329 "value": "qux", 330 }, 331 }, 332 map[string]string{ 333 "setDeep.#": "2", 334 "setDeep.20.index": "20", 335 "setDeep.20.value": "baz", 336 "setDeep.60.index": "60", 337 "setDeep.60.value": "qux", 338 }, 339 }, 340 { 341 []string{"setDeep"}, 342 []interface{}{ 343 map[string]interface{}{ 344 "index": 30, 345 "value": "one", 346 }, 347 map[string]interface{}{ 348 "index": 70, 349 "value": "two", 350 }, 351 }, 352 map[string]string{ 353 "setDeep.#": "2", 354 "setDeep.30.index": "30", 355 "setDeep.30.value": "one", 356 "setDeep.70.index": "70", 357 "setDeep.70.value": "two", 358 }, 359 }, 360 } 361 362 w := &MapFieldWriter{Schema: schema} 363 364 for n, tc := range values { 365 err := w.WriteField(tc.Addr, tc.Value) 366 if err != nil { 367 t.Fatalf("%d: err: %s", n, err) 368 } 369 370 actual := w.Map() 371 if !reflect.DeepEqual(actual, tc.Out) { 372 t.Fatalf("%d: bad: %#v", n, actual) 373 } 374 } 375 } 376 377 func TestMapFieldWriterCleanList(t *testing.T) { 378 schema := map[string]*Schema{ 379 "listDeep": &Schema{ 380 Type: TypeList, 381 Elem: &Resource{ 382 Schema: map[string]*Schema{ 383 "thing1": &Schema{Type: TypeString}, 384 "thing2": &Schema{Type: TypeString}, 385 }, 386 }, 387 }, 388 } 389 390 values := []struct { 391 Addr []string 392 Value interface{} 393 Out map[string]string 394 }{ 395 { 396 // Base list 397 []string{"listDeep"}, 398 []interface{}{ 399 map[string]interface{}{ 400 "thing1": "a", 401 "thing2": "b", 402 }, 403 map[string]interface{}{ 404 "thing1": "c", 405 "thing2": "d", 406 }, 407 map[string]interface{}{ 408 "thing1": "e", 409 "thing2": "f", 410 }, 411 map[string]interface{}{ 412 "thing1": "g", 413 "thing2": "h", 414 }, 415 }, 416 map[string]string{ 417 "listDeep.#": "4", 418 "listDeep.0.thing1": "a", 419 "listDeep.0.thing2": "b", 420 "listDeep.1.thing1": "c", 421 "listDeep.1.thing2": "d", 422 "listDeep.2.thing1": "e", 423 "listDeep.2.thing2": "f", 424 "listDeep.3.thing1": "g", 425 "listDeep.3.thing2": "h", 426 }, 427 }, 428 { 429 // Remove an element 430 []string{"listDeep"}, 431 []interface{}{ 432 map[string]interface{}{ 433 "thing1": "a", 434 "thing2": "b", 435 }, 436 map[string]interface{}{ 437 "thing1": "c", 438 "thing2": "d", 439 }, 440 map[string]interface{}{ 441 "thing1": "e", 442 "thing2": "f", 443 }, 444 }, 445 map[string]string{ 446 "listDeep.#": "3", 447 "listDeep.0.thing1": "a", 448 "listDeep.0.thing2": "b", 449 "listDeep.1.thing1": "c", 450 "listDeep.1.thing2": "d", 451 "listDeep.2.thing1": "e", 452 "listDeep.2.thing2": "f", 453 }, 454 }, 455 { 456 // Rewrite with missing keys. This should normally not be necessary, as 457 // hopefully the writers are writing zero values as necessary, but for 458 // brevity we want to make sure that what exists in the writer is exactly 459 // what the last write looked like coming from the provider. 460 []string{"listDeep"}, 461 []interface{}{ 462 map[string]interface{}{ 463 "thing1": "a", 464 }, 465 map[string]interface{}{ 466 "thing1": "c", 467 }, 468 map[string]interface{}{ 469 "thing1": "e", 470 }, 471 }, 472 map[string]string{ 473 "listDeep.#": "3", 474 "listDeep.0.thing1": "a", 475 "listDeep.1.thing1": "c", 476 "listDeep.2.thing1": "e", 477 }, 478 }, 479 } 480 481 w := &MapFieldWriter{Schema: schema} 482 483 for n, tc := range values { 484 err := w.WriteField(tc.Addr, tc.Value) 485 if err != nil { 486 t.Fatalf("%d: err: %s", n, err) 487 } 488 489 actual := w.Map() 490 if !reflect.DeepEqual(actual, tc.Out) { 491 t.Fatalf("%d: bad: %#v", n, actual) 492 } 493 } 494 } 495 496 func TestMapFieldWriterCleanMap(t *testing.T) { 497 schema := map[string]*Schema{ 498 "map": &Schema{ 499 Type: TypeMap, 500 }, 501 } 502 503 values := []struct { 504 Value interface{} 505 Out map[string]string 506 }{ 507 { 508 // Base map 509 map[string]interface{}{ 510 "thing1": "a", 511 "thing2": "b", 512 "thing3": "c", 513 "thing4": "d", 514 }, 515 map[string]string{ 516 "map.%": "4", 517 "map.thing1": "a", 518 "map.thing2": "b", 519 "map.thing3": "c", 520 "map.thing4": "d", 521 }, 522 }, 523 { 524 // Base map 525 map[string]interface{}{ 526 "thing1": "a", 527 "thing2": "b", 528 "thing4": "d", 529 }, 530 map[string]string{ 531 "map.%": "3", 532 "map.thing1": "a", 533 "map.thing2": "b", 534 "map.thing4": "d", 535 }, 536 }, 537 } 538 539 w := &MapFieldWriter{Schema: schema} 540 541 for n, tc := range values { 542 err := w.WriteField([]string{"map"}, tc.Value) 543 if err != nil { 544 t.Fatalf("%d: err: %s", n, err) 545 } 546 547 actual := w.Map() 548 if !reflect.DeepEqual(actual, tc.Out) { 549 t.Fatalf("%d: bad: %#v", n, actual) 550 } 551 } 552 }