github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_reader_diff_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 "github.com/opentofu/opentofu/internal/legacy/tofu" 13 ) 14 15 func TestDiffFieldReader_impl(t *testing.T) { 16 var _ FieldReader = new(DiffFieldReader) 17 } 18 19 func TestDiffFieldReader_NestedSetUpdate(t *testing.T) { 20 hashFn := func(a interface{}) int { 21 m := a.(map[string]interface{}) 22 return m["val"].(int) 23 } 24 25 schema := map[string]*Schema{ 26 "list_of_sets_1": &Schema{ 27 Type: TypeList, 28 Elem: &Resource{ 29 Schema: map[string]*Schema{ 30 "nested_set": &Schema{ 31 Type: TypeSet, 32 Elem: &Resource{ 33 Schema: map[string]*Schema{ 34 "val": &Schema{ 35 Type: TypeInt, 36 }, 37 }, 38 }, 39 Set: hashFn, 40 }, 41 }, 42 }, 43 }, 44 "list_of_sets_2": &Schema{ 45 Type: TypeList, 46 Elem: &Resource{ 47 Schema: map[string]*Schema{ 48 "nested_set": &Schema{ 49 Type: TypeSet, 50 Elem: &Resource{ 51 Schema: map[string]*Schema{ 52 "val": &Schema{ 53 Type: TypeInt, 54 }, 55 }, 56 }, 57 Set: hashFn, 58 }, 59 }, 60 }, 61 }, 62 } 63 64 r := &DiffFieldReader{ 65 Schema: schema, 66 Diff: &tofu.InstanceDiff{ 67 Attributes: map[string]*tofu.ResourceAttrDiff{ 68 "list_of_sets_1.0.nested_set.1.val": &tofu.ResourceAttrDiff{ 69 Old: "1", 70 New: "0", 71 NewRemoved: true, 72 }, 73 "list_of_sets_1.0.nested_set.2.val": &tofu.ResourceAttrDiff{ 74 New: "2", 75 }, 76 }, 77 }, 78 } 79 80 r.Source = &MultiLevelFieldReader{ 81 Readers: map[string]FieldReader{ 82 "diff": r, 83 "set": &MapFieldReader{Schema: schema}, 84 "state": &MapFieldReader{ 85 Map: &BasicMapReader{ 86 "list_of_sets_1.#": "1", 87 "list_of_sets_1.0.nested_set.#": "1", 88 "list_of_sets_1.0.nested_set.1.val": "1", 89 "list_of_sets_2.#": "1", 90 "list_of_sets_2.0.nested_set.#": "1", 91 "list_of_sets_2.0.nested_set.1.val": "1", 92 }, 93 Schema: schema, 94 }, 95 }, 96 Levels: []string{"state", "config"}, 97 } 98 99 out, err := r.ReadField([]string{"list_of_sets_2"}) 100 if err != nil { 101 t.Fatalf("err: %v", err) 102 } 103 104 s := &Set{F: hashFn} 105 s.Add(map[string]interface{}{"val": 1}) 106 expected := s.List() 107 108 l := out.Value.([]interface{}) 109 i := l[0].(map[string]interface{}) 110 actual := i["nested_set"].(*Set).List() 111 112 if !reflect.DeepEqual(expected, actual) { 113 t.Fatalf("bad: NestedSetUpdate\n\nexpected: %#v\n\ngot: %#v\n\n", expected, actual) 114 } 115 } 116 117 // https://github.com/hashicorp/terraform/issues/914 118 func TestDiffFieldReader_MapHandling(t *testing.T) { 119 schema := map[string]*Schema{ 120 "tags": &Schema{ 121 Type: TypeMap, 122 }, 123 } 124 r := &DiffFieldReader{ 125 Schema: schema, 126 Diff: &tofu.InstanceDiff{ 127 Attributes: map[string]*tofu.ResourceAttrDiff{ 128 "tags.%": &tofu.ResourceAttrDiff{ 129 Old: "1", 130 New: "2", 131 }, 132 "tags.baz": &tofu.ResourceAttrDiff{ 133 Old: "", 134 New: "qux", 135 }, 136 }, 137 }, 138 Source: &MapFieldReader{ 139 Schema: schema, 140 Map: BasicMapReader(map[string]string{ 141 "tags.%": "1", 142 "tags.foo": "bar", 143 }), 144 }, 145 } 146 147 result, err := r.ReadField([]string{"tags"}) 148 if err != nil { 149 t.Fatalf("ReadField failed: %#v", err) 150 } 151 152 expected := map[string]interface{}{ 153 "foo": "bar", 154 "baz": "qux", 155 } 156 157 if !reflect.DeepEqual(expected, result.Value) { 158 t.Fatalf("bad: DiffHandling\n\nexpected: %#v\n\ngot: %#v\n\n", expected, result.Value) 159 } 160 } 161 162 func TestDiffFieldReader_extra(t *testing.T) { 163 schema := map[string]*Schema{ 164 "stringComputed": &Schema{Type: TypeString}, 165 166 "listMap": &Schema{ 167 Type: TypeList, 168 Elem: &Schema{ 169 Type: TypeMap, 170 }, 171 }, 172 173 "mapRemove": &Schema{Type: TypeMap}, 174 175 "setChange": &Schema{ 176 Type: TypeSet, 177 Optional: true, 178 Elem: &Resource{ 179 Schema: map[string]*Schema{ 180 "index": &Schema{ 181 Type: TypeInt, 182 Required: true, 183 }, 184 185 "value": &Schema{ 186 Type: TypeString, 187 Required: true, 188 }, 189 }, 190 }, 191 Set: func(a interface{}) int { 192 m := a.(map[string]interface{}) 193 return m["index"].(int) 194 }, 195 }, 196 197 "setEmpty": &Schema{ 198 Type: TypeSet, 199 Optional: true, 200 Elem: &Resource{ 201 Schema: map[string]*Schema{ 202 "index": &Schema{ 203 Type: TypeInt, 204 Required: true, 205 }, 206 207 "value": &Schema{ 208 Type: TypeString, 209 Required: true, 210 }, 211 }, 212 }, 213 Set: func(a interface{}) int { 214 m := a.(map[string]interface{}) 215 return m["index"].(int) 216 }, 217 }, 218 } 219 220 r := &DiffFieldReader{ 221 Schema: schema, 222 Diff: &tofu.InstanceDiff{ 223 Attributes: map[string]*tofu.ResourceAttrDiff{ 224 "stringComputed": &tofu.ResourceAttrDiff{ 225 Old: "foo", 226 New: "bar", 227 NewComputed: true, 228 }, 229 230 "listMap.0.bar": &tofu.ResourceAttrDiff{ 231 NewRemoved: true, 232 }, 233 234 "mapRemove.bar": &tofu.ResourceAttrDiff{ 235 NewRemoved: true, 236 }, 237 238 "setChange.10.value": &tofu.ResourceAttrDiff{ 239 Old: "50", 240 New: "80", 241 }, 242 243 "setEmpty.#": &tofu.ResourceAttrDiff{ 244 Old: "2", 245 New: "0", 246 }, 247 }, 248 }, 249 250 Source: &MapFieldReader{ 251 Schema: schema, 252 Map: BasicMapReader(map[string]string{ 253 "listMap.#": "2", 254 "listMap.0.foo": "bar", 255 "listMap.0.bar": "baz", 256 "listMap.1.baz": "baz", 257 258 "mapRemove.foo": "bar", 259 "mapRemove.bar": "bar", 260 261 "setChange.#": "1", 262 "setChange.10.index": "10", 263 "setChange.10.value": "50", 264 265 "setEmpty.#": "2", 266 "setEmpty.10.index": "10", 267 "setEmpty.10.value": "50", 268 "setEmpty.20.index": "20", 269 "setEmpty.20.value": "50", 270 }), 271 }, 272 } 273 274 cases := map[string]struct { 275 Addr []string 276 Result FieldReadResult 277 Err bool 278 }{ 279 "stringComputed": { 280 []string{"stringComputed"}, 281 FieldReadResult{ 282 Value: "", 283 Exists: true, 284 Computed: true, 285 }, 286 false, 287 }, 288 289 "listMapRemoval": { 290 []string{"listMap"}, 291 FieldReadResult{ 292 Value: []interface{}{ 293 map[string]interface{}{ 294 "foo": "bar", 295 }, 296 map[string]interface{}{ 297 "baz": "baz", 298 }, 299 }, 300 Exists: true, 301 }, 302 false, 303 }, 304 305 "mapRemove": { 306 []string{"mapRemove"}, 307 FieldReadResult{ 308 Value: map[string]interface{}{ 309 "foo": "bar", 310 }, 311 Exists: true, 312 Computed: false, 313 }, 314 false, 315 }, 316 317 "setChange": { 318 []string{"setChange"}, 319 FieldReadResult{ 320 Value: []interface{}{ 321 map[string]interface{}{ 322 "index": 10, 323 "value": "80", 324 }, 325 }, 326 Exists: true, 327 }, 328 false, 329 }, 330 331 "setEmpty": { 332 []string{"setEmpty"}, 333 FieldReadResult{ 334 Value: []interface{}{}, 335 Exists: true, 336 }, 337 false, 338 }, 339 } 340 341 for name, tc := range cases { 342 out, err := r.ReadField(tc.Addr) 343 if err != nil != tc.Err { 344 t.Fatalf("%s: err: %s", name, err) 345 } 346 if s, ok := out.Value.(*Set); ok { 347 // If it is a set, convert to a list so its more easily checked. 348 out.Value = s.List() 349 } 350 if !reflect.DeepEqual(tc.Result, out) { 351 t.Fatalf("%s: bad: %#v", name, out) 352 } 353 } 354 } 355 356 func TestDiffFieldReader(t *testing.T) { 357 testFieldReader(t, func(s map[string]*Schema) FieldReader { 358 return &DiffFieldReader{ 359 Schema: s, 360 Diff: &tofu.InstanceDiff{ 361 Attributes: map[string]*tofu.ResourceAttrDiff{ 362 "bool": &tofu.ResourceAttrDiff{ 363 Old: "", 364 New: "true", 365 }, 366 367 "int": &tofu.ResourceAttrDiff{ 368 Old: "", 369 New: "42", 370 }, 371 372 "float": &tofu.ResourceAttrDiff{ 373 Old: "", 374 New: "3.1415", 375 }, 376 377 "string": &tofu.ResourceAttrDiff{ 378 Old: "", 379 New: "string", 380 }, 381 382 "stringComputed": &tofu.ResourceAttrDiff{ 383 Old: "foo", 384 New: "bar", 385 NewComputed: true, 386 }, 387 388 "list.#": &tofu.ResourceAttrDiff{ 389 Old: "0", 390 New: "2", 391 }, 392 393 "list.0": &tofu.ResourceAttrDiff{ 394 Old: "", 395 New: "foo", 396 }, 397 398 "list.1": &tofu.ResourceAttrDiff{ 399 Old: "", 400 New: "bar", 401 }, 402 403 "listInt.#": &tofu.ResourceAttrDiff{ 404 Old: "0", 405 New: "2", 406 }, 407 408 "listInt.0": &tofu.ResourceAttrDiff{ 409 Old: "", 410 New: "21", 411 }, 412 413 "listInt.1": &tofu.ResourceAttrDiff{ 414 Old: "", 415 New: "42", 416 }, 417 418 "map.foo": &tofu.ResourceAttrDiff{ 419 Old: "", 420 New: "bar", 421 }, 422 423 "map.bar": &tofu.ResourceAttrDiff{ 424 Old: "", 425 New: "baz", 426 }, 427 428 "mapInt.%": &tofu.ResourceAttrDiff{ 429 Old: "", 430 New: "2", 431 }, 432 "mapInt.one": &tofu.ResourceAttrDiff{ 433 Old: "", 434 New: "1", 435 }, 436 "mapInt.two": &tofu.ResourceAttrDiff{ 437 Old: "", 438 New: "2", 439 }, 440 441 "mapIntNestedSchema.%": &tofu.ResourceAttrDiff{ 442 Old: "", 443 New: "2", 444 }, 445 "mapIntNestedSchema.one": &tofu.ResourceAttrDiff{ 446 Old: "", 447 New: "1", 448 }, 449 "mapIntNestedSchema.two": &tofu.ResourceAttrDiff{ 450 Old: "", 451 New: "2", 452 }, 453 454 "mapFloat.%": &tofu.ResourceAttrDiff{ 455 Old: "", 456 New: "1", 457 }, 458 "mapFloat.oneDotTwo": &tofu.ResourceAttrDiff{ 459 Old: "", 460 New: "1.2", 461 }, 462 463 "mapBool.%": &tofu.ResourceAttrDiff{ 464 Old: "", 465 New: "2", 466 }, 467 "mapBool.True": &tofu.ResourceAttrDiff{ 468 Old: "", 469 New: "true", 470 }, 471 "mapBool.False": &tofu.ResourceAttrDiff{ 472 Old: "", 473 New: "false", 474 }, 475 476 "set.#": &tofu.ResourceAttrDiff{ 477 Old: "0", 478 New: "2", 479 }, 480 481 "set.10": &tofu.ResourceAttrDiff{ 482 Old: "", 483 New: "10", 484 }, 485 486 "set.50": &tofu.ResourceAttrDiff{ 487 Old: "", 488 New: "50", 489 }, 490 491 "setDeep.#": &tofu.ResourceAttrDiff{ 492 Old: "0", 493 New: "2", 494 }, 495 496 "setDeep.10.index": &tofu.ResourceAttrDiff{ 497 Old: "", 498 New: "10", 499 }, 500 501 "setDeep.10.value": &tofu.ResourceAttrDiff{ 502 Old: "", 503 New: "foo", 504 }, 505 506 "setDeep.50.index": &tofu.ResourceAttrDiff{ 507 Old: "", 508 New: "50", 509 }, 510 511 "setDeep.50.value": &tofu.ResourceAttrDiff{ 512 Old: "", 513 New: "bar", 514 }, 515 }, 516 }, 517 518 Source: &MapFieldReader{ 519 Schema: s, 520 Map: BasicMapReader(map[string]string{ 521 "listMap.#": "2", 522 "listMap.0.foo": "bar", 523 "listMap.0.bar": "baz", 524 "listMap.1.baz": "baz", 525 }), 526 }, 527 } 528 }) 529 }