// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package json import ( "reflect" "sort" "testing" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/lang/marks" "github.com/zclconf/go-cty/cty" ) func TestNewQueryStart(t *testing.T) { makeAddr := func(resType, resName string) addrs.AbsResourceInstance { t.Helper() return addrs.ResourceInstance{ Resource: addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: resType, Name: resName, }, Key: addrs.NoKey, }.Absolute(addrs.RootModuleInstance) } tests := []struct { name string addr addrs.AbsResourceInstance inputConfig cty.Value schema *configschema.Block want QueryStart }{ { name: "No sensitivity", addr: makeAddr("test_resource", "foo"), inputConfig: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("bar"), "count": cty.NumberIntVal(1), }), schema: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "foo": {Type: cty.String, Optional: true}, "count": {Type: cty.Number, Optional: true}, }, }, want: QueryStart{ Address: "test_resource.foo", ResourceType: "test_resource", SensitiveAttributePaths: []string{}, }, }, { name: "Sensitivity via Value Marks (top level)", addr: makeAddr("test_resource", "secret_val"), inputConfig: cty.ObjectVal(map[string]cty.Value{ "api_key": cty.StringVal("12345").Mark(marks.Sensitive), "public": cty.StringVal("visible"), }), schema: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "api_key": {Type: cty.String, Optional: true}, "public": {Type: cty.String, Optional: true}, }, }, want: QueryStart{ Address: "test_resource.secret_val", ResourceType: "test_resource", SensitiveAttributePaths: []string{".api_key"}, }, }, { name: "Sensitivity via Schema Definition", addr: makeAddr("test_resource", "schema_secret"), inputConfig: cty.ObjectVal(map[string]cty.Value{ "password": cty.StringVal("hunter2"), }), schema: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "password": {Type: cty.String, Optional: true, Sensitive: true}, }, }, want: QueryStart{ Address: "test_resource.schema_secret", ResourceType: "test_resource", SensitiveAttributePaths: []string{".password"}, }, }, { name: "Nested Map Value Sensitivity", addr: makeAddr("test_resource", "nested_map"), inputConfig: cty.ObjectVal(map[string]cty.Value{ "tags": cty.MapVal(map[string]cty.Value{ "env": cty.StringVal("prod"), "secret": cty.StringVal("hidden").Mark(marks.Sensitive), }), }), schema: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "tags": {Type: cty.Map(cty.String), Optional: true}, }, }, want: QueryStart{ Address: "test_resource.nested_map", ResourceType: "test_resource", SensitiveAttributePaths: []string{`.tags["secret"]`}, }, }, { name: "Nested List Value Sensitivity", addr: makeAddr("test_resource", "nested_list"), inputConfig: cty.ObjectVal(map[string]cty.Value{ "list": cty.ListVal([]cty.Value{ cty.StringVal("one"), cty.StringVal("two").Mark(marks.Sensitive), }), }), schema: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "list": {Type: cty.List(cty.String), Optional: true}, }, }, want: QueryStart{ Address: "test_resource.nested_list", ResourceType: "test_resource", SensitiveAttributePaths: []string{".list[1]"}, }, }, { name: "Complex Nested Schema Sensitivity (Nested Block)", addr: makeAddr("test_resource", "complex_schema"), inputConfig: cty.ObjectVal(map[string]cty.Value{ "config_block": cty.ObjectVal(map[string]cty.Value{ "token": cty.StringVal("abc"), }), }), schema: &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "config_block": { Nesting: configschema.NestingSingle, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "token": {Type: cty.String, Sensitive: true, Optional: true}, }, }, }, }, }, want: QueryStart{ Address: "test_resource.complex_schema", ResourceType: "test_resource", SensitiveAttributePaths: []string{".config_block.token"}, }, }, { name: "Mixed: Schema Sensitive AND Value Marked", addr: makeAddr("test_resource", "mixed"), inputConfig: cty.ObjectVal(map[string]cty.Value{ "double_secret": cty.StringVal("x").Mark(marks.Sensitive), }), schema: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "double_secret": {Type: cty.String, Sensitive: true, Optional: true}, }, }, want: QueryStart{ Address: "test_resource.mixed", ResourceType: "test_resource", // We expect NO duplicates if both sources flag it. SensitiveAttributePaths: []string{".double_secret"}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := NewQueryStart(tt.addr, tt.inputConfig, tt.schema) if got.Address != tt.want.Address { t.Errorf("Address = %q, want %q", got.Address, tt.want.Address) } if got.ResourceType != tt.want.ResourceType { t.Errorf("ResourceType = %q, want %q", got.ResourceType, tt.want.ResourceType) } // Sort slices for deterministic comparison sort.Strings(got.SensitiveAttributePaths) sort.Strings(tt.want.SensitiveAttributePaths) if !reflect.DeepEqual(got.SensitiveAttributePaths, tt.want.SensitiveAttributePaths) { t.Errorf("SensitiveInputConfig mismatch:\nGot: %v\nWant: %v", got.SensitiveAttributePaths, tt.want.SensitiveAttributePaths) } }) } }