github.com/pulumi/terraform@v1.4.0/pkg/addrs/move_endpoint_test.go (about)

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