package addrs import ( "fmt" "strings" "testing" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/terraform/internal/tfdiags" ) func TestModuleInstanceMoveDestination(t *testing.T) { tests := []struct { DeclModule string StmtFrom, StmtTo string Receiver string WantMatch bool WantResult string }{ { ``, `module.foo`, `module.bar`, `module.foo`, true, `module.bar`, }, { ``, `module.foo`, `module.bar`, `module.foo[1]`, true, `module.bar[1]`, }, { ``, `module.foo`, `module.bar`, `module.foo["a"]`, true, `module.bar["a"]`, }, { ``, `module.foo`, `module.bar.module.foo`, `module.foo`, true, `module.bar.module.foo`, }, { ``, `module.foo.module.bar`, `module.bar`, `module.foo.module.bar`, true, `module.bar`, }, { ``, `module.foo[1]`, `module.foo[2]`, `module.foo[1]`, true, `module.foo[2]`, }, { ``, `module.foo[1]`, `module.foo`, `module.foo[1]`, true, `module.foo`, }, { ``, `module.foo`, `module.foo[1]`, `module.foo`, true, `module.foo[1]`, }, { ``, `module.foo`, `module.foo[1]`, `module.foo.module.bar`, true, `module.foo[1].module.bar`, }, { ``, `module.foo`, `module.foo[1]`, `module.foo.module.bar[0]`, true, `module.foo[1].module.bar[0]`, }, { ``, `module.foo`, `module.bar.module.foo`, `module.foo[0]`, true, `module.bar.module.foo[0]`, }, { ``, `module.foo.module.bar`, `module.bar`, `module.foo.module.bar[0]`, true, `module.bar[0]`, }, { `foo`, `module.bar`, `module.baz`, `module.foo.module.bar`, true, `module.foo.module.baz`, }, { `foo`, `module.bar`, `module.baz`, `module.foo[1].module.bar`, true, `module.foo[1].module.baz`, }, { `foo`, `module.bar`, `module.bar[1]`, `module.foo[1].module.bar`, true, `module.foo[1].module.bar[1]`, }, { ``, `module.foo[1]`, `module.foo[2]`, `module.foo`, false, // the receiver has a non-matching instance key (NoKey) ``, }, { ``, `module.foo[1]`, `module.foo[2]`, `module.foo[2]`, false, // the receiver is already the "to" address ``, }, { ``, `module.foo`, `module.bar`, ``, false, // the root module can never be moved ``, }, { `foo`, `module.bar`, `module.bar[1]`, `module.boz`, false, // the receiver is outside the declaration module ``, }, { `foo.bar`, `module.bar`, `module.bar[1]`, `module.boz`, false, // the receiver is outside the declaration module ``, }, { `foo.bar`, `module.a`, `module.b`, `module.boz`, false, // the receiver is outside the declaration module ``, }, { ``, `module.a1.module.a2`, `module.b1.module.b2`, `module.c`, false, // the receiver is outside the declaration module ``, }, { ``, `module.a1.module.a2[0]`, `module.b1.module.b2[1]`, `module.c`, false, // the receiver is outside the declaration module ``, }, { ``, `module.a1.module.a2`, `module.b1.module.b2`, `module.a1.module.b2`, false, // the receiver is outside the declaration module ``, }, { ``, `module.a1.module.a2`, `module.b1.module.b2`, `module.b1.module.a2`, false, // the receiver is outside the declaration module ``, }, { ``, `module.a1.module.a2[0]`, `module.b1.module.b2[1]`, `module.a1.module.b2[0]`, false, // the receiver is outside the declaration module ``, }, { ``, `foo_instance.bar`, `foo_instance.baz`, `module.foo`, false, // a resource address can never match a module instance ``, }, } for _, test := range tests { t.Run( fmt.Sprintf( "%s: %s to %s with %s", test.DeclModule, test.StmtFrom, test.StmtTo, test.Receiver, ), func(t *testing.T) { parseStmtEP := func(t *testing.T, input string) *MoveEndpoint { t.Helper() traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos) if hclDiags.HasErrors() { // We're not trying to test the HCL parser here, so any // failures at this point are likely to be bugs in the // test case itself. t.Fatalf("syntax error: %s", hclDiags.Error()) } moveEp, diags := ParseMoveEndpoint(traversal) if diags.HasErrors() { t.Fatalf("unexpected error: %s", diags.Err().Error()) } return moveEp } fromEPLocal := parseStmtEP(t, test.StmtFrom) toEPLocal := parseStmtEP(t, test.StmtTo) declModule := RootModule if test.DeclModule != "" { declModule = strings.Split(test.DeclModule, ".") } fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal) if fromEP == nil || toEP == nil { t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal) } receiverAddr := RootModuleInstance if test.Receiver != "" { var diags tfdiags.Diagnostics receiverAddr, diags = ParseModuleInstanceStr(test.Receiver) if diags.HasErrors() { t.Fatalf("invalid reciever address: %s", diags.Err().Error()) } } gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP) if !test.WantMatch { if gotMatch { t.Errorf("unexpected match\nreceiver: %s\nfrom: %s\nto: %s\nresult: %s", test.Receiver, fromEP, toEP, gotAddr) } return } if !gotMatch { t.Errorf("unexpected non-match\nreceiver: %s\nfrom: %s\nto: %s", test.Receiver, fromEP, toEP) } if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr { t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr) } }, ) } } func TestAbsResourceInstanceMoveDestination(t *testing.T) { tests := []struct { DeclModule string StmtFrom, StmtTo string Receiver string WantMatch bool WantResult string }{ { ``, `test_object.beep`, `test_object.boop`, `test_object.beep`, true, `test_object.boop`, }, { ``, `test_object.beep`, `test_object.beep[2]`, `test_object.beep`, true, `test_object.beep[2]`, }, { ``, `test_object.beep`, `module.foo.test_object.beep`, `test_object.beep`, true, `module.foo.test_object.beep`, }, { ``, `test_object.beep[2]`, `module.foo.test_object.beep["a"]`, `test_object.beep[2]`, true, `module.foo.test_object.beep["a"]`, }, { ``, `test_object.beep`, `module.foo[0].test_object.beep`, `test_object.beep`, true, `module.foo[0].test_object.beep`, }, { ``, `module.foo.test_object.beep`, `test_object.beep`, `module.foo.test_object.beep`, true, `test_object.beep`, }, { ``, `module.foo[0].test_object.beep`, `test_object.beep`, `module.foo[0].test_object.beep`, true, `test_object.beep`, }, { `foo`, `test_object.beep`, `test_object.boop`, `module.foo[0].test_object.beep`, true, `module.foo[0].test_object.boop`, }, { `foo`, `test_object.beep`, `test_object.beep[1]`, `module.foo[0].test_object.beep`, true, `module.foo[0].test_object.beep[1]`, }, { ``, `test_object.beep`, `test_object.boop`, `test_object.boop`, false, // the reciever is already the "to" address ``, }, { ``, `test_object.beep[1]`, `test_object.beep[2]`, `test_object.beep[5]`, false, // the receiver has a non-matching instance key ``, }, { `foo`, `test_object.beep`, `test_object.boop`, `test_object.beep`, false, // the receiver is not inside an instance of module "foo" ``, }, { `foo.bar`, `test_object.beep`, `test_object.boop`, `test_object.beep`, false, // the receiver is not inside an instance of module "foo.bar" ``, }, { ``, `module.foo[0].test_object.beep`, `test_object.beep`, `module.foo[1].test_object.beep`, false, // receiver is in a different instance of module.foo ``, }, // Moving a module also moves all of the resources declared within it. // The following tests all cover variations of that rule. { ``, `module.foo`, `module.bar`, `module.foo.test_object.beep`, true, `module.bar.test_object.beep`, }, { ``, `module.foo`, `module.bar`, `module.foo[1].test_object.beep`, true, `module.bar[1].test_object.beep`, }, { ``, `module.foo`, `module.bar`, `module.foo["a"].test_object.beep`, true, `module.bar["a"].test_object.beep`, }, { ``, `module.foo`, `module.bar.module.foo`, `module.foo.test_object.beep`, true, `module.bar.module.foo.test_object.beep`, }, { ``, `module.foo.module.bar`, `module.bar`, `module.foo.module.bar.test_object.beep`, true, `module.bar.test_object.beep`, }, { ``, `module.foo[1]`, `module.foo[2]`, `module.foo[1].test_object.beep`, true, `module.foo[2].test_object.beep`, }, { ``, `module.foo[1]`, `module.foo`, `module.foo[1].test_object.beep`, true, `module.foo.test_object.beep`, }, { ``, `module.foo`, `module.foo[1]`, `module.foo.test_object.beep`, true, `module.foo[1].test_object.beep`, }, { ``, `module.foo`, `module.foo[1]`, `module.foo.module.bar.test_object.beep`, true, `module.foo[1].module.bar.test_object.beep`, }, { ``, `module.foo`, `module.foo[1]`, `module.foo.module.bar[0].test_object.beep`, true, `module.foo[1].module.bar[0].test_object.beep`, }, { ``, `module.foo`, `module.bar.module.foo`, `module.foo[0].test_object.beep`, true, `module.bar.module.foo[0].test_object.beep`, }, { ``, `module.foo.module.bar`, `module.bar`, `module.foo.module.bar[0].test_object.beep`, true, `module.bar[0].test_object.beep`, }, { `foo`, `module.bar`, `module.baz`, `module.foo.module.bar.test_object.beep`, true, `module.foo.module.baz.test_object.beep`, }, { `foo`, `module.bar`, `module.baz`, `module.foo[1].module.bar.test_object.beep`, true, `module.foo[1].module.baz.test_object.beep`, }, { `foo`, `module.bar`, `module.bar[1]`, `module.foo[1].module.bar.test_object.beep`, true, `module.foo[1].module.bar[1].test_object.beep`, }, { ``, `module.foo[1]`, `module.foo[2]`, `module.foo.test_object.beep`, false, // the receiver module has a non-matching instance key (NoKey) ``, }, { ``, `module.foo[1]`, `module.foo[2]`, `module.foo[2].test_object.beep`, false, // the receiver is already at the "to" address ``, }, { `foo`, `module.bar`, `module.bar[1]`, `module.boz.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { `foo.bar`, `module.bar`, `module.bar[1]`, `module.boz.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { `foo.bar`, `module.a`, `module.b`, `module.boz.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2`, `module.b1.module.b2`, `module.c.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2[0]`, `module.b1.module.b2[1]`, `module.c.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2`, `module.b1.module.b2`, `module.a1.module.b2.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2`, `module.b1.module.b2`, `module.b1.module.a2.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2[0]`, `module.b1.module.b2[1]`, `module.a1.module.b2[0].test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `foo_instance.bar`, `foo_instance.baz`, `module.foo.test_object.beep`, false, // the resource address is unrelated to the move statements ``, }, } for _, test := range tests { t.Run( fmt.Sprintf( "%s: %s to %s with %s", test.DeclModule, test.StmtFrom, test.StmtTo, test.Receiver, ), func(t *testing.T) { parseStmtEP := func(t *testing.T, input string) *MoveEndpoint { t.Helper() traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos) if hclDiags.HasErrors() { // We're not trying to test the HCL parser here, so any // failures at this point are likely to be bugs in the // test case itself. t.Fatalf("syntax error: %s", hclDiags.Error()) } moveEp, diags := ParseMoveEndpoint(traversal) if diags.HasErrors() { t.Fatalf("unexpected error: %s", diags.Err().Error()) } return moveEp } fromEPLocal := parseStmtEP(t, test.StmtFrom) toEPLocal := parseStmtEP(t, test.StmtTo) declModule := RootModule if test.DeclModule != "" { declModule = strings.Split(test.DeclModule, ".") } fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal) if fromEP == nil || toEP == nil { t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal) } receiverAddr, diags := ParseAbsResourceInstanceStr(test.Receiver) if diags.HasErrors() { t.Fatalf("invalid reciever address: %s", diags.Err().Error()) } gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP) if !test.WantMatch { if gotMatch { t.Errorf("unexpected match\nreceiver: %s\nfrom: %s\nto: %s\nresult: %s", test.Receiver, fromEP, toEP, gotAddr) } return } if !gotMatch { t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom: %s\nto: %s\ngot: (no match)\nwant: %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult) } if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr { t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr) } }, ) } } func TestAbsResourceMoveDestination(t *testing.T) { tests := []struct { DeclModule string StmtFrom, StmtTo string Receiver string WantMatch bool WantResult string }{ { ``, `test_object.beep`, `test_object.boop`, `test_object.beep`, true, `test_object.boop`, }, { ``, `test_object.beep`, `module.foo.test_object.beep`, `test_object.beep`, true, `module.foo.test_object.beep`, }, { ``, `test_object.beep`, `module.foo[0].test_object.beep`, `test_object.beep`, true, `module.foo[0].test_object.beep`, }, { ``, `module.foo.test_object.beep`, `test_object.beep`, `module.foo.test_object.beep`, true, `test_object.beep`, }, { ``, `module.foo[0].test_object.beep`, `test_object.beep`, `module.foo[0].test_object.beep`, true, `test_object.beep`, }, { `foo`, `test_object.beep`, `test_object.boop`, `module.foo[0].test_object.beep`, true, `module.foo[0].test_object.boop`, }, { ``, `test_object.beep`, `test_object.boop`, `test_object.boop`, false, // the reciever is already the "to" address ``, }, { `foo`, `test_object.beep`, `test_object.boop`, `test_object.beep`, false, // the receiver is not inside an instance of module "foo" ``, }, { `foo.bar`, `test_object.beep`, `test_object.boop`, `test_object.beep`, false, // the receiver is not inside an instance of module "foo.bar" ``, }, { ``, `module.foo[0].test_object.beep`, `test_object.beep`, `module.foo[1].test_object.beep`, false, // receiver is in a different instance of module.foo ``, }, // Moving a module also moves all of the resources declared within it. // The following tests all cover variations of that rule. { ``, `module.foo`, `module.bar`, `module.foo.test_object.beep`, true, `module.bar.test_object.beep`, }, { ``, `module.foo`, `module.bar`, `module.foo[1].test_object.beep`, true, `module.bar[1].test_object.beep`, }, { ``, `module.foo`, `module.bar`, `module.foo["a"].test_object.beep`, true, `module.bar["a"].test_object.beep`, }, { ``, `module.foo`, `module.bar.module.foo`, `module.foo.test_object.beep`, true, `module.bar.module.foo.test_object.beep`, }, { ``, `module.foo.module.bar`, `module.bar`, `module.foo.module.bar.test_object.beep`, true, `module.bar.test_object.beep`, }, { ``, `module.foo[1]`, `module.foo[2]`, `module.foo[1].test_object.beep`, true, `module.foo[2].test_object.beep`, }, { ``, `module.foo[1]`, `module.foo`, `module.foo[1].test_object.beep`, true, `module.foo.test_object.beep`, }, { ``, `module.foo`, `module.foo[1]`, `module.foo.test_object.beep`, true, `module.foo[1].test_object.beep`, }, { ``, `module.foo`, `module.foo[1]`, `module.foo.module.bar.test_object.beep`, true, `module.foo[1].module.bar.test_object.beep`, }, { ``, `module.foo`, `module.foo[1]`, `module.foo.module.bar[0].test_object.beep`, true, `module.foo[1].module.bar[0].test_object.beep`, }, { ``, `module.foo`, `module.bar.module.foo`, `module.foo[0].test_object.beep`, true, `module.bar.module.foo[0].test_object.beep`, }, { ``, `module.foo.module.bar`, `module.bar`, `module.foo.module.bar[0].test_object.beep`, true, `module.bar[0].test_object.beep`, }, { `foo`, `module.bar`, `module.baz`, `module.foo.module.bar.test_object.beep`, true, `module.foo.module.baz.test_object.beep`, }, { `foo`, `module.bar`, `module.baz`, `module.foo[1].module.bar.test_object.beep`, true, `module.foo[1].module.baz.test_object.beep`, }, { `foo`, `module.bar`, `module.bar[1]`, `module.foo[1].module.bar.test_object.beep`, true, `module.foo[1].module.bar[1].test_object.beep`, }, { ``, `module.foo[1]`, `module.foo[2]`, `module.foo.test_object.beep`, false, // the receiver module has a non-matching instance key (NoKey) ``, }, { ``, `module.foo[1]`, `module.foo[2]`, `module.foo[2].test_object.beep`, false, // the receiver is already at the "to" address ``, }, { `foo`, `module.bar`, `module.bar[1]`, `module.boz.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { `foo.bar`, `module.bar`, `module.bar[1]`, `module.boz.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { `foo.bar`, `module.a`, `module.b`, `module.boz.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2`, `module.b1.module.b2`, `module.c.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2[0]`, `module.b1.module.b2[1]`, `module.c.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2`, `module.b1.module.b2`, `module.a1.module.b2.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2`, `module.b1.module.b2`, `module.b1.module.a2.test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `module.a1.module.a2[0]`, `module.b1.module.b2[1]`, `module.a1.module.b2[0].test_object.beep`, false, // the receiver module is outside the declaration module ``, }, { ``, `foo_instance.bar`, `foo_instance.baz`, `module.foo.test_object.beep`, false, // the resource address is unrelated to the move statements ``, }, } for i, test := range tests { t.Run( fmt.Sprintf( "[%02d] %s: %s to %s with %s", i, test.DeclModule, test.StmtFrom, test.StmtTo, test.Receiver, ), func(t *testing.T) { parseStmtEP := func(t *testing.T, input string) *MoveEndpoint { t.Helper() traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos) if hclDiags.HasErrors() { // We're not trying to test the HCL parser here, so any // failures at this point are likely to be bugs in the // test case itself. t.Fatalf("syntax error: %s", hclDiags.Error()) } moveEp, diags := ParseMoveEndpoint(traversal) if diags.HasErrors() { t.Fatalf("unexpected error: %s", diags.Err().Error()) } return moveEp } fromEPLocal := parseStmtEP(t, test.StmtFrom) toEPLocal := parseStmtEP(t, test.StmtTo) declModule := RootModule if test.DeclModule != "" { declModule = strings.Split(test.DeclModule, ".") } fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal) if fromEP == nil || toEP == nil { t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal) } // We only have an AbsResourceInstance parser, not an // AbsResourceParser, and so we'll just cheat and parse this // as a resource instance but fail if it includes an instance // key. receiverInstanceAddr, diags := ParseAbsResourceInstanceStr(test.Receiver) if diags.HasErrors() { t.Fatalf("invalid reciever address: %s", diags.Err().Error()) } if receiverInstanceAddr.Resource.Key != NoKey { t.Fatalf("invalid reciever address: must be a resource, not a resource instance") } receiverAddr := receiverInstanceAddr.ContainingResource() gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP) if !test.WantMatch { if gotMatch { t.Errorf("unexpected match\nreceiver: %s (%T)\nfrom: %s\nto: %s\nresult: %s", test.Receiver, receiverAddr, fromEP, toEP, gotAddr) } return } if !gotMatch { t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom: %s\nto: %s\ngot: no match\nwant: %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult) } if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr { t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr) } }, ) } } func TestMoveEndpointChainAndNested(t *testing.T) { tests := []struct { Endpoint, Other AbsMoveable EndpointMod, OtherMod Module CanChainFrom, NestedWithin bool }{ { Endpoint: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, Other: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, CanChainFrom: true, NestedWithin: false, }, { Endpoint: mustParseModuleInstanceStr("module.foo[2]"), Other: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, CanChainFrom: false, NestedWithin: false, }, { Endpoint: mustParseModuleInstanceStr("module.foo[2].module.bar[2]"), Other: AbsModuleCall{ Module: RootModuleInstance, Call: ModuleCall{Name: "foo"}, }, CanChainFrom: false, NestedWithin: true, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz").ContainingResource(), Other: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, CanChainFrom: false, NestedWithin: true, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar[3].resource.baz[2]"), Other: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, CanChainFrom: false, NestedWithin: true, }, { Endpoint: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, Other: mustParseModuleInstanceStr("module.foo[2]"), CanChainFrom: false, NestedWithin: true, }, { Endpoint: mustParseModuleInstanceStr("module.foo[2]"), Other: mustParseModuleInstanceStr("module.foo[2]"), CanChainFrom: true, NestedWithin: false, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), Other: mustParseModuleInstanceStr("module.foo[2]"), CanChainFrom: false, NestedWithin: true, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz"), Other: mustParseModuleInstanceStr("module.foo[2]"), CanChainFrom: false, NestedWithin: true, }, { Endpoint: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), CanChainFrom: false, NestedWithin: false, }, { Endpoint: mustParseModuleInstanceStr("module.foo[2]"), Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), CanChainFrom: false, NestedWithin: false, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), CanChainFrom: true, NestedWithin: false, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz[2]").ContainingResource(), CanChainFrom: false, NestedWithin: true, }, { Endpoint: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), CanChainFrom: false, }, { Endpoint: mustParseModuleInstanceStr("module.foo[2]"), Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), CanChainFrom: false, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), CanChainFrom: false, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), CanChainFrom: true, }, { Endpoint: mustParseAbsResourceInstanceStr("resource.baz"), EndpointMod: Module{"foo"}, Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), CanChainFrom: true, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), Other: mustParseAbsResourceInstanceStr("resource.baz"), OtherMod: Module{"foo"}, CanChainFrom: true, }, { Endpoint: mustParseAbsResourceInstanceStr("resource.baz"), EndpointMod: Module{"foo"}, Other: mustParseAbsResourceInstanceStr("resource.baz"), OtherMod: Module{"foo"}, CanChainFrom: true, }, { Endpoint: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(), EndpointMod: Module{"foo"}, Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), CanChainFrom: true, }, { Endpoint: mustParseModuleInstanceStr("module.foo[2].module.baz"), Other: mustParseModuleInstanceStr("module.baz"), OtherMod: Module{"foo"}, CanChainFrom: true, }, { Endpoint: AbsModuleCall{ Call: ModuleCall{Name: "bing"}, }, EndpointMod: Module{"foo", "baz"}, Other: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.baz"), Call: ModuleCall{Name: "bing"}, }, OtherMod: Module{"foo"}, CanChainFrom: true, }, { Endpoint: mustParseAbsResourceInstanceStr("resource.baz"), EndpointMod: Module{"foo"}, Other: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(), NestedWithin: true, }, { Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"), Other: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(), OtherMod: Module{"foo"}, NestedWithin: true, }, { Endpoint: mustParseAbsResourceInstanceStr("resource.baz"), EndpointMod: Module{"foo"}, Other: mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(), OtherMod: Module{"foo"}, NestedWithin: true, }, { Endpoint: mustParseAbsResourceInstanceStr("ressurce.baz").ContainingResource(), EndpointMod: Module{"foo"}, Other: mustParseModuleInstanceStr("module.foo[2]"), NestedWithin: true, }, { Endpoint: AbsModuleCall{ Call: ModuleCall{Name: "bang"}, }, EndpointMod: Module{"foo", "baz", "bing"}, Other: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.baz"), Call: ModuleCall{Name: "bing"}, }, OtherMod: Module{"foo"}, NestedWithin: true, }, { Endpoint: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.bing"), Call: ModuleCall{Name: "bang"}, }, EndpointMod: Module{"foo", "baz"}, Other: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo.module.baz"), Call: ModuleCall{Name: "bing"}, }, NestedWithin: true, }, } for i, test := range tests { t.Run(fmt.Sprintf("[%02d]%s.CanChainFrom(%s)", i, test.Endpoint, test.Other), func(t *testing.T) { endpoint := &MoveEndpointInModule{ relSubject: test.Endpoint, module: test.EndpointMod, } other := &MoveEndpointInModule{ relSubject: test.Other, module: test.OtherMod, } if endpoint.CanChainFrom(other) != test.CanChainFrom { t.Errorf("expected %s CanChainFrom %s == %t", endpoint, other, test.CanChainFrom) } if endpoint.NestedWithin(other) != test.NestedWithin { t.Errorf("expected %s NestedWithin %s == %t", endpoint, other, test.NestedWithin) } }, ) } } func TestSelectsModule(t *testing.T) { tests := []struct { Endpoint *MoveEndpointInModule Addr ModuleInstance Selects bool }{ { Endpoint: &MoveEndpointInModule{ relSubject: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, }, Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[1]"), Selects: true, }, { Endpoint: &MoveEndpointInModule{ module: mustParseModuleInstanceStr("module.foo").Module(), relSubject: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.bar[2]"), Call: ModuleCall{Name: "baz"}, }, }, Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[2].module.baz"), Selects: true, }, { Endpoint: &MoveEndpointInModule{ module: mustParseModuleInstanceStr("module.foo").Module(), relSubject: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.bar[2]"), Call: ModuleCall{Name: "baz"}, }, }, Addr: mustParseModuleInstanceStr("module.foo[2].module.bar[1].module.baz"), Selects: false, }, { Endpoint: &MoveEndpointInModule{ relSubject: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.bar"), Call: ModuleCall{Name: "baz"}, }, }, Addr: mustParseModuleInstanceStr("module.bar[1].module.baz"), Selects: false, }, { Endpoint: &MoveEndpointInModule{ module: mustParseModuleInstanceStr("module.foo").Module(), relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`), }, Addr: mustParseModuleInstanceStr(`module.foo[1].module.bar`), Selects: true, }, { Endpoint: &MoveEndpointInModule{ relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), }, Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), Selects: true, }, { Endpoint: &MoveEndpointInModule{ relSubject: mustParseAbsResourceInstanceStr(`module.bar.module.baz["key"].resource.name`).ContainingResource(), }, Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), Selects: true, }, { Endpoint: &MoveEndpointInModule{ module: mustParseModuleInstanceStr("module.nope").Module(), relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`), }, Addr: mustParseModuleInstanceStr(`module.foo[1].module.bar`), Selects: false, }, { Endpoint: &MoveEndpointInModule{ relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), }, Addr: mustParseModuleInstanceStr(`module.bar.module.baz["nope"]`), Selects: false, }, { Endpoint: &MoveEndpointInModule{ relSubject: mustParseAbsResourceInstanceStr(`module.nope.module.baz["key"].resource.name`).ContainingResource(), }, Addr: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`), Selects: false, }, } for i, test := range tests { t.Run(fmt.Sprintf("[%02d]%s.SelectsModule(%s)", i, test.Endpoint, test.Addr), func(t *testing.T) { if test.Endpoint.SelectsModule(test.Addr) != test.Selects { t.Errorf("expected %s SelectsModule %s == %t", test.Endpoint, test.Addr, test.Selects) } }, ) } } func TestSelectsResource(t *testing.T) { matchingResource := Resource{ Mode: ManagedResourceMode, Type: "foo", Name: "matching", } unmatchingResource := Resource{ Mode: ManagedResourceMode, Type: "foo", Name: "unmatching", } childMod := Module{ "child", } childModMatchingInst := ModuleInstance{ ModuleInstanceStep{Name: "child", InstanceKey: StringKey("matching")}, } childModUnmatchingInst := ModuleInstance{ ModuleInstanceStep{Name: "child", InstanceKey: StringKey("unmatching")}, } tests := []struct { Endpoint *MoveEndpointInModule Addr AbsResource Selects bool }{ { Endpoint: &MoveEndpointInModule{ relSubject: matchingResource.Absolute(nil), }, Addr: matchingResource.Absolute(nil), Selects: true, // exact match }, { Endpoint: &MoveEndpointInModule{ relSubject: unmatchingResource.Absolute(nil), }, Addr: matchingResource.Absolute(nil), Selects: false, // wrong resource name }, { Endpoint: &MoveEndpointInModule{ relSubject: unmatchingResource.Instance(IntKey(1)).Absolute(nil), }, Addr: matchingResource.Absolute(nil), Selects: false, // wrong resource name }, { Endpoint: &MoveEndpointInModule{ relSubject: matchingResource.Instance(NoKey).Absolute(nil), }, Addr: matchingResource.Absolute(nil), Selects: true, // matches one instance }, { Endpoint: &MoveEndpointInModule{ relSubject: matchingResource.Instance(IntKey(0)).Absolute(nil), }, Addr: matchingResource.Absolute(nil), Selects: true, // matches one instance }, { Endpoint: &MoveEndpointInModule{ relSubject: matchingResource.Instance(StringKey("a")).Absolute(nil), }, Addr: matchingResource.Absolute(nil), Selects: true, // matches one instance }, { Endpoint: &MoveEndpointInModule{ module: childMod, relSubject: matchingResource.Absolute(nil), }, Addr: matchingResource.Absolute(childModMatchingInst), Selects: true, // in one of the instances of the module where the statement was written }, { Endpoint: &MoveEndpointInModule{ relSubject: matchingResource.Absolute(childModMatchingInst), }, Addr: matchingResource.Absolute(childModMatchingInst), Selects: true, // exact match }, { Endpoint: &MoveEndpointInModule{ relSubject: matchingResource.Instance(IntKey(2)).Absolute(childModMatchingInst), }, Addr: matchingResource.Absolute(childModMatchingInst), Selects: true, // matches one instance }, { Endpoint: &MoveEndpointInModule{ relSubject: matchingResource.Absolute(childModMatchingInst), }, Addr: matchingResource.Absolute(childModUnmatchingInst), Selects: false, // the containing module instance doesn't match }, { Endpoint: &MoveEndpointInModule{ relSubject: AbsModuleCall{ Module: mustParseModuleInstanceStr("module.foo[2]"), Call: ModuleCall{Name: "bar"}, }, }, Addr: matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")), Selects: false, // a module call can't match a resource }, { Endpoint: &MoveEndpointInModule{ relSubject: mustParseModuleInstanceStr("module.foo[2]"), }, Addr: matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")), Selects: false, // a module instance can't match a resource }, } for i, test := range tests { t.Run(fmt.Sprintf("[%02d]%s SelectsResource(%s)", i, test.Endpoint, test.Addr), func(t *testing.T) { if got, want := test.Endpoint.SelectsResource(test.Addr), test.Selects; got != want { t.Errorf("wrong result\nReceiver: %s\nArgument: %s\ngot: %t\nwant: %t", test.Endpoint, test.Addr, got, want) } }, ) } } func TestIsModuleMoveReIndex(t *testing.T) { tests := []struct { from, to AbsMoveable expect bool }{ { from: mustParseModuleInstanceStr(`module.bar`), to: mustParseModuleInstanceStr(`module.bar`), expect: true, }, { from: mustParseModuleInstanceStr(`module.bar`), to: mustParseModuleInstanceStr(`module.bar[0]`), expect: true, }, { from: AbsModuleCall{ Call: ModuleCall{Name: "bar"}, }, to: mustParseModuleInstanceStr(`module.bar[0]`), expect: true, }, { from: mustParseModuleInstanceStr(`module.bar["a"]`), to: AbsModuleCall{ Call: ModuleCall{Name: "bar"}, }, expect: true, }, { from: mustParseModuleInstanceStr(`module.foo`), to: mustParseModuleInstanceStr(`module.bar`), expect: false, }, { from: mustParseModuleInstanceStr(`module.bar`), to: mustParseModuleInstanceStr(`module.foo[0]`), expect: false, }, { from: AbsModuleCall{ Call: ModuleCall{Name: "bar"}, }, to: mustParseModuleInstanceStr(`module.foo[0]`), expect: false, }, { from: mustParseModuleInstanceStr(`module.bar["a"]`), to: AbsModuleCall{ Call: ModuleCall{Name: "foo"}, }, expect: false, }, { from: mustParseModuleInstanceStr(`module.bar.module.baz`), to: mustParseModuleInstanceStr(`module.bar.module.baz`), expect: true, }, { from: mustParseModuleInstanceStr(`module.bar.module.baz`), to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), expect: true, }, { from: mustParseModuleInstanceStr(`module.bar.module.baz`), to: mustParseModuleInstanceStr(`module.baz.module.baz`), expect: false, }, { from: mustParseModuleInstanceStr(`module.bar.module.baz`), to: mustParseModuleInstanceStr(`module.baz.module.baz[0]`), expect: false, }, { from: mustParseModuleInstanceStr(`module.bar.module.baz`), to: mustParseModuleInstanceStr(`module.bar[0].module.baz`), expect: true, }, { from: mustParseModuleInstanceStr(`module.bar[0].module.baz`), to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), expect: true, }, { from: mustParseModuleInstanceStr(`module.bar[0].module.baz`), to: mustParseModuleInstanceStr(`module.bar[1].module.baz[0]`), expect: true, }, { from: AbsModuleCall{ Call: ModuleCall{Name: "baz"}, }, to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), expect: false, }, { from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), to: AbsModuleCall{ Call: ModuleCall{Name: "baz"}, }, expect: false, }, { from: AbsModuleCall{ Module: mustParseModuleInstanceStr(`module.bar[0]`), Call: ModuleCall{Name: "baz"}, }, to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), expect: true, }, { from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), to: AbsModuleCall{ Module: mustParseModuleInstanceStr(`module.bar[0]`), Call: ModuleCall{Name: "baz"}, }, expect: true, }, { from: mustParseModuleInstanceStr(`module.baz`), to: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), expect: false, }, { from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`), to: mustParseModuleInstanceStr(`module.baz`), expect: false, }, } for i, test := range tests { t.Run(fmt.Sprintf("[%02d]IsModuleMoveReIndex(%s, %s)", i, test.from, test.to), func(t *testing.T) { from := &MoveEndpointInModule{ relSubject: test.from, } to := &MoveEndpointInModule{ relSubject: test.to, } if got := from.IsModuleReIndex(to); got != test.expect { t.Errorf("expected %t, got %t", test.expect, got) } }, ) } } func mustParseAbsResourceInstanceStr(s string) AbsResourceInstance { r, diags := ParseAbsResourceInstanceStr(s) if diags.HasErrors() { panic(diags.ErrWithWarnings().Error()) } return r }