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