Skip to content

Commit 305ec56

Browse files
tobixenclaude
andcommitted
Fix: parent features with explicit defaults not overridden by subfeature derivation
When a feature like create-calendar has an explicit default (full), it represents an independent capability. The _derive_from_subfeatures logic should not override that default based on subfeature statuses. Previously, create-calendar would incorrectly derive as unsupported when only create-calendar.set-displayname was set to unsupported (the Zimbra case: calendar creation works, but setting display name on creation does not). The fix guards the final derivation attempt (before falling back to default) with the same 'default' not in feature_info check used in the tree-walk phase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3e86b04 commit 305ec56

2 files changed

Lines changed: 30 additions & 7 deletions

File tree

caldav/compatibility_hints.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -468,11 +468,15 @@ def is_supported(self, feature, return_type=bool, return_defaults=True, accept_f
468468
if '.' not in feature_:
469469
if not return_defaults:
470470
return None
471-
# Before returning default, check if we have subfeatures with explicit values
472-
# If subfeatures exist and have mixed support levels, we should derive the parent status
473-
derived = self._derive_from_subfeatures(feature_, feature_info, return_type, accept_fragile)
474-
if derived is not None:
475-
return derived
471+
# For features WITHOUT an explicit default (i.e. pure grouping features),
472+
# derive status from subfeatures. Features WITH a default represent
473+
# independent capabilities and their default should not be overridden
474+
# by subfeature statuses (e.g. create-calendar is supported even if
475+
# create-calendar.set-displayname is not).
476+
if 'default' not in feature_info:
477+
derived = self._derive_from_subfeatures(feature_, feature_info, return_type, accept_fragile)
478+
if derived is not None:
479+
return derived
476480
return self._convert_node(self._default(feature_info), feature_info, return_type, accept_fragile)
477481
feature_ = feature_[:feature_.rfind('.')]
478482

@@ -970,8 +974,6 @@ def dotted_feature_set_list(self, compact=False):
970974
"calendar_order",
971975
"calendar_color"
972976
]
973-
## TODO: there may be more, it should be organized and moved here.
974-
## Search for 'zimbra' in the code repository!
975977
}
976978

977979
bedework = {

tests/test_compatibility_hints.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,27 @@ def test_independent_subfeature_not_derived(self) -> None:
324324
auto_result = fs.is_supported("create-calendar.auto", return_type=dict)
325325
assert auto_result == {"support": "unsupported"}
326326

327+
def test_parent_default_not_overridden_by_subfeature_derivation(self) -> None:
328+
"""Test that a parent with an explicit default is not overridden by subfeature derivation.
329+
330+
Zimbra scenario: create-calendar.set-displayname is unsupported, but
331+
create-calendar has an explicit default of 'full'. The parent feature
332+
represents an independent capability (calendar creation works), so the
333+
subfeature status should not override the default.
334+
"""
335+
fs = FeatureSet()
336+
fs._server_features = {
337+
"create-calendar.set-displayname": {"support": "unsupported"},
338+
}
339+
340+
# create-calendar should return its default (full), NOT derive unsupported
341+
# from .set-displayname
342+
result = fs.is_supported("create-calendar", return_type=dict)
343+
assert result == {"support": "full"}, (
344+
f"create-calendar should default to 'full' even when "
345+
f".set-displayname is unsupported, but got {result}"
346+
)
347+
327348
def test_hierarchical_vs_independent_subfeatures(self) -> None:
328349
"""Test that hierarchical subfeatures derive parent, but independent ones don't"""
329350
fs = FeatureSet()

0 commit comments

Comments
 (0)