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