github.com/pulumi/terraform@v1.4.0/pkg/addrs/move_endpoint_test.go (about) 1 package addrs 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/hashicorp/hcl/v2" 9 "github.com/hashicorp/hcl/v2/hclsyntax" 10 ) 11 12 func TestParseMoveEndpoint(t *testing.T) { 13 tests := []struct { 14 Input string 15 WantRel AbsMoveable // funny intermediate subset of AbsMoveable 16 WantErr string 17 }{ 18 { 19 `foo.bar`, 20 AbsResourceInstance{ 21 Module: RootModuleInstance, 22 Resource: ResourceInstance{ 23 Resource: Resource{ 24 Mode: ManagedResourceMode, 25 Type: "foo", 26 Name: "bar", 27 }, 28 Key: NoKey, 29 }, 30 }, 31 ``, 32 }, 33 { 34 `foo.bar[0]`, 35 AbsResourceInstance{ 36 Module: RootModuleInstance, 37 Resource: ResourceInstance{ 38 Resource: Resource{ 39 Mode: ManagedResourceMode, 40 Type: "foo", 41 Name: "bar", 42 }, 43 Key: IntKey(0), 44 }, 45 }, 46 ``, 47 }, 48 { 49 `foo.bar["a"]`, 50 AbsResourceInstance{ 51 Module: RootModuleInstance, 52 Resource: ResourceInstance{ 53 Resource: Resource{ 54 Mode: ManagedResourceMode, 55 Type: "foo", 56 Name: "bar", 57 }, 58 Key: StringKey("a"), 59 }, 60 }, 61 ``, 62 }, 63 { 64 `module.boop.foo.bar`, 65 AbsResourceInstance{ 66 Module: ModuleInstance{ 67 ModuleInstanceStep{Name: "boop"}, 68 }, 69 Resource: ResourceInstance{ 70 Resource: Resource{ 71 Mode: ManagedResourceMode, 72 Type: "foo", 73 Name: "bar", 74 }, 75 Key: NoKey, 76 }, 77 }, 78 ``, 79 }, 80 { 81 `module.boop.foo.bar[0]`, 82 AbsResourceInstance{ 83 Module: ModuleInstance{ 84 ModuleInstanceStep{Name: "boop"}, 85 }, 86 Resource: ResourceInstance{ 87 Resource: Resource{ 88 Mode: ManagedResourceMode, 89 Type: "foo", 90 Name: "bar", 91 }, 92 Key: IntKey(0), 93 }, 94 }, 95 ``, 96 }, 97 { 98 `module.boop.foo.bar["a"]`, 99 AbsResourceInstance{ 100 Module: ModuleInstance{ 101 ModuleInstanceStep{Name: "boop"}, 102 }, 103 Resource: ResourceInstance{ 104 Resource: Resource{ 105 Mode: ManagedResourceMode, 106 Type: "foo", 107 Name: "bar", 108 }, 109 Key: StringKey("a"), 110 }, 111 }, 112 ``, 113 }, 114 { 115 `data.foo.bar`, 116 AbsResourceInstance{ 117 Module: RootModuleInstance, 118 Resource: ResourceInstance{ 119 Resource: Resource{ 120 Mode: DataResourceMode, 121 Type: "foo", 122 Name: "bar", 123 }, 124 Key: NoKey, 125 }, 126 }, 127 ``, 128 }, 129 { 130 `data.foo.bar[0]`, 131 AbsResourceInstance{ 132 Module: RootModuleInstance, 133 Resource: ResourceInstance{ 134 Resource: Resource{ 135 Mode: DataResourceMode, 136 Type: "foo", 137 Name: "bar", 138 }, 139 Key: IntKey(0), 140 }, 141 }, 142 ``, 143 }, 144 { 145 `data.foo.bar["a"]`, 146 AbsResourceInstance{ 147 Module: RootModuleInstance, 148 Resource: ResourceInstance{ 149 Resource: Resource{ 150 Mode: DataResourceMode, 151 Type: "foo", 152 Name: "bar", 153 }, 154 Key: StringKey("a"), 155 }, 156 }, 157 ``, 158 }, 159 { 160 `module.boop.data.foo.bar`, 161 AbsResourceInstance{ 162 Module: ModuleInstance{ 163 ModuleInstanceStep{Name: "boop"}, 164 }, 165 Resource: ResourceInstance{ 166 Resource: Resource{ 167 Mode: DataResourceMode, 168 Type: "foo", 169 Name: "bar", 170 }, 171 Key: NoKey, 172 }, 173 }, 174 ``, 175 }, 176 { 177 `module.boop.data.foo.bar[0]`, 178 AbsResourceInstance{ 179 Module: ModuleInstance{ 180 ModuleInstanceStep{Name: "boop"}, 181 }, 182 Resource: ResourceInstance{ 183 Resource: Resource{ 184 Mode: DataResourceMode, 185 Type: "foo", 186 Name: "bar", 187 }, 188 Key: IntKey(0), 189 }, 190 }, 191 ``, 192 }, 193 { 194 `module.boop.data.foo.bar["a"]`, 195 AbsResourceInstance{ 196 Module: ModuleInstance{ 197 ModuleInstanceStep{Name: "boop"}, 198 }, 199 Resource: ResourceInstance{ 200 Resource: Resource{ 201 Mode: DataResourceMode, 202 Type: "foo", 203 Name: "bar", 204 }, 205 Key: StringKey("a"), 206 }, 207 }, 208 ``, 209 }, 210 { 211 `module.foo`, 212 ModuleInstance{ 213 ModuleInstanceStep{Name: "foo"}, 214 }, 215 ``, 216 }, 217 { 218 `module.foo[0]`, 219 ModuleInstance{ 220 ModuleInstanceStep{Name: "foo", InstanceKey: IntKey(0)}, 221 }, 222 ``, 223 }, 224 { 225 `module.foo["a"]`, 226 ModuleInstance{ 227 ModuleInstanceStep{Name: "foo", InstanceKey: StringKey("a")}, 228 }, 229 ``, 230 }, 231 { 232 `module.foo.module.bar`, 233 ModuleInstance{ 234 ModuleInstanceStep{Name: "foo"}, 235 ModuleInstanceStep{Name: "bar"}, 236 }, 237 ``, 238 }, 239 { 240 `module.foo[1].module.bar`, 241 ModuleInstance{ 242 ModuleInstanceStep{Name: "foo", InstanceKey: IntKey(1)}, 243 ModuleInstanceStep{Name: "bar"}, 244 }, 245 ``, 246 }, 247 { 248 `module.foo.module.bar[1]`, 249 ModuleInstance{ 250 ModuleInstanceStep{Name: "foo"}, 251 ModuleInstanceStep{Name: "bar", InstanceKey: IntKey(1)}, 252 }, 253 ``, 254 }, 255 { 256 `module.foo[0].module.bar[1]`, 257 ModuleInstance{ 258 ModuleInstanceStep{Name: "foo", InstanceKey: IntKey(0)}, 259 ModuleInstanceStep{Name: "bar", InstanceKey: IntKey(1)}, 260 }, 261 ``, 262 }, 263 { 264 `module`, 265 nil, 266 `Invalid address operator: Prefix "module." must be followed by a module name.`, 267 }, 268 { 269 `module[0]`, 270 nil, 271 `Invalid address operator: Prefix "module." must be followed by a module name.`, 272 }, 273 { 274 `module.foo.data`, 275 nil, 276 `Invalid address: Resource specification must include a resource type and name.`, 277 }, 278 { 279 `module.foo.data.bar`, 280 nil, 281 `Invalid address: Resource specification must include a resource type and name.`, 282 }, 283 { 284 `module.foo.data[0]`, 285 nil, 286 `Invalid address: Resource specification must include a resource type and name.`, 287 }, 288 { 289 `module.foo.data.bar[0]`, 290 nil, 291 `Invalid address: A resource name is required.`, 292 }, 293 { 294 `module.foo.bar`, 295 nil, 296 `Invalid address: Resource specification must include a resource type and name.`, 297 }, 298 { 299 `module.foo.bar[0]`, 300 nil, 301 `Invalid address: A resource name is required.`, 302 }, 303 } 304 305 for _, test := range tests { 306 t.Run(test.Input, func(t *testing.T) { 307 traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.InitialPos) 308 if hclDiags.HasErrors() { 309 // We're not trying to test the HCL parser here, so any 310 // failures at this point are likely to be bugs in the 311 // test case itself. 312 t.Fatalf("syntax error: %s", hclDiags.Error()) 313 } 314 315 moveEp, diags := ParseMoveEndpoint(traversal) 316 317 switch { 318 case test.WantErr != "": 319 if !diags.HasErrors() { 320 t.Fatalf("unexpected success\nwant error: %s", test.WantErr) 321 } 322 gotErr := diags.Err().Error() 323 if gotErr != test.WantErr { 324 t.Fatalf("wrong error\ngot: %s\nwant: %s", gotErr, test.WantErr) 325 } 326 default: 327 if diags.HasErrors() { 328 t.Fatalf("unexpected error: %s", diags.Err().Error()) 329 } 330 if diff := cmp.Diff(test.WantRel, moveEp.relSubject); diff != "" { 331 t.Errorf("wrong result\n%s", diff) 332 } 333 } 334 }) 335 } 336 } 337 338 func TestUnifyMoveEndpoints(t *testing.T) { 339 tests := []struct { 340 InputFrom, InputTo string 341 Module Module 342 WantFrom, WantTo string 343 }{ 344 { 345 InputFrom: `foo.bar`, 346 InputTo: `foo.baz`, 347 Module: RootModule, 348 WantFrom: `foo.bar[*]`, 349 WantTo: `foo.baz[*]`, 350 }, 351 { 352 InputFrom: `foo.bar`, 353 InputTo: `foo.baz`, 354 Module: RootModule.Child("a"), 355 WantFrom: `module.a[*].foo.bar[*]`, 356 WantTo: `module.a[*].foo.baz[*]`, 357 }, 358 { 359 InputFrom: `foo.bar`, 360 InputTo: `module.b[0].foo.baz`, 361 Module: RootModule.Child("a"), 362 WantFrom: `module.a[*].foo.bar[*]`, 363 WantTo: `module.a[*].module.b[0].foo.baz[*]`, 364 }, 365 { 366 InputFrom: `foo.bar`, 367 InputTo: `foo.bar["thing"]`, 368 Module: RootModule, 369 WantFrom: `foo.bar`, 370 WantTo: `foo.bar["thing"]`, 371 }, 372 { 373 InputFrom: `foo.bar["thing"]`, 374 InputTo: `foo.bar`, 375 Module: RootModule, 376 WantFrom: `foo.bar["thing"]`, 377 WantTo: `foo.bar`, 378 }, 379 { 380 InputFrom: `foo.bar["a"]`, 381 InputTo: `foo.bar["b"]`, 382 Module: RootModule, 383 WantFrom: `foo.bar["a"]`, 384 WantTo: `foo.bar["b"]`, 385 }, 386 { 387 InputFrom: `module.foo`, 388 InputTo: `module.bar`, 389 Module: RootModule, 390 WantFrom: `module.foo[*]`, 391 WantTo: `module.bar[*]`, 392 }, 393 { 394 InputFrom: `module.foo`, 395 InputTo: `module.bar.module.baz`, 396 Module: RootModule, 397 WantFrom: `module.foo[*]`, 398 WantTo: `module.bar.module.baz[*]`, 399 }, 400 { 401 InputFrom: `module.foo`, 402 InputTo: `module.bar.module.baz`, 403 Module: RootModule.Child("bloop"), 404 WantFrom: `module.bloop[*].module.foo[*]`, 405 WantTo: `module.bloop[*].module.bar.module.baz[*]`, 406 }, 407 { 408 InputFrom: `module.foo[0]`, 409 InputTo: `module.foo["a"]`, 410 Module: RootModule, 411 WantFrom: `module.foo[0]`, 412 WantTo: `module.foo["a"]`, 413 }, 414 { 415 InputFrom: `module.foo`, 416 InputTo: `module.foo["a"]`, 417 Module: RootModule, 418 WantFrom: `module.foo`, 419 WantTo: `module.foo["a"]`, 420 }, 421 { 422 InputFrom: `module.foo[0]`, 423 InputTo: `module.foo`, 424 Module: RootModule, 425 WantFrom: `module.foo[0]`, 426 WantTo: `module.foo`, 427 }, 428 { 429 InputFrom: `module.foo[0]`, 430 InputTo: `module.foo`, 431 Module: RootModule.Child("bloop"), 432 WantFrom: `module.bloop[*].module.foo[0]`, 433 WantTo: `module.bloop[*].module.foo`, 434 }, 435 { 436 InputFrom: `module.foo`, 437 InputTo: `foo.bar`, 438 Module: RootModule, 439 WantFrom: ``, // Can't unify module call with resource 440 WantTo: ``, 441 }, 442 { 443 InputFrom: `module.foo[0]`, 444 InputTo: `foo.bar`, 445 Module: RootModule, 446 WantFrom: ``, // Can't unify module instance with resource 447 WantTo: ``, 448 }, 449 { 450 InputFrom: `module.foo`, 451 InputTo: `foo.bar[0]`, 452 Module: RootModule, 453 WantFrom: ``, // Can't unify module call with resource instance 454 WantTo: ``, 455 }, 456 { 457 InputFrom: `module.foo[0]`, 458 InputTo: `foo.bar[0]`, 459 Module: RootModule, 460 WantFrom: ``, // Can't unify module instance with resource instance 461 WantTo: ``, 462 }, 463 } 464 465 for _, test := range tests { 466 t.Run(fmt.Sprintf("%s to %s in %s", test.InputFrom, test.InputTo, test.Module), func(t *testing.T) { 467 parseInput := func(input string) *MoveEndpoint { 468 t.Helper() 469 470 traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos) 471 if hclDiags.HasErrors() { 472 // We're not trying to test the HCL parser here, so any 473 // failures at this point are likely to be bugs in the 474 // test case itself. 475 t.Fatalf("syntax error: %s", hclDiags.Error()) 476 } 477 478 moveEp, diags := ParseMoveEndpoint(traversal) 479 if diags.HasErrors() { 480 t.Fatalf("unexpected error: %s", diags.Err().Error()) 481 } 482 return moveEp 483 } 484 485 fromEp := parseInput(test.InputFrom) 486 toEp := parseInput(test.InputTo) 487 488 gotFrom, gotTo := UnifyMoveEndpoints(test.Module, fromEp, toEp) 489 if got, want := gotFrom.String(), test.WantFrom; got != want { 490 t.Errorf("wrong 'from' result\ngot: %s\nwant: %s", got, want) 491 } 492 if got, want := gotTo.String(), test.WantTo; got != want { 493 t.Errorf("wrong 'to' result\ngot: %s\nwant: %s", got, want) 494 } 495 }) 496 } 497 } 498 499 func TestMoveEndpointConfigMoveable(t *testing.T) { 500 tests := []struct { 501 Input string 502 Module Module 503 Want ConfigMoveable 504 }{ 505 { 506 `foo.bar`, 507 RootModule, 508 ConfigResource{ 509 Module: RootModule, 510 Resource: Resource{ 511 Mode: ManagedResourceMode, 512 Type: "foo", 513 Name: "bar", 514 }, 515 }, 516 }, 517 { 518 `foo.bar[0]`, 519 RootModule, 520 ConfigResource{ 521 Module: RootModule, 522 Resource: Resource{ 523 Mode: ManagedResourceMode, 524 Type: "foo", 525 Name: "bar", 526 }, 527 }, 528 }, 529 { 530 `module.foo.bar.baz`, 531 RootModule, 532 ConfigResource{ 533 Module: Module{"foo"}, 534 Resource: Resource{ 535 Mode: ManagedResourceMode, 536 Type: "bar", 537 Name: "baz", 538 }, 539 }, 540 }, 541 { 542 `module.foo[0].bar.baz`, 543 RootModule, 544 ConfigResource{ 545 Module: Module{"foo"}, 546 Resource: Resource{ 547 Mode: ManagedResourceMode, 548 Type: "bar", 549 Name: "baz", 550 }, 551 }, 552 }, 553 { 554 `foo.bar`, 555 Module{"boop"}, 556 ConfigResource{ 557 Module: Module{"boop"}, 558 Resource: Resource{ 559 Mode: ManagedResourceMode, 560 Type: "foo", 561 Name: "bar", 562 }, 563 }, 564 }, 565 { 566 `module.bloop.foo.bar`, 567 Module{"bleep"}, 568 ConfigResource{ 569 Module: Module{"bleep", "bloop"}, 570 Resource: Resource{ 571 Mode: ManagedResourceMode, 572 Type: "foo", 573 Name: "bar", 574 }, 575 }, 576 }, 577 { 578 `module.foo.bar.baz`, 579 RootModule, 580 ConfigResource{ 581 Module: Module{"foo"}, 582 Resource: Resource{ 583 Mode: ManagedResourceMode, 584 Type: "bar", 585 Name: "baz", 586 }, 587 }, 588 }, 589 { 590 `module.foo`, 591 RootModule, 592 Module{"foo"}, 593 }, 594 { 595 `module.foo[0]`, 596 RootModule, 597 Module{"foo"}, 598 }, 599 { 600 `module.bloop`, 601 Module{"bleep"}, 602 Module{"bleep", "bloop"}, 603 }, 604 { 605 `module.bloop[0]`, 606 Module{"bleep"}, 607 Module{"bleep", "bloop"}, 608 }, 609 } 610 611 for _, test := range tests { 612 t.Run(fmt.Sprintf("%s in %s", test.Input, test.Module), func(t *testing.T) { 613 traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.InitialPos) 614 if hclDiags.HasErrors() { 615 // We're not trying to test the HCL parser here, so any 616 // failures at this point are likely to be bugs in the 617 // test case itself. 618 t.Fatalf("syntax error: %s", hclDiags.Error()) 619 } 620 621 moveEp, diags := ParseMoveEndpoint(traversal) 622 if diags.HasErrors() { 623 t.Fatalf("unexpected error: %s", diags.Err().Error()) 624 } 625 626 got := moveEp.ConfigMoveable(test.Module) 627 if diff := cmp.Diff(test.Want, got); diff != "" { 628 t.Errorf("wrong result\n%s", diff) 629 } 630 }) 631 } 632 }