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 }