mirror of
https://github.com/home-assistant/core.git
synced 2025-06-24 15:07:07 +00:00

* Add remote_calendar with storage * Use coordinator and remove storage * cleanup * cleanup * remove init from config_flow * add some tests * some fixes * test-before-setup * fix error handling * remove unneeded code * fix updates * load calendar in the event loop * allow redirects * test_update_failed * tests * address review * use error from local_calendar * adress more comments * remove unique_id * add unique entity_id * add excemption * abort_entries_match * unique_id * add , * cleanup * deduplicate call * don't raise for status end de-nest * multiline * test * tests * use raise_for_status again * use respx * just use config_entry argument that already is defined * Also assert on the config entry result title and data * improve config_flow * update quality scale * address review --------- Co-authored-by: Allen Porter <allen@thebends.org>
395 lines
11 KiB
Python
395 lines
11 KiB
Python
"""Tests for calendar platform of Remote Calendar."""
|
|
|
|
from datetime import datetime
|
|
import textwrap
|
|
|
|
from httpx import Response
|
|
import pytest
|
|
import respx
|
|
|
|
from homeassistant.const import STATE_OFF, STATE_ON
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from . import setup_integration
|
|
from .conftest import (
|
|
CALENDER_URL,
|
|
FRIENDLY_NAME,
|
|
TEST_ENTITY,
|
|
GetEventsFn,
|
|
event_fields,
|
|
)
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
|
|
@respx.mock
|
|
async def test_empty_calendar(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
get_events: GetEventsFn,
|
|
) -> None:
|
|
"""Test querying the API and fetching events."""
|
|
respx.get(CALENDER_URL).mock(
|
|
return_value=Response(
|
|
status_code=200,
|
|
text=textwrap.dedent(
|
|
"""BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
)
|
|
)
|
|
await setup_integration(hass, config_entry)
|
|
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
|
|
assert state.name == FRIENDLY_NAME
|
|
assert state.state == STATE_OFF
|
|
assert dict(state.attributes) == {
|
|
"friendly_name": FRIENDLY_NAME,
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"ics_content",
|
|
[
|
|
textwrap.dedent(
|
|
"""\
|
|
BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
SUMMARY:Bastille Day Party
|
|
DTSTART;TZID=Europe/Berlin:19970714T190000
|
|
DTEND;TZID=Europe/Berlin:19970715T060000
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
textwrap.dedent(
|
|
"""\
|
|
BEGIN:VCALENDAR
|
|
BEGIN:VEVENT
|
|
SUMMARY:Bastille Day Party
|
|
DTSTART:19970714T170000Z
|
|
DTEND:19970715T040000Z
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
textwrap.dedent(
|
|
"""\
|
|
BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
SUMMARY:Bastille Day Party
|
|
DTSTART;TZID=America/Regina:19970714T110000
|
|
DTEND;TZID=America/Regina:19970714T220000
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
textwrap.dedent(
|
|
"""\
|
|
BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
SUMMARY:Bastille Day Party
|
|
DTSTART;TZID=America/Los_Angeles:19970714T100000
|
|
DTEND;TZID=America/Los_Angeles:19970714T210000
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
],
|
|
)
|
|
@respx.mock
|
|
async def test_api_date_time_event(
|
|
get_events: GetEventsFn,
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
ics_content: str,
|
|
) -> None:
|
|
"""Test an event with a start/end date time."""
|
|
respx.get(CALENDER_URL).mock(
|
|
return_value=Response(
|
|
status_code=200,
|
|
text=ics_content,
|
|
)
|
|
)
|
|
await setup_integration(hass, config_entry)
|
|
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"},
|
|
}
|
|
]
|
|
|
|
# Query events in UTC
|
|
|
|
# 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
|
|
|
|
# Query events overlapping with start and end but in another timezone
|
|
events = await get_events("1997-07-12T23:00:00-01:00", "1997-07-14T17:00:00-01:00")
|
|
assert len(events) == 1
|
|
events = await get_events("1997-07-15T02:00:00-01:00", "1997-07-15T05:00:00-01:00")
|
|
assert len(events) == 1
|
|
|
|
|
|
@respx.mock
|
|
async def test_api_date_event(
|
|
get_events: GetEventsFn,
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test an event with a start/end date all day event."""
|
|
respx.get(CALENDER_URL).mock(
|
|
return_value=Response(
|
|
status_code=200,
|
|
text=textwrap.dedent(
|
|
"""\
|
|
BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
SUMMARY:Festival International de Jazz de Montreal
|
|
DTSTART:20070628
|
|
DTEND:20070709
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
)
|
|
)
|
|
await setup_integration(hass, config_entry)
|
|
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
|
|
|
|
|
|
@pytest.mark.freeze_time(datetime(2007, 6, 28, 12))
|
|
@respx.mock
|
|
async def test_active_event(
|
|
get_events: GetEventsFn,
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test an event with a start/end date time."""
|
|
respx.get(CALENDER_URL).mock(
|
|
return_value=Response(
|
|
status_code=200,
|
|
text=textwrap.dedent(
|
|
"""\
|
|
BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
SUMMARY:Festival International de Jazz de Montreal
|
|
LOCATION:Montreal
|
|
DTSTART:20070628
|
|
DTEND:20070709
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
)
|
|
)
|
|
await setup_integration(hass, config_entry)
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state
|
|
assert state.name == FRIENDLY_NAME
|
|
assert state.state == STATE_ON
|
|
assert dict(state.attributes) == {
|
|
"friendly_name": FRIENDLY_NAME,
|
|
"message": "Festival International de Jazz de Montreal",
|
|
"all_day": True,
|
|
"description": "",
|
|
"location": "Montreal",
|
|
"start_time": "2007-06-28 00:00:00",
|
|
"end_time": "2007-07-09 00:00:00",
|
|
}
|
|
|
|
|
|
@pytest.mark.freeze_time(datetime(2007, 6, 27, 12))
|
|
@respx.mock
|
|
async def test_upcoming_event(
|
|
get_events: GetEventsFn,
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test an event with a start/end date time."""
|
|
respx.get(CALENDER_URL).mock(
|
|
return_value=Response(
|
|
status_code=200,
|
|
text=textwrap.dedent(
|
|
"""\
|
|
BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
SUMMARY:Festival International de Jazz de Montreal
|
|
LOCATION:Montreal
|
|
DTSTART:20070628
|
|
DTEND:20070709
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
)
|
|
)
|
|
await setup_integration(hass, config_entry)
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state
|
|
assert state.name == FRIENDLY_NAME
|
|
assert state.state == STATE_OFF
|
|
assert dict(state.attributes) == {
|
|
"friendly_name": FRIENDLY_NAME,
|
|
"message": "Festival International de Jazz de Montreal",
|
|
"all_day": True,
|
|
"description": "",
|
|
"location": "Montreal",
|
|
"start_time": "2007-06-28 00:00:00",
|
|
"end_time": "2007-07-09 00:00:00",
|
|
}
|
|
|
|
|
|
@respx.mock
|
|
async def test_recurring_event(
|
|
get_events: GetEventsFn,
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test an event with a recurrence rule."""
|
|
respx.get(CALENDER_URL).mock(
|
|
return_value=Response(
|
|
status_code=200,
|
|
text=textwrap.dedent(
|
|
"""\
|
|
BEGIN:VCALENDAR
|
|
BEGIN:VEVENT
|
|
DTSTART:20220829T090000
|
|
DTEND:20220829T100000
|
|
SUMMARY:Monday meeting
|
|
RRULE:FREQ=WEEKLY;BYDAY=MO
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
)
|
|
)
|
|
await setup_integration(hass, config_entry)
|
|
|
|
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",
|
|
},
|
|
]
|
|
|
|
|
|
@respx.mock
|
|
@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(
|
|
get_events: GetEventsFn,
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
event_order: list[str],
|
|
) -> None:
|
|
"""Test the sort order of an all day events depending on the time zone."""
|
|
respx.get(CALENDER_URL).mock(
|
|
return_value=Response(
|
|
status_code=200,
|
|
text=textwrap.dedent(
|
|
"""\
|
|
BEGIN:VCALENDAR
|
|
|
|
BEGIN:VEVENT
|
|
DTSTART:20221008
|
|
DTEND:20221009
|
|
SUMMARY:All Day Event
|
|
END:VEVENT
|
|
|
|
BEGIN:VEVENT
|
|
DTSTART:20221007T230000Z
|
|
DTEND:20221008T233000Z
|
|
SUMMARY:One
|
|
END:VEVENT
|
|
|
|
BEGIN:VEVENT
|
|
DTSTART:20221008T010000Z
|
|
DTEND:20221008T020000Z
|
|
SUMMARY:Two
|
|
END:VEVENT
|
|
|
|
END:VCALENDAR
|
|
"""
|
|
),
|
|
)
|
|
)
|
|
await setup_integration(hass, config_entry)
|
|
|
|
events = await get_events("2022-10-06T00:00:00Z", "2022-10-09T00:00:00Z")
|
|
assert [event["summary"] for event in events] == event_order
|