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