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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
### Fixed

* Reusing a `CalDAVSearcher` across multiple `search()` calls could yield inconsistent results: the first call would return only pending tasks (correct), but subsequent calls would change behaviour because `icalendar_searcher.Searcher.check_component()` mutated the `include_completed` field from `None` to `False` as a side-effect. Fixed by passing a copy with `include_completed` already resolved to `filter_search_results()`, leaving the original searcher object unchanged. Fixes https://github.com/python-caldav/caldav/issues/650
* `_resolve_properties()` would crash with `UnboundLocalError` in production mode when a server returned an empty or unrecognisable PROPFIND response (the response paths did not match the request URI and there was more than one or zero paths returned). Fixed by returning `{}` instead of falling through to an unbound variable. Related: https://github.com/pycalendar/calendar-cli/issues/114

## [3.1.0] - 2026-03-19

Expand Down
1 change: 1 addition & 0 deletions caldav/davobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ def _resolve_properties(self, properties: dict) -> dict:
f"paths found: {list(properties.keys())}"
)
error.assert_(False)
return {}
self.props.update(rc)
return rc

Expand Down
25 changes: 25 additions & 0 deletions tests/test_caldav_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2713,3 +2713,28 @@ def test_meta_section_returns_multiple_dicts(self, tmp_path):
"https://work.example.com/dav/",
"https://personal.example.com/dav/",
}


class TestResolveProperties:
"""Tests for _resolve_properties unbound variable bug (issue #647 / calendar-cli #114)."""

def _make_calendar(self, path="/calendar/"):
client = DAVClient(url="https://example.com")
return Calendar(client=client, url=f"https://example.com{path}")

def test_resolve_properties_empty_dict_production_mode(self):
"""In PRODUCTION mode, error.assert_ only logs; _resolve_properties must
not crash with UnboundLocalError when properties dict is empty."""
cal = self._make_calendar()
with mock.patch.object(error, "debugmode", "PRODUCTION"):
result = cal._resolve_properties({})
assert result == {}

def test_resolve_properties_unmatched_paths_production_mode(self):
"""Same but with a non-empty properties dict where path does not match."""
cal = self._make_calendar("/calendar/")
with mock.patch.object(error, "debugmode", "PRODUCTION"):
result = cal._resolve_properties(
{"/other/path/": {"foo": "bar"}, "/yet/another/": {"baz": "qux"}}
)
assert result == {}
Loading