Fix remote calendar state after refreshing events (#175623)

This commit is contained in:
Allen Porter
2026-07-04 20:06:24 +02:00
committed by GitHub
parent d0b99bc435
commit 8a95ce6908
2 changed files with 83 additions and 6 deletions
@@ -8,7 +8,7 @@ from ical.event import Event
from ical.timeline import Timeline, materialize_timeline
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
@@ -88,15 +88,13 @@ class RemoteCalendarEntity(
return await self.hass.async_add_executor_job(events_in_range)
@override
async def async_update(self) -> None:
"""Refresh the timeline.
async def _async_update_timeline(self) -> None:
"""Refresh the timeline and write state.
This is called when the coordinator updates. Creating the timeline may
require walking through the entire calendar and handling recurring
events, so it is done as a separate task without blocking the event loop.
"""
await super().async_update()
def _get_timeline() -> Timeline | None:
"""Return a materialized timeline with upcoming events."""
@@ -111,6 +109,28 @@ class RemoteCalendarEntity(
self._timeline = await self.hass.async_add_executor_job(_get_timeline)
@override
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
await self._async_update_timeline()
self.async_write_ha_state()
@callback
@override
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.coordinator.config_entry.async_create_task(
self.hass,
self._async_handle_coordinator_update(),
name="remote calendar timeline update",
)
async def _async_handle_coordinator_update(self) -> None:
"""Refresh the timeline and write state."""
await self._async_update_timeline()
self.async_write_ha_state()
def _get_calendar_event(event: Event) -> CalendarEvent:
"""Return a CalendarEvent from an API event."""
@@ -1,6 +1,6 @@
"""Tests for calendar platform of Remote Calendar."""
from datetime import datetime
from datetime import datetime, timedelta
import pathlib
import textwrap
@@ -12,6 +12,7 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from . import setup_integration
from .conftest import (
@@ -530,3 +531,59 @@ async def test_event_edge_during_refresh_interval(
assert state
assert state.state == STATE_OFF
assert state.attributes.get("message") == "Event Two"
@respx.mock
@pytest.mark.freeze_time("2026-05-18 06:00:00+00:00")
async def test_coordinator_refresh_updates_upcoming_event_state(
hass: HomeAssistant,
config_entry: MockConfigEntry,
) -> None:
"""Test a coordinator refresh updates the materialized upcoming event."""
original_calendar = textwrap.dedent(
"""\
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SUMMARY:Wake up
DTSTART:20260518T064000
DTEND:20260518T065500
END:VEVENT
END:VCALENDAR
"""
)
updated_calendar = textwrap.dedent(
"""\
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
SUMMARY:Wake up
DTSTART:20260519T080000
DTEND:20260519T081500
END:VEVENT
END:VCALENDAR
"""
)
route = respx.get(CALENDER_URL).mock(
side_effect=[
Response(status_code=200, text=original_calendar),
# We currently update the calendar twice on startup, tracked
# in issue #148315
Response(status_code=200, text=original_calendar),
Response(status_code=200, text=updated_calendar),
]
)
await setup_integration(hass, config_entry)
state = hass.states.get(TEST_ENTITY)
assert state
assert state.attributes.get("start_time") == "2026-05-18 06:40:00"
# Advance clock to trigger the next update interval
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(days=1))
await hass.async_block_till_done()
state = hass.states.get(TEST_ENTITY)
assert state
assert state.attributes.get("start_time") == "2026-05-19 08:00:00"
assert route.call_count == 3