github.com/opentofu/opentofu@v1.7.1/internal/tofu/node_output_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/hashicorp/hcl/v2"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/opentofu/opentofu/internal/addrs"
    16  	"github.com/opentofu/opentofu/internal/checks"
    17  	"github.com/opentofu/opentofu/internal/configs"
    18  	"github.com/opentofu/opentofu/internal/lang/marks"
    19  	"github.com/opentofu/opentofu/internal/states"
    20  )
    21  
    22  func TestNodeApplyableOutputExecute_knownValue(t *testing.T) {
    23  	ctx := new(MockEvalContext)
    24  	ctx.StateState = states.NewState().SyncWrapper()
    25  	ctx.RefreshStateState = states.NewState().SyncWrapper()
    26  	ctx.ChecksState = checks.NewState(nil)
    27  
    28  	config := &configs.Output{Name: "map-output"}
    29  	addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
    30  	node := &NodeApplyableOutput{Config: config, Addr: addr}
    31  	val := cty.MapVal(map[string]cty.Value{
    32  		"a": cty.StringVal("b"),
    33  	})
    34  	ctx.EvaluateExprResult = val
    35  
    36  	err := node.Execute(ctx, walkApply)
    37  	if err != nil {
    38  		t.Fatalf("unexpected execute error: %s", err)
    39  	}
    40  
    41  	outputVal := ctx.StateState.OutputValue(addr)
    42  	if got, want := outputVal.Value, val; !got.RawEquals(want) {
    43  		t.Errorf("wrong output value in state\n got: %#v\nwant: %#v", got, want)
    44  	}
    45  
    46  	if !ctx.RefreshStateCalled {
    47  		t.Fatal("should have called RefreshState, but didn't")
    48  	}
    49  	refreshOutputVal := ctx.RefreshStateState.OutputValue(addr)
    50  	if got, want := refreshOutputVal.Value, val; !got.RawEquals(want) {
    51  		t.Fatalf("wrong output value in refresh state\n got: %#v\nwant: %#v", got, want)
    52  	}
    53  }
    54  
    55  func TestNodeApplyableOutputExecute_noState(t *testing.T) {
    56  	ctx := new(MockEvalContext)
    57  
    58  	config := &configs.Output{Name: "map-output"}
    59  	addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
    60  	node := &NodeApplyableOutput{Config: config, Addr: addr}
    61  	val := cty.MapVal(map[string]cty.Value{
    62  		"a": cty.StringVal("b"),
    63  	})
    64  	ctx.EvaluateExprResult = val
    65  
    66  	err := node.Execute(ctx, walkApply)
    67  	if err != nil {
    68  		t.Fatalf("unexpected execute error: %s", err)
    69  	}
    70  }
    71  
    72  func TestNodeApplyableOutputExecute_invalidDependsOn(t *testing.T) {
    73  	ctx := new(MockEvalContext)
    74  	ctx.StateState = states.NewState().SyncWrapper()
    75  	ctx.ChecksState = checks.NewState(nil)
    76  
    77  	config := &configs.Output{
    78  		Name: "map-output",
    79  		DependsOn: []hcl.Traversal{
    80  			{
    81  				hcl.TraverseRoot{Name: "test_instance"},
    82  				hcl.TraverseAttr{Name: "foo"},
    83  				hcl.TraverseAttr{Name: "bar"},
    84  			},
    85  		},
    86  	}
    87  	addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
    88  	node := &NodeApplyableOutput{Config: config, Addr: addr}
    89  	val := cty.MapVal(map[string]cty.Value{
    90  		"a": cty.StringVal("b"),
    91  	})
    92  	ctx.EvaluateExprResult = val
    93  
    94  	diags := node.Execute(ctx, walkApply)
    95  	if !diags.HasErrors() {
    96  		t.Fatal("expected execute error, but there was none")
    97  	}
    98  	if got, want := diags.Err().Error(), "Invalid depends_on reference"; !strings.Contains(got, want) {
    99  		t.Errorf("expected error to include %q, but was: %s", want, got)
   100  	}
   101  }
   102  
   103  func TestNodeApplyableOutputExecute_sensitiveValueNotOutput(t *testing.T) {
   104  	ctx := new(MockEvalContext)
   105  	ctx.StateState = states.NewState().SyncWrapper()
   106  	ctx.ChecksState = checks.NewState(nil)
   107  
   108  	config := &configs.Output{Name: "map-output"}
   109  	addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
   110  	node := &NodeApplyableOutput{Config: config, Addr: addr}
   111  	val := cty.MapVal(map[string]cty.Value{
   112  		"a": cty.StringVal("b").Mark(marks.Sensitive),
   113  	})
   114  	ctx.EvaluateExprResult = val
   115  
   116  	diags := node.Execute(ctx, walkApply)
   117  	if !diags.HasErrors() {
   118  		t.Fatal("expected execute error, but there was none")
   119  	}
   120  	if got, want := diags.Err().Error(), "Output refers to sensitive values"; !strings.Contains(got, want) {
   121  		t.Errorf("expected error to include %q, but was: %s", want, got)
   122  	}
   123  }
   124  
   125  func TestNodeApplyableOutputExecute_sensitiveValueAndOutput(t *testing.T) {
   126  	ctx := new(MockEvalContext)
   127  	ctx.StateState = states.NewState().SyncWrapper()
   128  	ctx.ChecksState = checks.NewState(nil)
   129  
   130  	config := &configs.Output{
   131  		Name:      "map-output",
   132  		Sensitive: true,
   133  	}
   134  	addr := addrs.OutputValue{Name: config.Name}.Absolute(addrs.RootModuleInstance)
   135  	node := &NodeApplyableOutput{Config: config, Addr: addr}
   136  	val := cty.MapVal(map[string]cty.Value{
   137  		"a": cty.StringVal("b").Mark(marks.Sensitive),
   138  	})
   139  	ctx.EvaluateExprResult = val
   140  
   141  	err := node.Execute(ctx, walkApply)
   142  	if err != nil {
   143  		t.Fatalf("unexpected execute error: %s", err)
   144  	}
   145  
   146  	// Unmarked value should be stored in state
   147  	outputVal := ctx.StateState.OutputValue(addr)
   148  	want, _ := val.UnmarkDeep()
   149  	if got := outputVal.Value; !got.RawEquals(want) {
   150  		t.Errorf("wrong output value in state\n got: %#v\nwant: %#v", got, want)
   151  	}
   152  }
   153  
   154  func TestNodeDestroyableOutputExecute(t *testing.T) {
   155  	outputAddr := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
   156  
   157  	state := states.NewState()
   158  	state.Module(addrs.RootModuleInstance).SetOutputValue("foo", cty.StringVal("bar"), false)
   159  	state.OutputValue(outputAddr)
   160  
   161  	ctx := &MockEvalContext{
   162  		StateState: state.SyncWrapper(),
   163  	}
   164  	node := NodeDestroyableOutput{Addr: outputAddr}
   165  
   166  	diags := node.Execute(ctx, walkApply)
   167  	if diags.HasErrors() {
   168  		t.Fatalf("Unexpected error: %s", diags.Err())
   169  	}
   170  	if state.OutputValue(outputAddr) != nil {
   171  		t.Fatal("Unexpected outputs in state after removal")
   172  	}
   173  }
   174  
   175  func TestNodeDestroyableOutputExecute_notInState(t *testing.T) {
   176  	outputAddr := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
   177  
   178  	state := states.NewState()
   179  
   180  	ctx := &MockEvalContext{
   181  		StateState: state.SyncWrapper(),
   182  	}
   183  	node := NodeDestroyableOutput{Addr: outputAddr}
   184  
   185  	diags := node.Execute(ctx, walkApply)
   186  	if diags.HasErrors() {
   187  		t.Fatalf("Unexpected error: %s", diags.Err())
   188  	}
   189  	if state.OutputValue(outputAddr) != nil {
   190  		t.Fatal("Unexpected outputs in state after removal")
   191  	}
   192  }