diff --git a/tests/components/caldav/test_calendar.py b/tests/components/caldav/test_calendar.py index f64cf699451..b7c9ed32244 100644 --- a/tests/components/caldav/test_calendar.py +++ b/tests/components/caldav/test_calendar.py @@ -1,9 +1,13 @@ """The tests for the webdav calendar component.""" +from collections.abc import Awaitable, Callable import datetime from http import HTTPStatus +from typing import Any from unittest.mock import MagicMock, Mock, patch from caldav.objects import Event +from freezegun import freeze_time +from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant.const import STATE_OFF, STATE_ON @@ -11,7 +15,7 @@ from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util -DEVICE_DATA = {"name": "Private Calendar", "device_id": "Private Calendar"} +from tests.typing import ClientSessionGenerator EVENTS = [ """BEGIN:VCALENDAR @@ -288,64 +292,64 @@ CALDAV_CONFIG = { "url": "http://test.local", "custom_calendars": [], } +UTC = "UTC" +AMERICA_NEW_YORK = "America/New_York" +ASIA_BAGHDAD = "Asia/Baghdad" + +TEST_ENTITY = "calendar.example" +CALENDAR_NAME = "Example" -@pytest.fixture -def set_tz(request): - """Set the default TZ to the one requested.""" - return request.getfixturevalue(request.param) - - -@pytest.fixture -def utc(hass): - """Set the default TZ to UTC.""" - hass.config.set_time_zone("UTC") - - -@pytest.fixture -def new_york(hass): - """Set the default TZ to America/New_York.""" - hass.config.set_time_zone("America/New_York") - - -@pytest.fixture -def baghdad(hass): - """Set the default TZ to Asia/Baghdad.""" - hass.config.set_time_zone("Asia/Baghdad") +@pytest.fixture(name="tz") +def mock_tz() -> str | None: + """Fixture to specify the Home Assistant timezone to use during the test.""" + return None @pytest.fixture(autouse=True) -def mock_http(hass): +def set_tz(hass: HomeAssistant, tz: str | None) -> None: + """Fixture to set the default TZ to the one requested.""" + if tz is not None: + hass.config.set_time_zone(tz) + + +@pytest.fixture(autouse=True) +def mock_http(hass: HomeAssistant) -> None: """Mock the http component.""" hass.http = Mock() -@pytest.fixture -def mock_dav_client(): - """Mock the dav client.""" - patch_dav_client = patch( - "caldav.DAVClient", return_value=_mocked_dav_client("First", "Second") - ) - with patch_dav_client as dav_client: - yield dav_client +@pytest.fixture(name="calendar_names") +def mock_calendar_names() -> list[str]: + """Fixture to provide calendars returned by CalDAV client.""" + return ["Example"] -@pytest.fixture(name="calendar") -def mock_private_cal(): - """Mock a private calendar.""" - _calendar = _mock_calendar("Private") - calendars = [_calendar] - client = _mocked_dav_client(calendars=calendars) - patch_dav_client = patch("caldav.DAVClient", return_value=client) - with patch_dav_client: - yield _calendar +@pytest.fixture(name="calendars") +def mock_calendars(calendar_names: list[str]) -> list[Mock]: + """Fixture to provide calendars returned by CalDAV client.""" + return [_mock_calendar(name) for name in calendar_names] + + +@pytest.fixture(name="dav_client", autouse=True) +def mock_dav_client(calendars: list[Mock]) -> Mock: + """Fixture to mock the DAVClient.""" + with patch( + "homeassistant.components.caldav.calendar.caldav.DAVClient" + ) as mock_client: + mock_client.return_value.principal.return_value.calendars.return_value = ( + calendars + ) + yield mock_client @pytest.fixture -def get_api_events(hass_client): +def get_api_events( + hass_client: ClientSessionGenerator, +) -> Callable[[str], Awaitable[dict[str, Any]]]: """Fixture to return events for a specific calendar using the API.""" - async def api_call(entity_id): + async def api_call(entity_id: str) -> dict[str, Any]: client = await hass_client() response = await client.get( # The start/end times are arbitrary since they are ignored by `_mock_calendar` @@ -358,24 +362,12 @@ def get_api_events(hass_client): return api_call -def _local_datetime(hours, minutes): +def _local_datetime(hours: int, minutes: int) -> datetime.datetime: """Build a datetime object for testing in the correct timezone.""" return dt_util.as_local(datetime.datetime(2017, 11, 27, hours, minutes, 0)) -def _mocked_dav_client(*names, calendars=None): - """Mock requests.get invocations.""" - if calendars is None: - calendars = [_mock_calendar(name) for name in names] - principal = Mock() - principal.calendars = MagicMock(return_value=calendars) - - client = Mock() - client.principal = MagicMock(return_value=principal) - return client - - -def _mock_calendar(name, supported_components=None): +def _mock_calendar(name: str, supported_components: list[str] | None = None) -> Mock: calendar = Mock() events = [] for idx, event in enumerate(EVENTS): @@ -388,77 +380,78 @@ def _mock_calendar(name, supported_components=None): return calendar -async def test_setup_component(hass: HomeAssistant, mock_dav_client) -> None: - """Test setup component with calendars.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() - - state = hass.states.get("calendar.first") - assert state.name == "First" - state = hass.states.get("calendar.second") - assert state.name == "Second" +@pytest.fixture(name="config") +def mock_config() -> dict[str, Any]: + """Fixture to provide calendar configuration.yaml.""" + return {} -async def test_setup_component_with_no_calendar_matching( - hass: HomeAssistant, mock_dav_client +@pytest.fixture(name="setup_platform_cb") +async def mock_setup_platform_cb( + hass: HomeAssistant, config: dict[str, Any] +) -> Callable[[], Awaitable[None]]: + """Fixture that returns a function to setup the calendar platform.""" + + async def _run() -> None: + assert await async_setup_component( + hass, "calendar", {"calendar": {**CALDAV_CONFIG, **config}} + ) + await hass.async_block_till_done() + + return _run + + +@pytest.mark.parametrize( + ("calendar_names", "config", "expected_entities"), + [ + (["First", "Second"], {}, ["calendar.first", "calendar.second"]), + ( + ["First", "Second"], + {"calendars": ["none"]}, + [], + ), + (["First", "Second"], {"calendars": ["Second"]}, ["calendar.second"]), + ( + ["First", "Second"], + { + "custom_calendars": { + "name": "HomeOffice", + "calendar": "Second", + "search": "HomeOffice", + }, + }, + ["calendar.second_homeoffice"], + ), + ], + ids=("config", "no_match", "match", "custom"), +) +async def test_setup_component_config( + hass: HomeAssistant, + config: dict[str, Any], + expected_entities: list[str], + setup_platform_cb: Callable[[], Awaitable[None]], ) -> None: """Test setup component with wrong calendar.""" - config = dict(CALDAV_CONFIG) - config["calendars"] = ["none"] + await setup_platform_cb() - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() - - all_calendar_states = hass.states.async_entity_ids("calendar") - assert not all_calendar_states + all_calendar_entities = hass.states.async_entity_ids("calendar") + assert all_calendar_entities == expected_entities -async def test_setup_component_with_a_calendar_match( - hass: HomeAssistant, mock_dav_client +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(17, 45)) +async def test_ongoing_event( + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: - """Test setup component with right calendar.""" - config = dict(CALDAV_CONFIG) - config["calendars"] = ["Second"] - - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() - - all_calendar_states = hass.states.async_entity_ids("calendar") - assert len(all_calendar_states) == 1 - state = hass.states.get("calendar.second") - assert state.name == "Second" - - -async def test_setup_component_with_one_custom_calendar( - hass: HomeAssistant, mock_dav_client -) -> None: - """Test setup component with custom calendars.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "HomeOffice", "calendar": "Second", "search": "HomeOffice"} - ] - - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() - - all_calendar_states = hass.states.async_entity_ids("calendar") - assert len(all_calendar_states) == 1 - state = hass.states.get("calendar.second_homeoffice") - assert state.name == "HomeOffice" - - -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(17, 45)) -async def test_ongoing_event(mock_now, hass: HomeAssistant, calendar, set_tz) -> None: """Test that the ongoing event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_ON assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a normal event", "all_day": False, "offset_reached": False, @@ -469,20 +462,20 @@ async def test_ongoing_event(mock_now, hass: HomeAssistant, calendar, set_tz) -> } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(17, 30)) +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(17, 30)) async def test_just_ended_event( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the next ongoing event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_ON assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a normal event", "all_day": False, "offset_reached": False, @@ -493,20 +486,20 @@ async def test_just_ended_event( } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(17, 00)) +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(17, 00)) async def test_ongoing_event_different_tz( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the ongoing event with another timezone is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_ON assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "Enjoy the sun", "all_day": False, "offset_reached": False, @@ -517,20 +510,20 @@ async def test_ongoing_event_different_tz( } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(19, 10)) +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(19, 10)) async def test_ongoing_floating_event_returned( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that floating events without timezones work.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_ON assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a floating Event", "all_day": False, "offset_reached": False, @@ -541,20 +534,20 @@ async def test_ongoing_floating_event_returned( } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(8, 30)) +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(8, 30)) async def test_ongoing_event_with_offset( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the offset is taken into account.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_OFF assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is an offset event", "all_day": False, "offset_reached": True, @@ -565,23 +558,36 @@ async def test_ongoing_event_with_offset( } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(12, 00)) -async def test_matching_filter(mock_now, hass: HomeAssistant, calendar, set_tz) -> None: +@pytest.mark.parametrize( + ("tz", "config"), + [ + ( + UTC, + { + "custom_calendars": [ + { + "name": CALENDAR_NAME, + "calendar": CALENDAR_NAME, + "search": "This is a normal event", + } + ] + }, + ) + ], +) +@freeze_time(_local_datetime(12, 00)) +async def test_matching_filter( + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] +) -> None: """Test that the matching event is returned.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": "This is a normal event"} - ] + await setup_platform_cb() - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() - - state = hass.states.get("calendar.private_private") - assert state.name == calendar.name + state = hass.states.get("calendar.example_example") + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_OFF assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a normal event", "all_day": False, "offset_reached": False, @@ -592,25 +598,37 @@ async def test_matching_filter(mock_now, hass: HomeAssistant, calendar, set_tz) } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(12, 00)) +@pytest.mark.parametrize( + ("tz", "config"), + [ + ( + UTC, + { + "custom_calendars": [ + { + "name": CALENDAR_NAME, + "calendar": CALENDAR_NAME, + "search": r".*rainy", + } + ] + }, + ) + ], +) +@freeze_time(_local_datetime(12, 00)) async def test_matching_filter_real_regexp( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the event matching the regexp is returned.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": r".*rainy"} - ] - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private_private") - assert state.name == calendar.name + state = hass.states.get("calendar.example_example") + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_OFF assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a normal event", "all_day": False, "offset_reached": False, @@ -621,138 +639,137 @@ async def test_matching_filter_real_regexp( } -@patch("homeassistant.util.dt.now", return_value=_local_datetime(20, 00)) +@pytest.mark.parametrize( + "config", + [ + { + "custom_calendars": [ + { + "name": CALENDAR_NAME, + "calendar": CALENDAR_NAME, + "search": "This is a normal event", + } + ] + } + ], +) +@freeze_time(_local_datetime(20, 00)) async def test_filter_matching_past_event( - mock_now, hass: HomeAssistant, calendar + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the matching past event is not returned.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": "This is a normal event"} - ] - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private_private") - assert state.name == calendar.name + state = hass.states.get("calendar.example_example") + assert state + assert state.name == CALENDAR_NAME assert state.state == "off" + assert dict(state.attributes) == { + "friendly_name": CALENDAR_NAME, + "offset_reached": False, + } -@patch("homeassistant.util.dt.now", return_value=_local_datetime(12, 00)) +@pytest.mark.parametrize( + "config", + [ + { + "custom_calendars": [ + { + "name": CALENDAR_NAME, + "calendar": CALENDAR_NAME, + "search": "This is a non-existing event", + } + ] + } + ], +) +@freeze_time(_local_datetime(12, 00)) async def test_no_result_with_filtering( - mock_now, hass: HomeAssistant, calendar + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that nothing is returned since nothing matches.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - { - "name": "Private", - "calendar": "Private", - "search": "This is a non-existing event", - } - ] + await setup_platform_cb() - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() - - state = hass.states.get("calendar.private_private") - assert state.name == calendar.name + state = hass.states.get("calendar.example_example") + assert state + assert state.name == CALENDAR_NAME assert state.state == "off" + assert dict(state.attributes) == { + "friendly_name": CALENDAR_NAME, + "offset_reached": False, + } -async def _day_event_returned(hass, calendar, config, date_time): - with patch("homeassistant.util.dt.now", return_value=date_time): - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() - - state = hass.states.get("calendar.private_private") - assert state.name == calendar.name - assert state.state == STATE_ON - assert dict(state.attributes) == { - "friendly_name": "Private", - "message": "This is an all day event", - "all_day": True, - "offset_reached": False, - "start_time": "2017-11-27 00:00:00", - "end_time": "2017-11-28 00:00:00", - "location": "Hamburg", - "description": "What a beautiful day", - } - - -@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True) -async def test_all_day_event_returned_early( - hass: HomeAssistant, calendar, set_tz +@pytest.mark.parametrize( + ("tz", "target_datetime"), + [ + # Early + (UTC, datetime.datetime(2017, 11, 27, 0, 30)), + (AMERICA_NEW_YORK, datetime.datetime(2017, 11, 27, 0, 30)), + (ASIA_BAGHDAD, datetime.datetime(2017, 11, 27, 0, 30)), + # Mid + (UTC, datetime.datetime(2017, 11, 27, 12, 30)), + (AMERICA_NEW_YORK, datetime.datetime(2017, 11, 27, 12, 30)), + (ASIA_BAGHDAD, datetime.datetime(2017, 11, 27, 12, 30)), + # Late + (UTC, datetime.datetime(2017, 11, 27, 23, 30)), + (AMERICA_NEW_YORK, datetime.datetime(2017, 11, 27, 23, 30)), + (ASIA_BAGHDAD, datetime.datetime(2017, 11, 27, 23, 30)), + ], +) +async def test_all_day_event( + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + target_datetime: datetime.datetime, ) -> None: """Test that the event lasting the whole day is returned, if it's early in the local day.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": ".*"} - ] - - await _day_event_returned( + freezer.move_to(target_datetime.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE)) + assert await async_setup_component( hass, - calendar, - config, - datetime.datetime(2017, 11, 27, 0, 30).replace( - tzinfo=dt_util.DEFAULT_TIME_ZONE - ), + "calendar", + { + "calendar": { + **CALDAV_CONFIG, + "custom_calendars": [ + {"name": CALENDAR_NAME, "calendar": CALENDAR_NAME, "search": ".*"} + ], + } + }, ) - - -@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True) -async def test_all_day_event_returned_mid( - hass: HomeAssistant, calendar, set_tz -) -> None: - """Test that the event lasting the whole day is returned, if it's in the middle of the local day.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": ".*"} - ] - - await _day_event_returned( - hass, - calendar, - config, - datetime.datetime(2017, 11, 27, 12, 30).replace( - tzinfo=dt_util.DEFAULT_TIME_ZONE - ), - ) - - -@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True) -async def test_all_day_event_returned_late( - hass: HomeAssistant, calendar, set_tz -) -> None: - """Test that the event lasting the whole day is returned, if it's late in the local day.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": ".*"} - ] - - await _day_event_returned( - hass, - calendar, - config, - datetime.datetime(2017, 11, 27, 23, 30).replace( - tzinfo=dt_util.DEFAULT_TIME_ZONE - ), - ) - - -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(21, 45)) -async def test_event_rrule(mock_now, hass: HomeAssistant, calendar, set_tz) -> None: - """Test that the future recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) await hass.async_block_till_done() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get("calendar.example_example") + assert state + assert state.name == CALENDAR_NAME + assert state.state == STATE_ON + assert dict(state.attributes) == { + "friendly_name": CALENDAR_NAME, + "message": "This is an all day event", + "all_day": True, + "offset_reached": False, + "start_time": "2017-11-27 00:00:00", + "end_time": "2017-11-28 00:00:00", + "location": "Hamburg", + "description": "What a beautiful day", + } + + +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(21, 45)) +async def test_event_rrule( + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] +) -> None: + """Test that the future recurring event is returned.""" + await setup_platform_cb() + + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_OFF assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a recurring event", "all_day": False, "offset_reached": False, @@ -763,20 +780,20 @@ async def test_event_rrule(mock_now, hass: HomeAssistant, calendar, set_tz) -> N } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(22, 15)) +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(22, 15)) async def test_event_rrule_ongoing( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the current recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_ON assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a recurring event", "all_day": False, "offset_reached": False, @@ -787,20 +804,20 @@ async def test_event_rrule_ongoing( } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(22, 45)) +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(22, 45)) async def test_event_rrule_duration( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the future recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_OFF assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a recurring event with a duration", "all_day": False, "offset_reached": False, @@ -811,20 +828,20 @@ async def test_event_rrule_duration( } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(23, 15)) +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(23, 15)) async def test_event_rrule_duration_ongoing( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the ongoing recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_ON assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a recurring event with a duration", "all_day": False, "offset_reached": False, @@ -835,20 +852,20 @@ async def test_event_rrule_duration_ongoing( } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch("homeassistant.util.dt.now", return_value=_local_datetime(23, 37)) +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(_local_datetime(23, 37)) async def test_event_rrule_endless( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the endless recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_OFF assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is a recurring event that never ends", "all_day": False, "offset_reached": False, @@ -859,95 +876,76 @@ async def test_event_rrule_endless( } -async def _event_rrule_all_day(hass, calendar, config, date_time): - with patch("homeassistant.util.dt.now", return_value=date_time): - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() - - state = hass.states.get("calendar.private_private") - assert state.name == calendar.name - assert state.state == STATE_ON - assert dict(state.attributes) == { - "friendly_name": "Private", - "message": "This is a recurring all day event", - "all_day": True, - "offset_reached": False, - "start_time": "2016-12-01 00:00:00", - "end_time": "2016-12-02 00:00:00", - "location": "Hamburg", - "description": "Groundhog Day", - } - - -@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True) -async def test_event_rrule_all_day_early(hass: HomeAssistant, calendar, set_tz) -> None: - """Test that the recurring all day event is returned early in the local day, and not on the first occurrence.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": ".*"} - ] - - await _event_rrule_all_day( - hass, - calendar, - config, - datetime.datetime(2016, 12, 1, 0, 30).replace(tzinfo=dt_util.DEFAULT_TIME_ZONE), - ) - - -@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True) -async def test_event_rrule_all_day_mid(hass: HomeAssistant, calendar, set_tz) -> None: - """Test that the recurring all day event is returned in the middle of the local day, and not on the first occurrence.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": ".*"} - ] - - await _event_rrule_all_day( - hass, - calendar, - config, - datetime.datetime(2016, 12, 1, 17, 30).replace( - tzinfo=dt_util.DEFAULT_TIME_ZONE - ), - ) - - -@pytest.mark.parametrize("set_tz", ["utc", "new_york", "baghdad"], indirect=True) -async def test_event_rrule_all_day_late(hass: HomeAssistant, calendar, set_tz) -> None: - """Test that the recurring all day event is returned late in the local day, and not on the first occurrence.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": ".*"} - ] - - await _event_rrule_all_day( - hass, - calendar, - config, - datetime.datetime(2016, 12, 1, 23, 30).replace( - tzinfo=dt_util.DEFAULT_TIME_ZONE - ), - ) - - -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch( - "homeassistant.util.dt.now", - return_value=dt_util.as_local(datetime.datetime(2015, 11, 27, 0, 15)), +@pytest.mark.parametrize( + ("tz", "target_datetime"), + [ + # Early + (UTC, datetime.datetime(2016, 12, 1, 0, 30)), + (AMERICA_NEW_YORK, datetime.datetime(2016, 12, 1, 0, 30)), + (ASIA_BAGHDAD, datetime.datetime(2016, 12, 1, 0, 30)), + # Mid + (UTC, datetime.datetime(2016, 12, 1, 17, 30)), + (AMERICA_NEW_YORK, datetime.datetime(2016, 12, 1, 17, 30)), + (ASIA_BAGHDAD, datetime.datetime(2016, 12, 1, 17, 30)), + # Late + (UTC, datetime.datetime(2016, 12, 1, 23, 30)), + (AMERICA_NEW_YORK, datetime.datetime(2016, 12, 1, 23, 30)), + (ASIA_BAGHDAD, datetime.datetime(2016, 12, 1, 23, 30)), + ], ) -async def test_event_rrule_hourly_on_first( - mock_now, hass: HomeAssistant, calendar, set_tz +async def test_event_rrule_all_day_early( + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + target_datetime: datetime.datetime, ) -> None: - """Test that the endless recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) + """Test that the recurring all day event is returned early in the local day, and not on the first occurrence.""" + freezer.move_to(target_datetime.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE)) + assert await async_setup_component( + hass, + "calendar", + { + "calendar": { + **CALDAV_CONFIG, + "custom_calendars": { + "name": CALENDAR_NAME, + "calendar": CALENDAR_NAME, + "search": ".*", + }, + }, + }, + ) await hass.async_block_till_done() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get("calendar.example_example") + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_ON assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, + "message": "This is a recurring all day event", + "all_day": True, + "offset_reached": False, + "start_time": "2016-12-01 00:00:00", + "end_time": "2016-12-02 00:00:00", + "location": "Hamburg", + "description": "Groundhog Day", + } + + +@pytest.mark.parametrize("tz", [UTC]) +@freeze_time(dt_util.as_local(datetime.datetime(2015, 11, 27, 0, 15))) +async def test_event_rrule_hourly_on_first( + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] +) -> None: + """Test that the endless recurring event is returned.""" + await setup_platform_cb() + + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME + assert state.state == STATE_ON + assert dict(state.attributes) == { + "friendly_name": CALENDAR_NAME, "message": "This is an hourly recurring event", "all_day": False, "offset_reached": False, @@ -958,23 +956,20 @@ async def test_event_rrule_hourly_on_first( } -@pytest.mark.parametrize("set_tz", ["utc"], indirect=True) -@patch( - "homeassistant.util.dt.now", - return_value=dt_util.as_local(datetime.datetime(2015, 11, 27, 11, 15)), -) +@pytest.mark.parametrize("tz", ["UTC"]) +@freeze_time(dt_util.as_local(datetime.datetime(2015, 11, 27, 11, 15))) async def test_event_rrule_hourly_on_last( - mock_now, hass: HomeAssistant, calendar, set_tz + hass: HomeAssistant, setup_platform_cb: Callable[[], Awaitable[None]] ) -> None: """Test that the endless recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_ON assert dict(state.attributes) == { - "friendly_name": "Private", + "friendly_name": CALENDAR_NAME, "message": "This is an hourly recurring event", "all_day": False, "offset_reached": False, @@ -985,77 +980,67 @@ async def test_event_rrule_hourly_on_last( } -@patch( - "homeassistant.util.dt.now", - return_value=dt_util.as_local(datetime.datetime(2015, 11, 27, 0, 45)), +@pytest.mark.parametrize( + ("target_datetime"), + [ + datetime.datetime(2015, 11, 27, 0, 45), + datetime.datetime(2015, 11, 27, 11, 45), + datetime.datetime(2015, 11, 27, 12, 15), + ], ) -async def test_event_rrule_hourly_off_first( - mock_now, hass: HomeAssistant, calendar +async def test_event_rrule_hourly( + hass: HomeAssistant, + setup_platform_cb: Callable[[], Awaitable[None]], + freezer: FrozenDateTimeFactory, + target_datetime: datetime.datetime, ) -> None: """Test that the endless recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + freezer.move_to(dt_util.as_local(target_datetime)) + await setup_platform_cb() - state = hass.states.get("calendar.private") - assert state.name == calendar.name + state = hass.states.get(TEST_ENTITY) + assert state + assert state.name == CALENDAR_NAME assert state.state == STATE_OFF -@patch( - "homeassistant.util.dt.now", - return_value=dt_util.as_local(datetime.datetime(2015, 11, 27, 11, 45)), -) -async def test_event_rrule_hourly_off_last( - mock_now, hass: HomeAssistant, calendar +async def test_get_events( + hass: HomeAssistant, + get_api_events: Callable[[str], Awaitable[dict[str, Any]]], + setup_platform_cb: Callable[[], Awaitable[None]], + calendars: list[Mock], ) -> None: - """Test that the endless recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() - - state = hass.states.get("calendar.private") - assert state.name == calendar.name - assert state.state == STATE_OFF - - -@patch( - "homeassistant.util.dt.now", - return_value=dt_util.as_local(datetime.datetime(2015, 11, 27, 12, 15)), -) -async def test_event_rrule_hourly_ended( - mock_now, hass: HomeAssistant, calendar -) -> None: - """Test that the endless recurring event is returned.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() - - state = hass.states.get("calendar.private") - assert state.name == calendar.name - assert state.state == STATE_OFF - - -async def test_get_events(hass: HomeAssistant, calendar, get_api_events) -> None: """Test that all events are returned on API.""" - assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) - await hass.async_block_till_done() + await setup_platform_cb() - events = await get_api_events("calendar.private") + events = await get_api_events(TEST_ENTITY) assert len(events) == 18 - assert calendar.call + assert calendars[0].call +@pytest.mark.parametrize( + "config", + [ + { + "custom_calendars": [ + { + "name": CALENDAR_NAME, + "calendar": CALENDAR_NAME, + "search": "This is a normal event", + } + ] + } + ], +) async def test_get_events_custom_calendars( - hass: HomeAssistant, calendar, get_api_events + hass: HomeAssistant, + get_api_events: Callable[[str], Awaitable[dict[str, Any]]], + setup_platform_cb: Callable[[], Awaitable[None]], ) -> None: """Test that only searched events are returned on API.""" - config = dict(CALDAV_CONFIG) - config["custom_calendars"] = [ - {"name": "Private", "calendar": "Private", "search": "This is a normal event"} - ] + await setup_platform_cb() - assert await async_setup_component(hass, "calendar", {"calendar": config}) - await hass.async_block_till_done() - - events = await get_api_events("calendar.private_private") + events = await get_api_events("calendar.example_example") assert events == [ { "end": {"dateTime": "2017-11-27T10:00:00-08:00"}, @@ -1070,31 +1055,34 @@ async def test_get_events_custom_calendars( ] +@pytest.mark.parametrize( + ("calendars"), + [ + [ + _mock_calendar("Calendar 1", supported_components=["VEVENT"]), + _mock_calendar("Calendar 2", supported_components=["VEVENT", "VJOURNAL"]), + _mock_calendar("Calendar 3", supported_components=["VTODO"]), + # Fallback to allow when no components are supported to be conservative + _mock_calendar("Calendar 4", supported_components=[]), + ] + ], +) async def test_calendar_components( hass: HomeAssistant, + dav_client: Mock, ) -> None: """Test that only calendars that support events are created.""" - calendars = [ - _mock_calendar("Calendar 1", supported_components=["VEVENT"]), - _mock_calendar("Calendar 2", supported_components=["VEVENT", "VJOURNAL"]), - _mock_calendar("Calendar 3", supported_components=["VTODO"]), - # Fallback to allow when no components are supported to be conservative - _mock_calendar("Calendar 4", supported_components=[]), - ] - with patch( - "homeassistant.components.caldav.calendar.caldav.DAVClient", - return_value=_mocked_dav_client(calendars=calendars), - ): - assert await async_setup_component( - hass, "calendar", {"calendar": CALDAV_CONFIG} - ) - await hass.async_block_till_done() + + assert await async_setup_component(hass, "calendar", {"calendar": CALDAV_CONFIG}) + await hass.async_block_till_done() state = hass.states.get("calendar.calendar_1") + assert state assert state.name == "Calendar 1" assert state.state == STATE_OFF state = hass.states.get("calendar.calendar_2") + assert state assert state.name == "Calendar 2" assert state.state == STATE_OFF @@ -1103,5 +1091,6 @@ async def test_calendar_components( assert not state state = hass.states.get("calendar.calendar_4") + assert state assert state.name == "Calendar 4" assert state.state == STATE_OFF