// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package configs import ( "testing" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hcltest" "github.com/hashicorp/terraform/internal/addrs" "github.com/google/go-cmp/cmp" "github.com/zclconf/go-cty/cty" ) func TestRemovedBlock_decode(t *testing.T) { blockRange := hcl.Range{ Filename: "mock.tf", Start: hcl.Pos{Line: 3, Column: 12, Byte: 27}, End: hcl.Pos{Line: 3, Column: 19, Byte: 34}, } foo_expr := hcltest.MockExprTraversalSrc("test_instance.foo") foo_index_expr := hcltest.MockExprTraversalSrc("test_instance.foo[1]") mod_foo_expr := hcltest.MockExprTraversalSrc("module.foo") mod_foo_index_expr := hcltest.MockExprTraversalSrc("module.foo[1]") tests := map[string]struct { input *hcl.Block want *Removed err string }{ "destroy true": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: foo_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "lifecycle", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "destroy": { Name: "destroy", Expr: hcltest.MockExprLiteral(cty.BoolVal(true)), }, }, }), }, }, }), DefRange: blockRange, }, &Removed{ From: mustRemoveEndpointFromExpr(foo_expr), Destroy: true, Managed: &ManagedResource{}, DeclRange: blockRange, }, ``, }, "destroy false": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: foo_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "lifecycle", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "destroy": { Name: "destroy", Expr: hcltest.MockExprLiteral(cty.BoolVal(false)), }, }, }), }, }, }), DefRange: blockRange, }, &Removed{ From: mustRemoveEndpointFromExpr(foo_expr), Destroy: false, Managed: &ManagedResource{}, DeclRange: blockRange, }, ``, }, "provisioner when = destroy": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: foo_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "provisioner", Labels: []string{"remote-exec"}, LabelRanges: []hcl.Range{{}}, Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "when": { Name: "when", Expr: hcltest.MockExprTraversalSrc("destroy"), }, }, }), }, }, }), DefRange: blockRange, }, &Removed{ From: mustRemoveEndpointFromExpr(foo_expr), Destroy: true, Managed: &ManagedResource{ Provisioners: []*Provisioner{ { Type: "remote-exec", Config: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{}, Blocks: hcl.Blocks{}, }), When: ProvisionerWhenDestroy, OnFailure: ProvisionerOnFailureFail, }, }, }, DeclRange: blockRange, }, ``, }, "provisioner when = create": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: foo_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "provisioner", Labels: []string{"local-exec"}, LabelRanges: []hcl.Range{{}}, Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "when": { Name: "when", Expr: hcltest.MockExprTraversalSrc("create"), }, }, }), }, }, }), DefRange: blockRange, }, &Removed{ From: mustRemoveEndpointFromExpr(foo_expr), Destroy: true, Managed: &ManagedResource{ Provisioners: []*Provisioner{ { Type: "local-exec", Config: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{}, Blocks: hcl.Blocks{}, }), When: ProvisionerWhenCreate, OnFailure: ProvisionerOnFailureFail, }, }, }, DeclRange: blockRange, }, `Invalid provisioner block`, }, "provisioner no when": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: foo_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "connection", Body: hcltest.MockBody(&hcl.BodyContent{}), }, &hcl.Block{ Type: "provisioner", Labels: []string{"local-exec"}, LabelRanges: []hcl.Range{{}}, Body: hcltest.MockBody(&hcl.BodyContent{}), }, }, }), DefRange: blockRange, }, &Removed{ From: mustRemoveEndpointFromExpr(foo_expr), Destroy: true, Managed: &ManagedResource{ Connection: &Connection{ Config: hcltest.MockBody(&hcl.BodyContent{}), }, Provisioners: []*Provisioner{ { Type: "local-exec", Config: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{}, Blocks: hcl.Blocks{}, }), When: ProvisionerWhenCreate, OnFailure: ProvisionerOnFailureFail, }, }, }, DeclRange: blockRange, }, `Invalid provisioner block`, }, "modules": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: mod_foo_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "lifecycle", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "destroy": { Name: "destroy", Expr: hcltest.MockExprLiteral(cty.BoolVal(true)), }, }, }), }, }, }), DefRange: blockRange, }, &Removed{ From: mustRemoveEndpointFromExpr(mod_foo_expr), Destroy: true, DeclRange: blockRange, }, ``, }, "provisioner for module": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: mod_foo_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "provisioner", Labels: []string{"local-exec"}, LabelRanges: []hcl.Range{{}}, Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "when": { Name: "when", Expr: hcltest.MockExprTraversalSrc("destroy"), }, }, }), }, }, }), DefRange: blockRange, }, &Removed{ From: mustRemoveEndpointFromExpr(mod_foo_expr), Destroy: true, DeclRange: blockRange, }, `Invalid provisioner block`, }, "connection for module": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: mod_foo_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "connection", Body: hcltest.MockBody(&hcl.BodyContent{}), }, }, }), DefRange: blockRange, }, &Removed{ From: mustRemoveEndpointFromExpr(mod_foo_expr), Destroy: true, DeclRange: blockRange, }, `Invalid connection block`, }, // KEM Unspecified behaviour "no lifecycle block": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: foo_expr, }, }, }), DefRange: blockRange, }, &Removed{ From: mustRemoveEndpointFromExpr(foo_expr), Destroy: true, Managed: &ManagedResource{}, DeclRange: blockRange, }, ``, }, "error: missing argument": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Blocks: hcl.Blocks{ &hcl.Block{ Type: "lifecycle", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "destroy": { Name: "destroy", Expr: hcltest.MockExprLiteral(cty.BoolVal(true)), }, }, }), }, }, }), DefRange: blockRange, }, &Removed{ Destroy: true, DeclRange: blockRange, }, "Missing required argument", }, "error: indexed resource instance": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: foo_index_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "lifecycle", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "destroy": { Name: "destroy", Expr: hcltest.MockExprLiteral(cty.BoolVal(true)), }, }, }), }, }, }), DefRange: blockRange, }, &Removed{ From: nil, Destroy: true, DeclRange: blockRange, }, `Resource instance keys not allowed`, }, "error: indexed module instance": { &hcl.Block{ Type: "removed", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "from": { Name: "from", Expr: mod_foo_index_expr, }, }, Blocks: hcl.Blocks{ &hcl.Block{ Type: "lifecycle", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "destroy": { Name: "destroy", Expr: hcltest.MockExprLiteral(cty.BoolVal(true)), }, }, }), }, }, }), DefRange: blockRange, }, &Removed{ From: nil, Destroy: true, DeclRange: blockRange, }, `Module instance keys not allowed`, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { got, diags := decodeRemovedBlock(test.input) if diags.HasErrors() { if test.err == "" { t.Fatalf("unexpected error: %s", diags.Errs()) } if gotErr := diags[0].Summary; gotErr != test.err { t.Errorf("wrong error, got %q, want %q", gotErr, test.err) } } else if test.err != "" { t.Fatal("expected error") } if !cmp.Equal(got, test.want, cmp.AllowUnexported(addrs.MoveEndpoint{})) { t.Fatalf("wrong result: %s", cmp.Diff(got, test.want)) } }) } } func mustRemoveEndpointFromExpr(expr hcl.Expression) *addrs.RemoveTarget { traversal, hcldiags := hcl.AbsTraversalForExpr(expr) if hcldiags.HasErrors() { panic(hcldiags.Errs()) } ep, diags := addrs.ParseRemoveTarget(traversal) if diags.HasErrors() { panic(diags.Err()) } return ep }