Skip to content

Commit b6fe4e6

Browse files
Copilottobio
andcommitted
Add null value removal to SanitizedValue function and unit tests
Co-authored-by: tobio <444668+tobio@users.noreply.github.com>
1 parent d75d300 commit b6fe4e6

File tree

5 files changed

+139
-0
lines changed

5 files changed

+139
-0
lines changed

internal/kibana/connectors/acc_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,38 @@ func TestAccResourceKibanaConnectorCasesWebhook(t *testing.T) {
119119
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "secrets", regexp.MustCompile(`\"password\":\"password2\"`)),
120120
),
121121
},
122+
{
123+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(tc.minVersion),
124+
ConfigDirectory: acctest.NamedTestCaseDirectory("null_headers"),
125+
ConfigVariables: vars,
126+
Check: resource.ComposeTestCheckFunc(
127+
testCommonAttributes(connectorName, ".cases-webhook"),
128+
129+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "config", regexp.MustCompile(`\"createIncidentJson\":\"{}\"`)),
130+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "config", regexp.MustCompile(`\"createIncidentResponseKey\":\"key\"`)),
131+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "config", regexp.MustCompile(`\"createIncidentUrl\":\"https://www\.elastic\.co/\"`)),
132+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "config", regexp.MustCompile(`\"getIncidentResponseExternalTitleKey\":\"title\"`)),
133+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "config", regexp.MustCompile(`\"getIncidentUrl\":\"https://www\.elastic\.co/\"`)),
134+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "config", regexp.MustCompile(`\"updateIncidentJson\":\"{}\"`)),
135+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "config", regexp.MustCompile(`\"updateIncidentUrl\":\"https://www.elastic\.co/\"`)),
136+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "config", regexp.MustCompile(`\"viewIncidentUrl\":\"https://www\.elastic\.co/\"`)),
137+
// Verify that null headers field is removed from the config
138+
func(s *terraform.State) error {
139+
rs, ok := s.RootModule().Resources["elasticstack_kibana_action_connector.test"]
140+
if !ok {
141+
return fmt.Errorf("resource not found")
142+
}
143+
configStr := rs.Primary.Attributes["config"]
144+
if regexp.MustCompile(`\"headers\"`).MatchString(configStr) {
145+
return fmt.Errorf("headers field should not be present in config when null, got: %s", configStr)
146+
}
147+
return nil
148+
},
149+
150+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "secrets", regexp.MustCompile(`\"user\":\"user1\"`)),
151+
resource.TestMatchResourceAttr("elasticstack_kibana_action_connector.test", "secrets", regexp.MustCompile(`\"password\":\"password1\"`)),
152+
),
153+
},
122154
},
123155
})
124156
})

internal/kibana/connectors/config_value.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func (v ConfigValue) SanitizedValue() (string, diag.Diagnostics) {
6767
}
6868

6969
delete(unsanitizedMap, connectorTypeIDKey)
70+
removeNulls(unsanitizedMap)
7071
sanitizedValue, err := json.Marshal(unsanitizedMap)
7172
if err != nil {
7273
diags.AddError("Failed to marshal sanitized config value", err.Error())
@@ -76,6 +77,17 @@ func (v ConfigValue) SanitizedValue() (string, diag.Diagnostics) {
7677
return string(sanitizedValue), diags
7778
}
7879

80+
// removeNulls recursively removes all null values from the map
81+
func removeNulls(m map[string]interface{}) {
82+
for key, value := range m {
83+
if value == nil {
84+
delete(m, key)
85+
} else if nestedMap, ok := value.(map[string]interface{}); ok {
86+
removeNulls(nestedMap)
87+
}
88+
}
89+
}
90+
7991
// StringSemanticEquals returns true if the given config object value is semantically equal to the current config object value.
8092
// The comparison will ignore any default values present in one value, but unset in the other.
8193
func (v ConfigValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) {

internal/kibana/connectors/config_value_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,47 @@ func TestConfigValue_SanitizedValue(t *testing.T) {
125125
expectError: true,
126126
errorContains: "Failed to unmarshal config value",
127127
},
128+
{
129+
name: "JSON with null values gets sanitized - top level",
130+
configValue: ConfigValue{
131+
Normalized: jsontypes.NewNormalizedValue(`{"key": "value", "nullField": null, "another": "field"}`),
132+
},
133+
expectedResult: `{"another":"field","key":"value"}`,
134+
expectError: false,
135+
},
136+
{
137+
name: "JSON with null values gets sanitized - nested",
138+
configValue: ConfigValue{
139+
Normalized: jsontypes.NewNormalizedValue(`{"key": "value", "nested": {"field": "value", "nullField": null}}`),
140+
},
141+
expectedResult: `{"key":"value","nested":{"field":"value"}}`,
142+
expectError: false,
143+
},
144+
{
145+
name: "JSON with null values gets sanitized - mixed",
146+
configValue: ConfigValue{
147+
Normalized: jsontypes.NewNormalizedValue(`{"key": "value", "nullTop": null, "nested": {"field": "value", "nullNested": null}, "another": null}`),
148+
},
149+
expectedResult: `{"key":"value","nested":{"field":"value"}}`,
150+
expectError: false,
151+
},
152+
{
153+
name: "JSON with only null values results in empty object",
154+
configValue: ConfigValue{
155+
Normalized: jsontypes.NewNormalizedValue(`{"nullField1": null, "nullField2": null}`),
156+
},
157+
expectedResult: `{}`,
158+
expectError: false,
159+
},
160+
{
161+
name: "JSON with null and connector type ID gets both sanitized",
162+
configValue: ConfigValue{
163+
Normalized: jsontypes.NewNormalizedValue(`{"key": "value", "nullField": null, "__tf_provider_connector_type_id": "test-connector"}`),
164+
connectorTypeID: "test-connector",
165+
},
166+
expectedResult: `{"key":"value"}`,
167+
expectError: false,
168+
},
128169
}
129170

130171
for _, tt := range tests {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
variable "connector_name" {
2+
description = "The connector name"
3+
type = string
4+
}
5+
6+
resource "elasticstack_kibana_action_connector" "test" {
7+
name = var.connector_name
8+
config = jsonencode({
9+
createIncidentJson = "{}"
10+
createIncidentResponseKey = "key"
11+
createIncidentUrl = "https://www.elastic.co/"
12+
getIncidentResponseExternalTitleKey = "title"
13+
getIncidentUrl = "https://www.elastic.co/"
14+
headers = null
15+
updateIncidentJson = "{}"
16+
updateIncidentUrl = "https://www.elastic.co/"
17+
viewIncidentUrl = "https://www.elastic.co/"
18+
})
19+
secrets = jsonencode({
20+
user = "user1"
21+
password = "password1"
22+
})
23+
connector_type_id = ".cases-webhook"
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
variable "connector_name" {
2+
description = "The connector name"
3+
type = string
4+
}
5+
6+
variable "connector_id" {
7+
description = "Connector ID"
8+
type = string
9+
}
10+
11+
resource "elasticstack_kibana_action_connector" "test" {
12+
name = var.connector_name
13+
connector_id = var.connector_id
14+
config = jsonencode({
15+
createIncidentJson = "{}"
16+
createIncidentResponseKey = "key"
17+
createIncidentUrl = "https://www.elastic.co/"
18+
getIncidentResponseExternalTitleKey = "title"
19+
getIncidentUrl = "https://www.elastic.co/"
20+
headers = null
21+
updateIncidentJson = "{}"
22+
updateIncidentUrl = "https://www.elastic.co/"
23+
viewIncidentUrl = "https://www.elastic.co/"
24+
})
25+
secrets = jsonencode({
26+
user = "user1"
27+
password = "password1"
28+
})
29+
connector_type_id = ".cases-webhook"
30+
}

0 commit comments

Comments
 (0)