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