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