github.com/opentofu/opentofu@v1.7.1/internal/addrs/move_endpoint_module_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 addrs
     7  
     8  import (
     9  	"fmt"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/hashicorp/hcl/v2"
    14  	"github.com/hashicorp/hcl/v2/hclsyntax"
    15  	"github.com/opentofu/opentofu/internal/tfdiags"
    16  )
    17  
    18  func TestModuleInstanceMoveDestination(t *testing.T) {
    19  	tests := []struct {
    20  		DeclModule       string
    21  		StmtFrom, StmtTo string
    22  		Receiver         string
    23  		WantMatch        bool
    24  		WantResult       string
    25  	}{
    26  		{
    27  			``,
    28  			`module.foo`,
    29  			`module.bar`,
    30  			`module.foo`,
    31  			true,
    32  			`module.bar`,
    33  		},
    34  		{
    35  			``,
    36  			`module.foo`,
    37  			`module.bar`,
    38  			`module.foo[1]`,
    39  			true,
    40  			`module.bar[1]`,
    41  		},
    42  		{
    43  			``,
    44  			`module.foo`,
    45  			`module.bar`,
    46  			`module.foo["a"]`,
    47  			true,
    48  			`module.bar["a"]`,
    49  		},
    50  		{
    51  			``,
    52  			`module.foo`,
    53  			`module.bar.module.foo`,
    54  			`module.foo`,
    55  			true,
    56  			`module.bar.module.foo`,
    57  		},
    58  		{
    59  			``,
    60  			`module.foo.module.bar`,
    61  			`module.bar`,
    62  			`module.foo.module.bar`,
    63  			true,
    64  			`module.bar`,
    65  		},
    66  		{
    67  			``,
    68  			`module.foo[1]`,
    69  			`module.foo[2]`,
    70  			`module.foo[1]`,
    71  			true,
    72  			`module.foo[2]`,
    73  		},
    74  		{
    75  			``,
    76  			`module.foo[1]`,
    77  			`module.foo`,
    78  			`module.foo[1]`,
    79  			true,
    80  			`module.foo`,
    81  		},
    82  		{
    83  			``,
    84  			`module.foo`,
    85  			`module.foo[1]`,
    86  			`module.foo`,
    87  			true,
    88  			`module.foo[1]`,
    89  		},
    90  		{
    91  			``,
    92  			`module.foo`,
    93  			`module.foo[1]`,
    94  			`module.foo.module.bar`,
    95  			true,
    96  			`module.foo[1].module.bar`,
    97  		},
    98  		{
    99  			``,
   100  			`module.foo`,
   101  			`module.foo[1]`,
   102  			`module.foo.module.bar[0]`,
   103  			true,
   104  			`module.foo[1].module.bar[0]`,
   105  		},
   106  		{
   107  			``,
   108  			`module.foo`,
   109  			`module.bar.module.foo`,
   110  			`module.foo[0]`,
   111  			true,
   112  			`module.bar.module.foo[0]`,
   113  		},
   114  		{
   115  			``,
   116  			`module.foo.module.bar`,
   117  			`module.bar`,
   118  			`module.foo.module.bar[0]`,
   119  			true,
   120  			`module.bar[0]`,
   121  		},
   122  		{
   123  			`foo`,
   124  			`module.bar`,
   125  			`module.baz`,
   126  			`module.foo.module.bar`,
   127  			true,
   128  			`module.foo.module.baz`,
   129  		},
   130  		{
   131  			`foo`,
   132  			`module.bar`,
   133  			`module.baz`,
   134  			`module.foo[1].module.bar`,
   135  			true,
   136  			`module.foo[1].module.baz`,
   137  		},
   138  		{
   139  			`foo`,
   140  			`module.bar`,
   141  			`module.bar[1]`,
   142  			`module.foo[1].module.bar`,
   143  			true,
   144  			`module.foo[1].module.bar[1]`,
   145  		},
   146  		{
   147  			``,
   148  			`module.foo[1]`,
   149  			`module.foo[2]`,
   150  			`module.foo`,
   151  			false, // the receiver has a non-matching instance key (NoKey)
   152  			``,
   153  		},
   154  		{
   155  			``,
   156  			`module.foo[1]`,
   157  			`module.foo[2]`,
   158  			`module.foo[2]`,
   159  			false, // the receiver is already the "to" address
   160  			``,
   161  		},
   162  		{
   163  			``,
   164  			`module.foo`,
   165  			`module.bar`,
   166  			``,
   167  			false, // the root module can never be moved
   168  			``,
   169  		},
   170  		{
   171  			`foo`,
   172  			`module.bar`,
   173  			`module.bar[1]`,
   174  			`module.boz`,
   175  			false, // the receiver is outside the declaration module
   176  			``,
   177  		},
   178  		{
   179  			`foo.bar`,
   180  			`module.bar`,
   181  			`module.bar[1]`,
   182  			`module.boz`,
   183  			false, // the receiver is outside the declaration module
   184  			``,
   185  		},
   186  		{
   187  			`foo.bar`,
   188  			`module.a`,
   189  			`module.b`,
   190  			`module.boz`,
   191  			false, // the receiver is outside the declaration module
   192  			``,
   193  		},
   194  		{
   195  			``,
   196  			`module.a1.module.a2`,
   197  			`module.b1.module.b2`,
   198  			`module.c`,
   199  			false, // the receiver is outside the declaration module
   200  			``,
   201  		},
   202  		{
   203  			``,
   204  			`module.a1.module.a2[0]`,
   205  			`module.b1.module.b2[1]`,
   206  			`module.c`,
   207  			false, // the receiver is outside the declaration module
   208  			``,
   209  		},
   210  		{
   211  			``,
   212  			`module.a1.module.a2`,
   213  			`module.b1.module.b2`,
   214  			`module.a1.module.b2`,
   215  			false, // the receiver is outside the declaration module
   216  			``,
   217  		},
   218  		{
   219  			``,
   220  			`module.a1.module.a2`,
   221  			`module.b1.module.b2`,
   222  			`module.b1.module.a2`,
   223  			false, // the receiver is outside the declaration module
   224  			``,
   225  		},
   226  		{
   227  			``,
   228  			`module.a1.module.a2[0]`,
   229  			`module.b1.module.b2[1]`,
   230  			`module.a1.module.b2[0]`,
   231  			false, // the receiver is outside the declaration module
   232  			``,
   233  		},
   234  		{
   235  			``,
   236  			`foo_instance.bar`,
   237  			`foo_instance.baz`,
   238  			`module.foo`,
   239  			false, // a resource address can never match a module instance
   240  			``,
   241  		},
   242  	}
   243  
   244  	for _, test := range tests {
   245  		t.Run(
   246  			fmt.Sprintf(
   247  				"%s: %s to %s with %s",
   248  				test.DeclModule,
   249  				test.StmtFrom, test.StmtTo,
   250  				test.Receiver,
   251  			),
   252  			func(t *testing.T) {
   253  
   254  				parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
   255  					t.Helper()
   256  
   257  					traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
   258  					if hclDiags.HasErrors() {
   259  						// We're not trying to test the HCL parser here, so any
   260  						// failures at this point are likely to be bugs in the
   261  						// test case itself.
   262  						t.Fatalf("syntax error: %s", hclDiags.Error())
   263  					}
   264  
   265  					moveEp, diags := ParseMoveEndpoint(traversal)
   266  					if diags.HasErrors() {
   267  						t.Fatalf("unexpected error: %s", diags.Err().Error())
   268  					}
   269  					return moveEp
   270  				}
   271  
   272  				fromEPLocal := parseStmtEP(t, test.StmtFrom)
   273  				toEPLocal := parseStmtEP(t, test.StmtTo)
   274  
   275  				declModule := RootModule
   276  				if test.DeclModule != "" {
   277  					declModule = strings.Split(test.DeclModule, ".")
   278  				}
   279  				fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
   280  				if fromEP == nil || toEP == nil {
   281  					t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto:   %s", fromEPLocal, toEPLocal)
   282  				}
   283  
   284  				receiverAddr := RootModuleInstance
   285  				if test.Receiver != "" {
   286  					var diags tfdiags.Diagnostics
   287  					receiverAddr, diags = ParseModuleInstanceStr(test.Receiver)
   288  					if diags.HasErrors() {
   289  						t.Fatalf("invalid reciever address: %s", diags.Err().Error())
   290  					}
   291  				}
   292  				gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
   293  				if !test.WantMatch {
   294  					if gotMatch {
   295  						t.Errorf("unexpected match\nreceiver: %s\nfrom:     %s\nto:       %s\nresult:   %s", test.Receiver, fromEP, toEP, gotAddr)
   296  					}
   297  					return
   298  				}
   299  
   300  				if !gotMatch {
   301  					t.Errorf("unexpected non-match\nreceiver: %s\nfrom:     %s\nto:       %s", test.Receiver, fromEP, toEP)
   302  				}
   303  
   304  				if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
   305  					t.Errorf("wrong result\ngot:  %s\nwant: %s", gotStr, wantStr)
   306  				}
   307  			},
   308  		)
   309  	}
   310  }
   311  
   312  func TestAbsResourceInstanceMoveDestination(t *testing.T) {
   313  	tests := []struct {
   314  		DeclModule       string
   315  		StmtFrom, StmtTo string
   316  		Receiver         string
   317  		WantMatch        bool
   318  		WantResult       string
   319  	}{
   320  		{
   321  			``,
   322  			`test_object.beep`,
   323  			`test_object.boop`,
   324  			`test_object.beep`,
   325  			true,
   326  			`test_object.boop`,
   327  		},
   328  		{
   329  			``,
   330  			`test_object.beep`,
   331  			`test_object.beep[2]`,
   332  			`test_object.beep`,
   333  			true,
   334  			`test_object.beep[2]`,
   335  		},
   336  		{
   337  			``,
   338  			`test_object.beep`,
   339  			`module.foo.test_object.beep`,
   340  			`test_object.beep`,
   341  			true,
   342  			`module.foo.test_object.beep`,
   343  		},
   344  		{
   345  			``,
   346  			`test_object.beep[2]`,
   347  			`module.foo.test_object.beep["a"]`,
   348  			`test_object.beep[2]`,
   349  			true,
   350  			`module.foo.test_object.beep["a"]`,
   351  		},
   352  		{
   353  			``,
   354  			`test_object.beep`,
   355  			`module.foo[0].test_object.beep`,
   356  			`test_object.beep`,
   357  			true,
   358  			`module.foo[0].test_object.beep`,
   359  		},
   360  		{
   361  			``,
   362  			`module.foo.test_object.beep`,
   363  			`test_object.beep`,
   364  			`module.foo.test_object.beep`,
   365  			true,
   366  			`test_object.beep`,
   367  		},
   368  		{
   369  			``,
   370  			`module.foo[0].test_object.beep`,
   371  			`test_object.beep`,
   372  			`module.foo[0].test_object.beep`,
   373  			true,
   374  			`test_object.beep`,
   375  		},
   376  		{
   377  			`foo`,
   378  			`test_object.beep`,
   379  			`test_object.boop`,
   380  			`module.foo[0].test_object.beep`,
   381  			true,
   382  			`module.foo[0].test_object.boop`,
   383  		},
   384  		{
   385  			`foo`,
   386  			`test_object.beep`,
   387  			`test_object.beep[1]`,
   388  			`module.foo[0].test_object.beep`,
   389  			true,
   390  			`module.foo[0].test_object.beep[1]`,
   391  		},
   392  		{
   393  			``,
   394  			`test_object.beep`,
   395  			`test_object.boop`,
   396  			`test_object.boop`,
   397  			false, // the reciever is already the "to" address
   398  			``,
   399  		},
   400  		{
   401  			``,
   402  			`test_object.beep[1]`,
   403  			`test_object.beep[2]`,
   404  			`test_object.beep[5]`,
   405  			false, // the receiver has a non-matching instance key
   406  			``,
   407  		},
   408  		{
   409  			`foo`,
   410  			`test_object.beep`,
   411  			`test_object.boop`,
   412  			`test_object.beep`,
   413  			false, // the receiver is not inside an instance of module "foo"
   414  			``,
   415  		},
   416  		{
   417  			`foo.bar`,
   418  			`test_object.beep`,
   419  			`test_object.boop`,
   420  			`test_object.beep`,
   421  			false, // the receiver is not inside an instance of module "foo.bar"
   422  			``,
   423  		},
   424  		{
   425  			``,
   426  			`module.foo[0].test_object.beep`,
   427  			`test_object.beep`,
   428  			`module.foo[1].test_object.beep`,
   429  			false, // receiver is in a different instance of module.foo
   430  			``,
   431  		},
   432  
   433  		// Moving a module also moves all of the resources declared within it.
   434  		// The following tests all cover variations of that rule.
   435  		{
   436  			``,
   437  			`module.foo`,
   438  			`module.bar`,
   439  			`module.foo.test_object.beep`,
   440  			true,
   441  			`module.bar.test_object.beep`,
   442  		},
   443  		{
   444  			``,
   445  			`module.foo`,
   446  			`module.bar`,
   447  			`module.foo[1].test_object.beep`,
   448  			true,
   449  			`module.bar[1].test_object.beep`,
   450  		},
   451  		{
   452  			``,
   453  			`module.foo`,
   454  			`module.bar`,
   455  			`module.foo["a"].test_object.beep`,
   456  			true,
   457  			`module.bar["a"].test_object.beep`,
   458  		},
   459  		{
   460  			``,
   461  			`module.foo`,
   462  			`module.bar.module.foo`,
   463  			`module.foo.test_object.beep`,
   464  			true,
   465  			`module.bar.module.foo.test_object.beep`,
   466  		},
   467  		{
   468  			``,
   469  			`module.foo.module.bar`,
   470  			`module.bar`,
   471  			`module.foo.module.bar.test_object.beep`,
   472  			true,
   473  			`module.bar.test_object.beep`,
   474  		},
   475  		{
   476  			``,
   477  			`module.foo[1]`,
   478  			`module.foo[2]`,
   479  			`module.foo[1].test_object.beep`,
   480  			true,
   481  			`module.foo[2].test_object.beep`,
   482  		},
   483  		{
   484  			``,
   485  			`module.foo[1]`,
   486  			`module.foo`,
   487  			`module.foo[1].test_object.beep`,
   488  			true,
   489  			`module.foo.test_object.beep`,
   490  		},
   491  		{
   492  			``,
   493  			`module.foo`,
   494  			`module.foo[1]`,
   495  			`module.foo.test_object.beep`,
   496  			true,
   497  			`module.foo[1].test_object.beep`,
   498  		},
   499  		{
   500  			``,
   501  			`module.foo`,
   502  			`module.foo[1]`,
   503  			`module.foo.module.bar.test_object.beep`,
   504  			true,
   505  			`module.foo[1].module.bar.test_object.beep`,
   506  		},
   507  		{
   508  			``,
   509  			`module.foo`,
   510  			`module.foo[1]`,
   511  			`module.foo.module.bar[0].test_object.beep`,
   512  			true,
   513  			`module.foo[1].module.bar[0].test_object.beep`,
   514  		},
   515  		{
   516  			``,
   517  			`module.foo`,
   518  			`module.bar.module.foo`,
   519  			`module.foo[0].test_object.beep`,
   520  			true,
   521  			`module.bar.module.foo[0].test_object.beep`,
   522  		},
   523  		{
   524  			``,
   525  			`module.foo.module.bar`,
   526  			`module.bar`,
   527  			`module.foo.module.bar[0].test_object.beep`,
   528  			true,
   529  			`module.bar[0].test_object.beep`,
   530  		},
   531  		{
   532  			`foo`,
   533  			`module.bar`,
   534  			`module.baz`,
   535  			`module.foo.module.bar.test_object.beep`,
   536  			true,
   537  			`module.foo.module.baz.test_object.beep`,
   538  		},
   539  		{
   540  			`foo`,
   541  			`module.bar`,
   542  			`module.baz`,
   543  			`module.foo[1].module.bar.test_object.beep`,
   544  			true,
   545  			`module.foo[1].module.baz.test_object.beep`,
   546  		},
   547  		{
   548  			`foo`,
   549  			`module.bar`,
   550  			`module.bar[1]`,
   551  			`module.foo[1].module.bar.test_object.beep`,
   552  			true,
   553  			`module.foo[1].module.bar[1].test_object.beep`,
   554  		},
   555  		{
   556  			``,
   557  			`module.foo[1]`,
   558  			`module.foo[2]`,
   559  			`module.foo.test_object.beep`,
   560  			false, // the receiver module has a non-matching instance key (NoKey)
   561  			``,
   562  		},
   563  		{
   564  			``,
   565  			`module.foo[1]`,
   566  			`module.foo[2]`,
   567  			`module.foo[2].test_object.beep`,
   568  			false, // the receiver is already at the "to" address
   569  			``,
   570  		},
   571  		{
   572  			`foo`,
   573  			`module.bar`,
   574  			`module.bar[1]`,
   575  			`module.boz.test_object.beep`,
   576  			false, // the receiver module is outside the declaration module
   577  			``,
   578  		},
   579  		{
   580  			`foo.bar`,
   581  			`module.bar`,
   582  			`module.bar[1]`,
   583  			`module.boz.test_object.beep`,
   584  			false, // the receiver module is outside the declaration module
   585  			``,
   586  		},
   587  		{
   588  			`foo.bar`,
   589  			`module.a`,
   590  			`module.b`,
   591  			`module.boz.test_object.beep`,
   592  			false, // the receiver module is outside the declaration module
   593  			``,
   594  		},
   595  		{
   596  			``,
   597  			`module.a1.module.a2`,
   598  			`module.b1.module.b2`,
   599  			`module.c.test_object.beep`,
   600  			false, // the receiver module is outside the declaration module
   601  			``,
   602  		},
   603  		{
   604  			``,
   605  			`module.a1.module.a2[0]`,
   606  			`module.b1.module.b2[1]`,
   607  			`module.c.test_object.beep`,
   608  			false, // the receiver module is outside the declaration module
   609  			``,
   610  		},
   611  		{
   612  			``,
   613  			`module.a1.module.a2`,
   614  			`module.b1.module.b2`,
   615  			`module.a1.module.b2.test_object.beep`,
   616  			false, // the receiver module is outside the declaration module
   617  			``,
   618  		},
   619  		{
   620  			``,
   621  			`module.a1.module.a2`,
   622  			`module.b1.module.b2`,
   623  			`module.b1.module.a2.test_object.beep`,
   624  			false, // the receiver module is outside the declaration module
   625  			``,
   626  		},
   627  		{
   628  			``,
   629  			`module.a1.module.a2[0]`,
   630  			`module.b1.module.b2[1]`,
   631  			`module.a1.module.b2[0].test_object.beep`,
   632  			false, // the receiver module is outside the declaration module
   633  			``,
   634  		},
   635  		{
   636  			``,
   637  			`foo_instance.bar`,
   638  			`foo_instance.baz`,
   639  			`module.foo.test_object.beep`,
   640  			false, // the resource address is unrelated to the move statements
   641  			``,
   642  		},
   643  	}
   644  
   645  	for _, test := range tests {
   646  		t.Run(
   647  			fmt.Sprintf(
   648  				"%s: %s to %s with %s",
   649  				test.DeclModule,
   650  				test.StmtFrom, test.StmtTo,
   651  				test.Receiver,
   652  			),
   653  			func(t *testing.T) {
   654  
   655  				parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
   656  					t.Helper()
   657  
   658  					traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
   659  					if hclDiags.HasErrors() {
   660  						// We're not trying to test the HCL parser here, so any
   661  						// failures at this point are likely to be bugs in the
   662  						// test case itself.
   663  						t.Fatalf("syntax error: %s", hclDiags.Error())
   664  					}
   665  
   666  					moveEp, diags := ParseMoveEndpoint(traversal)
   667  					if diags.HasErrors() {
   668  						t.Fatalf("unexpected error: %s", diags.Err().Error())
   669  					}
   670  					return moveEp
   671  				}
   672  
   673  				fromEPLocal := parseStmtEP(t, test.StmtFrom)
   674  				toEPLocal := parseStmtEP(t, test.StmtTo)
   675  
   676  				declModule := RootModule
   677  				if test.DeclModule != "" {
   678  					declModule = strings.Split(test.DeclModule, ".")
   679  				}
   680  				fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
   681  				if fromEP == nil || toEP == nil {
   682  					t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto:   %s", fromEPLocal, toEPLocal)
   683  				}
   684  
   685  				receiverAddr, diags := ParseAbsResourceInstanceStr(test.Receiver)
   686  				if diags.HasErrors() {
   687  					t.Fatalf("invalid reciever address: %s", diags.Err().Error())
   688  				}
   689  				gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
   690  				if !test.WantMatch {
   691  					if gotMatch {
   692  						t.Errorf("unexpected match\nreceiver: %s\nfrom:     %s\nto:       %s\nresult:   %s", test.Receiver, fromEP, toEP, gotAddr)
   693  					}
   694  					return
   695  				}
   696  
   697  				if !gotMatch {
   698  					t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom:     %s\nto:       %s\ngot:      (no match)\nwant:     %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult)
   699  				}
   700  
   701  				if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
   702  					t.Errorf("wrong result\ngot:  %s\nwant: %s", gotStr, wantStr)
   703  				}
   704  			},
   705  		)
   706  	}
   707  }
   708  
   709  func TestAbsResourceMoveDestination(t *testing.T) {
   710  	tests := []struct {
   711  		DeclModule       string
   712  		StmtFrom, StmtTo string
   713  		Receiver         string
   714  		WantMatch        bool
   715  		WantResult       string
   716  	}{
   717  		{
   718  			``,
   719  			`test_object.beep`,
   720  			`test_object.boop`,
   721  			`test_object.beep`,
   722  			true,
   723  			`test_object.boop`,
   724  		},
   725  		{
   726  			``,
   727  			`test_object.beep`,
   728  			`module.foo.test_object.beep`,
   729  			`test_object.beep`,
   730  			true,
   731  			`module.foo.test_object.beep`,
   732  		},
   733  		{
   734  			``,
   735  			`test_object.beep`,
   736  			`module.foo[0].test_object.beep`,
   737  			`test_object.beep`,
   738  			true,
   739  			`module.foo[0].test_object.beep`,
   740  		},
   741  		{
   742  			``,
   743  			`module.foo.test_object.beep`,
   744  			`test_object.beep`,
   745  			`module.foo.test_object.beep`,
   746  			true,
   747  			`test_object.beep`,
   748  		},
   749  		{
   750  			``,
   751  			`module.foo[0].test_object.beep`,
   752  			`test_object.beep`,
   753  			`module.foo[0].test_object.beep`,
   754  			true,
   755  			`test_object.beep`,
   756  		},
   757  		{
   758  			`foo`,
   759  			`test_object.beep`,
   760  			`test_object.boop`,
   761  			`module.foo[0].test_object.beep`,
   762  			true,
   763  			`module.foo[0].test_object.boop`,
   764  		},
   765  		{
   766  			``,
   767  			`test_object.beep`,
   768  			`test_object.boop`,
   769  			`test_object.boop`,
   770  			false, // the reciever is already the "to" address
   771  			``,
   772  		},
   773  		{
   774  			`foo`,
   775  			`test_object.beep`,
   776  			`test_object.boop`,
   777  			`test_object.beep`,
   778  			false, // the receiver is not inside an instance of module "foo"
   779  			``,
   780  		},
   781  		{
   782  			`foo.bar`,
   783  			`test_object.beep`,
   784  			`test_object.boop`,
   785  			`test_object.beep`,
   786  			false, // the receiver is not inside an instance of module "foo.bar"
   787  			``,
   788  		},
   789  		{
   790  			``,
   791  			`module.foo[0].test_object.beep`,
   792  			`test_object.beep`,
   793  			`module.foo[1].test_object.beep`,
   794  			false, // receiver is in a different instance of module.foo
   795  			``,
   796  		},
   797  
   798  		// Moving a module also moves all of the resources declared within it.
   799  		// The following tests all cover variations of that rule.
   800  		{
   801  			``,
   802  			`module.foo`,
   803  			`module.bar`,
   804  			`module.foo.test_object.beep`,
   805  			true,
   806  			`module.bar.test_object.beep`,
   807  		},
   808  		{
   809  			``,
   810  			`module.foo`,
   811  			`module.bar`,
   812  			`module.foo[1].test_object.beep`,
   813  			true,
   814  			`module.bar[1].test_object.beep`,
   815  		},
   816  		{
   817  			``,
   818  			`module.foo`,
   819  			`module.bar`,
   820  			`module.foo["a"].test_object.beep`,
   821  			true,
   822  			`module.bar["a"].test_object.beep`,
   823  		},
   824  		{
   825  			``,
   826  			`module.foo`,
   827  			`module.bar.module.foo`,
   828  			`module.foo.test_object.beep`,
   829  			true,
   830  			`module.bar.module.foo.test_object.beep`,
   831  		},
   832  		{
   833  			``,
   834  			`module.foo.module.bar`,
   835  			`module.bar`,
   836  			`module.foo.module.bar.test_object.beep`,
   837  			true,
   838  			`module.bar.test_object.beep`,
   839  		},
   840  		{
   841  			``,
   842  			`module.foo[1]`,
   843  			`module.foo[2]`,
   844  			`module.foo[1].test_object.beep`,
   845  			true,
   846  			`module.foo[2].test_object.beep`,
   847  		},
   848  		{
   849  			``,
   850  			`module.foo[1]`,
   851  			`module.foo`,
   852  			`module.foo[1].test_object.beep`,
   853  			true,
   854  			`module.foo.test_object.beep`,
   855  		},
   856  		{
   857  			``,
   858  			`module.foo`,
   859  			`module.foo[1]`,
   860  			`module.foo.test_object.beep`,
   861  			true,
   862  			`module.foo[1].test_object.beep`,
   863  		},
   864  		{
   865  			``,
   866  			`module.foo`,
   867  			`module.foo[1]`,
   868  			`module.foo.module.bar.test_object.beep`,
   869  			true,
   870  			`module.foo[1].module.bar.test_object.beep`,
   871  		},
   872  		{
   873  			``,
   874  			`module.foo`,
   875  			`module.foo[1]`,
   876  			`module.foo.module.bar[0].test_object.beep`,
   877  			true,
   878  			`module.foo[1].module.bar[0].test_object.beep`,
   879  		},
   880  		{
   881  			``,
   882  			`module.foo`,
   883  			`module.bar.module.foo`,
   884  			`module.foo[0].test_object.beep`,
   885  			true,
   886  			`module.bar.module.foo[0].test_object.beep`,
   887  		},
   888  		{
   889  			``,
   890  			`module.foo.module.bar`,
   891  			`module.bar`,
   892  			`module.foo.module.bar[0].test_object.beep`,
   893  			true,
   894  			`module.bar[0].test_object.beep`,
   895  		},
   896  		{
   897  			`foo`,
   898  			`module.bar`,
   899  			`module.baz`,
   900  			`module.foo.module.bar.test_object.beep`,
   901  			true,
   902  			`module.foo.module.baz.test_object.beep`,
   903  		},
   904  		{
   905  			`foo`,
   906  			`module.bar`,
   907  			`module.baz`,
   908  			`module.foo[1].module.bar.test_object.beep`,
   909  			true,
   910  			`module.foo[1].module.baz.test_object.beep`,
   911  		},
   912  		{
   913  			`foo`,
   914  			`module.bar`,
   915  			`module.bar[1]`,
   916  			`module.foo[1].module.bar.test_object.beep`,
   917  			true,
   918  			`module.foo[1].module.bar[1].test_object.beep`,
   919  		},
   920  		{
   921  			``,
   922  			`module.foo[1]`,
   923  			`module.foo[2]`,
   924  			`module.foo.test_object.beep`,
   925  			false, // the receiver module has a non-matching instance key (NoKey)
   926  			``,
   927  		},
   928  		{
   929  			``,
   930  			`module.foo[1]`,
   931  			`module.foo[2]`,
   932  			`module.foo[2].test_object.beep`,
   933  			false, // the receiver is already at the "to" address
   934  			``,
   935  		},
   936  		{
   937  			`foo`,
   938  			`module.bar`,
   939  			`module.bar[1]`,
   940  			`module.boz.test_object.beep`,
   941  			false, // the receiver module is outside the declaration module
   942  			``,
   943  		},
   944  		{
   945  			`foo.bar`,
   946  			`module.bar`,
   947  			`module.bar[1]`,
   948  			`module.boz.test_object.beep`,
   949  			false, // the receiver module is outside the declaration module
   950  			``,
   951  		},
   952  		{
   953  			`foo.bar`,
   954  			`module.a`,
   955  			`module.b`,
   956  			`module.boz.test_object.beep`,
   957  			false, // the receiver module is outside the declaration module
   958  			``,
   959  		},
   960  		{
   961  			``,
   962  			`module.a1.module.a2`,
   963  			`module.b1.module.b2`,
   964  			`module.c.test_object.beep`,
   965  			false, // the receiver module is outside the declaration module
   966  			``,
   967  		},
   968  		{
   969  			``,
   970  			`module.a1.module.a2[0]`,
   971  			`module.b1.module.b2[1]`,
   972  			`module.c.test_object.beep`,
   973  			false, // the receiver module is outside the declaration module
   974  			``,
   975  		},
   976  		{
   977  			``,
   978  			`module.a1.module.a2`,
   979  			`module.b1.module.b2`,
   980  			`module.a1.module.b2.test_object.beep`,
   981  			false, // the receiver module is outside the declaration module
   982  			``,
   983  		},
   984  		{
   985  			``,
   986  			`module.a1.module.a2`,
   987  			`module.b1.module.b2`,
   988  			`module.b1.module.a2.test_object.beep`,
   989  			false, // the receiver module is outside the declaration module
   990  			``,
   991  		},
   992  		{
   993  			``,
   994  			`module.a1.module.a2[0]`,
   995  			`module.b1.module.b2[1]`,
   996  			`module.a1.module.b2[0].test_object.beep`,
   997  			false, // the receiver module is outside the declaration module
   998  			``,
   999  		},
  1000  		{
  1001  			``,
  1002  			`foo_instance.bar`,
  1003  			`foo_instance.baz`,
  1004  			`module.foo.test_object.beep`,
  1005  			false, // the resource address is unrelated to the move statements
  1006  			``,
  1007  		},
  1008  	}
  1009  
  1010  	for i, test := range tests {
  1011  		t.Run(
  1012  			fmt.Sprintf(
  1013  				"[%02d] %s: %s to %s with %s",
  1014  				i,
  1015  				test.DeclModule,
  1016  				test.StmtFrom, test.StmtTo,
  1017  				test.Receiver,
  1018  			),
  1019  			func(t *testing.T) {
  1020  
  1021  				parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
  1022  					t.Helper()
  1023  
  1024  					traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
  1025  					if hclDiags.HasErrors() {
  1026  						// We're not trying to test the HCL parser here, so any
  1027  						// failures at this point are likely to be bugs in the
  1028  						// test case itself.
  1029  						t.Fatalf("syntax error: %s", hclDiags.Error())
  1030  					}
  1031  
  1032  					moveEp, diags := ParseMoveEndpoint(traversal)
  1033  					if diags.HasErrors() {
  1034  						t.Fatalf("unexpected error: %s", diags.Err().Error())
  1035  					}
  1036  					return moveEp
  1037  				}
  1038  
  1039  				fromEPLocal := parseStmtEP(t, test.StmtFrom)
  1040  				toEPLocal := parseStmtEP(t, test.StmtTo)
  1041  
  1042  				declModule := RootModule
  1043  				if test.DeclModule != "" {
  1044  					declModule = strings.Split(test.DeclModule, ".")
  1045  				}
  1046  				fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
  1047  				if fromEP == nil || toEP == nil {
  1048  					t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto:   %s", fromEPLocal, toEPLocal)
  1049  				}
  1050  
  1051  				// We only have an AbsResourceInstance parser, not an
  1052  				// AbsResourceParser, and so we'll just cheat and parse this
  1053  				// as a resource instance but fail if it includes an instance
  1054  				// key.
  1055  				receiverInstanceAddr, diags := ParseAbsResourceInstanceStr(test.Receiver)
  1056  				if diags.HasErrors() {
  1057  					t.Fatalf("invalid reciever address: %s", diags.Err().Error())
  1058  				}
  1059  				if receiverInstanceAddr.Resource.Key != NoKey {
  1060  					t.Fatalf("invalid reciever address: must be a resource, not a resource instance")
  1061  				}
  1062  				receiverAddr := receiverInstanceAddr.ContainingResource()
  1063  				gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
  1064  				if !test.WantMatch {
  1065  					if gotMatch {
  1066  						t.Errorf("unexpected match\nreceiver: %s (%T)\nfrom:     %s\nto:       %s\nresult:   %s", test.Receiver, receiverAddr, fromEP, toEP, gotAddr)
  1067  					}
  1068  					return
  1069  				}
  1070  
  1071  				if !gotMatch {
  1072  					t.Fatalf("unexpected non-match\nreceiver: %s (%T)\nfrom:     %s\nto:       %s\ngot:      no match\nwant:     %s", test.Receiver, receiverAddr, fromEP, toEP, test.WantResult)
  1073  				}
  1074  
  1075  				if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
  1076  					t.Errorf("wrong result\ngot:  %s\nwant: %s", gotStr, wantStr)
  1077  				}
  1078  			},
  1079  		)
  1080  	}
  1081  }
  1082  
  1083  func TestMoveEndpointChainAndNested(t *testing.T) {
  1084  	tests := []struct {
  1085  		Endpoint, Other            AbsMoveable
  1086  		EndpointMod, OtherMod      Module
  1087  		CanChainFrom, NestedWithin bool
  1088  	}{
  1089  		{
  1090  			Endpoint: AbsModuleCall{
  1091  				Module: mustParseModuleInstanceStr("module.foo[2]"),
  1092  				Call:   ModuleCall{Name: "bar"},
  1093  			},
  1094  			Other: AbsModuleCall{
  1095  				Module: mustParseModuleInstanceStr("module.foo[2]"),
  1096  				Call:   ModuleCall{Name: "bar"},
  1097  			},
  1098  			CanChainFrom: true,
  1099  			NestedWithin: false,
  1100  		},
  1101  
  1102  		{
  1103  			Endpoint: mustParseModuleInstanceStr("module.foo[2]"),
  1104  			Other: AbsModuleCall{
  1105  				Module: mustParseModuleInstanceStr("module.foo[2]"),
  1106  				Call:   ModuleCall{Name: "bar"},
  1107  			},
  1108  			CanChainFrom: false,
  1109  			NestedWithin: false,
  1110  		},
  1111  
  1112  		{
  1113  			Endpoint: mustParseModuleInstanceStr("module.foo[2].module.bar[2]"),
  1114  			Other: AbsModuleCall{
  1115  				Module: RootModuleInstance,
  1116  				Call:   ModuleCall{Name: "foo"},
  1117  			},
  1118  			CanChainFrom: false,
  1119  			NestedWithin: true,
  1120  		},
  1121  
  1122  		{
  1123  			Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz").ContainingResource(),
  1124  			Other: AbsModuleCall{
  1125  				Module: mustParseModuleInstanceStr("module.foo[2]"),
  1126  				Call:   ModuleCall{Name: "bar"},
  1127  			},
  1128  			CanChainFrom: false,
  1129  			NestedWithin: true,
  1130  		},
  1131  
  1132  		{
  1133  			Endpoint: mustParseAbsResourceInstanceStr("module.foo[2].module.bar[3].resource.baz[2]"),
  1134  			Other: AbsModuleCall{
  1135  				Module: mustParseModuleInstanceStr("module.foo[2]"),
  1136  				Call:   ModuleCall{Name: "bar"},
  1137  			},
  1138  			CanChainFrom: false,
  1139  			NestedWithin: true,
  1140  		},
  1141  
  1142  		{
  1143  			Endpoint: AbsModuleCall{
  1144  				Module: mustParseModuleInstanceStr("module.foo[2]"),
  1145  				Call:   ModuleCall{Name: "bar"},
  1146  			},
  1147  			Other:        mustParseModuleInstanceStr("module.foo[2]"),
  1148  			CanChainFrom: false,
  1149  			NestedWithin: true,
  1150  		},
  1151  
  1152  		{
  1153  			Endpoint:     mustParseModuleInstanceStr("module.foo[2]"),
  1154  			Other:        mustParseModuleInstanceStr("module.foo[2]"),
  1155  			CanChainFrom: true,
  1156  			NestedWithin: false,
  1157  		},
  1158  
  1159  		{
  1160  			Endpoint:     mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
  1161  			Other:        mustParseModuleInstanceStr("module.foo[2]"),
  1162  			CanChainFrom: false,
  1163  			NestedWithin: true,
  1164  		},
  1165  
  1166  		{
  1167  			Endpoint:     mustParseAbsResourceInstanceStr("module.foo[2].module.bar.resource.baz"),
  1168  			Other:        mustParseModuleInstanceStr("module.foo[2]"),
  1169  			CanChainFrom: false,
  1170  			NestedWithin: true,
  1171  		},
  1172  
  1173  		{
  1174  			Endpoint: AbsModuleCall{
  1175  				Module: mustParseModuleInstanceStr("module.foo[2]"),
  1176  				Call:   ModuleCall{Name: "bar"},
  1177  			},
  1178  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
  1179  			CanChainFrom: false,
  1180  			NestedWithin: false,
  1181  		},
  1182  
  1183  		{
  1184  			Endpoint:     mustParseModuleInstanceStr("module.foo[2]"),
  1185  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
  1186  			CanChainFrom: false,
  1187  			NestedWithin: false,
  1188  		},
  1189  
  1190  		{
  1191  			Endpoint:     mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
  1192  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
  1193  			CanChainFrom: true,
  1194  			NestedWithin: false,
  1195  		},
  1196  
  1197  		{
  1198  			Endpoint:     mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
  1199  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz[2]").ContainingResource(),
  1200  			CanChainFrom: false,
  1201  			NestedWithin: true,
  1202  		},
  1203  
  1204  		{
  1205  			Endpoint: AbsModuleCall{
  1206  				Module: mustParseModuleInstanceStr("module.foo[2]"),
  1207  				Call:   ModuleCall{Name: "bar"},
  1208  			},
  1209  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
  1210  			CanChainFrom: false,
  1211  		},
  1212  
  1213  		{
  1214  			Endpoint:     mustParseModuleInstanceStr("module.foo[2]"),
  1215  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
  1216  			CanChainFrom: false,
  1217  		},
  1218  		{
  1219  			Endpoint:     mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
  1220  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
  1221  			CanChainFrom: false,
  1222  		},
  1223  
  1224  		{
  1225  			Endpoint:     mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
  1226  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
  1227  			CanChainFrom: true,
  1228  		},
  1229  
  1230  		{
  1231  			Endpoint:     mustParseAbsResourceInstanceStr("resource.baz"),
  1232  			EndpointMod:  Module{"foo"},
  1233  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
  1234  			CanChainFrom: true,
  1235  		},
  1236  
  1237  		{
  1238  			Endpoint:     mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
  1239  			Other:        mustParseAbsResourceInstanceStr("resource.baz"),
  1240  			OtherMod:     Module{"foo"},
  1241  			CanChainFrom: true,
  1242  		},
  1243  
  1244  		{
  1245  			Endpoint:     mustParseAbsResourceInstanceStr("resource.baz"),
  1246  			EndpointMod:  Module{"foo"},
  1247  			Other:        mustParseAbsResourceInstanceStr("resource.baz"),
  1248  			OtherMod:     Module{"foo"},
  1249  			CanChainFrom: true,
  1250  		},
  1251  
  1252  		{
  1253  			Endpoint:     mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(),
  1254  			EndpointMod:  Module{"foo"},
  1255  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
  1256  			CanChainFrom: true,
  1257  		},
  1258  
  1259  		{
  1260  			Endpoint:     mustParseModuleInstanceStr("module.foo[2].module.baz"),
  1261  			Other:        mustParseModuleInstanceStr("module.baz"),
  1262  			OtherMod:     Module{"foo"},
  1263  			CanChainFrom: true,
  1264  		},
  1265  
  1266  		{
  1267  			Endpoint: AbsModuleCall{
  1268  				Call: ModuleCall{Name: "bing"},
  1269  			},
  1270  			EndpointMod: Module{"foo", "baz"},
  1271  			Other: AbsModuleCall{
  1272  				Module: mustParseModuleInstanceStr("module.baz"),
  1273  				Call:   ModuleCall{Name: "bing"},
  1274  			},
  1275  			OtherMod:     Module{"foo"},
  1276  			CanChainFrom: true,
  1277  		},
  1278  
  1279  		{
  1280  			Endpoint:     mustParseAbsResourceInstanceStr("resource.baz"),
  1281  			EndpointMod:  Module{"foo"},
  1282  			Other:        mustParseAbsResourceInstanceStr("module.foo[2].resource.baz").ContainingResource(),
  1283  			NestedWithin: true,
  1284  		},
  1285  
  1286  		{
  1287  			Endpoint:     mustParseAbsResourceInstanceStr("module.foo[2].resource.baz"),
  1288  			Other:        mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(),
  1289  			OtherMod:     Module{"foo"},
  1290  			NestedWithin: true,
  1291  		},
  1292  
  1293  		{
  1294  			Endpoint:     mustParseAbsResourceInstanceStr("resource.baz"),
  1295  			EndpointMod:  Module{"foo"},
  1296  			Other:        mustParseAbsResourceInstanceStr("resource.baz").ContainingResource(),
  1297  			OtherMod:     Module{"foo"},
  1298  			NestedWithin: true,
  1299  		},
  1300  
  1301  		{
  1302  			Endpoint:     mustParseAbsResourceInstanceStr("ressurce.baz").ContainingResource(),
  1303  			EndpointMod:  Module{"foo"},
  1304  			Other:        mustParseModuleInstanceStr("module.foo[2]"),
  1305  			NestedWithin: true,
  1306  		},
  1307  
  1308  		{
  1309  			Endpoint: AbsModuleCall{
  1310  				Call: ModuleCall{Name: "bang"},
  1311  			},
  1312  			EndpointMod: Module{"foo", "baz", "bing"},
  1313  			Other: AbsModuleCall{
  1314  				Module: mustParseModuleInstanceStr("module.baz"),
  1315  				Call:   ModuleCall{Name: "bing"},
  1316  			},
  1317  			OtherMod:     Module{"foo"},
  1318  			NestedWithin: true,
  1319  		},
  1320  
  1321  		{
  1322  			Endpoint: AbsModuleCall{
  1323  				Module: mustParseModuleInstanceStr("module.bing"),
  1324  				Call:   ModuleCall{Name: "bang"},
  1325  			},
  1326  			EndpointMod: Module{"foo", "baz"},
  1327  			Other: AbsModuleCall{
  1328  				Module: mustParseModuleInstanceStr("module.foo.module.baz"),
  1329  				Call:   ModuleCall{Name: "bing"},
  1330  			},
  1331  			NestedWithin: true,
  1332  		},
  1333  	}
  1334  
  1335  	for i, test := range tests {
  1336  		t.Run(fmt.Sprintf("[%02d]%s.CanChainFrom(%s)", i, test.Endpoint, test.Other),
  1337  			func(t *testing.T) {
  1338  				endpoint := &MoveEndpointInModule{
  1339  					relSubject: test.Endpoint,
  1340  					module:     test.EndpointMod,
  1341  				}
  1342  
  1343  				other := &MoveEndpointInModule{
  1344  					relSubject: test.Other,
  1345  					module:     test.OtherMod,
  1346  				}
  1347  
  1348  				if endpoint.CanChainFrom(other) != test.CanChainFrom {
  1349  					t.Errorf("expected %s CanChainFrom %s == %t", endpoint, other, test.CanChainFrom)
  1350  				}
  1351  
  1352  				if endpoint.NestedWithin(other) != test.NestedWithin {
  1353  					t.Errorf("expected %s NestedWithin %s == %t", endpoint, other, test.NestedWithin)
  1354  				}
  1355  			},
  1356  		)
  1357  	}
  1358  }
  1359  
  1360  func TestSelectsModule(t *testing.T) {
  1361  	tests := []struct {
  1362  		Endpoint *MoveEndpointInModule
  1363  		Addr     ModuleInstance
  1364  		Selects  bool
  1365  	}{
  1366  		{
  1367  			Endpoint: &MoveEndpointInModule{
  1368  				relSubject: AbsModuleCall{
  1369  					Module: mustParseModuleInstanceStr("module.foo[2]"),
  1370  					Call:   ModuleCall{Name: "bar"},
  1371  				},
  1372  			},
  1373  			Addr:    mustParseModuleInstanceStr("module.foo[2].module.bar[1]"),
  1374  			Selects: true,
  1375  		},
  1376  		{
  1377  			Endpoint: &MoveEndpointInModule{
  1378  				module: mustParseModuleInstanceStr("module.foo").Module(),
  1379  				relSubject: AbsModuleCall{
  1380  					Module: mustParseModuleInstanceStr("module.bar[2]"),
  1381  					Call:   ModuleCall{Name: "baz"},
  1382  				},
  1383  			},
  1384  			Addr:    mustParseModuleInstanceStr("module.foo[2].module.bar[2].module.baz"),
  1385  			Selects: true,
  1386  		},
  1387  		{
  1388  			Endpoint: &MoveEndpointInModule{
  1389  				module: mustParseModuleInstanceStr("module.foo").Module(),
  1390  				relSubject: AbsModuleCall{
  1391  					Module: mustParseModuleInstanceStr("module.bar[2]"),
  1392  					Call:   ModuleCall{Name: "baz"},
  1393  				},
  1394  			},
  1395  			Addr:    mustParseModuleInstanceStr("module.foo[2].module.bar[1].module.baz"),
  1396  			Selects: false,
  1397  		},
  1398  		{
  1399  			Endpoint: &MoveEndpointInModule{
  1400  				relSubject: AbsModuleCall{
  1401  					Module: mustParseModuleInstanceStr("module.bar"),
  1402  					Call:   ModuleCall{Name: "baz"},
  1403  				},
  1404  			},
  1405  			Addr:    mustParseModuleInstanceStr("module.bar[1].module.baz"),
  1406  			Selects: false,
  1407  		},
  1408  		{
  1409  			Endpoint: &MoveEndpointInModule{
  1410  				module:     mustParseModuleInstanceStr("module.foo").Module(),
  1411  				relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`),
  1412  			},
  1413  			Addr:    mustParseModuleInstanceStr(`module.foo[1].module.bar`),
  1414  			Selects: true,
  1415  		},
  1416  		{
  1417  			Endpoint: &MoveEndpointInModule{
  1418  				relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
  1419  			},
  1420  			Addr:    mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
  1421  			Selects: true,
  1422  		},
  1423  		{
  1424  			Endpoint: &MoveEndpointInModule{
  1425  				relSubject: mustParseAbsResourceInstanceStr(`module.bar.module.baz["key"].resource.name`).ContainingResource(),
  1426  			},
  1427  			Addr:    mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
  1428  			Selects: true,
  1429  		},
  1430  		{
  1431  			Endpoint: &MoveEndpointInModule{
  1432  				module:     mustParseModuleInstanceStr("module.nope").Module(),
  1433  				relSubject: mustParseAbsResourceInstanceStr(`module.bar.resource.name["key"]`),
  1434  			},
  1435  			Addr:    mustParseModuleInstanceStr(`module.foo[1].module.bar`),
  1436  			Selects: false,
  1437  		},
  1438  		{
  1439  			Endpoint: &MoveEndpointInModule{
  1440  				relSubject: mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
  1441  			},
  1442  			Addr:    mustParseModuleInstanceStr(`module.bar.module.baz["nope"]`),
  1443  			Selects: false,
  1444  		},
  1445  		{
  1446  			Endpoint: &MoveEndpointInModule{
  1447  				relSubject: mustParseAbsResourceInstanceStr(`module.nope.module.baz["key"].resource.name`).ContainingResource(),
  1448  			},
  1449  			Addr:    mustParseModuleInstanceStr(`module.bar.module.baz["key"]`),
  1450  			Selects: false,
  1451  		},
  1452  	}
  1453  
  1454  	for i, test := range tests {
  1455  		t.Run(fmt.Sprintf("[%02d]%s.SelectsModule(%s)", i, test.Endpoint, test.Addr),
  1456  			func(t *testing.T) {
  1457  				if test.Endpoint.SelectsModule(test.Addr) != test.Selects {
  1458  					t.Errorf("expected %s SelectsModule %s == %t", test.Endpoint, test.Addr, test.Selects)
  1459  				}
  1460  			},
  1461  		)
  1462  	}
  1463  }
  1464  
  1465  func TestSelectsResource(t *testing.T) {
  1466  	matchingResource := Resource{
  1467  		Mode: ManagedResourceMode,
  1468  		Type: "foo",
  1469  		Name: "matching",
  1470  	}
  1471  	unmatchingResource := Resource{
  1472  		Mode: ManagedResourceMode,
  1473  		Type: "foo",
  1474  		Name: "unmatching",
  1475  	}
  1476  	childMod := Module{
  1477  		"child",
  1478  	}
  1479  	childModMatchingInst := ModuleInstance{
  1480  		ModuleInstanceStep{Name: "child", InstanceKey: StringKey("matching")},
  1481  	}
  1482  	childModUnmatchingInst := ModuleInstance{
  1483  		ModuleInstanceStep{Name: "child", InstanceKey: StringKey("unmatching")},
  1484  	}
  1485  
  1486  	tests := []struct {
  1487  		Endpoint *MoveEndpointInModule
  1488  		Addr     AbsResource
  1489  		Selects  bool
  1490  	}{
  1491  		{
  1492  			Endpoint: &MoveEndpointInModule{
  1493  				relSubject: matchingResource.Absolute(nil),
  1494  			},
  1495  			Addr:    matchingResource.Absolute(nil),
  1496  			Selects: true, // exact match
  1497  		},
  1498  		{
  1499  			Endpoint: &MoveEndpointInModule{
  1500  				relSubject: unmatchingResource.Absolute(nil),
  1501  			},
  1502  			Addr:    matchingResource.Absolute(nil),
  1503  			Selects: false, // wrong resource name
  1504  		},
  1505  		{
  1506  			Endpoint: &MoveEndpointInModule{
  1507  				relSubject: unmatchingResource.Instance(IntKey(1)).Absolute(nil),
  1508  			},
  1509  			Addr:    matchingResource.Absolute(nil),
  1510  			Selects: false, // wrong resource name
  1511  		},
  1512  		{
  1513  			Endpoint: &MoveEndpointInModule{
  1514  				relSubject: matchingResource.Instance(NoKey).Absolute(nil),
  1515  			},
  1516  			Addr:    matchingResource.Absolute(nil),
  1517  			Selects: true, // matches one instance
  1518  		},
  1519  		{
  1520  			Endpoint: &MoveEndpointInModule{
  1521  				relSubject: matchingResource.Instance(IntKey(0)).Absolute(nil),
  1522  			},
  1523  			Addr:    matchingResource.Absolute(nil),
  1524  			Selects: true, // matches one instance
  1525  		},
  1526  		{
  1527  			Endpoint: &MoveEndpointInModule{
  1528  				relSubject: matchingResource.Instance(StringKey("a")).Absolute(nil),
  1529  			},
  1530  			Addr:    matchingResource.Absolute(nil),
  1531  			Selects: true, // matches one instance
  1532  		},
  1533  		{
  1534  			Endpoint: &MoveEndpointInModule{
  1535  				module:     childMod,
  1536  				relSubject: matchingResource.Absolute(nil),
  1537  			},
  1538  			Addr:    matchingResource.Absolute(childModMatchingInst),
  1539  			Selects: true, // in one of the instances of the module where the statement was written
  1540  		},
  1541  		{
  1542  			Endpoint: &MoveEndpointInModule{
  1543  				relSubject: matchingResource.Absolute(childModMatchingInst),
  1544  			},
  1545  			Addr:    matchingResource.Absolute(childModMatchingInst),
  1546  			Selects: true, // exact match
  1547  		},
  1548  		{
  1549  			Endpoint: &MoveEndpointInModule{
  1550  				relSubject: matchingResource.Instance(IntKey(2)).Absolute(childModMatchingInst),
  1551  			},
  1552  			Addr:    matchingResource.Absolute(childModMatchingInst),
  1553  			Selects: true, // matches one instance
  1554  		},
  1555  		{
  1556  			Endpoint: &MoveEndpointInModule{
  1557  				relSubject: matchingResource.Absolute(childModMatchingInst),
  1558  			},
  1559  			Addr:    matchingResource.Absolute(childModUnmatchingInst),
  1560  			Selects: false, // the containing module instance doesn't match
  1561  		},
  1562  		{
  1563  			Endpoint: &MoveEndpointInModule{
  1564  				relSubject: AbsModuleCall{
  1565  					Module: mustParseModuleInstanceStr("module.foo[2]"),
  1566  					Call:   ModuleCall{Name: "bar"},
  1567  				},
  1568  			},
  1569  			Addr:    matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")),
  1570  			Selects: false, // a module call can't match a resource
  1571  		},
  1572  		{
  1573  			Endpoint: &MoveEndpointInModule{
  1574  				relSubject: mustParseModuleInstanceStr("module.foo[2]"),
  1575  			},
  1576  			Addr:    matchingResource.Absolute(mustParseModuleInstanceStr("module.foo[2]")),
  1577  			Selects: false, // a module instance can't match a resource
  1578  		},
  1579  	}
  1580  
  1581  	for i, test := range tests {
  1582  		t.Run(fmt.Sprintf("[%02d]%s SelectsResource(%s)", i, test.Endpoint, test.Addr),
  1583  			func(t *testing.T) {
  1584  				if got, want := test.Endpoint.SelectsResource(test.Addr), test.Selects; got != want {
  1585  					t.Errorf("wrong result\nReceiver: %s\nArgument: %s\ngot:  %t\nwant: %t", test.Endpoint, test.Addr, got, want)
  1586  				}
  1587  			},
  1588  		)
  1589  	}
  1590  }
  1591  
  1592  func TestIsModuleMoveReIndex(t *testing.T) {
  1593  	tests := []struct {
  1594  		from, to AbsMoveable
  1595  		expect   bool
  1596  	}{
  1597  		{
  1598  			from:   mustParseModuleInstanceStr(`module.bar`),
  1599  			to:     mustParseModuleInstanceStr(`module.bar`),
  1600  			expect: true,
  1601  		},
  1602  		{
  1603  			from:   mustParseModuleInstanceStr(`module.bar`),
  1604  			to:     mustParseModuleInstanceStr(`module.bar[0]`),
  1605  			expect: true,
  1606  		},
  1607  		{
  1608  			from: AbsModuleCall{
  1609  				Call: ModuleCall{Name: "bar"},
  1610  			},
  1611  			to:     mustParseModuleInstanceStr(`module.bar[0]`),
  1612  			expect: true,
  1613  		},
  1614  		{
  1615  			from: mustParseModuleInstanceStr(`module.bar["a"]`),
  1616  			to: AbsModuleCall{
  1617  				Call: ModuleCall{Name: "bar"},
  1618  			},
  1619  			expect: true,
  1620  		},
  1621  		{
  1622  			from:   mustParseModuleInstanceStr(`module.foo`),
  1623  			to:     mustParseModuleInstanceStr(`module.bar`),
  1624  			expect: false,
  1625  		},
  1626  		{
  1627  			from:   mustParseModuleInstanceStr(`module.bar`),
  1628  			to:     mustParseModuleInstanceStr(`module.foo[0]`),
  1629  			expect: false,
  1630  		},
  1631  		{
  1632  			from: AbsModuleCall{
  1633  				Call: ModuleCall{Name: "bar"},
  1634  			},
  1635  			to:     mustParseModuleInstanceStr(`module.foo[0]`),
  1636  			expect: false,
  1637  		},
  1638  		{
  1639  			from: mustParseModuleInstanceStr(`module.bar["a"]`),
  1640  			to: AbsModuleCall{
  1641  				Call: ModuleCall{Name: "foo"},
  1642  			},
  1643  			expect: false,
  1644  		},
  1645  		{
  1646  			from:   mustParseModuleInstanceStr(`module.bar.module.baz`),
  1647  			to:     mustParseModuleInstanceStr(`module.bar.module.baz`),
  1648  			expect: true,
  1649  		},
  1650  		{
  1651  			from:   mustParseModuleInstanceStr(`module.bar.module.baz`),
  1652  			to:     mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
  1653  			expect: true,
  1654  		},
  1655  		{
  1656  			from:   mustParseModuleInstanceStr(`module.bar.module.baz`),
  1657  			to:     mustParseModuleInstanceStr(`module.baz.module.baz`),
  1658  			expect: false,
  1659  		},
  1660  		{
  1661  			from:   mustParseModuleInstanceStr(`module.bar.module.baz`),
  1662  			to:     mustParseModuleInstanceStr(`module.baz.module.baz[0]`),
  1663  			expect: false,
  1664  		},
  1665  		{
  1666  			from:   mustParseModuleInstanceStr(`module.bar.module.baz`),
  1667  			to:     mustParseModuleInstanceStr(`module.bar[0].module.baz`),
  1668  			expect: true,
  1669  		},
  1670  		{
  1671  			from:   mustParseModuleInstanceStr(`module.bar[0].module.baz`),
  1672  			to:     mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
  1673  			expect: true,
  1674  		},
  1675  		{
  1676  			from:   mustParseModuleInstanceStr(`module.bar[0].module.baz`),
  1677  			to:     mustParseModuleInstanceStr(`module.bar[1].module.baz[0]`),
  1678  			expect: true,
  1679  		},
  1680  		{
  1681  			from: AbsModuleCall{
  1682  				Call: ModuleCall{Name: "baz"},
  1683  			},
  1684  			to:     mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
  1685  			expect: false,
  1686  		},
  1687  		{
  1688  			from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
  1689  			to: AbsModuleCall{
  1690  				Call: ModuleCall{Name: "baz"},
  1691  			},
  1692  			expect: false,
  1693  		},
  1694  
  1695  		{
  1696  			from: AbsModuleCall{
  1697  				Module: mustParseModuleInstanceStr(`module.bar[0]`),
  1698  				Call:   ModuleCall{Name: "baz"},
  1699  			},
  1700  			to:     mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
  1701  			expect: true,
  1702  		},
  1703  
  1704  		{
  1705  			from: mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
  1706  			to: AbsModuleCall{
  1707  				Module: mustParseModuleInstanceStr(`module.bar[0]`),
  1708  				Call:   ModuleCall{Name: "baz"},
  1709  			},
  1710  			expect: true,
  1711  		},
  1712  
  1713  		{
  1714  			from:   mustParseModuleInstanceStr(`module.baz`),
  1715  			to:     mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
  1716  			expect: false,
  1717  		},
  1718  		{
  1719  			from:   mustParseModuleInstanceStr(`module.bar.module.baz[0]`),
  1720  			to:     mustParseModuleInstanceStr(`module.baz`),
  1721  			expect: false,
  1722  		},
  1723  	}
  1724  
  1725  	for i, test := range tests {
  1726  		t.Run(fmt.Sprintf("[%02d]IsModuleMoveReIndex(%s, %s)", i, test.from, test.to),
  1727  			func(t *testing.T) {
  1728  				from := &MoveEndpointInModule{
  1729  					relSubject: test.from,
  1730  				}
  1731  
  1732  				to := &MoveEndpointInModule{
  1733  					relSubject: test.to,
  1734  				}
  1735  
  1736  				if got := from.IsModuleReIndex(to); got != test.expect {
  1737  					t.Errorf("expected %t, got %t", test.expect, got)
  1738  				}
  1739  			},
  1740  		)
  1741  	}
  1742  }
  1743  
  1744  func mustParseAbsResourceInstanceStr(s string) AbsResourceInstance {
  1745  	r, diags := ParseAbsResourceInstanceStr(s)
  1746  	if diags.HasErrors() {
  1747  		panic(diags.ErrWithWarnings().Error())
  1748  	}
  1749  	return r
  1750  }