kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/configs/configschema/coerce_value_test.go (about) 1 package configschema 2 3 import ( 4 "testing" 5 6 "github.com/zclconf/go-cty/cty" 7 8 "kubeform.dev/terraform-backend-sdk/tfdiags" 9 ) 10 11 func TestCoerceValue(t *testing.T) { 12 tests := map[string]struct { 13 Schema *Block 14 Input cty.Value 15 WantValue cty.Value 16 WantErr string 17 }{ 18 "empty schema and value": { 19 &Block{}, 20 cty.EmptyObjectVal, 21 cty.EmptyObjectVal, 22 ``, 23 }, 24 "attribute present": { 25 &Block{ 26 Attributes: map[string]*Attribute{ 27 "foo": { 28 Type: cty.String, 29 Optional: true, 30 }, 31 }, 32 }, 33 cty.ObjectVal(map[string]cty.Value{ 34 "foo": cty.True, 35 }), 36 cty.ObjectVal(map[string]cty.Value{ 37 "foo": cty.StringVal("true"), 38 }), 39 ``, 40 }, 41 "single block present": { 42 &Block{ 43 BlockTypes: map[string]*NestedBlock{ 44 "foo": { 45 Block: Block{}, 46 Nesting: NestingSingle, 47 }, 48 }, 49 }, 50 cty.ObjectVal(map[string]cty.Value{ 51 "foo": cty.EmptyObjectVal, 52 }), 53 cty.ObjectVal(map[string]cty.Value{ 54 "foo": cty.EmptyObjectVal, 55 }), 56 ``, 57 }, 58 "single block wrong type": { 59 &Block{ 60 BlockTypes: map[string]*NestedBlock{ 61 "foo": { 62 Block: Block{}, 63 Nesting: NestingSingle, 64 }, 65 }, 66 }, 67 cty.ObjectVal(map[string]cty.Value{ 68 "foo": cty.True, 69 }), 70 cty.DynamicVal, 71 `.foo: an object is required`, 72 }, 73 "list block with one item": { 74 &Block{ 75 BlockTypes: map[string]*NestedBlock{ 76 "foo": { 77 Block: Block{}, 78 Nesting: NestingList, 79 }, 80 }, 81 }, 82 cty.ObjectVal(map[string]cty.Value{ 83 "foo": cty.ListVal([]cty.Value{cty.EmptyObjectVal}), 84 }), 85 cty.ObjectVal(map[string]cty.Value{ 86 "foo": cty.ListVal([]cty.Value{cty.EmptyObjectVal}), 87 }), 88 ``, 89 }, 90 "set block with one item": { 91 &Block{ 92 BlockTypes: map[string]*NestedBlock{ 93 "foo": { 94 Block: Block{}, 95 Nesting: NestingSet, 96 }, 97 }, 98 }, 99 cty.ObjectVal(map[string]cty.Value{ 100 "foo": cty.ListVal([]cty.Value{cty.EmptyObjectVal}), // can implicitly convert to set 101 }), 102 cty.ObjectVal(map[string]cty.Value{ 103 "foo": cty.SetVal([]cty.Value{cty.EmptyObjectVal}), 104 }), 105 ``, 106 }, 107 "map block with one item": { 108 &Block{ 109 BlockTypes: map[string]*NestedBlock{ 110 "foo": { 111 Block: Block{}, 112 Nesting: NestingMap, 113 }, 114 }, 115 }, 116 cty.ObjectVal(map[string]cty.Value{ 117 "foo": cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}), 118 }), 119 cty.ObjectVal(map[string]cty.Value{ 120 "foo": cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}), 121 }), 122 ``, 123 }, 124 "list block with one item having an attribute": { 125 &Block{ 126 BlockTypes: map[string]*NestedBlock{ 127 "foo": { 128 Block: Block{ 129 Attributes: map[string]*Attribute{ 130 "bar": { 131 Type: cty.String, 132 Required: true, 133 }, 134 }, 135 }, 136 Nesting: NestingList, 137 }, 138 }, 139 }, 140 cty.ObjectVal(map[string]cty.Value{ 141 "foo": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 142 "bar": cty.StringVal("hello"), 143 })}), 144 }), 145 cty.ObjectVal(map[string]cty.Value{ 146 "foo": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 147 "bar": cty.StringVal("hello"), 148 })}), 149 }), 150 ``, 151 }, 152 "list block with one item having a missing attribute": { 153 &Block{ 154 BlockTypes: map[string]*NestedBlock{ 155 "foo": { 156 Block: Block{ 157 Attributes: map[string]*Attribute{ 158 "bar": { 159 Type: cty.String, 160 Required: true, 161 }, 162 }, 163 }, 164 Nesting: NestingList, 165 }, 166 }, 167 }, 168 cty.ObjectVal(map[string]cty.Value{ 169 "foo": cty.ListVal([]cty.Value{cty.EmptyObjectVal}), 170 }), 171 cty.DynamicVal, 172 `.foo[0]: attribute "bar" is required`, 173 }, 174 "list block with one item having an extraneous attribute": { 175 &Block{ 176 BlockTypes: map[string]*NestedBlock{ 177 "foo": { 178 Block: Block{}, 179 Nesting: NestingList, 180 }, 181 }, 182 }, 183 cty.ObjectVal(map[string]cty.Value{ 184 "foo": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 185 "bar": cty.StringVal("hello"), 186 })}), 187 }), 188 cty.DynamicVal, 189 `.foo[0]: unexpected attribute "bar"`, 190 }, 191 "missing optional attribute": { 192 &Block{ 193 Attributes: map[string]*Attribute{ 194 "foo": { 195 Type: cty.String, 196 Optional: true, 197 }, 198 }, 199 }, 200 cty.EmptyObjectVal, 201 cty.ObjectVal(map[string]cty.Value{ 202 "foo": cty.NullVal(cty.String), 203 }), 204 ``, 205 }, 206 "missing optional single block": { 207 &Block{ 208 BlockTypes: map[string]*NestedBlock{ 209 "foo": { 210 Block: Block{}, 211 Nesting: NestingSingle, 212 }, 213 }, 214 }, 215 cty.EmptyObjectVal, 216 cty.ObjectVal(map[string]cty.Value{ 217 "foo": cty.NullVal(cty.EmptyObject), 218 }), 219 ``, 220 }, 221 "missing optional list block": { 222 &Block{ 223 BlockTypes: map[string]*NestedBlock{ 224 "foo": { 225 Block: Block{}, 226 Nesting: NestingList, 227 }, 228 }, 229 }, 230 cty.EmptyObjectVal, 231 cty.ObjectVal(map[string]cty.Value{ 232 "foo": cty.ListValEmpty(cty.EmptyObject), 233 }), 234 ``, 235 }, 236 "missing optional set block": { 237 &Block{ 238 BlockTypes: map[string]*NestedBlock{ 239 "foo": { 240 Block: Block{}, 241 Nesting: NestingSet, 242 }, 243 }, 244 }, 245 cty.EmptyObjectVal, 246 cty.ObjectVal(map[string]cty.Value{ 247 "foo": cty.SetValEmpty(cty.EmptyObject), 248 }), 249 ``, 250 }, 251 "missing optional map block": { 252 &Block{ 253 BlockTypes: map[string]*NestedBlock{ 254 "foo": { 255 Block: Block{}, 256 Nesting: NestingMap, 257 }, 258 }, 259 }, 260 cty.EmptyObjectVal, 261 cty.ObjectVal(map[string]cty.Value{ 262 "foo": cty.MapValEmpty(cty.EmptyObject), 263 }), 264 ``, 265 }, 266 "missing required attribute": { 267 &Block{ 268 Attributes: map[string]*Attribute{ 269 "foo": { 270 Type: cty.String, 271 Required: true, 272 }, 273 }, 274 }, 275 cty.EmptyObjectVal, 276 cty.DynamicVal, 277 `attribute "foo" is required`, 278 }, 279 "missing required single block": { 280 &Block{ 281 BlockTypes: map[string]*NestedBlock{ 282 "foo": { 283 Block: Block{}, 284 Nesting: NestingSingle, 285 MinItems: 1, 286 MaxItems: 1, 287 }, 288 }, 289 }, 290 cty.EmptyObjectVal, 291 cty.ObjectVal(map[string]cty.Value{ 292 "foo": cty.NullVal(cty.EmptyObject), 293 }), 294 ``, 295 }, 296 "unknown nested list": { 297 &Block{ 298 Attributes: map[string]*Attribute{ 299 "attr": { 300 Type: cty.String, 301 Required: true, 302 }, 303 }, 304 BlockTypes: map[string]*NestedBlock{ 305 "foo": { 306 Block: Block{}, 307 Nesting: NestingList, 308 MinItems: 2, 309 }, 310 }, 311 }, 312 cty.ObjectVal(map[string]cty.Value{ 313 "attr": cty.StringVal("test"), 314 "foo": cty.UnknownVal(cty.EmptyObject), 315 }), 316 cty.ObjectVal(map[string]cty.Value{ 317 "attr": cty.StringVal("test"), 318 "foo": cty.UnknownVal(cty.List(cty.EmptyObject)), 319 }), 320 "", 321 }, 322 "unknowns in nested list": { 323 &Block{ 324 BlockTypes: map[string]*NestedBlock{ 325 "foo": { 326 Block: Block{ 327 Attributes: map[string]*Attribute{ 328 "attr": { 329 Type: cty.String, 330 Required: true, 331 }, 332 }, 333 }, 334 Nesting: NestingList, 335 MinItems: 2, 336 }, 337 }, 338 }, 339 cty.ObjectVal(map[string]cty.Value{ 340 "foo": cty.ListVal([]cty.Value{ 341 cty.ObjectVal(map[string]cty.Value{ 342 "attr": cty.UnknownVal(cty.String), 343 }), 344 }), 345 }), 346 cty.ObjectVal(map[string]cty.Value{ 347 "foo": cty.ListVal([]cty.Value{ 348 cty.ObjectVal(map[string]cty.Value{ 349 "attr": cty.UnknownVal(cty.String), 350 }), 351 }), 352 }), 353 "", 354 }, 355 "unknown nested set": { 356 &Block{ 357 Attributes: map[string]*Attribute{ 358 "attr": { 359 Type: cty.String, 360 Required: true, 361 }, 362 }, 363 BlockTypes: map[string]*NestedBlock{ 364 "foo": { 365 Block: Block{}, 366 Nesting: NestingSet, 367 MinItems: 1, 368 }, 369 }, 370 }, 371 cty.ObjectVal(map[string]cty.Value{ 372 "attr": cty.StringVal("test"), 373 "foo": cty.UnknownVal(cty.EmptyObject), 374 }), 375 cty.ObjectVal(map[string]cty.Value{ 376 "attr": cty.StringVal("test"), 377 "foo": cty.UnknownVal(cty.Set(cty.EmptyObject)), 378 }), 379 "", 380 }, 381 "unknown nested map": { 382 &Block{ 383 Attributes: map[string]*Attribute{ 384 "attr": { 385 Type: cty.String, 386 Required: true, 387 }, 388 }, 389 BlockTypes: map[string]*NestedBlock{ 390 "foo": { 391 Block: Block{}, 392 Nesting: NestingMap, 393 MinItems: 1, 394 }, 395 }, 396 }, 397 cty.ObjectVal(map[string]cty.Value{ 398 "attr": cty.StringVal("test"), 399 "foo": cty.UnknownVal(cty.Map(cty.String)), 400 }), 401 cty.ObjectVal(map[string]cty.Value{ 402 "attr": cty.StringVal("test"), 403 "foo": cty.UnknownVal(cty.Map(cty.EmptyObject)), 404 }), 405 "", 406 }, 407 "extraneous attribute": { 408 &Block{}, 409 cty.ObjectVal(map[string]cty.Value{ 410 "foo": cty.StringVal("bar"), 411 }), 412 cty.DynamicVal, 413 `unexpected attribute "foo"`, 414 }, 415 "wrong attribute type": { 416 &Block{ 417 Attributes: map[string]*Attribute{ 418 "foo": { 419 Type: cty.Number, 420 Required: true, 421 }, 422 }, 423 }, 424 cty.ObjectVal(map[string]cty.Value{ 425 "foo": cty.False, 426 }), 427 cty.DynamicVal, 428 `.foo: number required`, 429 }, 430 "unset computed value": { 431 &Block{ 432 Attributes: map[string]*Attribute{ 433 "foo": { 434 Type: cty.String, 435 Optional: true, 436 Computed: true, 437 }, 438 }, 439 }, 440 cty.ObjectVal(map[string]cty.Value{}), 441 cty.ObjectVal(map[string]cty.Value{ 442 "foo": cty.NullVal(cty.String), 443 }), 444 ``, 445 }, 446 "dynamic value attributes": { 447 &Block{ 448 BlockTypes: map[string]*NestedBlock{ 449 "foo": { 450 Nesting: NestingMap, 451 Block: Block{ 452 Attributes: map[string]*Attribute{ 453 "bar": { 454 Type: cty.String, 455 Optional: true, 456 Computed: true, 457 }, 458 "baz": { 459 Type: cty.DynamicPseudoType, 460 Optional: true, 461 Computed: true, 462 }, 463 }, 464 }, 465 }, 466 }, 467 }, 468 cty.ObjectVal(map[string]cty.Value{ 469 "foo": cty.ObjectVal(map[string]cty.Value{ 470 "a": cty.ObjectVal(map[string]cty.Value{ 471 "bar": cty.StringVal("beep"), 472 }), 473 "b": cty.ObjectVal(map[string]cty.Value{ 474 "bar": cty.StringVal("boop"), 475 "baz": cty.NumberIntVal(8), 476 }), 477 }), 478 }), 479 cty.ObjectVal(map[string]cty.Value{ 480 "foo": cty.ObjectVal(map[string]cty.Value{ 481 "a": cty.ObjectVal(map[string]cty.Value{ 482 "bar": cty.StringVal("beep"), 483 "baz": cty.NullVal(cty.DynamicPseudoType), 484 }), 485 "b": cty.ObjectVal(map[string]cty.Value{ 486 "bar": cty.StringVal("boop"), 487 "baz": cty.NumberIntVal(8), 488 }), 489 }), 490 }), 491 ``, 492 }, 493 "dynamic attributes in map": { 494 // Convert a block represented as a map to an object if a 495 // DynamicPseudoType causes the element types to mismatch. 496 &Block{ 497 BlockTypes: map[string]*NestedBlock{ 498 "foo": { 499 Nesting: NestingMap, 500 Block: Block{ 501 Attributes: map[string]*Attribute{ 502 "bar": { 503 Type: cty.String, 504 Optional: true, 505 Computed: true, 506 }, 507 "baz": { 508 Type: cty.DynamicPseudoType, 509 Optional: true, 510 Computed: true, 511 }, 512 }, 513 }, 514 }, 515 }, 516 }, 517 cty.ObjectVal(map[string]cty.Value{ 518 "foo": cty.MapVal(map[string]cty.Value{ 519 "a": cty.ObjectVal(map[string]cty.Value{ 520 "bar": cty.StringVal("beep"), 521 }), 522 "b": cty.ObjectVal(map[string]cty.Value{ 523 "bar": cty.StringVal("boop"), 524 }), 525 }), 526 }), 527 cty.ObjectVal(map[string]cty.Value{ 528 "foo": cty.ObjectVal(map[string]cty.Value{ 529 "a": cty.ObjectVal(map[string]cty.Value{ 530 "bar": cty.StringVal("beep"), 531 "baz": cty.NullVal(cty.DynamicPseudoType), 532 }), 533 "b": cty.ObjectVal(map[string]cty.Value{ 534 "bar": cty.StringVal("boop"), 535 "baz": cty.NullVal(cty.DynamicPseudoType), 536 }), 537 }), 538 }), 539 ``, 540 }, 541 "nested types": { 542 // handle NestedTypes 543 &Block{ 544 Attributes: map[string]*Attribute{ 545 "foo": { 546 NestedType: &Object{ 547 Nesting: NestingList, 548 Attributes: map[string]*Attribute{ 549 "bar": { 550 Type: cty.String, 551 Required: true, 552 }, 553 "baz": { 554 Type: cty.Map(cty.String), 555 Optional: true, 556 }, 557 }, 558 }, 559 Optional: true, 560 }, 561 "fob": { 562 NestedType: &Object{ 563 Nesting: NestingSet, 564 Attributes: map[string]*Attribute{ 565 "bar": { 566 Type: cty.String, 567 Optional: true, 568 }, 569 }, 570 }, 571 Optional: true, 572 }, 573 }, 574 }, 575 cty.ObjectVal(map[string]cty.Value{ 576 "foo": cty.ListVal([]cty.Value{ 577 cty.ObjectVal(map[string]cty.Value{ 578 "bar": cty.StringVal("beep"), 579 }), 580 cty.ObjectVal(map[string]cty.Value{ 581 "bar": cty.StringVal("boop"), 582 }), 583 }), 584 }), 585 cty.ObjectVal(map[string]cty.Value{ 586 "foo": cty.ListVal([]cty.Value{ 587 cty.ObjectVal(map[string]cty.Value{ 588 "bar": cty.StringVal("beep"), 589 "baz": cty.NullVal(cty.Map(cty.String)), 590 }), 591 cty.ObjectVal(map[string]cty.Value{ 592 "bar": cty.StringVal("boop"), 593 "baz": cty.NullVal(cty.Map(cty.String)), 594 }), 595 }), 596 "fob": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 597 "bar": cty.String, 598 }))), 599 }), 600 ``, 601 }, 602 } 603 604 for name, test := range tests { 605 t.Run(name, func(t *testing.T) { 606 gotValue, gotErrObj := test.Schema.CoerceValue(test.Input) 607 608 if gotErrObj == nil { 609 if test.WantErr != "" { 610 t.Fatalf("coersion succeeded; want error: %q", test.WantErr) 611 } 612 } else { 613 gotErr := tfdiags.FormatError(gotErrObj) 614 if gotErr != test.WantErr { 615 t.Fatalf("wrong error\ngot: %s\nwant: %s", gotErr, test.WantErr) 616 } 617 return 618 } 619 620 if !gotValue.RawEquals(test.WantValue) { 621 t.Errorf("wrong result\ninput: %#v\ngot: %#v\nwant: %#v", test.Input, gotValue, test.WantValue) 622 } 623 }) 624 } 625 }