mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
Coerce previously persisted local calendars to have valid durations (#90970)
This commit is contained in:
parent
87c22c3ad5
commit
3595e2fd5a
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import date, datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -186,14 +186,23 @@ def _parse_event(event: dict[str, Any]) -> Event:
|
|||||||
|
|
||||||
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."""
|
||||||
|
start: datetime | date
|
||||||
|
end: datetime | date
|
||||||
|
if isinstance(event.start, datetime) and isinstance(event.end, datetime):
|
||||||
|
start = dt_util.as_local(event.start)
|
||||||
|
end = dt_util.as_local(event.end)
|
||||||
|
if (end - start) <= timedelta(seconds=0):
|
||||||
|
end = start + timedelta(minutes=30)
|
||||||
|
else:
|
||||||
|
start = event.start
|
||||||
|
end = event.end
|
||||||
|
if (end - start) <= timedelta(days=0):
|
||||||
|
end = start + timedelta(days=1)
|
||||||
|
|
||||||
return CalendarEvent(
|
return CalendarEvent(
|
||||||
summary=event.summary,
|
summary=event.summary,
|
||||||
start=dt_util.as_local(event.start)
|
start=start,
|
||||||
if isinstance(event.start, datetime)
|
end=end,
|
||||||
else event.start,
|
|
||||||
end=dt_util.as_local(event.end)
|
|
||||||
if isinstance(event.end, datetime)
|
|
||||||
else event.end,
|
|
||||||
description=event.description,
|
description=event.description,
|
||||||
uid=event.uid,
|
uid=event.uid,
|
||||||
rrule=event.rrule.as_rrule_str() if event.rrule else None,
|
rrule=event.rrule.as_rrule_str() if event.rrule else None,
|
||||||
|
@ -26,10 +26,10 @@ TEST_ENTITY = "calendar.light_schedule"
|
|||||||
class FakeStore(LocalCalendarStore):
|
class FakeStore(LocalCalendarStore):
|
||||||
"""Mock storage implementation."""
|
"""Mock storage implementation."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, path: Path) -> None:
|
def __init__(self, hass: HomeAssistant, path: Path, ics_content: str) -> None:
|
||||||
"""Initialize FakeStore."""
|
"""Initialize FakeStore."""
|
||||||
super().__init__(hass, path)
|
super().__init__(hass, path)
|
||||||
self._content = ""
|
self._content = ics_content
|
||||||
|
|
||||||
def _load(self) -> str:
|
def _load(self) -> str:
|
||||||
"""Read from calendar storage."""
|
"""Read from calendar storage."""
|
||||||
@ -40,15 +40,21 @@ class FakeStore(LocalCalendarStore):
|
|||||||
self._content = ics_content
|
self._content = ics_content
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="ics_content", autouse=True)
|
||||||
|
def mock_ics_content() -> str:
|
||||||
|
"""Fixture to allow tests to set initial ics content for the calendar store."""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="store", autouse=True)
|
@pytest.fixture(name="store", autouse=True)
|
||||||
def mock_store() -> Generator[None, None, None]:
|
def mock_store(ics_content: str) -> Generator[None, None, None]:
|
||||||
"""Test cleanup, remove any media storage persisted during the test."""
|
"""Test cleanup, remove any media storage persisted during the test."""
|
||||||
|
|
||||||
stores: dict[Path, FakeStore] = {}
|
stores: dict[Path, FakeStore] = {}
|
||||||
|
|
||||||
def new_store(hass: HomeAssistant, path: Path) -> FakeStore:
|
def new_store(hass: HomeAssistant, path: Path) -> FakeStore:
|
||||||
if path not in stores:
|
if path not in stores:
|
||||||
stores[path] = FakeStore(hass, path)
|
stores[path] = FakeStore(hass, path, ics_content)
|
||||||
return stores[path]
|
return stores[path]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Tests for calendar platform of local calendar."""
|
"""Tests for calendar platform of local calendar."""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import textwrap
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -940,3 +941,91 @@ async def test_create_event_service(
|
|||||||
"location": "Test Location",
|
"location": "Test Location",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"ics_content",
|
||||||
|
[
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
BEGIN:VEVENT
|
||||||
|
SUMMARY:Bastille Day Party
|
||||||
|
DTSTART:19970714
|
||||||
|
DTEND:19970714
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
BEGIN:VEVENT
|
||||||
|
SUMMARY:Bastille Day Party
|
||||||
|
DTSTART:19970714
|
||||||
|
DTEND:19970710
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ids=["no_duration", "negative"],
|
||||||
|
)
|
||||||
|
async def test_invalid_all_day_event(
|
||||||
|
ws_client: ClientFixture,
|
||||||
|
setup_integration: None,
|
||||||
|
get_events: GetEventsFn,
|
||||||
|
) -> None:
|
||||||
|
"""Test all day events with invalid durations, which are coerced to be valid."""
|
||||||
|
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": {"date": "1997-07-14"},
|
||||||
|
"end": {"date": "1997-07-15"},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"ics_content",
|
||||||
|
[
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
BEGIN:VEVENT
|
||||||
|
SUMMARY:Bastille Day Party
|
||||||
|
DTSTART:19970714T110000
|
||||||
|
DTEND:19970714T110000
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
BEGIN:VEVENT
|
||||||
|
SUMMARY:Bastille Day Party
|
||||||
|
DTSTART:19970714T110000
|
||||||
|
DTEND:19970710T100000
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ids=["no_duration", "negative"],
|
||||||
|
)
|
||||||
|
async def test_invalid_event_duration(
|
||||||
|
ws_client: ClientFixture,
|
||||||
|
setup_integration: None,
|
||||||
|
get_events: GetEventsFn,
|
||||||
|
) -> None:
|
||||||
|
"""Test events with invalid durations, which are coerced to be valid."""
|
||||||
|
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-14T11:30:00-06:00"},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user