Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions ftf_cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def check_conflicting_ui_properties(spec_obj, path="spec"):
Recursively check for conflicting UI properties in spec fields.

Validates that:
1. patternProperties and x-ui-yaml-editor: true are not both present on the same field
1. patternProperties with object type and x-ui-yaml-editor: true are not both present on the same field
2. x-ui-override-disable: true and x-ui-overrides-only: true are not both present on the same field

Raises UsageError with clear error message if conflicts are found.
Expand All @@ -383,17 +383,22 @@ def check_conflicting_ui_properties(spec_obj, path="spec"):

for key, value in spec_obj.items():
if isinstance(value, dict):
# Check for patternProperties + x-ui-yaml-editor conflict
# Check for patternProperties + x-ui-yaml-editor conflict based on pattern type
has_pattern_properties = "patternProperties" in value
has_yaml_editor = value.get("x-ui-yaml-editor", False)

if has_pattern_properties and has_yaml_editor:
raise click.UsageError(
f"Configuration conflict at {path}.{key}: "
f"Fields cannot have both 'patternProperties' and 'x-ui-yaml-editor: true'. "
f"These properties are mutually exclusive - use either patternProperties for "
f"dynamic key-value structures or x-ui-yaml-editor for free-form YAML editing."
)
# Check the types of all patternProperties
pp = value["patternProperties"]
for pattern_key, pp_val in pp.items():
pattern_type = pp_val.get("type")
if pattern_type == "object":
raise click.UsageError(
f"Configuration conflict at {path}.{key}: "
f"Fields with patternProperties of type 'object' cannot have 'x-ui-yaml-editor: true'. "
f"Use either patternProperties with object type for structured dynamic content "
f"or x-ui-yaml-editor for free-form YAML editing."
)

# Check for x-ui-override-disable + x-ui-overrides-only conflict
has_override_disable = value.get("x-ui-override-disable", False)
Expand Down
58 changes: 53 additions & 5 deletions tests/test_utils_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,15 @@ def test_no_conflicting_properties_pass():
"field2": {"type": "string", "x-ui-yaml-editor": True},
"field3": {"type": "object", "patternProperties": {"^.*$": {"type": "object"}}},
"field4": {"type": "string", "x-ui-override-disable": True},
"field5": {"type": "string", "x-ui-overrides-only": True}
"field5": {"type": "string", "x-ui-overrides-only": True},
"field6": {"type": "object", "patternProperties": {"^.*$": {"type": "string"}}, "x-ui-yaml-editor": True}
}
# Should pass silently
check_conflicting_ui_properties(spec)


def test_pattern_properties_with_yaml_editor_raises():
"""Test that patternProperties + x-ui-yaml-editor conflict is detected."""
def test_pattern_properties_object_type_with_yaml_editor_raises():
"""Test that patternProperties with object type + x-ui-yaml-editor conflict is detected."""
spec = {
"field": {
"type": "object",
Expand All @@ -80,9 +81,8 @@ def test_pattern_properties_with_yaml_editor_raises():
with pytest.raises(click.UsageError) as excinfo:
check_conflicting_ui_properties(spec)
assert "Configuration conflict at spec.field" in str(excinfo.value)
assert "patternProperties" in str(excinfo.value)
assert "patternProperties of type 'object'" in str(excinfo.value)
assert "x-ui-yaml-editor: true" in str(excinfo.value)
assert "mutually exclusive" in str(excinfo.value)


def test_override_disable_with_overrides_only_raises():
Expand Down Expand Up @@ -121,6 +121,19 @@ def test_nested_conflicting_properties_raises():
assert "Configuration conflict at spec.level1.level2.field" in str(excinfo.value)


def test_pattern_properties_string_type_with_yaml_editor_pass():
"""Test that patternProperties with string type + x-ui-yaml-editor is allowed."""
spec = {
"field": {
"type": "object",
"patternProperties": {"^.*$": {"type": "string"}},
"x-ui-yaml-editor": True
}
}
# Should pass silently
check_conflicting_ui_properties(spec)


def test_pattern_properties_with_yaml_editor_false_pass():
"""Test that patternProperties with x-ui-yaml-editor: false is allowed."""
spec = {
Expand Down Expand Up @@ -196,3 +209,38 @@ def test_non_dict_values_ignored():
}
# Should pass silently
check_conflicting_ui_properties(spec)


def test_pattern_properties_mixed_types_with_yaml_editor():
"""Test behavior when patternProperties has mixed types (string and object)."""
spec = {
"field": {
"type": "object",
"patternProperties": {
"^string_.*$": {"type": "string"},
"^object_.*$": {"type": "object"}
},
"x-ui-yaml-editor": True
}
}
# Should raise error because one of the patternProperties is object type
with pytest.raises(click.UsageError) as excinfo:
check_conflicting_ui_properties(spec)
assert "Configuration conflict at spec.field" in str(excinfo.value)
assert "patternProperties of type 'object'" in str(excinfo.value)


def test_pattern_properties_only_string_types_with_yaml_editor_pass():
"""Test that multiple patternProperties all with string type + yaml-editor passes."""
spec = {
"field": {
"type": "object",
"patternProperties": {
"^config_.*$": {"type": "string"},
"^setting_.*$": {"type": "string"}
},
"x-ui-yaml-editor": True
}
}
# Should pass silently
check_conflicting_ui_properties(spec)