Skip to content

Commit

Permalink
Fix local calendar handling of empty recurrence ids (#112745)
Browse files Browse the repository at this point in the history
* Fix handling of empty recurrence ids

* Revert logging changes
  • Loading branch information
allenporter committed Mar 9, 2024
1 parent 3405bda commit 2789060
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 2 deletions.
13 changes: 11 additions & 2 deletions homeassistant/components/calendar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ def _validate_rrule(value: Any) -> str:
return str(value)


def _empty_as_none(value: str | None) -> str | None:
"""Convert any empty string values to None."""
return value or None


CREATE_EVENT_SERVICE = "create_event"
CREATE_EVENT_SCHEMA = vol.All(
cv.has_at_least_one_key(EVENT_START_DATE, EVENT_START_DATETIME, EVENT_IN),
Expand Down Expand Up @@ -735,7 +740,9 @@ async def handle_calendar_event_create(
vol.Required("type"): "calendar/event/delete",
vol.Required("entity_id"): cv.entity_id,
vol.Required(EVENT_UID): cv.string,
vol.Optional(EVENT_RECURRENCE_ID): cv.string,
vol.Optional(EVENT_RECURRENCE_ID): vol.Any(
vol.All(cv.string, _empty_as_none), None
),
vol.Optional(EVENT_RECURRENCE_RANGE): cv.string,
}
)
Expand Down Expand Up @@ -779,7 +786,9 @@ async def handle_calendar_event_delete(
vol.Required("type"): "calendar/event/update",
vol.Required("entity_id"): cv.entity_id,
vol.Required(EVENT_UID): cv.string,
vol.Optional(EVENT_RECURRENCE_ID): cv.string,
vol.Optional(EVENT_RECURRENCE_ID): vol.Any(
vol.All(cv.string, _empty_as_none), None
),
vol.Optional(EVENT_RECURRENCE_RANGE): cv.string,
vol.Required(CONF_EVENT): WEBSOCKET_EVENT_SCHEMA,
}
Expand Down
92 changes: 92 additions & 0 deletions tests/components/local_calendar/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,46 @@ async def test_websocket_delete_recurring(
]


async def test_websocket_delete_empty_recurrence_id(
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
) -> None:
"""Test websocket delete command with an empty recurrence id no-op."""
client = await ws_client()
await client.cmd_result(
"create",
{
"entity_id": TEST_ENTITY,
"event": {
"summary": "Bastille Day Party",
"dtstart": "1997-07-14T17:00:00+00:00",
"dtend": "1997-07-15T04:00:00+00:00",
},
},
)

events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00")
assert list(map(event_fields, events)) == [
{
"summary": "Bastille Day Party",
"start": {"dateTime": "1997-07-14T11:00:00-06:00"},
"end": {"dateTime": "1997-07-14T22:00:00-06:00"},
}
]
uid = events[0]["uid"]

# Delete the event with an empty recurrence id
await client.cmd_result(
"delete",
{
"entity_id": TEST_ENTITY,
"uid": uid,
"recurrence_id": "",
},
)
events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00")
assert list(map(event_fields, events)) == []


async def test_websocket_update(
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
) -> None:
Expand Down Expand Up @@ -458,6 +498,58 @@ async def test_websocket_update(
]


async def test_websocket_update_empty_recurrence(
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
) -> None:
"""Test an edit with an empty recurrence id (no-op)."""
client = await ws_client()
await client.cmd_result(
"create",
{
"entity_id": TEST_ENTITY,
"event": {
"summary": "Bastille Day Party",
"dtstart": "1997-07-14T17:00:00+00:00",
"dtend": "1997-07-15T04:00:00+00:00",
},
},
)

events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00")
assert list(map(event_fields, events)) == [
{
"summary": "Bastille Day Party",
"start": {"dateTime": "1997-07-14T11:00:00-06:00"},
"end": {"dateTime": "1997-07-14T22:00:00-06:00"},
}
]
uid = events[0]["uid"]

# Update the event with an empty string for the recurrence id which should
# have no effect.
await client.cmd_result(
"update",
{
"entity_id": TEST_ENTITY,
"uid": uid,
"recurrence_id": "",
"event": {
"summary": "Bastille Day Party [To be rescheduled]",
"dtstart": "1997-07-15T11:00:00-06:00",
"dtend": "1997-07-15T22:00:00-06:00",
},
},
)
events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00")
assert list(map(event_fields, events)) == [
{
"summary": "Bastille Day Party [To be rescheduled]",
"start": {"dateTime": "1997-07-15T11:00:00-06:00"},
"end": {"dateTime": "1997-07-15T22:00:00-06:00"},
}
]


async def test_websocket_update_recurring_this_and_future(
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
) -> None:
Expand Down

0 comments on commit 2789060

Please sign in to comment.