Skip to content

Commit 8cbc3f5

Browse files
committed
fix: ensure conditional requirements are applied only to originally required fields
1 parent 0aa99ef commit 8cbc3f5

1 file changed

Lines changed: 168 additions & 6 deletions

File tree

internal/pkg/encoding/json/schema/schema_test.go

Lines changed: 168 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -729,8 +729,9 @@ func TestNormalizeSchema_OptionsDependencies_NestedObject(t *testing.T) {
729729
func TestNormalizeSchema_OptionsDependencies_NotInRequired(t *testing.T) {
730730
t.Parallel()
731731

732-
// Schema with options.dependencies but the field is not in the required array
733-
// The transformation should still add the if/then construct but not modify required
732+
// Schema with options.dependencies but the field is NOT in the required array.
733+
// options.dependencies is for UI visibility, NOT for making fields required.
734+
// Therefore, if the field was not originally required, we should NOT add if/then for it.
734735
schema := []byte(`
735736
{
736737
"type": "object",
@@ -765,10 +766,10 @@ func TestNormalizeSchema_OptionsDependencies_NotInRequired(t *testing.T) {
765766
require.True(t, ok)
766767
assert.Equal(t, []any{"mode"}, required)
767768

768-
// Check that allOf with if/then is still added
769-
allOf, ok := result["allOf"].([]any)
770-
require.True(t, ok)
771-
require.Len(t, allOf, 1)
769+
// Check that NO allOf was added - optional_field was not in required,
770+
// so we don't need conditional requirement logic
771+
_, hasAllOf := result["allOf"]
772+
assert.False(t, hasAllOf, "allOf should NOT be added for fields that were not originally required")
772773
}
773774

774775
func TestNormalizeSchema_OptionsDependencies_Validation(t *testing.T) {
@@ -962,6 +963,167 @@ func TestNormalizeSchema_ExistingAllOfIfThen(t *testing.T) {
962963
require.NoError(t, err, "Should pass validation when append_date=1 and append_date_format is provided")
963964
}
964965

966+
func TestNormalizeSchema_OptionsDependencies_MissingDependencyField(t *testing.T) {
967+
t.Parallel()
968+
969+
// This test uses the actual schema from kds-team.app-orchestration-trigger-queue-v2 component.
970+
// triggerActionOnFailure is NOT in the required array.
971+
// triggerActionOnFailure has options.dependencies: { waitUntilFinish: true }
972+
// actionOnFailureSettings has options.dependencies: { triggerActionOnFailure: true }
973+
schema := []byte(`
974+
{
975+
"type": "object",
976+
"required": ["waitUntilFinish", "#kbcToken", "kbcUrl", "orchestrationId"],
977+
"properties": {
978+
"waitUntilFinish": {
979+
"type": "boolean"
980+
},
981+
"triggerActionOnFailure": {
982+
"type": "boolean",
983+
"options": {
984+
"dependencies": {
985+
"waitUntilFinish": true
986+
}
987+
}
988+
},
989+
"actionOnFailureSettings": {
990+
"type": "object",
991+
"required": ["failureConfigurationId"],
992+
"properties": {
993+
"failureConfigurationId": {
994+
"type": "string"
995+
}
996+
},
997+
"options": {
998+
"dependencies": {
999+
"triggerActionOnFailure": true
1000+
}
1001+
}
1002+
},
1003+
"#kbcToken": {
1004+
"type": "string"
1005+
},
1006+
"kbcUrl": {
1007+
"type": "string"
1008+
},
1009+
"orchestrationId": {
1010+
"type": "string"
1011+
}
1012+
}
1013+
}
1014+
`)
1015+
1016+
// First, let's see what the normalized schema looks like
1017+
normalized, err := NormalizeSchema(schema)
1018+
require.NoError(t, err)
1019+
1020+
// Pretty print for debugging
1021+
var prettySchema map[string]any
1022+
json.Unmarshal(normalized, &prettySchema)
1023+
prettyBytes, _ := json.MarshalIndent(prettySchema, "", " ")
1024+
t.Logf("Normalized schema:\n%s", string(prettyBytes))
1025+
1026+
// Test 1: triggerActionOnFailure field is missing entirely (fire-and-forget config)
1027+
// This should PASS - actionOnFailureSettings should NOT be required
1028+
// BUG: Currently this fails with "missing property actionOnFailureSettings"
1029+
content1 := orderedmap.FromPairs([]orderedmap.Pair{
1030+
{
1031+
Key: "parameters",
1032+
Value: orderedmap.FromPairs([]orderedmap.Pair{
1033+
{Key: "waitUntilFinish", Value: false},
1034+
{Key: "#kbcToken", Value: "xxx"},
1035+
{Key: "kbcUrl", Value: "-"},
1036+
{Key: "orchestrationId", Value: "123"},
1037+
// triggerActionOnFailure is NOT present at all
1038+
}),
1039+
},
1040+
})
1041+
err = ValidateContent(schema, content1)
1042+
require.NoError(t, err, "Should pass when triggerActionOnFailure field is missing (undefined)")
1043+
1044+
// Test 2: triggerActionOnFailure is explicitly false
1045+
// This should PASS - actionOnFailureSettings should NOT be required
1046+
content2 := orderedmap.FromPairs([]orderedmap.Pair{
1047+
{
1048+
Key: "parameters",
1049+
Value: orderedmap.FromPairs([]orderedmap.Pair{
1050+
{Key: "waitUntilFinish", Value: true},
1051+
{Key: "#kbcToken", Value: "xxx"},
1052+
{Key: "kbcUrl", Value: "-"},
1053+
{Key: "triggerActionOnFailure", Value: false},
1054+
{Key: "orchestrationId", Value: "123"},
1055+
}),
1056+
},
1057+
})
1058+
err = ValidateContent(schema, content2)
1059+
require.NoError(t, err, "Should pass when triggerActionOnFailure is explicitly false")
1060+
1061+
// Test 3: triggerActionOnFailure is true but actionOnFailureSettings is missing
1062+
// This should PASS - actionOnFailureSettings was NOT in the required array,
1063+
// options.dependencies is only for UI visibility, not for making fields required.
1064+
content3 := orderedmap.FromPairs([]orderedmap.Pair{
1065+
{
1066+
Key: "parameters",
1067+
Value: orderedmap.FromPairs([]orderedmap.Pair{
1068+
{Key: "waitUntilFinish", Value: true},
1069+
{Key: "#kbcToken", Value: "xxx"},
1070+
{Key: "kbcUrl", Value: "-"},
1071+
{Key: "triggerActionOnFailure", Value: true},
1072+
{Key: "orchestrationId", Value: "123"},
1073+
}),
1074+
},
1075+
})
1076+
err = ValidateContent(schema, content3)
1077+
require.NoError(t, err, "Should pass - actionOnFailureSettings was NOT in required, options.dependencies is for visibility only")
1078+
1079+
// Test 4: triggerActionOnFailure is true and actionOnFailureSettings is provided but missing internal required field
1080+
// This should FAIL - failureConfigurationId is required WITHIN actionOnFailureSettings
1081+
content4 := orderedmap.FromPairs([]orderedmap.Pair{
1082+
{
1083+
Key: "parameters",
1084+
Value: orderedmap.FromPairs([]orderedmap.Pair{
1085+
{Key: "waitUntilFinish", Value: true},
1086+
{Key: "#kbcToken", Value: "xxx"},
1087+
{Key: "kbcUrl", Value: "-"},
1088+
{Key: "triggerActionOnFailure", Value: true},
1089+
{Key: "orchestrationId", Value: "123"},
1090+
{
1091+
Key: "actionOnFailureSettings",
1092+
Value: orderedmap.FromPairs([]orderedmap.Pair{
1093+
// missing failureConfigurationId
1094+
}),
1095+
},
1096+
}),
1097+
},
1098+
})
1099+
err = ValidateContent(schema, content4)
1100+
require.Error(t, err, "Should fail - failureConfigurationId is required within actionOnFailureSettings")
1101+
assert.Contains(t, err.Error(), "failureConfigurationId")
1102+
1103+
// Test 5: triggerActionOnFailure is true and actionOnFailureSettings is provided with required field
1104+
// This should PASS
1105+
content5 := orderedmap.FromPairs([]orderedmap.Pair{
1106+
{
1107+
Key: "parameters",
1108+
Value: orderedmap.FromPairs([]orderedmap.Pair{
1109+
{Key: "waitUntilFinish", Value: true},
1110+
{Key: "#kbcToken", Value: "xxx"},
1111+
{Key: "kbcUrl", Value: "-"},
1112+
{Key: "triggerActionOnFailure", Value: true},
1113+
{Key: "orchestrationId", Value: "123"},
1114+
{
1115+
Key: "actionOnFailureSettings",
1116+
Value: orderedmap.FromPairs([]orderedmap.Pair{
1117+
{Key: "failureConfigurationId", Value: "456"},
1118+
}),
1119+
},
1120+
}),
1121+
},
1122+
})
1123+
err = ValidateContent(schema, content5)
1124+
require.NoError(t, err, "Should pass when actionOnFailureSettings is provided with required fields")
1125+
}
1126+
9651127
func getTestSchema() []byte {
9661128
return []byte(`
9671129
{

0 commit comments

Comments
 (0)