github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/graph_builder_apply_test.go (about)

     1  package durgaform
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/zclconf/go-cty/cty"
    10  
    11  	"github.com/eliastor/durgaform/internal/addrs"
    12  	"github.com/eliastor/durgaform/internal/plans"
    13  	"github.com/eliastor/durgaform/internal/states"
    14  )
    15  
    16  func TestApplyGraphBuilder_impl(t *testing.T) {
    17  	var _ GraphBuilder = new(ApplyGraphBuilder)
    18  }
    19  
    20  func TestApplyGraphBuilder(t *testing.T) {
    21  	changes := &plans.Changes{
    22  		Resources: []*plans.ResourceInstanceChangeSrc{
    23  			{
    24  				Addr: mustResourceInstanceAddr("test_object.create"),
    25  				ChangeSrc: plans.ChangeSrc{
    26  					Action: plans.Create,
    27  				},
    28  			},
    29  			{
    30  				Addr: mustResourceInstanceAddr("test_object.other"),
    31  				ChangeSrc: plans.ChangeSrc{
    32  					Action: plans.Update,
    33  				},
    34  			},
    35  			{
    36  				Addr: mustResourceInstanceAddr("module.child.test_object.create"),
    37  				ChangeSrc: plans.ChangeSrc{
    38  					Action: plans.Create,
    39  				},
    40  			},
    41  			{
    42  				Addr: mustResourceInstanceAddr("module.child.test_object.other"),
    43  				ChangeSrc: plans.ChangeSrc{
    44  					Action: plans.Create,
    45  				},
    46  			},
    47  		},
    48  	}
    49  
    50  	b := &ApplyGraphBuilder{
    51  		Config:  testModule(t, "graph-builder-apply-basic"),
    52  		Changes: changes,
    53  		Plugins: simpleMockPluginLibrary(),
    54  	}
    55  
    56  	g, err := b.Build(addrs.RootModuleInstance)
    57  	if err != nil {
    58  		t.Fatalf("err: %s", err)
    59  	}
    60  
    61  	if g.Path.String() != addrs.RootModuleInstance.String() {
    62  		t.Fatalf("wrong path %q", g.Path.String())
    63  	}
    64  
    65  	got := strings.TrimSpace(g.String())
    66  	want := strings.TrimSpace(testApplyGraphBuilderStr)
    67  	if diff := cmp.Diff(want, got); diff != "" {
    68  		t.Fatalf("wrong result\n%s", diff)
    69  	}
    70  }
    71  
    72  // This tests the ordering of two resources where a non-CBD depends
    73  // on a CBD. GH-11349.
    74  func TestApplyGraphBuilder_depCbd(t *testing.T) {
    75  	changes := &plans.Changes{
    76  		Resources: []*plans.ResourceInstanceChangeSrc{
    77  			{
    78  				Addr: mustResourceInstanceAddr("test_object.A"),
    79  				ChangeSrc: plans.ChangeSrc{
    80  					Action: plans.CreateThenDelete,
    81  				},
    82  			},
    83  			{
    84  				Addr: mustResourceInstanceAddr("test_object.B"),
    85  				ChangeSrc: plans.ChangeSrc{
    86  					Action: plans.Update,
    87  				},
    88  			},
    89  		},
    90  	}
    91  
    92  	state := states.NewState()
    93  	root := state.EnsureModule(addrs.RootModuleInstance)
    94  	root.SetResourceInstanceCurrent(
    95  		mustResourceInstanceAddr("test_object.A").Resource,
    96  		&states.ResourceInstanceObjectSrc{
    97  			Status:    states.ObjectReady,
    98  			AttrsJSON: []byte(`{"id":"A"}`),
    99  		},
   100  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   101  	)
   102  	root.SetResourceInstanceCurrent(
   103  		mustResourceInstanceAddr("test_object.B").Resource,
   104  		&states.ResourceInstanceObjectSrc{
   105  			Status:       states.ObjectReady,
   106  			AttrsJSON:    []byte(`{"id":"B","test_list":["x"]}`),
   107  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")},
   108  		},
   109  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   110  	)
   111  
   112  	b := &ApplyGraphBuilder{
   113  		Config:  testModule(t, "graph-builder-apply-dep-cbd"),
   114  		Changes: changes,
   115  		Plugins: simpleMockPluginLibrary(),
   116  		State:   state,
   117  	}
   118  
   119  	g, err := b.Build(addrs.RootModuleInstance)
   120  	if err != nil {
   121  		t.Fatalf("err: %s", err)
   122  	}
   123  
   124  	if g.Path.String() != addrs.RootModuleInstance.String() {
   125  		t.Fatalf("wrong path %q", g.Path.String())
   126  	}
   127  
   128  	// We're going to go hunting for our deposed instance node here, so we
   129  	// can find out its key to use in the assertions below.
   130  	var dk states.DeposedKey
   131  	for _, v := range g.Vertices() {
   132  		tv, ok := v.(*NodeDestroyDeposedResourceInstanceObject)
   133  		if !ok {
   134  			continue
   135  		}
   136  		if dk != states.NotDeposed {
   137  			t.Fatalf("more than one deposed instance node in the graph; want only one")
   138  		}
   139  		dk = tv.DeposedKey
   140  	}
   141  	if dk == states.NotDeposed {
   142  		t.Fatalf("no deposed instance node in the graph; want one")
   143  	}
   144  
   145  	destroyName := fmt.Sprintf("test_object.A (destroy deposed %s)", dk)
   146  
   147  	// Create A, Modify B, Destroy A
   148  	testGraphHappensBefore(
   149  		t, g,
   150  		"test_object.A",
   151  		destroyName,
   152  	)
   153  	testGraphHappensBefore(
   154  		t, g,
   155  		"test_object.A",
   156  		"test_object.B",
   157  	)
   158  	testGraphHappensBefore(
   159  		t, g,
   160  		"test_object.B",
   161  		destroyName,
   162  	)
   163  }
   164  
   165  // This tests the ordering of two resources that are both CBD that
   166  // require destroy/create.
   167  func TestApplyGraphBuilder_doubleCBD(t *testing.T) {
   168  	changes := &plans.Changes{
   169  		Resources: []*plans.ResourceInstanceChangeSrc{
   170  			{
   171  				Addr: mustResourceInstanceAddr("test_object.A"),
   172  				ChangeSrc: plans.ChangeSrc{
   173  					Action: plans.CreateThenDelete,
   174  				},
   175  			},
   176  			{
   177  				Addr: mustResourceInstanceAddr("test_object.B"),
   178  				ChangeSrc: plans.ChangeSrc{
   179  					Action: plans.CreateThenDelete,
   180  				},
   181  			},
   182  		},
   183  	}
   184  
   185  	b := &ApplyGraphBuilder{
   186  		Config:  testModule(t, "graph-builder-apply-double-cbd"),
   187  		Changes: changes,
   188  		Plugins: simpleMockPluginLibrary(),
   189  	}
   190  
   191  	g, err := b.Build(addrs.RootModuleInstance)
   192  	if err != nil {
   193  		t.Fatalf("err: %s", err)
   194  	}
   195  
   196  	if g.Path.String() != addrs.RootModuleInstance.String() {
   197  		t.Fatalf("wrong path %q", g.Path.String())
   198  	}
   199  
   200  	// We're going to go hunting for our deposed instance node here, so we
   201  	// can find out its key to use in the assertions below.
   202  	var destroyA, destroyB string
   203  	for _, v := range g.Vertices() {
   204  		tv, ok := v.(*NodeDestroyDeposedResourceInstanceObject)
   205  		if !ok {
   206  			continue
   207  		}
   208  
   209  		switch tv.Addr.Resource.Resource.Name {
   210  		case "A":
   211  			destroyA = fmt.Sprintf("test_object.A (destroy deposed %s)", tv.DeposedKey)
   212  		case "B":
   213  			destroyB = fmt.Sprintf("test_object.B (destroy deposed %s)", tv.DeposedKey)
   214  		default:
   215  			t.Fatalf("unknown instance: %s", tv.Addr)
   216  		}
   217  	}
   218  
   219  	// Create A, Modify B, Destroy A
   220  	testGraphHappensBefore(
   221  		t, g,
   222  		"test_object.A",
   223  		destroyA,
   224  	)
   225  	testGraphHappensBefore(
   226  		t, g,
   227  		"test_object.A",
   228  		"test_object.B",
   229  	)
   230  	testGraphHappensBefore(
   231  		t, g,
   232  		"test_object.B",
   233  		destroyB,
   234  	)
   235  }
   236  
   237  // This tests the ordering of two resources being destroyed that depend
   238  // on each other from only state. GH-11749
   239  func TestApplyGraphBuilder_destroyStateOnly(t *testing.T) {
   240  	changes := &plans.Changes{
   241  		Resources: []*plans.ResourceInstanceChangeSrc{
   242  			{
   243  				Addr: mustResourceInstanceAddr("module.child.test_object.A"),
   244  				ChangeSrc: plans.ChangeSrc{
   245  					Action: plans.Delete,
   246  				},
   247  			},
   248  			{
   249  				Addr: mustResourceInstanceAddr("module.child.test_object.B"),
   250  				ChangeSrc: plans.ChangeSrc{
   251  					Action: plans.Delete,
   252  				},
   253  			},
   254  		},
   255  	}
   256  
   257  	state := states.NewState()
   258  	root := state.EnsureModule(addrs.RootModuleInstance)
   259  	child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
   260  	root.SetResourceInstanceCurrent(
   261  		mustResourceInstanceAddr("test_object.A").Resource,
   262  		&states.ResourceInstanceObjectSrc{
   263  			Status:    states.ObjectReady,
   264  			AttrsJSON: []byte(`{"id":"foo"}`),
   265  		},
   266  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   267  	)
   268  	child.SetResourceInstanceCurrent(
   269  		mustResourceInstanceAddr("test_object.B").Resource,
   270  		&states.ResourceInstanceObjectSrc{
   271  			Status:       states.ObjectReady,
   272  			AttrsJSON:    []byte(`{"id":"bar"}`),
   273  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.test_object.A")},
   274  		},
   275  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   276  	)
   277  
   278  	b := &ApplyGraphBuilder{
   279  		Config:  testModule(t, "empty"),
   280  		Changes: changes,
   281  		State:   state,
   282  		Plugins: simpleMockPluginLibrary(),
   283  	}
   284  
   285  	g, diags := b.Build(addrs.RootModuleInstance)
   286  	if diags.HasErrors() {
   287  		t.Fatalf("err: %s", diags.Err())
   288  	}
   289  
   290  	if g.Path.String() != addrs.RootModuleInstance.String() {
   291  		t.Fatalf("wrong path %q", g.Path.String())
   292  	}
   293  
   294  	testGraphHappensBefore(
   295  		t, g,
   296  		"module.child.test_object.B (destroy)",
   297  		"module.child.test_object.A (destroy)")
   298  }
   299  
   300  // This tests the ordering of destroying a single count of a resource.
   301  func TestApplyGraphBuilder_destroyCount(t *testing.T) {
   302  	changes := &plans.Changes{
   303  		Resources: []*plans.ResourceInstanceChangeSrc{
   304  			{
   305  				Addr: mustResourceInstanceAddr("test_object.A[1]"),
   306  				ChangeSrc: plans.ChangeSrc{
   307  					Action: plans.Delete,
   308  				},
   309  			},
   310  			{
   311  				Addr: mustResourceInstanceAddr("test_object.B"),
   312  				ChangeSrc: plans.ChangeSrc{
   313  					Action: plans.Update,
   314  				},
   315  			},
   316  		},
   317  	}
   318  
   319  	state := states.NewState()
   320  	root := state.RootModule()
   321  	addrA := mustResourceInstanceAddr("test_object.A[1]")
   322  	root.SetResourceInstanceCurrent(
   323  		addrA.Resource,
   324  		&states.ResourceInstanceObjectSrc{
   325  			Status:    states.ObjectReady,
   326  			AttrsJSON: []byte(`{"id":"B"}`),
   327  		},
   328  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   329  	)
   330  	root.SetResourceInstanceCurrent(
   331  		mustResourceInstanceAddr("test_object.B").Resource,
   332  		&states.ResourceInstanceObjectSrc{
   333  			Status:       states.ObjectReady,
   334  			AttrsJSON:    []byte(`{"id":"B"}`),
   335  			Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()},
   336  		},
   337  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   338  	)
   339  
   340  	b := &ApplyGraphBuilder{
   341  		Config:  testModule(t, "graph-builder-apply-count"),
   342  		Changes: changes,
   343  		Plugins: simpleMockPluginLibrary(),
   344  		State:   state,
   345  	}
   346  
   347  	g, err := b.Build(addrs.RootModuleInstance)
   348  	if err != nil {
   349  		t.Fatalf("err: %s", err)
   350  	}
   351  
   352  	if g.Path.String() != addrs.RootModuleInstance.String() {
   353  		t.Fatalf("wrong module path %q", g.Path)
   354  	}
   355  
   356  	got := strings.TrimSpace(g.String())
   357  	want := strings.TrimSpace(testApplyGraphBuilderDestroyCountStr)
   358  	if diff := cmp.Diff(want, got); diff != "" {
   359  		t.Fatalf("wrong result\n%s", diff)
   360  	}
   361  }
   362  
   363  func TestApplyGraphBuilder_moduleDestroy(t *testing.T) {
   364  	changes := &plans.Changes{
   365  		Resources: []*plans.ResourceInstanceChangeSrc{
   366  			{
   367  				Addr: mustResourceInstanceAddr("module.A.test_object.foo"),
   368  				ChangeSrc: plans.ChangeSrc{
   369  					Action: plans.Delete,
   370  				},
   371  			},
   372  			{
   373  				Addr: mustResourceInstanceAddr("module.B.test_object.foo"),
   374  				ChangeSrc: plans.ChangeSrc{
   375  					Action: plans.Delete,
   376  				},
   377  			},
   378  		},
   379  	}
   380  
   381  	state := states.NewState()
   382  	modA := state.EnsureModule(addrs.RootModuleInstance.Child("A", addrs.NoKey))
   383  	modA.SetResourceInstanceCurrent(
   384  		mustResourceInstanceAddr("test_object.foo").Resource,
   385  		&states.ResourceInstanceObjectSrc{
   386  			Status:    states.ObjectReady,
   387  			AttrsJSON: []byte(`{"id":"foo"}`),
   388  		},
   389  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   390  	)
   391  	modB := state.EnsureModule(addrs.RootModuleInstance.Child("B", addrs.NoKey))
   392  	modB.SetResourceInstanceCurrent(
   393  		mustResourceInstanceAddr("test_object.foo").Resource,
   394  		&states.ResourceInstanceObjectSrc{
   395  			Status:       states.ObjectReady,
   396  			AttrsJSON:    []byte(`{"id":"foo","value":"foo"}`),
   397  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.A.test_object.foo")},
   398  		},
   399  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   400  	)
   401  
   402  	b := &ApplyGraphBuilder{
   403  		Config:  testModule(t, "graph-builder-apply-module-destroy"),
   404  		Changes: changes,
   405  		Plugins: simpleMockPluginLibrary(),
   406  		State:   state,
   407  	}
   408  
   409  	g, err := b.Build(addrs.RootModuleInstance)
   410  	if err != nil {
   411  		t.Fatalf("err: %s", err)
   412  	}
   413  
   414  	testGraphHappensBefore(
   415  		t, g,
   416  		"module.B.test_object.foo (destroy)",
   417  		"module.A.test_object.foo (destroy)",
   418  	)
   419  }
   420  
   421  func TestApplyGraphBuilder_targetModule(t *testing.T) {
   422  	changes := &plans.Changes{
   423  		Resources: []*plans.ResourceInstanceChangeSrc{
   424  			{
   425  				Addr: mustResourceInstanceAddr("test_object.foo"),
   426  				ChangeSrc: plans.ChangeSrc{
   427  					Action: plans.Update,
   428  				},
   429  			},
   430  			{
   431  				Addr: mustResourceInstanceAddr("module.child2.test_object.foo"),
   432  				ChangeSrc: plans.ChangeSrc{
   433  					Action: plans.Update,
   434  				},
   435  			},
   436  		},
   437  	}
   438  
   439  	b := &ApplyGraphBuilder{
   440  		Config:  testModule(t, "graph-builder-apply-target-module"),
   441  		Changes: changes,
   442  		Plugins: simpleMockPluginLibrary(),
   443  		Targets: []addrs.Targetable{
   444  			addrs.RootModuleInstance.Child("child2", addrs.NoKey),
   445  		},
   446  	}
   447  
   448  	g, err := b.Build(addrs.RootModuleInstance)
   449  	if err != nil {
   450  		t.Fatalf("err: %s", err)
   451  	}
   452  
   453  	testGraphNotContains(t, g, "module.child1.output.instance_id")
   454  }
   455  
   456  // Ensure that an update resulting from the removal of a resource happens after
   457  // that resource is destroyed.
   458  func TestApplyGraphBuilder_updateFromOrphan(t *testing.T) {
   459  	schemas := simpleTestSchemas()
   460  	instanceSchema := schemas.Providers[addrs.NewDefaultProvider("test")].ResourceTypes["test_object"]
   461  
   462  	bBefore, _ := plans.NewDynamicValue(
   463  		cty.ObjectVal(map[string]cty.Value{
   464  			"id":          cty.StringVal("b_id"),
   465  			"test_string": cty.StringVal("a_id"),
   466  		}), instanceSchema.ImpliedType())
   467  	bAfter, _ := plans.NewDynamicValue(
   468  		cty.ObjectVal(map[string]cty.Value{
   469  			"id":          cty.StringVal("b_id"),
   470  			"test_string": cty.StringVal("changed"),
   471  		}), instanceSchema.ImpliedType())
   472  
   473  	changes := &plans.Changes{
   474  		Resources: []*plans.ResourceInstanceChangeSrc{
   475  			{
   476  				Addr: mustResourceInstanceAddr("test_object.a"),
   477  				ChangeSrc: plans.ChangeSrc{
   478  					Action: plans.Delete,
   479  				},
   480  			},
   481  			{
   482  				Addr: mustResourceInstanceAddr("test_object.b"),
   483  				ChangeSrc: plans.ChangeSrc{
   484  					Action: plans.Update,
   485  					Before: bBefore,
   486  					After:  bAfter,
   487  				},
   488  			},
   489  		},
   490  	}
   491  
   492  	state := states.NewState()
   493  	root := state.EnsureModule(addrs.RootModuleInstance)
   494  	root.SetResourceInstanceCurrent(
   495  		addrs.Resource{
   496  			Mode: addrs.ManagedResourceMode,
   497  			Type: "test_object",
   498  			Name: "a",
   499  		}.Instance(addrs.NoKey),
   500  		&states.ResourceInstanceObjectSrc{
   501  			Status:    states.ObjectReady,
   502  			AttrsJSON: []byte(`{"id":"a_id"}`),
   503  		},
   504  		addrs.AbsProviderConfig{
   505  			Provider: addrs.NewDefaultProvider("test"),
   506  			Module:   addrs.RootModule,
   507  		},
   508  	)
   509  	root.SetResourceInstanceCurrent(
   510  		addrs.Resource{
   511  			Mode: addrs.ManagedResourceMode,
   512  			Type: "test_object",
   513  			Name: "b",
   514  		}.Instance(addrs.NoKey),
   515  		&states.ResourceInstanceObjectSrc{
   516  			Status:    states.ObjectReady,
   517  			AttrsJSON: []byte(`{"id":"b_id","test_string":"a_id"}`),
   518  			Dependencies: []addrs.ConfigResource{
   519  				{
   520  					Resource: addrs.Resource{
   521  						Mode: addrs.ManagedResourceMode,
   522  						Type: "test_object",
   523  						Name: "a",
   524  					},
   525  					Module: root.Addr.Module(),
   526  				},
   527  			},
   528  		},
   529  		addrs.AbsProviderConfig{
   530  			Provider: addrs.NewDefaultProvider("test"),
   531  			Module:   addrs.RootModule,
   532  		},
   533  	)
   534  
   535  	b := &ApplyGraphBuilder{
   536  		Config:  testModule(t, "graph-builder-apply-orphan-update"),
   537  		Changes: changes,
   538  		Plugins: simpleMockPluginLibrary(),
   539  		State:   state,
   540  	}
   541  
   542  	g, err := b.Build(addrs.RootModuleInstance)
   543  	if err != nil {
   544  		t.Fatalf("err: %s", err)
   545  	}
   546  
   547  	expected := strings.TrimSpace(`
   548  test_object.a (destroy)
   549  test_object.b
   550    test_object.a (destroy)
   551  `)
   552  
   553  	instanceGraph := filterInstances(g)
   554  	got := strings.TrimSpace(instanceGraph.String())
   555  
   556  	if got != expected {
   557  		t.Fatalf("expected:\n%s\ngot:\n%s", expected, got)
   558  	}
   559  }
   560  
   561  // Ensure that an update resulting from the removal of a resource happens before
   562  // a CBD resource is destroyed.
   563  func TestApplyGraphBuilder_updateFromCBDOrphan(t *testing.T) {
   564  	schemas := simpleTestSchemas()
   565  	instanceSchema := schemas.Providers[addrs.NewDefaultProvider("test")].ResourceTypes["test_object"]
   566  
   567  	bBefore, _ := plans.NewDynamicValue(
   568  		cty.ObjectVal(map[string]cty.Value{
   569  			"id":          cty.StringVal("b_id"),
   570  			"test_string": cty.StringVal("a_id"),
   571  		}), instanceSchema.ImpliedType())
   572  	bAfter, _ := plans.NewDynamicValue(
   573  		cty.ObjectVal(map[string]cty.Value{
   574  			"id":          cty.StringVal("b_id"),
   575  			"test_string": cty.StringVal("changed"),
   576  		}), instanceSchema.ImpliedType())
   577  
   578  	changes := &plans.Changes{
   579  		Resources: []*plans.ResourceInstanceChangeSrc{
   580  			{
   581  				Addr: mustResourceInstanceAddr("test_object.a"),
   582  				ChangeSrc: plans.ChangeSrc{
   583  					Action: plans.Delete,
   584  				},
   585  			},
   586  			{
   587  				Addr: mustResourceInstanceAddr("test_object.b"),
   588  				ChangeSrc: plans.ChangeSrc{
   589  					Action: plans.Update,
   590  					Before: bBefore,
   591  					After:  bAfter,
   592  				},
   593  			},
   594  		},
   595  	}
   596  
   597  	state := states.NewState()
   598  	root := state.EnsureModule(addrs.RootModuleInstance)
   599  	root.SetResourceInstanceCurrent(
   600  		addrs.Resource{
   601  			Mode: addrs.ManagedResourceMode,
   602  			Type: "test_object",
   603  			Name: "a",
   604  		}.Instance(addrs.NoKey),
   605  		&states.ResourceInstanceObjectSrc{
   606  			Status:              states.ObjectReady,
   607  			AttrsJSON:           []byte(`{"id":"a_id"}`),
   608  			CreateBeforeDestroy: true,
   609  		},
   610  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   611  	)
   612  	root.SetResourceInstanceCurrent(
   613  		addrs.Resource{
   614  			Mode: addrs.ManagedResourceMode,
   615  			Type: "test_object",
   616  			Name: "b",
   617  		}.Instance(addrs.NoKey),
   618  		&states.ResourceInstanceObjectSrc{
   619  			Status:    states.ObjectReady,
   620  			AttrsJSON: []byte(`{"id":"b_id","test_string":"a_id"}`),
   621  			Dependencies: []addrs.ConfigResource{
   622  				{
   623  					Resource: addrs.Resource{
   624  						Mode: addrs.ManagedResourceMode,
   625  						Type: "test_object",
   626  						Name: "a",
   627  					},
   628  					Module: root.Addr.Module(),
   629  				},
   630  			},
   631  		},
   632  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"]`),
   633  	)
   634  
   635  	b := &ApplyGraphBuilder{
   636  		Config:  testModule(t, "graph-builder-apply-orphan-update"),
   637  		Changes: changes,
   638  		Plugins: simpleMockPluginLibrary(),
   639  		State:   state,
   640  	}
   641  
   642  	g, err := b.Build(addrs.RootModuleInstance)
   643  	if err != nil {
   644  		t.Fatalf("err: %s", err)
   645  	}
   646  
   647  	expected := strings.TrimSpace(`
   648  test_object.a (destroy)
   649    test_object.b
   650  test_object.b
   651  `)
   652  
   653  	instanceGraph := filterInstances(g)
   654  	got := strings.TrimSpace(instanceGraph.String())
   655  
   656  	if got != expected {
   657  		t.Fatalf("expected:\n%s\ngot:\n%s", expected, got)
   658  	}
   659  }
   660  
   661  // The orphan clean up node should not be connected to a provider
   662  func TestApplyGraphBuilder_orphanedWithProvider(t *testing.T) {
   663  	changes := &plans.Changes{
   664  		Resources: []*plans.ResourceInstanceChangeSrc{
   665  			{
   666  				Addr: mustResourceInstanceAddr("test_object.A"),
   667  				ChangeSrc: plans.ChangeSrc{
   668  					Action: plans.Delete,
   669  				},
   670  			},
   671  		},
   672  	}
   673  
   674  	state := states.NewState()
   675  	root := state.EnsureModule(addrs.RootModuleInstance)
   676  	root.SetResourceInstanceCurrent(
   677  		mustResourceInstanceAddr("test_object.A").Resource,
   678  		&states.ResourceInstanceObjectSrc{
   679  			Status:    states.ObjectReady,
   680  			AttrsJSON: []byte(`{"id":"A"}`),
   681  		},
   682  		mustProviderConfig(`provider["registry.durgaform.io/hashicorp/test"].foo`),
   683  	)
   684  
   685  	b := &ApplyGraphBuilder{
   686  		Config:  testModule(t, "graph-builder-orphan-alias"),
   687  		Changes: changes,
   688  		Plugins: simpleMockPluginLibrary(),
   689  		State:   state,
   690  	}
   691  
   692  	g, err := b.Build(addrs.RootModuleInstance)
   693  	if err != nil {
   694  		t.Fatal(err)
   695  	}
   696  
   697  	// The cleanup node has no state or config of its own, so would create a
   698  	// default provider which we don't want.
   699  	testGraphNotContains(t, g, "provider.test")
   700  }
   701  
   702  const testApplyGraphBuilderStr = `
   703  module.child (close)
   704    module.child.test_object.other
   705  module.child (expand)
   706  module.child.test_object.create
   707    module.child.test_object.create (expand)
   708  module.child.test_object.create (expand)
   709    module.child (expand)
   710    provider["registry.durgaform.io/hashicorp/test"]
   711  module.child.test_object.other
   712    module.child.test_object.create
   713    module.child.test_object.other (expand)
   714  module.child.test_object.other (expand)
   715    module.child (expand)
   716    provider["registry.durgaform.io/hashicorp/test"]
   717  provider["registry.durgaform.io/hashicorp/test"]
   718  provider["registry.durgaform.io/hashicorp/test"] (close)
   719    module.child.test_object.other
   720    test_object.other
   721  root
   722    module.child (close)
   723    provider["registry.durgaform.io/hashicorp/test"] (close)
   724  test_object.create
   725    test_object.create (expand)
   726  test_object.create (expand)
   727    provider["registry.durgaform.io/hashicorp/test"]
   728  test_object.other
   729    test_object.create
   730    test_object.other (expand)
   731  test_object.other (expand)
   732    provider["registry.durgaform.io/hashicorp/test"]
   733  `
   734  
   735  const testApplyGraphBuilderDestroyCountStr = `
   736  provider["registry.durgaform.io/hashicorp/test"]
   737  provider["registry.durgaform.io/hashicorp/test"] (close)
   738    test_object.B
   739  root
   740    provider["registry.durgaform.io/hashicorp/test"] (close)
   741  test_object.A (expand)
   742    provider["registry.durgaform.io/hashicorp/test"]
   743  test_object.A[1] (destroy)
   744    provider["registry.durgaform.io/hashicorp/test"]
   745  test_object.B
   746    test_object.A (expand)
   747    test_object.A[1] (destroy)
   748    test_object.B (expand)
   749  test_object.B (expand)
   750    provider["registry.durgaform.io/hashicorp/test"]
   751  `