Add Local calendar edit support (#84141)
* Add update support for calendars and implement in local calendar * Fix supported feature for update calendar * Increase test coverage for websocket error cases * Improve test coverage for update failure cases * Improve test coverage by sharing code between update and create * Update homeassistant/components/calendar/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
b01efc55a2
commit
624c93bb38
5 changed files with 455 additions and 24 deletions
|
@ -74,6 +74,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
|
|
||||||
websocket_api.async_register_command(hass, handle_calendar_event_create)
|
websocket_api.async_register_command(hass, handle_calendar_event_create)
|
||||||
websocket_api.async_register_command(hass, handle_calendar_event_delete)
|
websocket_api.async_register_command(hass, handle_calendar_event_delete)
|
||||||
|
websocket_api.async_register_command(hass, handle_calendar_event_update)
|
||||||
|
|
||||||
await component.async_setup(config)
|
await component.async_setup(config)
|
||||||
return True
|
return True
|
||||||
|
@ -297,6 +298,16 @@ class CalendarEntity(Entity):
|
||||||
"""Delete an event on the calendar."""
|
"""Delete an event on the calendar."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
async def async_update_event(
|
||||||
|
self,
|
||||||
|
uid: str,
|
||||||
|
event: dict[str, Any],
|
||||||
|
recurrence_id: str | None = None,
|
||||||
|
recurrence_range: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Delete an event on the calendar."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class CalendarEventView(http.HomeAssistantView):
|
class CalendarEventView(http.HomeAssistantView):
|
||||||
"""View to retrieve calendar content."""
|
"""View to retrieve calendar content."""
|
||||||
|
@ -500,3 +511,61 @@ async def handle_calendar_event_delete(
|
||||||
connection.send_error(msg["id"], "failed", str(ex))
|
connection.send_error(msg["id"], "failed", str(ex))
|
||||||
else:
|
else:
|
||||||
connection.send_result(msg["id"])
|
connection.send_result(msg["id"])
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
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_RANGE): cv.string,
|
||||||
|
vol.Required(CONF_EVENT): vol.Schema(
|
||||||
|
vol.All(
|
||||||
|
{
|
||||||
|
vol.Required(EVENT_START): vol.Any(cv.date, cv.datetime),
|
||||||
|
vol.Required(EVENT_END): vol.Any(cv.date, cv.datetime),
|
||||||
|
vol.Required(EVENT_SUMMARY): cv.string,
|
||||||
|
vol.Optional(EVENT_DESCRIPTION): cv.string,
|
||||||
|
vol.Optional(EVENT_RRULE): _validate_rrule,
|
||||||
|
},
|
||||||
|
_has_same_type(EVENT_START, EVENT_END),
|
||||||
|
_has_consistent_timezone(EVENT_START, EVENT_END),
|
||||||
|
_is_sorted(EVENT_START, EVENT_END),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@websocket_api.async_response
|
||||||
|
async def handle_calendar_event_update(
|
||||||
|
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Handle creation of a calendar event."""
|
||||||
|
component: EntityComponent[CalendarEntity] = hass.data[DOMAIN]
|
||||||
|
if not (entity := component.get_entity(msg["entity_id"])):
|
||||||
|
connection.send_error(msg["id"], ERR_NOT_FOUND, "Entity not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
if (
|
||||||
|
not entity.supported_features
|
||||||
|
or not entity.supported_features & CalendarEntityFeature.UPDATE_EVENT
|
||||||
|
):
|
||||||
|
connection.send_message(
|
||||||
|
websocket_api.error_message(
|
||||||
|
msg["id"], ERR_NOT_SUPPORTED, "Calendar does not support event update"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await entity.async_update_event(
|
||||||
|
msg[EVENT_UID],
|
||||||
|
msg[CONF_EVENT],
|
||||||
|
recurrence_id=msg.get(EVENT_RECURRENCE_ID),
|
||||||
|
recurrence_range=msg.get(EVENT_RECURRENCE_RANGE),
|
||||||
|
)
|
||||||
|
except (HomeAssistantError, ValueError) as ex:
|
||||||
|
_LOGGER.error("Error handling Calendar Event call: %s", ex)
|
||||||
|
connection.send_error(msg["id"], "failed", str(ex))
|
||||||
|
else:
|
||||||
|
connection.send_result(msg["id"])
|
||||||
|
|
|
@ -10,6 +10,7 @@ class CalendarEntityFeature(IntEnum):
|
||||||
|
|
||||||
CREATE_EVENT = 1
|
CREATE_EVENT = 1
|
||||||
DELETE_EVENT = 2
|
DELETE_EVENT = 2
|
||||||
|
UPDATE_EVENT = 4
|
||||||
|
|
||||||
|
|
||||||
# rfc5545 fields
|
# rfc5545 fields
|
||||||
|
|
|
@ -15,11 +15,7 @@ from pydantic import ValidationError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.calendar import (
|
from homeassistant.components.calendar import (
|
||||||
EVENT_DESCRIPTION,
|
|
||||||
EVENT_END,
|
|
||||||
EVENT_RRULE,
|
EVENT_RRULE,
|
||||||
EVENT_START,
|
|
||||||
EVENT_SUMMARY,
|
|
||||||
CalendarEntity,
|
CalendarEntity,
|
||||||
CalendarEntityFeature,
|
CalendarEntityFeature,
|
||||||
CalendarEvent,
|
CalendarEvent,
|
||||||
|
@ -55,7 +51,9 @@ class LocalCalendarEntity(CalendarEntity):
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
CalendarEntityFeature.CREATE_EVENT | CalendarEntityFeature.DELETE_EVENT
|
CalendarEntityFeature.CREATE_EVENT
|
||||||
|
| CalendarEntityFeature.DELETE_EVENT
|
||||||
|
| CalendarEntityFeature.UPDATE_EVENT
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -104,22 +102,7 @@ class LocalCalendarEntity(CalendarEntity):
|
||||||
|
|
||||||
async def async_create_event(self, **kwargs: Any) -> None:
|
async def async_create_event(self, **kwargs: Any) -> None:
|
||||||
"""Add a new event to calendar."""
|
"""Add a new event to calendar."""
|
||||||
event_data = {
|
event = _parse_event(kwargs)
|
||||||
EVENT_SUMMARY: kwargs[EVENT_SUMMARY],
|
|
||||||
EVENT_START: kwargs[EVENT_START],
|
|
||||||
EVENT_END: kwargs[EVENT_END],
|
|
||||||
EVENT_DESCRIPTION: kwargs.get(EVENT_DESCRIPTION),
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
event = Event.parse_obj(event_data)
|
|
||||||
except ValidationError as err:
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Error parsing event input fields: %s (%s)", event_data, str(err)
|
|
||||||
)
|
|
||||||
raise vol.Invalid("Error parsing event input fields") from err
|
|
||||||
if rrule := kwargs.get(EVENT_RRULE):
|
|
||||||
event.rrule = Recur.from_rrule(rrule)
|
|
||||||
|
|
||||||
EventStore(self._calendar).add(event)
|
EventStore(self._calendar).add(event)
|
||||||
await self._async_store()
|
await self._async_store()
|
||||||
await self.async_update_ha_state(force_refresh=True)
|
await self.async_update_ha_state(force_refresh=True)
|
||||||
|
@ -142,6 +125,38 @@ class LocalCalendarEntity(CalendarEntity):
|
||||||
await self._async_store()
|
await self._async_store()
|
||||||
await self.async_update_ha_state(force_refresh=True)
|
await self.async_update_ha_state(force_refresh=True)
|
||||||
|
|
||||||
|
async def async_update_event(
|
||||||
|
self,
|
||||||
|
uid: str,
|
||||||
|
event: dict[str, Any],
|
||||||
|
recurrence_id: str | None = None,
|
||||||
|
recurrence_range: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Update an existing event on the calendar."""
|
||||||
|
new_event = _parse_event(event)
|
||||||
|
range_value: Range = Range.NONE
|
||||||
|
if recurrence_range == Range.THIS_AND_FUTURE:
|
||||||
|
range_value = Range.THIS_AND_FUTURE
|
||||||
|
EventStore(self._calendar).edit(
|
||||||
|
uid,
|
||||||
|
new_event,
|
||||||
|
recurrence_id=recurrence_id,
|
||||||
|
recurrence_range=range_value,
|
||||||
|
)
|
||||||
|
await self._async_store()
|
||||||
|
await self.async_update_ha_state(force_refresh=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_event(event: dict[str, Any]) -> Event:
|
||||||
|
"""Parse an ical event from a home assistant event dictionary."""
|
||||||
|
if rrule := event.get(EVENT_RRULE):
|
||||||
|
event[EVENT_RRULE] = Recur.from_rrule(rrule)
|
||||||
|
try:
|
||||||
|
return Event.parse_obj(event)
|
||||||
|
except ValidationError as err:
|
||||||
|
_LOGGER.debug("Error parsing event input fields: %s (%s)", event, str(err))
|
||||||
|
raise vol.Invalid("Error parsing event input fields") from err
|
||||||
|
|
||||||
|
|
||||||
def _get_calendar_event(event: Event) -> CalendarEvent:
|
def _get_calendar_event(event: Event) -> CalendarEvent:
|
||||||
"""Return a CalendarEvent from an API event."""
|
"""Return a CalendarEvent from an API event."""
|
||||||
|
|
|
@ -3,6 +3,8 @@ from datetime import timedelta
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.bootstrap import async_setup_component
|
from homeassistant.bootstrap import async_setup_component
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
@ -67,3 +69,91 @@ async def test_calendars_http_api(hass, hass_client):
|
||||||
{"entity_id": "calendar.calendar_1", "name": "Calendar 1"},
|
{"entity_id": "calendar.calendar_1", "name": "Calendar 1"},
|
||||||
{"entity_id": "calendar.calendar_2", "name": "Calendar 2"},
|
{"entity_id": "calendar.calendar_2", "name": "Calendar 2"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"payload,code",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"type": "calendar/event/create",
|
||||||
|
"entity_id": "calendar.calendar_1",
|
||||||
|
"event": {
|
||||||
|
"summary": "Bastille Day Party",
|
||||||
|
"dtstart": "1997-07-14T17:00:00+00:00",
|
||||||
|
"dtend": "1997-07-15T04:00:00+00:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"not_supported",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"type": "calendar/event/create",
|
||||||
|
"entity_id": "calendar.calendar_99",
|
||||||
|
"event": {
|
||||||
|
"summary": "Bastille Day Party",
|
||||||
|
"dtstart": "1997-07-14T17:00:00+00:00",
|
||||||
|
"dtend": "1997-07-15T04:00:00+00:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"not_found",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"type": "calendar/event/delete",
|
||||||
|
"entity_id": "calendar.calendar_1",
|
||||||
|
"uid": "some-uid",
|
||||||
|
},
|
||||||
|
"not_supported",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"type": "calendar/event/delete",
|
||||||
|
"entity_id": "calendar.calendar_99",
|
||||||
|
"uid": "some-uid",
|
||||||
|
},
|
||||||
|
"not_found",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"type": "calendar/event/update",
|
||||||
|
"entity_id": "calendar.calendar_1",
|
||||||
|
"uid": "some-uid",
|
||||||
|
"event": {
|
||||||
|
"summary": "Bastille Day Party",
|
||||||
|
"dtstart": "1997-07-14T17:00:00+00:00",
|
||||||
|
"dtend": "1997-07-15T04:00:00+00:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"not_supported",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"type": "calendar/event/update",
|
||||||
|
"entity_id": "calendar.calendar_99",
|
||||||
|
"uid": "some-uid",
|
||||||
|
"event": {
|
||||||
|
"summary": "Bastille Day Party",
|
||||||
|
"dtstart": "1997-07-14T17:00:00+00:00",
|
||||||
|
"dtend": "1997-07-15T04:00:00+00:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"not_found",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_unsupported_websocket(hass, hass_ws_client, payload, code):
|
||||||
|
"""Test unsupported websocket command."""
|
||||||
|
await async_setup_component(hass, "calendar", {"calendar": {"platform": "demo"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
**payload,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
resp = await client.receive_json()
|
||||||
|
assert resp.get("id") == 1
|
||||||
|
assert resp.get("error")
|
||||||
|
assert resp["error"].get("code") == code
|
||||||
|
|
|
@ -174,7 +174,7 @@ async def test_empty_calendar(
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
assert dict(state.attributes) == {
|
assert dict(state.attributes) == {
|
||||||
"friendly_name": FRIENDLY_NAME,
|
"friendly_name": FRIENDLY_NAME,
|
||||||
"supported_features": 3,
|
"supported_features": 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ async def test_active_event(
|
||||||
"location": "",
|
"location": "",
|
||||||
"start_time": start.strftime(DATE_STR_FORMAT),
|
"start_time": start.strftime(DATE_STR_FORMAT),
|
||||||
"end_time": end.strftime(DATE_STR_FORMAT),
|
"end_time": end.strftime(DATE_STR_FORMAT),
|
||||||
"supported_features": 3,
|
"supported_features": 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -328,7 +328,7 @@ async def test_upcoming_event(
|
||||||
"location": "",
|
"location": "",
|
||||||
"start_time": start.strftime(DATE_STR_FORMAT),
|
"start_time": start.strftime(DATE_STR_FORMAT),
|
||||||
"end_time": end.strftime(DATE_STR_FORMAT),
|
"end_time": end.strftime(DATE_STR_FORMAT),
|
||||||
"supported_features": 3,
|
"supported_features": 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -521,6 +521,238 @@ async def test_websocket_delete_recurring(
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_websocket_update(
|
||||||
|
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
|
||||||
|
):
|
||||||
|
"""Test websocket update command."""
|
||||||
|
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
|
||||||
|
await client.cmd_result(
|
||||||
|
"update",
|
||||||
|
{
|
||||||
|
"entity_id": TEST_ENTITY,
|
||||||
|
"uid": uid,
|
||||||
|
"event": {
|
||||||
|
"summary": "Bastille Day Party [To be rescheduled]",
|
||||||
|
"dtstart": "1997-07-14",
|
||||||
|
"dtend": "1997-07-15",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
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": {"date": "1997-07-14"},
|
||||||
|
"end": {"date": "1997-07-15"},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_websocket_update_recurring_this_and_future(
|
||||||
|
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
|
||||||
|
):
|
||||||
|
"""Test updating a recurring event."""
|
||||||
|
client = await ws_client()
|
||||||
|
await client.cmd_result(
|
||||||
|
"create",
|
||||||
|
{
|
||||||
|
"entity_id": TEST_ENTITY,
|
||||||
|
"event": {
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"dtstart": "2022-08-22T08:30:00",
|
||||||
|
"dtend": "2022-08-22T09:00:00",
|
||||||
|
"rrule": "FREQ=DAILY",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
events = await get_events("2022-08-22T00:00:00", "2022-08-26T00:00:00")
|
||||||
|
assert list(map(event_fields, events)) == [
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-22T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-22T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220822T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-23T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-23T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220823T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-24T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-24T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220824T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-25T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-25T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220825T083000",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
uid = events[0]["uid"]
|
||||||
|
assert [event["uid"] for event in events] == [uid] * 4
|
||||||
|
|
||||||
|
# Update a single instance and confirm the change is reflected
|
||||||
|
await client.cmd_result(
|
||||||
|
"update",
|
||||||
|
{
|
||||||
|
"entity_id": TEST_ENTITY,
|
||||||
|
"uid": uid,
|
||||||
|
"recurrence_id": "20220824T083000",
|
||||||
|
"recurrence_range": "THISANDFUTURE",
|
||||||
|
"event": {
|
||||||
|
"summary": "Morning Routine [Adjusted]",
|
||||||
|
"dtstart": "2022-08-24T08:00:00",
|
||||||
|
"dtend": "2022-08-24T08:30:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
events = await get_events("2022-08-22T00:00:00", "2022-08-26T00:00:00")
|
||||||
|
assert list(map(event_fields, events)) == [
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-22T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-22T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220822T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-23T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-23T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220823T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine [Adjusted]",
|
||||||
|
"start": {"dateTime": "2022-08-24T08:00:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-24T08:30:00-06:00"},
|
||||||
|
"recurrence_id": "20220824T080000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine [Adjusted]",
|
||||||
|
"start": {"dateTime": "2022-08-25T08:00:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-25T08:30:00-06:00"},
|
||||||
|
"recurrence_id": "20220825T080000",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_websocket_update_recurring(
|
||||||
|
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
|
||||||
|
):
|
||||||
|
"""Test updating a recurring event."""
|
||||||
|
client = await ws_client()
|
||||||
|
await client.cmd_result(
|
||||||
|
"create",
|
||||||
|
{
|
||||||
|
"entity_id": TEST_ENTITY,
|
||||||
|
"event": {
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"dtstart": "2022-08-22T08:30:00",
|
||||||
|
"dtend": "2022-08-22T09:00:00",
|
||||||
|
"rrule": "FREQ=DAILY",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
events = await get_events("2022-08-22T00:00:00", "2022-08-26T00:00:00")
|
||||||
|
assert list(map(event_fields, events)) == [
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-22T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-22T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220822T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-23T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-23T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220823T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-24T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-24T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220824T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-25T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-25T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220825T083000",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
uid = events[0]["uid"]
|
||||||
|
assert [event["uid"] for event in events] == [uid] * 4
|
||||||
|
|
||||||
|
# Update a single instance and confirm the change is reflected
|
||||||
|
await client.cmd_result(
|
||||||
|
"update",
|
||||||
|
{
|
||||||
|
"entity_id": TEST_ENTITY,
|
||||||
|
"uid": uid,
|
||||||
|
"recurrence_id": "20220824T083000",
|
||||||
|
"event": {
|
||||||
|
"summary": "Morning Routine [Adjusted]",
|
||||||
|
"dtstart": "2022-08-24T08:00:00",
|
||||||
|
"dtend": "2022-08-24T08:30:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
events = await get_events("2022-08-22T00:00:00", "2022-08-26T00:00:00")
|
||||||
|
assert list(map(event_fields, events)) == [
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-22T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-22T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220822T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-23T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-23T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220823T083000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine [Adjusted]",
|
||||||
|
"start": {"dateTime": "2022-08-24T08:00:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-24T08:30:00-06:00"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"summary": "Morning Routine",
|
||||||
|
"start": {"dateTime": "2022-08-25T08:30:00-06:00"},
|
||||||
|
"end": {"dateTime": "2022-08-25T09:00:00-06:00"},
|
||||||
|
"recurrence_id": "20220825T083000",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"rrule",
|
"rrule",
|
||||||
[
|
[
|
||||||
|
@ -704,3 +936,27 @@ async def test_invalid_date_formats(
|
||||||
assert "error" in result
|
assert "error" in result
|
||||||
assert "code" in result.get("error")
|
assert "code" in result.get("error")
|
||||||
assert result["error"]["code"] == "invalid_format"
|
assert result["error"]["code"] == "invalid_format"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_invalid_event_id(
|
||||||
|
ws_client: ClientFixture,
|
||||||
|
setup_integration: None,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
):
|
||||||
|
"""Test updating an event with an invalid event uid."""
|
||||||
|
client = await ws_client()
|
||||||
|
resp = await client.cmd(
|
||||||
|
"update",
|
||||||
|
{
|
||||||
|
"entity_id": TEST_ENTITY,
|
||||||
|
"uid": "uid-does-not-exist",
|
||||||
|
"event": {
|
||||||
|
"summary": "Bastille Day Party [To be rescheduled]",
|
||||||
|
"dtstart": "1997-07-14",
|
||||||
|
"dtend": "1997-07-15",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert not resp.get("success")
|
||||||
|
assert "error" in resp
|
||||||
|
assert resp.get("error").get("code") == "failed"
|
||||||
|
|
Loading…
Add table
Reference in a new issue