| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package jsonplan | |
| import ( | |
| "encoding/json" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/zclconf/go-cty/cty" | |
| "github.com/zclconf/go-cty/cty/msgpack" | |
| "github.com/hashicorp/terraform/internal/addrs" | |
| "github.com/hashicorp/terraform/internal/configs/configschema" | |
| "github.com/hashicorp/terraform/internal/plans" | |
| "github.com/hashicorp/terraform/internal/providers" | |
| "github.com/hashicorp/terraform/internal/terraform" | |
| ) | |
| func TestMarshalActionInvocations(t *testing.T) { | |
| action := addrs.AbsActionInstance{ | |
| Module: addrs.RootModuleInstance, | |
| Action: addrs.ActionInstance{ | |
| Action: addrs.Action{ | |
| Type: "test_action", | |
| Name: "test", | |
| }, | |
| Key: addrs.NoKey, | |
| }, | |
| } | |
| provider := addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.Provider{ | |
| Type: "test", | |
| Namespace: "hashicorp", | |
| Hostname: addrs.DefaultProviderRegistryHost, | |
| }, | |
| } | |
| schemas := &terraform.Schemas{ | |
| Providers: map[addrs.Provider]providers.ProviderSchema{ | |
| provider.Provider: { | |
| Actions: map[string]providers.ActionSchema{ | |
| "test_action": { | |
| ConfigSchema: &configschema.Block{ | |
| Attributes: map[string]*configschema.Attribute{ | |
| "optional": { | |
| Type: cty.String, | |
| }, | |
| "sensitive": { | |
| Type: cty.String, | |
| Sensitive: true, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| tcs := map[string]struct { | |
| input *plans.ActionInvocationInstanceSrc | |
| output ActionInvocation | |
| }{ | |
| "no metadata": { | |
| input: &plans.ActionInvocationInstanceSrc{ | |
| Addr: action, | |
| ActionTrigger: new(plans.InvokeActionTrigger), | |
| ConfigValue: mustDynamicValue(t, cty.ObjectVal(map[string]cty.Value{ | |
| "optional": cty.StringVal("hello"), | |
| "sensitive": cty.StringVal("world"), | |
| })), | |
| SensitiveConfigPaths: nil, | |
| ProviderAddr: provider, | |
| }, | |
| output: ActionInvocation{ | |
| Address: "action.test_action.test", | |
| Type: "test_action", | |
| Name: "test", | |
| ConfigValues: mustJson(t, map[string]interface{}{ | |
| "optional": "hello", | |
| "sensitive": "world", | |
| }), | |
| ConfigSensitive: mustJson(t, map[string]interface{}{ | |
| "sensitive": true, | |
| }), | |
| ConfigUnknown: mustJson(t, map[string]interface{}{}), | |
| ProviderName: "registry.terraform.io/hashicorp/test", | |
| InvokeActionTrigger: new(InvokeActionTrigger), | |
| }, | |
| }, | |
| "unknown value": { | |
| input: &plans.ActionInvocationInstanceSrc{ | |
| Addr: action, | |
| ActionTrigger: new(plans.InvokeActionTrigger), | |
| ConfigValue: mustDynamicValue(t, cty.ObjectVal(map[string]cty.Value{ | |
| "optional": cty.UnknownVal(cty.String), | |
| "sensitive": cty.StringVal("world"), | |
| })), | |
| SensitiveConfigPaths: nil, | |
| ProviderAddr: provider, | |
| }, | |
| output: ActionInvocation{ | |
| Address: "action.test_action.test", | |
| Type: "test_action", | |
| Name: "test", | |
| ConfigValues: mustJson(t, map[string]interface{}{ | |
| "sensitive": "world", | |
| }), | |
| ConfigSensitive: mustJson(t, map[string]interface{}{ | |
| "sensitive": true, | |
| }), | |
| ConfigUnknown: mustJson(t, map[string]interface{}{ | |
| "optional": true, | |
| }), | |
| ProviderName: "registry.terraform.io/hashicorp/test", | |
| InvokeActionTrigger: new(InvokeActionTrigger), | |
| }, | |
| }, | |
| "extra sensitive": { | |
| input: &plans.ActionInvocationInstanceSrc{ | |
| Addr: action, | |
| ActionTrigger: new(plans.InvokeActionTrigger), | |
| ConfigValue: mustDynamicValue(t, cty.ObjectVal(map[string]cty.Value{ | |
| "optional": cty.StringVal("hello"), | |
| "sensitive": cty.StringVal("world"), | |
| })), | |
| SensitiveConfigPaths: []cty.Path{cty.GetAttrPath("optional")}, | |
| ProviderAddr: provider, | |
| }, | |
| output: ActionInvocation{ | |
| Address: "action.test_action.test", | |
| Type: "test_action", | |
| Name: "test", | |
| ConfigValues: mustJson(t, map[string]interface{}{ | |
| "optional": "hello", | |
| "sensitive": "world", | |
| }), | |
| ConfigSensitive: mustJson(t, map[string]interface{}{ | |
| "optional": true, | |
| "sensitive": true, | |
| }), | |
| ConfigUnknown: mustJson(t, map[string]interface{}{}), | |
| ProviderName: "registry.terraform.io/hashicorp/test", | |
| InvokeActionTrigger: new(InvokeActionTrigger), | |
| }, | |
| }, | |
| "trigger address": { | |
| input: &plans.ActionInvocationInstanceSrc{ | |
| Addr: action, | |
| ActionTrigger: &plans.InvokeActionTrigger{ | |
| CallingResourceAddr: func() *addrs.AbsResourceInstance { | |
| a := mustAddr("test_resource.foo[0]") | |
| return &a | |
| }(), | |
| }, | |
| ConfigValue: mustDynamicValue(t, cty.ObjectVal(map[string]cty.Value{ | |
| "optional": cty.StringVal("hello"), | |
| "sensitive": cty.StringVal("world"), | |
| })), | |
| SensitiveConfigPaths: nil, | |
| ProviderAddr: provider, | |
| }, | |
| output: ActionInvocation{ | |
| Address: "action.test_action.test", | |
| Type: "test_action", | |
| Name: "test", | |
| ConfigValues: mustJson(t, map[string]interface{}{ | |
| "optional": "hello", | |
| "sensitive": "world", | |
| }), | |
| ConfigSensitive: mustJson(t, map[string]interface{}{ | |
| "sensitive": true, | |
| }), | |
| ConfigUnknown: mustJson(t, map[string]interface{}{}), | |
| ProviderName: "registry.terraform.io/hashicorp/test", | |
| InvokeActionTrigger: &InvokeActionTrigger{ | |
| CallingResourceAddress: "test_resource.foo[0]", | |
| }, | |
| }, | |
| }, | |
| } | |
| for name, tc := range tcs { | |
| t.Run(name, func(t *testing.T) { | |
| out, err := MarshalActionInvocation(tc.input, schemas) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if diff := cmp.Diff(tc.output, out); len(diff) > 0 { | |
| t.Fatal(diff) | |
| } | |
| }) | |
| } | |
| } | |
| func mustDynamicValue(t *testing.T, value cty.Value) []byte { | |
| out, err := msgpack.Marshal(value, value.Type()) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| return out | |
| } | |
| func mustJson(t *testing.T, data interface{}) json.RawMessage { | |
| out, err := json.Marshal(data) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| return out | |
| } |