github.com/opentofu/opentofu@v1.7.1/internal/genconfig/generate_config_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 genconfig 7 8 import ( 9 "strings" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/zclconf/go-cty/cty" 14 15 "github.com/opentofu/opentofu/internal/addrs" 16 "github.com/opentofu/opentofu/internal/configs/configschema" 17 ) 18 19 func TestConfigGeneration(t *testing.T) { 20 tcs := map[string]struct { 21 schema *configschema.Block 22 addr addrs.AbsResourceInstance 23 provider addrs.LocalProviderConfig 24 value cty.Value 25 expected string 26 }{ 27 "simple_resource": { 28 schema: &configschema.Block{ 29 BlockTypes: map[string]*configschema.NestedBlock{ 30 "list_block": { 31 Block: configschema.Block{ 32 Attributes: map[string]*configschema.Attribute{ 33 "nested_value": { 34 Type: cty.String, 35 Optional: true, 36 }, 37 }, 38 }, 39 Nesting: configschema.NestingSingle, 40 }, 41 }, 42 Attributes: map[string]*configschema.Attribute{ 43 "id": { 44 Type: cty.String, 45 Computed: true, 46 }, 47 "value": { 48 Type: cty.String, 49 Optional: true, 50 }, 51 }, 52 }, 53 addr: addrs.AbsResourceInstance{ 54 Module: nil, 55 Resource: addrs.ResourceInstance{ 56 Resource: addrs.Resource{ 57 Mode: addrs.ManagedResourceMode, 58 Type: "tfcoremock_simple_resource", 59 Name: "empty", 60 }, 61 Key: nil, 62 }, 63 }, 64 provider: addrs.LocalProviderConfig{ 65 LocalName: "tfcoremock", 66 }, 67 value: cty.NilVal, 68 expected: ` 69 resource "tfcoremock_simple_resource" "empty" { 70 value = null # OPTIONAL string 71 list_block { # OPTIONAL block 72 nested_value = null # OPTIONAL string 73 } 74 }`, 75 }, 76 "simple_resource_with_state": { 77 schema: &configschema.Block{ 78 BlockTypes: map[string]*configschema.NestedBlock{ 79 "list_block": { 80 Block: configschema.Block{ 81 Attributes: map[string]*configschema.Attribute{ 82 "nested_value": { 83 Type: cty.String, 84 Optional: true, 85 }, 86 }, 87 }, 88 Nesting: configschema.NestingSingle, 89 }, 90 }, 91 Attributes: map[string]*configschema.Attribute{ 92 "id": { 93 Type: cty.String, 94 Computed: true, 95 }, 96 "value": { 97 Type: cty.String, 98 Optional: true, 99 }, 100 }, 101 }, 102 addr: addrs.AbsResourceInstance{ 103 Module: nil, 104 Resource: addrs.ResourceInstance{ 105 Resource: addrs.Resource{ 106 Mode: addrs.ManagedResourceMode, 107 Type: "tfcoremock_simple_resource", 108 Name: "empty", 109 }, 110 Key: nil, 111 }, 112 }, 113 provider: addrs.LocalProviderConfig{ 114 LocalName: "tfcoremock", 115 }, 116 value: cty.ObjectVal(map[string]cty.Value{ 117 "id": cty.StringVal("D2320658"), 118 "value": cty.StringVal("Hello, world!"), 119 "list_block": cty.ObjectVal(map[string]cty.Value{ 120 "nested_value": cty.StringVal("Hello, solar system!"), 121 }), 122 }), 123 expected: ` 124 resource "tfcoremock_simple_resource" "empty" { 125 value = "Hello, world!" 126 list_block { 127 nested_value = "Hello, solar system!" 128 } 129 }`, 130 }, 131 "simple_resource_with_partial_state": { 132 schema: &configschema.Block{ 133 BlockTypes: map[string]*configschema.NestedBlock{ 134 "list_block": { 135 Block: configschema.Block{ 136 Attributes: map[string]*configschema.Attribute{ 137 "nested_value": { 138 Type: cty.String, 139 Optional: true, 140 }, 141 }, 142 }, 143 Nesting: configschema.NestingSingle, 144 }, 145 }, 146 Attributes: map[string]*configschema.Attribute{ 147 "id": { 148 Type: cty.String, 149 Computed: true, 150 }, 151 "value": { 152 Type: cty.String, 153 Optional: true, 154 }, 155 }, 156 }, 157 addr: addrs.AbsResourceInstance{ 158 Module: nil, 159 Resource: addrs.ResourceInstance{ 160 Resource: addrs.Resource{ 161 Mode: addrs.ManagedResourceMode, 162 Type: "tfcoremock_simple_resource", 163 Name: "empty", 164 }, 165 Key: nil, 166 }, 167 }, 168 provider: addrs.LocalProviderConfig{ 169 LocalName: "tfcoremock", 170 }, 171 value: cty.ObjectVal(map[string]cty.Value{ 172 "id": cty.StringVal("D2320658"), 173 "list_block": cty.ObjectVal(map[string]cty.Value{ 174 "nested_value": cty.StringVal("Hello, solar system!"), 175 }), 176 }), 177 expected: ` 178 resource "tfcoremock_simple_resource" "empty" { 179 value = null 180 list_block { 181 nested_value = "Hello, solar system!" 182 } 183 }`, 184 }, 185 "simple_resource_with_alternate_provider": { 186 schema: &configschema.Block{ 187 BlockTypes: map[string]*configschema.NestedBlock{ 188 "list_block": { 189 Block: configschema.Block{ 190 Attributes: map[string]*configschema.Attribute{ 191 "nested_value": { 192 Type: cty.String, 193 Optional: true, 194 }, 195 }, 196 }, 197 Nesting: configschema.NestingSingle, 198 }, 199 }, 200 Attributes: map[string]*configschema.Attribute{ 201 "id": { 202 Type: cty.String, 203 Computed: true, 204 }, 205 "value": { 206 Type: cty.String, 207 Optional: true, 208 }, 209 }, 210 }, 211 addr: addrs.AbsResourceInstance{ 212 Module: nil, 213 Resource: addrs.ResourceInstance{ 214 Resource: addrs.Resource{ 215 Mode: addrs.ManagedResourceMode, 216 Type: "tfcoremock_simple_resource", 217 Name: "empty", 218 }, 219 Key: nil, 220 }, 221 }, 222 provider: addrs.LocalProviderConfig{ 223 LocalName: "mock", 224 }, 225 value: cty.ObjectVal(map[string]cty.Value{ 226 "id": cty.StringVal("D2320658"), 227 "value": cty.StringVal("Hello, world!"), 228 "list_block": cty.ObjectVal(map[string]cty.Value{ 229 "nested_value": cty.StringVal("Hello, solar system!"), 230 }), 231 }), 232 expected: ` 233 resource "tfcoremock_simple_resource" "empty" { 234 provider = mock 235 value = "Hello, world!" 236 list_block { 237 nested_value = "Hello, solar system!" 238 } 239 }`, 240 }, 241 "simple_resource_with_aliased_provider": { 242 schema: &configschema.Block{ 243 BlockTypes: map[string]*configschema.NestedBlock{ 244 "list_block": { 245 Block: configschema.Block{ 246 Attributes: map[string]*configschema.Attribute{ 247 "nested_value": { 248 Type: cty.String, 249 Optional: true, 250 }, 251 }, 252 }, 253 Nesting: configschema.NestingSingle, 254 }, 255 }, 256 Attributes: map[string]*configschema.Attribute{ 257 "id": { 258 Type: cty.String, 259 Computed: true, 260 }, 261 "value": { 262 Type: cty.String, 263 Optional: true, 264 }, 265 }, 266 }, 267 addr: addrs.AbsResourceInstance{ 268 Module: nil, 269 Resource: addrs.ResourceInstance{ 270 Resource: addrs.Resource{ 271 Mode: addrs.ManagedResourceMode, 272 Type: "tfcoremock_simple_resource", 273 Name: "empty", 274 }, 275 Key: nil, 276 }, 277 }, 278 provider: addrs.LocalProviderConfig{ 279 LocalName: "tfcoremock", 280 Alias: "alternate", 281 }, 282 value: cty.ObjectVal(map[string]cty.Value{ 283 "id": cty.StringVal("D2320658"), 284 "value": cty.StringVal("Hello, world!"), 285 "list_block": cty.ObjectVal(map[string]cty.Value{ 286 "nested_value": cty.StringVal("Hello, solar system!"), 287 }), 288 }), 289 expected: ` 290 resource "tfcoremock_simple_resource" "empty" { 291 provider = tfcoremock.alternate 292 value = "Hello, world!" 293 list_block { 294 nested_value = "Hello, solar system!" 295 } 296 }`, 297 }, 298 "resource_with_nulls": { 299 schema: &configschema.Block{ 300 Attributes: map[string]*configschema.Attribute{ 301 "id": { 302 Type: cty.String, 303 Computed: true, 304 }, 305 "single": { 306 NestedType: &configschema.Object{ 307 Attributes: map[string]*configschema.Attribute{}, 308 Nesting: configschema.NestingSingle, 309 }, 310 Required: true, 311 }, 312 "list": { 313 NestedType: &configschema.Object{ 314 Attributes: map[string]*configschema.Attribute{ 315 "nested_id": { 316 Type: cty.String, 317 Optional: true, 318 }, 319 }, 320 Nesting: configschema.NestingList, 321 }, 322 Required: true, 323 }, 324 "map": { 325 NestedType: &configschema.Object{ 326 Attributes: map[string]*configschema.Attribute{ 327 "nested_id": { 328 Type: cty.String, 329 Optional: true, 330 }, 331 }, 332 Nesting: configschema.NestingMap, 333 }, 334 Required: true, 335 }, 336 }, 337 BlockTypes: map[string]*configschema.NestedBlock{ 338 "nested_single": { 339 Nesting: configschema.NestingSingle, 340 Block: configschema.Block{ 341 Attributes: map[string]*configschema.Attribute{ 342 "nested_id": { 343 Type: cty.String, 344 Optional: true, 345 }, 346 }, 347 }, 348 }, 349 // No configschema.NestingGroup example for this test, because this block type can never be null in state. 350 "nested_list": { 351 Nesting: configschema.NestingList, 352 Block: configschema.Block{ 353 Attributes: map[string]*configschema.Attribute{ 354 "nested_id": { 355 Type: cty.String, 356 Optional: true, 357 }, 358 }, 359 }, 360 }, 361 "nested_set": { 362 Nesting: configschema.NestingSet, 363 Block: configschema.Block{ 364 Attributes: map[string]*configschema.Attribute{ 365 "nested_id": { 366 Type: cty.String, 367 Optional: true, 368 }, 369 }, 370 }, 371 }, 372 "nested_map": { 373 Nesting: configschema.NestingMap, 374 Block: configschema.Block{ 375 Attributes: map[string]*configschema.Attribute{ 376 "nested_id": { 377 Type: cty.String, 378 Optional: true, 379 }, 380 }, 381 }, 382 }, 383 }, 384 }, 385 addr: addrs.AbsResourceInstance{ 386 Module: nil, 387 Resource: addrs.ResourceInstance{ 388 Resource: addrs.Resource{ 389 Mode: addrs.ManagedResourceMode, 390 Type: "tfcoremock_simple_resource", 391 Name: "empty", 392 }, 393 Key: nil, 394 }, 395 }, 396 provider: addrs.LocalProviderConfig{ 397 LocalName: "tfcoremock", 398 }, 399 value: cty.ObjectVal(map[string]cty.Value{ 400 "id": cty.StringVal("D2320658"), 401 "single": cty.NullVal(cty.Object(map[string]cty.Type{})), 402 "list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 403 "nested_id": cty.String, 404 }))), 405 "map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ 406 "nested_id": cty.String, 407 }))), 408 "nested_single": cty.NullVal(cty.Object(map[string]cty.Type{ 409 "nested_id": cty.String, 410 })), 411 "nested_list": cty.ListValEmpty(cty.Object(map[string]cty.Type{ 412 "nested_id": cty.String, 413 })), 414 "nested_set": cty.SetValEmpty(cty.Object(map[string]cty.Type{ 415 "nested_id": cty.String, 416 })), 417 "nested_map": cty.MapValEmpty(cty.Object(map[string]cty.Type{ 418 "nested_id": cty.String, 419 })), 420 }), 421 expected: ` 422 resource "tfcoremock_simple_resource" "empty" { 423 list = null 424 map = null 425 single = null 426 }`, 427 }, 428 } 429 for name, tc := range tcs { 430 t.Run(name, func(t *testing.T) { 431 err := tc.schema.InternalValidate() 432 if err != nil { 433 t.Fatalf("schema failed InternalValidate: %s", err) 434 } 435 contents, diags := GenerateResourceContents(tc.addr, tc.schema, tc.provider, tc.value) 436 if len(diags) > 0 { 437 t.Errorf("expected no diagnostics but found %s", diags) 438 } 439 440 got := WrapResourceContents(tc.addr, contents) 441 want := strings.TrimSpace(tc.expected) 442 if diff := cmp.Diff(got, want); len(diff) > 0 { 443 t.Errorf("got:\n%s\nwant:\n%s\ndiff:\n%s", got, want, diff) 444 } 445 }) 446 } 447 }