963 lines
30 KiB
Python
963 lines
30 KiB
Python
"""Tests for calendar platform of local calendar."""
|
|
|
|
from collections.abc import Awaitable, Callable
|
|
import datetime
|
|
from http import HTTPStatus
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from unittest.mock import patch
|
|
import urllib
|
|
|
|
from aiohttp import ClientSession, ClientWebSocketResponse
|
|
import pytest
|
|
|
|
from homeassistant.components.local_calendar import LocalCalendarStore
|
|
from homeassistant.components.local_calendar.const import CONF_CALENDAR_NAME, DOMAIN
|
|
from homeassistant.const import STATE_OFF, STATE_ON
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.template import DATE_STR_FORMAT
|
|
from homeassistant.setup import async_setup_component
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
CALENDAR_NAME = "Light Schedule"
|
|
FRIENDLY_NAME = "Light schedule"
|
|
TEST_ENTITY = "calendar.light_schedule"
|
|
|
|
|
|
class FakeStore(LocalCalendarStore):
|
|
"""Mock storage implementation."""
|
|
|
|
def __init__(self, hass: HomeAssistant, path: Path) -> None:
|
|
"""Initialize FakeStore."""
|
|
super().__init__(hass, path)
|
|
self._content = ""
|
|
|
|
def _load(self) -> str:
|
|
"""Read from calendar storage."""
|
|
return self._content
|
|
|
|
def _store(self, ics_content: str) -> None:
|
|
"""Persist the calendar storage."""
|
|
self._content = ics_content
|
|
|
|
|
|
@pytest.fixture(name="store", autouse=True)
|
|
def mock_store() -> None:
|
|
"""Test cleanup, remove any media storage persisted during the test."""
|
|
|
|
def new_store(hass: HomeAssistant, path: Path) -> FakeStore:
|
|
return FakeStore(hass, path)
|
|
|
|
with patch(
|
|
"homeassistant.components.local_calendar.LocalCalendarStore", new=new_store
|
|
):
|
|
yield
|
|
|
|
|
|
@pytest.fixture(name="time_zone")
|
|
def mock_time_zone() -> str:
|
|
"""Fixture for time zone to use in tests."""
|
|
# Set our timezone to CST/Regina so we can check calculations
|
|
# This keeps UTC-6 all year round
|
|
return "America/Regina"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def set_time_zone(hass: HomeAssistant, time_zone: str):
|
|
"""Set the time zone for the tests."""
|
|
# Set our timezone to CST/Regina so we can check calculations
|
|
# This keeps UTC-6 all year round
|
|
hass.config.set_time_zone(time_zone)
|
|
|
|
|
|
@pytest.fixture(name="config_entry")
|
|
def mock_config_entry() -> MockConfigEntry:
|
|
"""Fixture for mock configuration entry."""
|
|
return MockConfigEntry(domain=DOMAIN, data={CONF_CALENDAR_NAME: CALENDAR_NAME})
|
|
|
|
|
|
@pytest.fixture(name="setup_integration")
|
|
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
|
"""Set up the integration."""
|
|
config_entry.add_to_hass(hass)
|
|
assert await async_setup_component(hass, DOMAIN, {})
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
GetEventsFn = Callable[[str, str], Awaitable[dict[str, Any]]]
|
|
|
|
|
|
@pytest.fixture(name="get_events")
|
|
def get_events_fixture(
|
|
hass_client: Callable[..., Awaitable[ClientSession]]
|
|
) -> GetEventsFn:
|
|
"""Fetch calendar events from the HTTP API."""
|
|
|
|
async def _fetch(start: str, end: str) -> None:
|
|
client = await hass_client()
|
|
response = await client.get(
|
|
f"/api/calendars/{TEST_ENTITY}?start={urllib.parse.quote(start)}&end={urllib.parse.quote(end)}"
|
|
)
|
|
assert response.status == HTTPStatus.OK
|
|
return await response.json()
|
|
|
|
return _fetch
|
|
|
|
|
|
def event_fields(data: dict[str, str]) -> dict[str, str]:
|
|
"""Filter event API response to minimum fields."""
|
|
return {
|
|
k: data.get(k)
|
|
for k in ["summary", "start", "end", "recurrence_id"]
|
|
if data.get(k)
|
|
}
|
|
|
|
|
|
class Client:
|
|
"""Test client with helper methods for calendar websocket."""
|
|
|
|
def __init__(self, client):
|
|
"""Initialize Client."""
|
|
self.client = client
|
|
self.id = 0
|
|
|
|
async def cmd(self, cmd: str, payload: dict[str, Any] = None) -> dict[str, Any]:
|
|
"""Send a command and receive the json result."""
|
|
self.id += 1
|
|
await self.client.send_json(
|
|
{
|
|
"id": self.id,
|
|
"type": f"calendar/event/{cmd}",
|
|
**(payload if payload is not None else {}),
|
|
}
|
|
)
|
|
resp = await self.client.receive_json()
|
|
assert resp.get("id") == self.id
|
|
return resp
|
|
|
|
async def cmd_result(self, cmd: str, payload: dict[str, Any] = None) -> Any:
|
|
"""Send a command and parse the result."""
|
|
resp = await self.cmd(cmd, payload)
|
|
assert resp.get("success")
|
|
assert resp.get("type") == "result"
|
|
return resp.get("result")
|
|
|
|
|
|
ClientFixture = Callable[[], Awaitable[Client]]
|
|
|
|
|
|
@pytest.fixture
|
|
async def ws_client(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: Callable[[HomeAssistant], Awaitable[ClientWebSocketResponse]],
|
|
) -> ClientFixture:
|
|
"""Fixture for creating the test websocket client."""
|
|
|
|
async def create_client() -> Client:
|
|
ws_client = await hass_ws_client(hass)
|
|
return Client(ws_client)
|
|
|
|
return create_client
|
|
|
|
|
|
async def test_empty_calendar(
|
|
hass: HomeAssistant, setup_integration: None, get_events: GetEventsFn
|
|
):
|
|
"""Test querying the API and fetching events."""
|
|
events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00")
|
|
assert len(events) == 0
|
|
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state.name == FRIENDLY_NAME
|
|
assert state.state == STATE_OFF
|
|
assert dict(state.attributes) == {
|
|
"friendly_name": FRIENDLY_NAME,
|
|
"supported_features": 7,
|
|
}
|
|
|
|
|
|
async def test_api_date_time_event(
|
|
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
|
|
):
|
|
"""Test an event with a start/end date time."""
|
|
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:00Z", "1997-07-16T00:00:00Z")
|
|
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"},
|
|
}
|
|
]
|
|
|
|
# Time range before event
|
|
events = await get_events("1997-07-13T00:00:00Z", "1997-07-14T16:00:00Z")
|
|
assert len(events) == 0
|
|
# Time range after event
|
|
events = await get_events("1997-07-15T05:00:00Z", "1997-07-15T06:00:00Z")
|
|
assert len(events) == 0
|
|
|
|
# Overlap with event start
|
|
events = await get_events("1997-07-13T00:00:00Z", "1997-07-14T18:00:00Z")
|
|
assert len(events) == 1
|
|
# Overlap with event end
|
|
events = await get_events("1997-07-15T03:00:00Z", "1997-07-15T06:00:00Z")
|
|
assert len(events) == 1
|
|
|
|
|
|
async def test_api_date_event(
|
|
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
|
|
):
|
|
"""Test an event with a start/end date all day event."""
|
|
client = await ws_client()
|
|
await client.cmd_result(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Festival International de Jazz de Montreal",
|
|
"dtstart": "2007-06-28",
|
|
"dtend": "2007-07-09",
|
|
},
|
|
},
|
|
)
|
|
|
|
events = await get_events("2007-06-20T00:00:00", "2007-07-20T00:00:00")
|
|
assert list(map(event_fields, events)) == [
|
|
{
|
|
"summary": "Festival International de Jazz de Montreal",
|
|
"start": {"date": "2007-06-28"},
|
|
"end": {"date": "2007-07-09"},
|
|
}
|
|
]
|
|
|
|
# Time range before event (timezone is -6)
|
|
events = await get_events("2007-06-26T00:00:00Z", "2007-06-28T01:00:00Z")
|
|
assert len(events) == 0
|
|
# Time range after event
|
|
events = await get_events("2007-07-10T00:00:00Z", "2007-07-11T00:00:00Z")
|
|
assert len(events) == 0
|
|
|
|
# Overlap with event start (timezone is -6)
|
|
events = await get_events("2007-06-26T00:00:00Z", "2007-06-28T08:00:00Z")
|
|
assert len(events) == 1
|
|
# Overlap with event end
|
|
events = await get_events("2007-07-09T00:00:00Z", "2007-07-11T00:00:00Z")
|
|
assert len(events) == 1
|
|
|
|
|
|
async def test_active_event(
|
|
hass: HomeAssistant,
|
|
ws_client: ClientFixture,
|
|
setup_integration: None,
|
|
):
|
|
"""Test an event with a start/end date time."""
|
|
start = dt_util.now() - datetime.timedelta(minutes=30)
|
|
end = dt_util.now() + datetime.timedelta(minutes=30)
|
|
client = await ws_client()
|
|
await client.cmd_result(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Evening lights",
|
|
"dtstart": start.isoformat(),
|
|
"dtend": end.isoformat(),
|
|
},
|
|
},
|
|
)
|
|
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state.name == FRIENDLY_NAME
|
|
assert state.state == STATE_ON
|
|
assert dict(state.attributes) == {
|
|
"friendly_name": FRIENDLY_NAME,
|
|
"message": "Evening lights",
|
|
"all_day": False,
|
|
"description": "",
|
|
"location": "",
|
|
"start_time": start.strftime(DATE_STR_FORMAT),
|
|
"end_time": end.strftime(DATE_STR_FORMAT),
|
|
"supported_features": 7,
|
|
}
|
|
|
|
|
|
async def test_upcoming_event(
|
|
hass: HomeAssistant,
|
|
ws_client: ClientFixture,
|
|
setup_integration: None,
|
|
):
|
|
"""Test an event with a start/end date time."""
|
|
start = dt_util.now() + datetime.timedelta(days=1)
|
|
end = dt_util.now() + datetime.timedelta(days=1, hours=1)
|
|
client = await ws_client()
|
|
await client.cmd_result(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Evening lights",
|
|
"dtstart": start.isoformat(),
|
|
"dtend": end.isoformat(),
|
|
},
|
|
},
|
|
)
|
|
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state.name == FRIENDLY_NAME
|
|
assert state.state == STATE_OFF
|
|
assert dict(state.attributes) == {
|
|
"friendly_name": FRIENDLY_NAME,
|
|
"message": "Evening lights",
|
|
"all_day": False,
|
|
"description": "",
|
|
"location": "",
|
|
"start_time": start.strftime(DATE_STR_FORMAT),
|
|
"end_time": end.strftime(DATE_STR_FORMAT),
|
|
"supported_features": 7,
|
|
}
|
|
|
|
|
|
async def test_recurring_event(
|
|
ws_client: ClientFixture,
|
|
setup_integration: None,
|
|
hass: HomeAssistant,
|
|
get_events: GetEventsFn,
|
|
):
|
|
"""Test an event with a recurrence rule."""
|
|
client = await ws_client()
|
|
await client.cmd_result(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Monday meeting",
|
|
"dtstart": "2022-08-29T09:00:00",
|
|
"dtend": "2022-08-29T10:00:00",
|
|
"rrule": "FREQ=WEEKLY",
|
|
},
|
|
},
|
|
)
|
|
|
|
events = await get_events("2022-08-20T00:00:00", "2022-09-20T00:00:00")
|
|
assert list(map(event_fields, events)) == [
|
|
{
|
|
"summary": "Monday meeting",
|
|
"start": {"dateTime": "2022-08-29T09:00:00-06:00"},
|
|
"end": {"dateTime": "2022-08-29T10:00:00-06:00"},
|
|
"recurrence_id": "20220829T090000",
|
|
},
|
|
{
|
|
"summary": "Monday meeting",
|
|
"start": {"dateTime": "2022-09-05T09:00:00-06:00"},
|
|
"end": {"dateTime": "2022-09-05T10:00:00-06:00"},
|
|
"recurrence_id": "20220905T090000",
|
|
},
|
|
{
|
|
"summary": "Monday meeting",
|
|
"start": {"dateTime": "2022-09-12T09:00:00-06:00"},
|
|
"end": {"dateTime": "2022-09-12T10:00:00-06:00"},
|
|
"recurrence_id": "20220912T090000",
|
|
},
|
|
{
|
|
"summary": "Monday meeting",
|
|
"start": {"dateTime": "2022-09-19T09:00:00-06:00"},
|
|
"end": {"dateTime": "2022-09-19T10:00:00-06:00"},
|
|
"recurrence_id": "20220919T090000",
|
|
},
|
|
]
|
|
|
|
|
|
async def test_websocket_delete(
|
|
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
|
|
):
|
|
"""Test websocket delete 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"]
|
|
|
|
# Delete the event
|
|
await client.cmd_result(
|
|
"delete",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"uid": uid,
|
|
},
|
|
)
|
|
events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00")
|
|
assert list(map(event_fields, events)) == []
|
|
|
|
|
|
async def test_websocket_delete_recurring(
|
|
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
|
|
):
|
|
"""Test deleting 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
|
|
|
|
# Cancel a single instance and confirm it was removed
|
|
await client.cmd_result(
|
|
"delete",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"uid": uid,
|
|
"recurrence_id": "20220824T083000",
|
|
},
|
|
)
|
|
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-25T08:30:00-06:00"},
|
|
"end": {"dateTime": "2022-08-25T09:00:00-06:00"},
|
|
"recurrence_id": "20220825T083000",
|
|
},
|
|
]
|
|
|
|
# Delete all and future and confirm multiple were removed
|
|
await client.cmd_result(
|
|
"delete",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"uid": uid,
|
|
"recurrence_id": "20220823T083000",
|
|
"recurrence_range": "THISANDFUTURE",
|
|
},
|
|
)
|
|
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",
|
|
},
|
|
]
|
|
|
|
|
|
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(
|
|
"rrule",
|
|
[
|
|
"FREQ=SECONDLY",
|
|
"FREQ=MINUTELY",
|
|
"FREQ=HOURLY",
|
|
"invalid",
|
|
"",
|
|
],
|
|
)
|
|
async def test_invalid_rrule(
|
|
ws_client: ClientFixture,
|
|
setup_integration: None,
|
|
hass: HomeAssistant,
|
|
get_events: GetEventsFn,
|
|
rrule: str,
|
|
):
|
|
"""Test an event with a recurrence rule."""
|
|
client = await ws_client()
|
|
resp = await client.cmd(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Monday meeting",
|
|
"dtstart": "2022-08-29T09:00:00",
|
|
"dtend": "2022-08-29T10:00:00",
|
|
"rrule": rrule,
|
|
},
|
|
},
|
|
)
|
|
assert not resp.get("success")
|
|
assert "error" in resp
|
|
assert resp.get("error").get("code") == "invalid_format"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"time_zone,event_order",
|
|
[
|
|
("America/Los_Angeles", ["One", "Two", "All Day Event"]),
|
|
("America/Regina", ["One", "Two", "All Day Event"]),
|
|
("UTC", ["One", "All Day Event", "Two"]),
|
|
("Asia/Tokyo", ["All Day Event", "One", "Two"]),
|
|
],
|
|
)
|
|
async def test_all_day_iter_order(
|
|
hass: HomeAssistant,
|
|
ws_client: ClientFixture,
|
|
setup_integration: None,
|
|
get_events: GetEventsFn,
|
|
event_order: list[str],
|
|
):
|
|
"""Test the sort order of an all day events depending on the time zone."""
|
|
client = await ws_client()
|
|
await client.cmd_result(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "All Day Event",
|
|
"dtstart": "2022-10-08",
|
|
"dtend": "2022-10-09",
|
|
},
|
|
},
|
|
)
|
|
await client.cmd_result(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "One",
|
|
"dtstart": "2022-10-07T23:00:00+00:00",
|
|
"dtend": "2022-10-07T23:30:00+00:00",
|
|
},
|
|
},
|
|
)
|
|
await client.cmd_result(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Two",
|
|
"dtstart": "2022-10-08T01:00:00+00:00",
|
|
"dtend": "2022-10-08T02:00:00+00:00",
|
|
},
|
|
},
|
|
)
|
|
|
|
events = await get_events("2022-10-06T00:00:00Z", "2022-10-09T00:00:00Z")
|
|
assert [event["summary"] for event in events] == event_order
|
|
|
|
|
|
async def test_start_end_types(
|
|
ws_client: ClientFixture,
|
|
setup_integration: None,
|
|
):
|
|
"""Test a start and end with different date and date time types."""
|
|
client = await ws_client()
|
|
result = await client.cmd(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Bastille Day Party",
|
|
"dtstart": "1997-07-15",
|
|
"dtend": "1997-07-14T17:00:00+00:00",
|
|
},
|
|
},
|
|
)
|
|
assert not result.get("success")
|
|
assert "error" in result
|
|
assert "code" in result.get("error")
|
|
assert result["error"]["code"] == "invalid_format"
|
|
|
|
|
|
async def test_end_before_start(
|
|
ws_client: ClientFixture,
|
|
setup_integration: None,
|
|
):
|
|
"""Test an event with a start/end date time."""
|
|
client = await ws_client()
|
|
result = await client.cmd(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Bastille Day Party",
|
|
"dtstart": "1997-07-15T04:00:00+00:00",
|
|
"dtend": "1997-07-14T17:00:00+00:00",
|
|
},
|
|
},
|
|
)
|
|
assert not result.get("success")
|
|
assert "error" in result
|
|
assert "code" in result.get("error")
|
|
assert result["error"]["code"] == "invalid_format"
|
|
|
|
|
|
async def test_invalid_recurrence_rule(
|
|
ws_client: ClientFixture,
|
|
setup_integration: None,
|
|
):
|
|
"""Test an event with a recurrence rule."""
|
|
client = await ws_client()
|
|
result = await client.cmd(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Monday meeting",
|
|
"dtstart": "2022-08-29T09:00:00",
|
|
"dtend": "2022-08-29T10:00:00",
|
|
"rrule": "FREQ=invalid;'",
|
|
},
|
|
},
|
|
)
|
|
assert not result.get("success")
|
|
assert "error" in result
|
|
assert "code" in result.get("error")
|
|
assert result["error"]["code"] == "invalid_format"
|
|
|
|
|
|
async def test_invalid_date_formats(
|
|
ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn
|
|
):
|
|
"""Exercises a validation error within rfc5545 parsing in ical."""
|
|
client = await ws_client()
|
|
result = await client.cmd(
|
|
"create",
|
|
{
|
|
"entity_id": TEST_ENTITY,
|
|
"event": {
|
|
"summary": "Bastille Day Party",
|
|
# Can't mix offset aware and floating dates
|
|
"dtstart": "1997-07-15T04:00:00+08:00",
|
|
"dtend": "1997-07-14T17:00:00",
|
|
},
|
|
},
|
|
)
|
|
assert not result.get("success")
|
|
assert "error" in result
|
|
assert "code" in result.get("error")
|
|
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"
|