github.com/opentofu/opentofu@v1.7.1/internal/plugin/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/tfplugin5" 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 }, 56 &configschema.Block{ 57 Attributes: map[string]*configschema.Attribute{ 58 "computed": { 59 Type: cty.List(cty.Bool), 60 Computed: true, 61 }, 62 "optional": { 63 Type: cty.String, 64 Optional: true, 65 }, 66 "optional_computed": { 67 Type: cty.Map(cty.Bool), 68 Optional: true, 69 Computed: true, 70 }, 71 "required": { 72 Type: cty.Number, 73 Required: true, 74 }, 75 }, 76 }, 77 }, 78 "blocks": { 79 &proto.Schema_Block{ 80 BlockTypes: []*proto.Schema_NestedBlock{ 81 { 82 TypeName: "list", 83 Nesting: proto.Schema_NestedBlock_LIST, 84 Block: &proto.Schema_Block{}, 85 }, 86 { 87 TypeName: "map", 88 Nesting: proto.Schema_NestedBlock_MAP, 89 Block: &proto.Schema_Block{}, 90 }, 91 { 92 TypeName: "set", 93 Nesting: proto.Schema_NestedBlock_SET, 94 Block: &proto.Schema_Block{}, 95 }, 96 { 97 TypeName: "single", 98 Nesting: proto.Schema_NestedBlock_SINGLE, 99 Block: &proto.Schema_Block{ 100 Attributes: []*proto.Schema_Attribute{ 101 { 102 Name: "foo", 103 Type: []byte(`"dynamic"`), 104 Required: true, 105 }, 106 }, 107 }, 108 }, 109 }, 110 }, 111 &configschema.Block{ 112 BlockTypes: map[string]*configschema.NestedBlock{ 113 "list": &configschema.NestedBlock{ 114 Nesting: configschema.NestingList, 115 }, 116 "map": &configschema.NestedBlock{ 117 Nesting: configschema.NestingMap, 118 }, 119 "set": &configschema.NestedBlock{ 120 Nesting: configschema.NestingSet, 121 }, 122 "single": &configschema.NestedBlock{ 123 Nesting: configschema.NestingSingle, 124 Block: configschema.Block{ 125 Attributes: map[string]*configschema.Attribute{ 126 "foo": { 127 Type: cty.DynamicPseudoType, 128 Required: true, 129 }, 130 }, 131 }, 132 }, 133 }, 134 }, 135 }, 136 "deep block nesting": { 137 &proto.Schema_Block{ 138 BlockTypes: []*proto.Schema_NestedBlock{ 139 { 140 TypeName: "single", 141 Nesting: proto.Schema_NestedBlock_SINGLE, 142 Block: &proto.Schema_Block{ 143 BlockTypes: []*proto.Schema_NestedBlock{ 144 { 145 TypeName: "list", 146 Nesting: proto.Schema_NestedBlock_LIST, 147 Block: &proto.Schema_Block{ 148 BlockTypes: []*proto.Schema_NestedBlock{ 149 { 150 TypeName: "set", 151 Nesting: proto.Schema_NestedBlock_SET, 152 Block: &proto.Schema_Block{}, 153 }, 154 }, 155 }, 156 }, 157 }, 158 }, 159 }, 160 }, 161 }, 162 &configschema.Block{ 163 BlockTypes: map[string]*configschema.NestedBlock{ 164 "single": &configschema.NestedBlock{ 165 Nesting: configschema.NestingSingle, 166 Block: configschema.Block{ 167 BlockTypes: map[string]*configschema.NestedBlock{ 168 "list": &configschema.NestedBlock{ 169 Nesting: configschema.NestingList, 170 Block: configschema.Block{ 171 BlockTypes: map[string]*configschema.NestedBlock{ 172 "set": &configschema.NestedBlock{ 173 Nesting: configschema.NestingSet, 174 }, 175 }, 176 }, 177 }, 178 }, 179 }, 180 }, 181 }, 182 }, 183 }, 184 } 185 186 for name, tc := range tests { 187 t.Run(name, func(t *testing.T) { 188 converted := ProtoToConfigSchema(tc.Block) 189 if !cmp.Equal(converted, tc.Want, typeComparer, valueComparer, equateEmpty) { 190 t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, valueComparer, equateEmpty)) 191 } 192 }) 193 } 194 } 195 196 // Test that we can convert configschema to protobuf types and back again. 197 func TestConvertProtoSchemaBlocks(t *testing.T) { 198 tests := map[string]struct { 199 Want *proto.Schema_Block 200 Block *configschema.Block 201 }{ 202 "attributes": { 203 &proto.Schema_Block{ 204 Attributes: []*proto.Schema_Attribute{ 205 { 206 Name: "computed", 207 Type: []byte(`["list","bool"]`), 208 Computed: true, 209 }, 210 { 211 Name: "optional", 212 Type: []byte(`"string"`), 213 Optional: true, 214 }, 215 { 216 Name: "optional_computed", 217 Type: []byte(`["map","bool"]`), 218 Optional: true, 219 Computed: true, 220 }, 221 { 222 Name: "required", 223 Type: []byte(`"number"`), 224 Required: true, 225 }, 226 }, 227 }, 228 &configschema.Block{ 229 Attributes: map[string]*configschema.Attribute{ 230 "computed": { 231 Type: cty.List(cty.Bool), 232 Computed: true, 233 }, 234 "optional": { 235 Type: cty.String, 236 Optional: true, 237 }, 238 "optional_computed": { 239 Type: cty.Map(cty.Bool), 240 Optional: true, 241 Computed: true, 242 }, 243 "required": { 244 Type: cty.Number, 245 Required: true, 246 }, 247 }, 248 }, 249 }, 250 "blocks": { 251 &proto.Schema_Block{ 252 BlockTypes: []*proto.Schema_NestedBlock{ 253 { 254 TypeName: "list", 255 Nesting: proto.Schema_NestedBlock_LIST, 256 Block: &proto.Schema_Block{}, 257 }, 258 { 259 TypeName: "map", 260 Nesting: proto.Schema_NestedBlock_MAP, 261 Block: &proto.Schema_Block{}, 262 }, 263 { 264 TypeName: "set", 265 Nesting: proto.Schema_NestedBlock_SET, 266 Block: &proto.Schema_Block{}, 267 }, 268 { 269 TypeName: "single", 270 Nesting: proto.Schema_NestedBlock_SINGLE, 271 Block: &proto.Schema_Block{ 272 Attributes: []*proto.Schema_Attribute{ 273 { 274 Name: "foo", 275 Type: []byte(`"dynamic"`), 276 Required: true, 277 }, 278 }, 279 }, 280 }, 281 }, 282 }, 283 &configschema.Block{ 284 BlockTypes: map[string]*configschema.NestedBlock{ 285 "list": &configschema.NestedBlock{ 286 Nesting: configschema.NestingList, 287 }, 288 "map": &configschema.NestedBlock{ 289 Nesting: configschema.NestingMap, 290 }, 291 "set": &configschema.NestedBlock{ 292 Nesting: configschema.NestingSet, 293 }, 294 "single": &configschema.NestedBlock{ 295 Nesting: configschema.NestingSingle, 296 Block: configschema.Block{ 297 Attributes: map[string]*configschema.Attribute{ 298 "foo": { 299 Type: cty.DynamicPseudoType, 300 Required: true, 301 }, 302 }, 303 }, 304 }, 305 }, 306 }, 307 }, 308 "deep block nesting": { 309 &proto.Schema_Block{ 310 BlockTypes: []*proto.Schema_NestedBlock{ 311 { 312 TypeName: "single", 313 Nesting: proto.Schema_NestedBlock_SINGLE, 314 Block: &proto.Schema_Block{ 315 BlockTypes: []*proto.Schema_NestedBlock{ 316 { 317 TypeName: "list", 318 Nesting: proto.Schema_NestedBlock_LIST, 319 Block: &proto.Schema_Block{ 320 BlockTypes: []*proto.Schema_NestedBlock{ 321 { 322 TypeName: "set", 323 Nesting: proto.Schema_NestedBlock_SET, 324 Block: &proto.Schema_Block{}, 325 }, 326 }, 327 }, 328 }, 329 }, 330 }, 331 }, 332 }, 333 }, 334 &configschema.Block{ 335 BlockTypes: map[string]*configschema.NestedBlock{ 336 "single": &configschema.NestedBlock{ 337 Nesting: configschema.NestingSingle, 338 Block: configschema.Block{ 339 BlockTypes: map[string]*configschema.NestedBlock{ 340 "list": &configschema.NestedBlock{ 341 Nesting: configschema.NestingList, 342 Block: configschema.Block{ 343 BlockTypes: map[string]*configschema.NestedBlock{ 344 "set": &configschema.NestedBlock{ 345 Nesting: configschema.NestingSet, 346 }, 347 }, 348 }, 349 }, 350 }, 351 }, 352 }, 353 }, 354 }, 355 }, 356 } 357 358 for name, tc := range tests { 359 t.Run(name, func(t *testing.T) { 360 converted := ConfigSchemaToProto(tc.Block) 361 if !cmp.Equal(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported) { 362 t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported)) 363 } 364 }) 365 } 366 }