github.com/hugorut/terraform@v1.1.3/src/lang/funcs/defaults_test.go (about) 1 package funcs 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/zclconf/go-cty/cty" 8 ) 9 10 func TestDefaults(t *testing.T) { 11 tests := []struct { 12 Input, Defaults cty.Value 13 Want cty.Value 14 WantErr string 15 }{ 16 { // When *either* input or default are unknown, an unknown is returned. 17 Input: cty.ObjectVal(map[string]cty.Value{ 18 "a": cty.UnknownVal(cty.String), 19 }), 20 Defaults: cty.ObjectVal(map[string]cty.Value{ 21 "a": cty.StringVal("hello"), 22 }), 23 Want: cty.ObjectVal(map[string]cty.Value{ 24 "a": cty.UnknownVal(cty.String), 25 }), 26 }, 27 { 28 // When *either* input or default are unknown, an unknown is 29 // returned with marks from both input and defaults. 30 Input: cty.ObjectVal(map[string]cty.Value{ 31 "a": cty.UnknownVal(cty.String), 32 }), 33 Defaults: cty.ObjectVal(map[string]cty.Value{ 34 "a": cty.StringVal("hello").Mark("marked"), 35 }), 36 Want: cty.ObjectVal(map[string]cty.Value{ 37 "a": cty.UnknownVal(cty.String).Mark("marked"), 38 }), 39 }, 40 { 41 Input: cty.ObjectVal(map[string]cty.Value{ 42 "a": cty.NullVal(cty.String), 43 }), 44 Defaults: cty.ObjectVal(map[string]cty.Value{ 45 "a": cty.StringVal("hello"), 46 }), 47 Want: cty.ObjectVal(map[string]cty.Value{ 48 "a": cty.StringVal("hello"), 49 }), 50 }, 51 { 52 Input: cty.ObjectVal(map[string]cty.Value{ 53 "a": cty.StringVal("hey"), 54 }), 55 Defaults: cty.ObjectVal(map[string]cty.Value{ 56 "a": cty.StringVal("hello"), 57 }), 58 Want: cty.ObjectVal(map[string]cty.Value{ 59 "a": cty.StringVal("hey"), 60 }), 61 }, 62 { 63 Input: cty.ObjectVal(map[string]cty.Value{ 64 "a": cty.NullVal(cty.String), 65 }), 66 Defaults: cty.ObjectVal(map[string]cty.Value{ 67 "a": cty.NullVal(cty.String), 68 }), 69 Want: cty.ObjectVal(map[string]cty.Value{ 70 "a": cty.NullVal(cty.String), 71 }), 72 }, 73 { 74 Input: cty.ObjectVal(map[string]cty.Value{ 75 "a": cty.NullVal(cty.String), 76 }), 77 Defaults: cty.ObjectVal(map[string]cty.Value{}), 78 Want: cty.ObjectVal(map[string]cty.Value{ 79 "a": cty.NullVal(cty.String), 80 }), 81 }, 82 { 83 Input: cty.ObjectVal(map[string]cty.Value{}), 84 Defaults: cty.ObjectVal(map[string]cty.Value{ 85 "a": cty.NullVal(cty.String), 86 }), 87 WantErr: `.a: target type does not expect an attribute named "a"`, 88 }, 89 90 { 91 Input: cty.ObjectVal(map[string]cty.Value{ 92 "a": cty.ListVal([]cty.Value{ 93 cty.NullVal(cty.String), 94 }), 95 }), 96 Defaults: cty.ObjectVal(map[string]cty.Value{ 97 "a": cty.StringVal("hello"), 98 }), 99 Want: cty.ObjectVal(map[string]cty.Value{ 100 "a": cty.ListVal([]cty.Value{ 101 cty.StringVal("hello"), 102 }), 103 }), 104 }, 105 { 106 Input: cty.ObjectVal(map[string]cty.Value{ 107 "a": cty.ListVal([]cty.Value{ 108 cty.NullVal(cty.String), 109 cty.StringVal("hey"), 110 cty.NullVal(cty.String), 111 }), 112 }), 113 Defaults: cty.ObjectVal(map[string]cty.Value{ 114 "a": cty.StringVal("hello"), 115 }), 116 Want: cty.ObjectVal(map[string]cty.Value{ 117 "a": cty.ListVal([]cty.Value{ 118 cty.StringVal("hello"), 119 cty.StringVal("hey"), 120 cty.StringVal("hello"), 121 }), 122 }), 123 }, 124 { 125 // Using defaults with single set elements is a pretty 126 // odd thing to do, but this behavior is just here because 127 // it generalizes from how we handle collections. It's 128 // tested only to ensure it doesn't change accidentally 129 // in future. 130 Input: cty.ObjectVal(map[string]cty.Value{ 131 "a": cty.SetVal([]cty.Value{ 132 cty.NullVal(cty.String), 133 cty.StringVal("hey"), 134 }), 135 }), 136 Defaults: cty.ObjectVal(map[string]cty.Value{ 137 "a": cty.StringVal("hello"), 138 }), 139 Want: cty.ObjectVal(map[string]cty.Value{ 140 "a": cty.SetVal([]cty.Value{ 141 cty.StringVal("hey"), 142 cty.StringVal("hello"), 143 }), 144 }), 145 }, 146 { 147 Input: cty.ObjectVal(map[string]cty.Value{ 148 "a": cty.MapVal(map[string]cty.Value{ 149 "x": cty.NullVal(cty.String), 150 "y": cty.StringVal("hey"), 151 "z": cty.NullVal(cty.String), 152 }), 153 }), 154 Defaults: cty.ObjectVal(map[string]cty.Value{ 155 "a": cty.StringVal("hello"), 156 }), 157 Want: cty.ObjectVal(map[string]cty.Value{ 158 "a": cty.MapVal(map[string]cty.Value{ 159 "x": cty.StringVal("hello"), 160 "y": cty.StringVal("hey"), 161 "z": cty.StringVal("hello"), 162 }), 163 }), 164 }, 165 { 166 Input: cty.ObjectVal(map[string]cty.Value{ 167 "a": cty.ListVal([]cty.Value{ 168 cty.ObjectVal(map[string]cty.Value{ 169 "b": cty.StringVal("hey"), 170 }), 171 cty.ObjectVal(map[string]cty.Value{ 172 "b": cty.NullVal(cty.String), 173 }), 174 cty.ObjectVal(map[string]cty.Value{ 175 "b": cty.StringVal("hey"), 176 }), 177 }), 178 }), 179 Defaults: cty.ObjectVal(map[string]cty.Value{ 180 "a": cty.ObjectVal(map[string]cty.Value{ 181 "b": cty.StringVal("hello"), 182 }), 183 }), 184 Want: cty.ObjectVal(map[string]cty.Value{ 185 "a": cty.ListVal([]cty.Value{ 186 cty.ObjectVal(map[string]cty.Value{ 187 "b": cty.StringVal("hey"), 188 }), 189 cty.ObjectVal(map[string]cty.Value{ 190 "b": cty.StringVal("hello"), 191 }), 192 cty.ObjectVal(map[string]cty.Value{ 193 "b": cty.StringVal("hey"), 194 }), 195 }), 196 }), 197 }, 198 { 199 Input: cty.ListVal([]cty.Value{ 200 cty.ObjectVal(map[string]cty.Value{ 201 "b": cty.StringVal("hey"), 202 }), 203 cty.ObjectVal(map[string]cty.Value{ 204 "b": cty.NullVal(cty.String), 205 }), 206 cty.ObjectVal(map[string]cty.Value{ 207 "b": cty.StringVal("hey"), 208 }), 209 }), 210 Defaults: cty.ObjectVal(map[string]cty.Value{ 211 "b": cty.StringVal("hello"), 212 }), 213 Want: cty.ListVal([]cty.Value{ 214 cty.ObjectVal(map[string]cty.Value{ 215 "b": cty.StringVal("hey"), 216 }), 217 cty.ObjectVal(map[string]cty.Value{ 218 "b": cty.StringVal("hello"), 219 }), 220 cty.ObjectVal(map[string]cty.Value{ 221 "b": cty.StringVal("hey"), 222 }), 223 }), 224 }, 225 { 226 Input: cty.ObjectVal(map[string]cty.Value{ 227 "a": cty.SetVal([]cty.Value{ 228 cty.ObjectVal(map[string]cty.Value{ 229 "b": cty.StringVal("boop"), 230 }), 231 cty.ObjectVal(map[string]cty.Value{ 232 "b": cty.NullVal(cty.String), 233 }), 234 cty.ObjectVal(map[string]cty.Value{ 235 "b": cty.StringVal("hey"), 236 }), 237 }), 238 }), 239 Defaults: cty.ObjectVal(map[string]cty.Value{ 240 "a": cty.ObjectVal(map[string]cty.Value{ 241 "b": cty.StringVal("hello"), 242 }), 243 }), 244 Want: cty.ObjectVal(map[string]cty.Value{ 245 "a": cty.SetVal([]cty.Value{ 246 cty.ObjectVal(map[string]cty.Value{ 247 "b": cty.StringVal("boop"), 248 }), 249 cty.ObjectVal(map[string]cty.Value{ 250 "b": cty.StringVal("hello"), 251 }), 252 cty.ObjectVal(map[string]cty.Value{ 253 "b": cty.StringVal("hey"), 254 }), 255 }), 256 }), 257 }, 258 { 259 Input: cty.ObjectVal(map[string]cty.Value{ 260 "a": cty.SetVal([]cty.Value{ 261 cty.ObjectVal(map[string]cty.Value{ 262 "b": cty.StringVal("hello"), 263 }), 264 cty.ObjectVal(map[string]cty.Value{ 265 "b": cty.NullVal(cty.String), 266 }), 267 }), 268 }), 269 Defaults: cty.ObjectVal(map[string]cty.Value{ 270 "a": cty.ObjectVal(map[string]cty.Value{ 271 "b": cty.StringVal("hello"), 272 }), 273 }), 274 Want: cty.ObjectVal(map[string]cty.Value{ 275 "a": cty.SetVal([]cty.Value{ 276 // After applying defaults, the one with a null value 277 // coalesced with the one with a non-null value, 278 // and so there's only one left. 279 cty.ObjectVal(map[string]cty.Value{ 280 "b": cty.StringVal("hello"), 281 }), 282 }), 283 }), 284 }, 285 { 286 Input: cty.ObjectVal(map[string]cty.Value{ 287 "a": cty.MapVal(map[string]cty.Value{ 288 "boop": cty.ObjectVal(map[string]cty.Value{ 289 "b": cty.StringVal("hey"), 290 }), 291 "beep": cty.ObjectVal(map[string]cty.Value{ 292 "b": cty.NullVal(cty.String), 293 }), 294 }), 295 }), 296 Defaults: cty.ObjectVal(map[string]cty.Value{ 297 "a": cty.ObjectVal(map[string]cty.Value{ 298 "b": cty.StringVal("hello"), 299 }), 300 }), 301 Want: cty.ObjectVal(map[string]cty.Value{ 302 "a": cty.MapVal(map[string]cty.Value{ 303 "boop": cty.ObjectVal(map[string]cty.Value{ 304 "b": cty.StringVal("hey"), 305 }), 306 "beep": cty.ObjectVal(map[string]cty.Value{ 307 "b": cty.StringVal("hello"), 308 }), 309 }), 310 }), 311 }, 312 { 313 Input: cty.ObjectVal(map[string]cty.Value{ 314 "a": cty.ListVal([]cty.Value{ 315 cty.ObjectVal(map[string]cty.Value{ 316 "b": cty.StringVal("hey"), 317 }), 318 cty.ObjectVal(map[string]cty.Value{ 319 "b": cty.NullVal(cty.String), 320 }), 321 cty.ObjectVal(map[string]cty.Value{ 322 "b": cty.StringVal("hey"), 323 }), 324 }), 325 }), 326 Defaults: cty.ObjectVal(map[string]cty.Value{ 327 "a": cty.StringVal("hello"), 328 }), 329 WantErr: `.a: the default value for a collection of an object type must itself be an object type, not string`, 330 }, 331 { 332 Input: cty.ObjectVal(map[string]cty.Value{ 333 "a": cty.ListVal([]cty.Value{ 334 cty.NullVal(cty.String), 335 cty.StringVal("hey"), 336 cty.NullVal(cty.String), 337 }), 338 }), 339 Defaults: cty.ObjectVal(map[string]cty.Value{ 340 // The default value for a list must be a single value 341 // of the list's element type which provides defaults 342 // for each element separately, so the default for a 343 // list of string should be just a single string, not 344 // a list of string. 345 "a": cty.ListVal([]cty.Value{ 346 cty.StringVal("hello"), 347 }), 348 }), 349 WantErr: `.a: invalid default value for string: string required`, 350 }, 351 { 352 Input: cty.ObjectVal(map[string]cty.Value{ 353 "a": cty.TupleVal([]cty.Value{ 354 cty.NullVal(cty.String), 355 cty.StringVal("hey"), 356 cty.NullVal(cty.String), 357 }), 358 }), 359 Defaults: cty.ObjectVal(map[string]cty.Value{ 360 "a": cty.StringVal("hello"), 361 }), 362 WantErr: `.a: the default value for a tuple type must itself be a tuple type, not string`, 363 }, 364 { 365 Input: cty.ObjectVal(map[string]cty.Value{ 366 "a": cty.TupleVal([]cty.Value{ 367 cty.NullVal(cty.String), 368 cty.StringVal("hey"), 369 cty.NullVal(cty.String), 370 }), 371 }), 372 Defaults: cty.ObjectVal(map[string]cty.Value{ 373 "a": cty.TupleVal([]cty.Value{ 374 cty.StringVal("hello 0"), 375 cty.StringVal("hello 1"), 376 cty.StringVal("hello 2"), 377 }), 378 }), 379 Want: cty.ObjectVal(map[string]cty.Value{ 380 "a": cty.TupleVal([]cty.Value{ 381 cty.StringVal("hello 0"), 382 cty.StringVal("hey"), 383 cty.StringVal("hello 2"), 384 }), 385 }), 386 }, 387 { 388 // There's no reason to use this function for plain primitive 389 // types, because the "default" argument in a variable definition 390 // already has the equivalent behavior. This function is only 391 // to deal with the situation of a complex-typed variable where 392 // only parts of the data structure are optional. 393 Input: cty.NullVal(cty.String), 394 Defaults: cty.StringVal("hello"), 395 WantErr: `only object types and collections of object types can have defaults applied`, 396 }, 397 // When applying default values to structural types, null objects or 398 // tuples in the input should be passed through. 399 { 400 Input: cty.ObjectVal(map[string]cty.Value{ 401 "a": cty.NullVal(cty.Object(map[string]cty.Type{ 402 "x": cty.String, 403 "y": cty.String, 404 })), 405 "b": cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})), 406 }), 407 Defaults: cty.ObjectVal(map[string]cty.Value{ 408 "a": cty.ObjectVal(map[string]cty.Value{ 409 "x": cty.StringVal("hello"), 410 "y": cty.StringVal("there"), 411 }), 412 "b": cty.TupleVal([]cty.Value{ 413 cty.StringVal("how are"), 414 cty.StringVal("you?"), 415 }), 416 }), 417 Want: cty.ObjectVal(map[string]cty.Value{ 418 "a": cty.NullVal(cty.Object(map[string]cty.Type{ 419 "x": cty.String, 420 "y": cty.String, 421 })), 422 "b": cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})), 423 }), 424 }, 425 // When applying default values to structural types, we permit null 426 // values in the defaults, and just pass through the input value. 427 { 428 Input: cty.ObjectVal(map[string]cty.Value{ 429 "a": cty.ListVal([]cty.Value{ 430 cty.ObjectVal(map[string]cty.Value{ 431 "p": cty.StringVal("xyz"), 432 "q": cty.StringVal("xyz"), 433 }), 434 }), 435 "b": cty.SetVal([]cty.Value{ 436 cty.TupleVal([]cty.Value{ 437 cty.NumberIntVal(0), 438 cty.NumberIntVal(2), 439 }), 440 cty.TupleVal([]cty.Value{ 441 cty.NumberIntVal(1), 442 cty.NumberIntVal(3), 443 }), 444 }), 445 "c": cty.NullVal(cty.String), 446 }), 447 Defaults: cty.ObjectVal(map[string]cty.Value{ 448 "c": cty.StringVal("tada"), 449 }), 450 Want: cty.ObjectVal(map[string]cty.Value{ 451 "a": cty.ListVal([]cty.Value{ 452 cty.ObjectVal(map[string]cty.Value{ 453 "p": cty.StringVal("xyz"), 454 "q": cty.StringVal("xyz"), 455 }), 456 }), 457 "b": cty.SetVal([]cty.Value{ 458 cty.TupleVal([]cty.Value{ 459 cty.NumberIntVal(0), 460 cty.NumberIntVal(2), 461 }), 462 cty.TupleVal([]cty.Value{ 463 cty.NumberIntVal(1), 464 cty.NumberIntVal(3), 465 }), 466 }), 467 "c": cty.StringVal("tada"), 468 }), 469 }, 470 // When applying default values to collection types, null collections in the 471 // input should result in empty collections in the output. 472 { 473 Input: cty.ObjectVal(map[string]cty.Value{ 474 "a": cty.NullVal(cty.List(cty.String)), 475 "b": cty.NullVal(cty.Map(cty.String)), 476 "c": cty.NullVal(cty.Set(cty.String)), 477 }), 478 Defaults: cty.ObjectVal(map[string]cty.Value{ 479 "a": cty.StringVal("hello"), 480 "b": cty.StringVal("hi"), 481 "c": cty.StringVal("greetings"), 482 }), 483 Want: cty.ObjectVal(map[string]cty.Value{ 484 "a": cty.ListValEmpty(cty.String), 485 "b": cty.MapValEmpty(cty.String), 486 "c": cty.SetValEmpty(cty.String), 487 }), 488 }, 489 // When specifying fallbacks, we allow mismatched primitive attribute 490 // types so long as a safe conversion is possible. This means that we 491 // can accept number or boolean values for string attributes. 492 { 493 Input: cty.ObjectVal(map[string]cty.Value{ 494 "a": cty.NullVal(cty.String), 495 "b": cty.NullVal(cty.String), 496 "c": cty.NullVal(cty.String), 497 }), 498 Defaults: cty.ObjectVal(map[string]cty.Value{ 499 "a": cty.NumberIntVal(5), 500 "b": cty.True, 501 "c": cty.StringVal("greetings"), 502 }), 503 Want: cty.ObjectVal(map[string]cty.Value{ 504 "a": cty.StringVal("5"), 505 "b": cty.StringVal("true"), 506 "c": cty.StringVal("greetings"), 507 }), 508 }, 509 // Fallbacks with mismatched primitive attribute types which do not 510 // have safe conversions must not pass the suitable fallback check, 511 // even if unsafe conversion would be possible. 512 { 513 Input: cty.ObjectVal(map[string]cty.Value{ 514 "a": cty.NullVal(cty.Bool), 515 }), 516 Defaults: cty.ObjectVal(map[string]cty.Value{ 517 "a": cty.StringVal("5"), 518 }), 519 WantErr: ".a: invalid default value for bool: bool required", 520 }, 521 // marks: we should preserve marks from both input value and defaults as leafily as possible 522 { 523 Input: cty.ObjectVal(map[string]cty.Value{ 524 "a": cty.NullVal(cty.String), 525 }), 526 Defaults: cty.ObjectVal(map[string]cty.Value{ 527 "a": cty.StringVal("hello").Mark("world"), 528 }), 529 Want: cty.ObjectVal(map[string]cty.Value{ 530 "a": cty.StringVal("hello").Mark("world"), 531 }), 532 }, 533 { // "unused" marks don't carry over 534 Input: cty.ObjectVal(map[string]cty.Value{ 535 "a": cty.NullVal(cty.String).Mark("a"), 536 }), 537 Defaults: cty.ObjectVal(map[string]cty.Value{ 538 "a": cty.StringVal("hello"), 539 }), 540 Want: cty.ObjectVal(map[string]cty.Value{ 541 "a": cty.StringVal("hello"), 542 }), 543 }, 544 { // Marks on tuples remain attached to individual elements 545 Input: cty.ObjectVal(map[string]cty.Value{ 546 "a": cty.TupleVal([]cty.Value{ 547 cty.NullVal(cty.String), 548 cty.StringVal("hey").Mark("input"), 549 cty.NullVal(cty.String), 550 }), 551 }), 552 Defaults: cty.ObjectVal(map[string]cty.Value{ 553 "a": cty.TupleVal([]cty.Value{ 554 cty.StringVal("hello 0").Mark("fallback"), 555 cty.StringVal("hello 1"), 556 cty.StringVal("hello 2"), 557 }), 558 }), 559 Want: cty.ObjectVal(map[string]cty.Value{ 560 "a": cty.TupleVal([]cty.Value{ 561 cty.StringVal("hello 0").Mark("fallback"), 562 cty.StringVal("hey").Mark("input"), 563 cty.StringVal("hello 2"), 564 }), 565 }), 566 }, 567 { // Marks from list elements 568 Input: cty.ObjectVal(map[string]cty.Value{ 569 "a": cty.ListVal([]cty.Value{ 570 cty.NullVal(cty.String), 571 cty.StringVal("hey").Mark("input"), 572 cty.NullVal(cty.String), 573 }), 574 }), 575 Defaults: cty.ObjectVal(map[string]cty.Value{ 576 "a": cty.StringVal("hello 0").Mark("fallback"), 577 }), 578 Want: cty.ObjectVal(map[string]cty.Value{ 579 "a": cty.ListVal([]cty.Value{ 580 cty.StringVal("hello 0").Mark("fallback"), 581 cty.StringVal("hey").Mark("input"), 582 cty.StringVal("hello 0").Mark("fallback"), 583 }), 584 }), 585 }, 586 { 587 // Sets don't allow individually-marked elements, so the marks 588 // end up aggregating on the set itself anyway in this case. 589 Input: cty.ObjectVal(map[string]cty.Value{ 590 "a": cty.SetVal([]cty.Value{ 591 cty.NullVal(cty.String), 592 cty.NullVal(cty.String), 593 cty.StringVal("hey").Mark("input"), 594 }), 595 }), 596 Defaults: cty.ObjectVal(map[string]cty.Value{ 597 "a": cty.StringVal("hello 0").Mark("fallback"), 598 }), 599 Want: cty.ObjectVal(map[string]cty.Value{ 600 "a": cty.SetVal([]cty.Value{ 601 cty.StringVal("hello 0"), 602 cty.StringVal("hey"), 603 cty.StringVal("hello 0"), 604 }).WithMarks(cty.NewValueMarks("fallback", "input")), 605 }), 606 }, 607 { 608 Input: cty.ObjectVal(map[string]cty.Value{ 609 "a": cty.ListVal([]cty.Value{ 610 cty.NullVal(cty.String), 611 }), 612 }), 613 Defaults: cty.ObjectVal(map[string]cty.Value{ 614 "a": cty.StringVal("hello").Mark("beep"), 615 }).Mark("boop"), 616 // This is the least-intuitive case. The mark "boop" is attached to 617 // the default object, not it's elements, but both marks end up 618 // aggregated on the list element. 619 Want: cty.ObjectVal(map[string]cty.Value{ 620 "a": cty.ListVal([]cty.Value{ 621 cty.StringVal("hello").WithMarks(cty.NewValueMarks("beep", "boop")), 622 }), 623 }), 624 }, 625 } 626 627 for _, test := range tests { 628 t.Run(fmt.Sprintf("defaults(%#v, %#v)", test.Input, test.Defaults), func(t *testing.T) { 629 got, gotErr := Defaults(test.Input, test.Defaults) 630 631 if test.WantErr != "" { 632 if gotErr == nil { 633 t.Fatalf("unexpected success\nwant error: %s", test.WantErr) 634 } 635 if got, want := gotErr.Error(), test.WantErr; got != want { 636 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 637 } 638 return 639 } else if gotErr != nil { 640 t.Fatalf("unexpected error\ngot: %s", gotErr.Error()) 641 } 642 643 if !test.Want.RawEquals(got) { 644 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 645 } 646 }) 647 } 648 }