mirror of
https://github.com/home-assistant/core.git
synced 2026-10-06 14:29:21 -04:00
Filter cancelled events out of caldav (#180947)
This commit is contained in:
@@ -31,11 +31,9 @@ OFFSET = "!!"
|
||||
def _get_status(vevent: CalendarObjectResource) -> CalendarEventStatus | None:
|
||||
"""Return the rfc5545 STATUS of a VEVENT, if a calendar entity reports it.
|
||||
|
||||
Anything outside the supported set is dropped rather than passed on, which
|
||||
covers both the cancelled status a calendar entity does not report and the
|
||||
iana-tokens and x-names that rfc5545 also permits here: reporting no status
|
||||
at all is closer to the truth than reporting one the consumer cannot
|
||||
interpret.
|
||||
Cancelled events never reach this, they are filtered out before an event is
|
||||
built. Anything else outside the supported set is dropped: rfc5545 also
|
||||
permits iana-tokens and x-names here, which a consumer cannot interpret.
|
||||
"""
|
||||
if (value := get_attr_value(vevent, "status")) is None:
|
||||
return None
|
||||
@@ -46,6 +44,17 @@ def _get_status(vevent: CalendarObjectResource) -> CalendarEventStatus | None:
|
||||
return None
|
||||
|
||||
|
||||
def _is_cancelled(vevent: CalendarObjectResource) -> bool:
|
||||
"""Return whether a VEVENT has been called off.
|
||||
|
||||
rfc5545 keeps a cancelled event in the calendar rather than deleting it, so
|
||||
a calendar has to be read to find out. A calendar entity does not return
|
||||
such events.
|
||||
"""
|
||||
value = get_attr_value(vevent, "status")
|
||||
return value is not None and value.lower() == "cancelled"
|
||||
|
||||
|
||||
class CalDavUpdateCoordinator(DataUpdateCoordinator[CalendarEvent | None]):
|
||||
"""Class to utilize the calendar dav client object to get next event."""
|
||||
|
||||
@@ -101,6 +110,8 @@ class CalDavUpdateCoordinator(DataUpdateCoordinator[CalendarEvent | None]):
|
||||
vevent = event.vobject_instance.vevent
|
||||
if not self.is_matching(vevent, self.search):
|
||||
continue
|
||||
if _is_cancelled(vevent):
|
||||
continue
|
||||
event_list.append(
|
||||
CalendarEvent(
|
||||
summary=get_attr_value(vevent, "summary") or "",
|
||||
@@ -196,6 +207,7 @@ class CalDavUpdateCoordinator(DataUpdateCoordinator[CalendarEvent | None]):
|
||||
self.is_matching(vevent, self.search)
|
||||
and (not self.is_all_day(vevent) or self.include_all_day)
|
||||
and not self.is_over(vevent)
|
||||
and not _is_cancelled(vevent)
|
||||
)
|
||||
),
|
||||
None,
|
||||
|
||||
@@ -1174,7 +1174,6 @@ END:VCALENDAR"""
|
||||
pytest.param("TENTATIVE", "tentative", id="tentative"),
|
||||
pytest.param("CONFIRMED", "confirmed", id="confirmed"),
|
||||
pytest.param("Tentative", "tentative", id="mixed_case"),
|
||||
pytest.param("CANCELLED", None, id="cancelled_is_not_reported"),
|
||||
pytest.param("X-VENDOR-SPECIFIC", None, id="unsupported_value"),
|
||||
],
|
||||
)
|
||||
@@ -1193,6 +1192,54 @@ async def test_get_events_with_status(
|
||||
assert events[0]["status"] == expected_status
|
||||
|
||||
|
||||
def _mock_calendar_holding(name: str, vevents: list[str]) -> Mock:
|
||||
"""Return a mock calendar holding exactly the given VEVENTs."""
|
||||
calendar = _mock_calendar(name)
|
||||
calendar.search = MagicMock(
|
||||
return_value=[
|
||||
Event(None, f"{idx}.ics", vevent, calendar, str(idx))
|
||||
for idx, vevent in enumerate(vevents)
|
||||
]
|
||||
)
|
||||
return calendar
|
||||
|
||||
|
||||
async def test_cancelled_event_is_not_returned(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test that an event called off is not returned by the API."""
|
||||
events = await _get_api_events_for_vevent(
|
||||
hass,
|
||||
hass_client,
|
||||
ICS_WITH_STATUS.format(status="CANCELLED"),
|
||||
"status-event-uid",
|
||||
)
|
||||
|
||||
assert events == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tz", [UTC])
|
||||
@pytest.mark.parametrize(
|
||||
("calendars"),
|
||||
[[_mock_calendar_holding("Example", [ICS_WITH_STATUS.format(status="CANCELLED")])]],
|
||||
)
|
||||
@pytest.mark.freeze_time(_local_datetime(17, 30))
|
||||
async def test_cancelled_event_is_not_the_ongoing_event(
|
||||
hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]]
|
||||
) -> None:
|
||||
"""Test that an event called off does not turn the entity on.
|
||||
|
||||
The event would be ongoing at this time were it not cancelled, so this
|
||||
covers the state path rather than the API one.
|
||||
"""
|
||||
await setup_platform_cb()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("calendars"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user