opentofu/internal/terraform/node_output_test.go
Martin Atkins 3785619f93 core: Use the new checks package for condition tracking
The "checks" package is an expansion what we previously called
plans.Conditions to accommodate a new requirement that we be able to track
which checks we're expecting to run even if we don't actually get around
to running them, which will be helpful when we start using checks as part
of our module testing story because test reporting tools appreciate there
being a relatively consistent set of test cases from one run to the next.

So far this should be essentially a no-op change from an external
functionality standpoint, aside from some minor adjustments to how we
report some of the error and warning cases from condition evaluation in
light of the fact that the "checks" package can now track errors as a
different outcome than a failure of a valid check.

As is often the case with anything which changes what we track
in the EvalContext and persist between plan and apply, Terraform Core is
pretty brittle and so this had knock-on effects elsewhere too. Again, the
goal is for these changes to not create any material externally-visible
difference, and just to accommodate the new assumption that there will
always be a "checks" object available for tracking during a graph walk.
2022-08-26 15:47:29 -07:00

188 lines
5.6 KiB
Go

package terraform
import (
"strings"
"testing"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/checks"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/states"
)
func TestNodeApplyableOutputExecute_knownValue(t *testing.T) {
ctx := new(MockEvalContext)
ctx.StateState = states.NewState().SyncWrapper()
ctx.RefreshStateState = states.NewState().SyncWrapper()
ctx.ChecksState = checks.NewState(nil)
config := &configs.Output{Name: "map-output"}
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
node := &NodeApplyableOutput{Config: config, Addr: addr}
val := cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("b"),
})
ctx.EvaluateExprResult = val
err := node.Execute(ctx, walkApply)
if err != nil {
t.Fatalf("unexpected execute error: %s", err)
}
outputVal := ctx.StateState.OutputValue(addr)
if got, want := outputVal.Value, val; !got.RawEquals(want) {
t.Errorf("wrong output value in state\n got: %#v\nwant: %#v", got, want)
}
if !ctx.RefreshStateCalled {
t.Fatal("should have called RefreshState, but didn't")
}
refreshOutputVal := ctx.RefreshStateState.OutputValue(addr)
if got, want := refreshOutputVal.Value, val; !got.RawEquals(want) {
t.Fatalf("wrong output value in refresh state\n got: %#v\nwant: %#v", got, want)
}
}
func TestNodeApplyableOutputExecute_noState(t *testing.T) {
ctx := new(MockEvalContext)
config := &configs.Output{Name: "map-output"}
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
node := &NodeApplyableOutput{Config: config, Addr: addr}
val := cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("b"),
})
ctx.EvaluateExprResult = val
err := node.Execute(ctx, walkApply)
if err != nil {
t.Fatalf("unexpected execute error: %s", err)
}
}
func TestNodeApplyableOutputExecute_invalidDependsOn(t *testing.T) {
ctx := new(MockEvalContext)
ctx.StateState = states.NewState().SyncWrapper()
ctx.ChecksState = checks.NewState(nil)
config := &configs.Output{
Name: "map-output",
DependsOn: []hcl.Traversal{
{
hcl.TraverseRoot{Name: "test_instance"},
hcl.TraverseAttr{Name: "foo"},
hcl.TraverseAttr{Name: "bar"},
},
},
}
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
node := &NodeApplyableOutput{Config: config, Addr: addr}
val := cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("b"),
})
ctx.EvaluateExprResult = val
diags := node.Execute(ctx, walkApply)
if !diags.HasErrors() {
t.Fatal("expected execute error, but there was none")
}
if got, want := diags.Err().Error(), "Invalid depends_on reference"; !strings.Contains(got, want) {
t.Errorf("expected error to include %q, but was: %s", want, got)
}
}
func TestNodeApplyableOutputExecute_sensitiveValueNotOutput(t *testing.T) {
ctx := new(MockEvalContext)
ctx.StateState = states.NewState().SyncWrapper()
ctx.ChecksState = checks.NewState(nil)
config := &configs.Output{Name: "map-output"}
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
node := &NodeApplyableOutput{Config: config, Addr: addr}
val := cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("b").Mark(marks.Sensitive),
})
ctx.EvaluateExprResult = val
diags := node.Execute(ctx, walkApply)
if !diags.HasErrors() {
t.Fatal("expected execute error, but there was none")
}
if got, want := diags.Err().Error(), "Output refers to sensitive values"; !strings.Contains(got, want) {
t.Errorf("expected error to include %q, but was: %s", want, got)
}
}
func TestNodeApplyableOutputExecute_sensitiveValueAndOutput(t *testing.T) {
ctx := new(MockEvalContext)
ctx.StateState = states.NewState().SyncWrapper()
ctx.ChecksState = checks.NewState(nil)
config := &configs.Output{
Name: "map-output",
Sensitive: true,
}
addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
node := &NodeApplyableOutput{Config: config, Addr: addr}
val := cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("b").Mark(marks.Sensitive),
})
ctx.EvaluateExprResult = val
err := node.Execute(ctx, walkApply)
if err != nil {
t.Fatalf("unexpected execute error: %s", err)
}
// Unmarked value should be stored in state
outputVal := ctx.StateState.OutputValue(addr)
want, _ := val.UnmarkDeep()
if got := outputVal.Value; !got.RawEquals(want) {
t.Errorf("wrong output value in state\n got: %#v\nwant: %#v", got, want)
}
}
func TestNodeDestroyableOutputExecute(t *testing.T) {
outputAddr := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
state := states.NewState()
state.Module(addrs.RootModuleInstance).SetOutputValue("foo", cty.StringVal("bar"), false)
state.OutputValue(outputAddr)
ctx := &MockEvalContext{
StateState: state.SyncWrapper(),
}
node := NodeDestroyableOutput{Addr: outputAddr}
diags := node.Execute(ctx, walkApply)
if diags.HasErrors() {
t.Fatalf("Unexpected error: %s", diags.Err())
}
if state.OutputValue(outputAddr) != nil {
t.Fatal("Unexpected outputs in state after removal")
}
}
func TestNodeDestroyableOutputExecute_notInState(t *testing.T) {
outputAddr := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
state := states.NewState()
ctx := &MockEvalContext{
StateState: state.SyncWrapper(),
}
node := NodeDestroyableOutput{Addr: outputAddr}
diags := node.Execute(ctx, walkApply)
if diags.HasErrors() {
t.Fatalf("Unexpected error: %s", diags.Err())
}
if state.OutputValue(outputAddr) != nil {
t.Fatal("Unexpected outputs in state after removal")
}
}