55from http import HTTPStatus
66from typing import Any
77from unittest .mock import MagicMock , Mock
8+ import zoneinfo
89
910from caldav .objects import Event
1011from freezegun import freeze_time
1112from freezegun .api import FrozenDateTimeFactory
1213import pytest
1314
15+ from homeassistant .components .calendar import CalendarEntityFeature
1416from homeassistant .const import STATE_OFF , STATE_ON , Platform
1517from homeassistant .core import HomeAssistant
1618from homeassistant .setup import async_setup_component
@@ -455,6 +457,7 @@ async def test_ongoing_event(
455457 "end_time" : "2017-11-27 18:00:00" ,
456458 "location" : "Hamburg" ,
457459 "description" : "Surprisingly rainy" ,
460+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
458461 }
459462
460463
@@ -479,6 +482,7 @@ async def test_just_ended_event(
479482 "end_time" : "2017-11-27 18:00:00" ,
480483 "location" : "Hamburg" ,
481484 "description" : "Surprisingly rainy" ,
485+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
482486 }
483487
484488
@@ -503,6 +507,7 @@ async def test_ongoing_event_different_tz(
503507 "description" : "Sunny day" ,
504508 "end_time" : "2017-11-27 17:30:00" ,
505509 "location" : "San Francisco" ,
510+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
506511 }
507512
508513
@@ -527,6 +532,7 @@ async def test_ongoing_floating_event_returned(
527532 "end_time" : "2017-11-27 20:00:00" ,
528533 "location" : "Hamburg" ,
529534 "description" : "What a day" ,
535+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
530536 }
531537
532538
@@ -551,6 +557,7 @@ async def test_ongoing_event_with_offset(
551557 "end_time" : "2017-11-27 11:00:00" ,
552558 "location" : "Hamburg" ,
553559 "description" : "Surprisingly shiny" ,
560+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
554561 }
555562
556563
@@ -591,6 +598,7 @@ async def test_matching_filter(
591598 "end_time" : "2017-11-27 18:00:00" ,
592599 "location" : "Hamburg" ,
593600 "description" : "Surprisingly rainy" ,
601+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
594602 }
595603
596604
@@ -632,6 +640,7 @@ async def test_matching_filter_real_regexp(
632640 "end_time" : "2017-11-27 18:00:00" ,
633641 "location" : "Hamburg" ,
634642 "description" : "Surprisingly rainy" ,
643+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
635644 }
636645
637646
@@ -664,6 +673,7 @@ async def test_filter_matching_past_event(
664673 assert dict (state .attributes ) == {
665674 "friendly_name" : CALENDAR_NAME ,
666675 "offset_reached" : False ,
676+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
667677 }
668678
669679
@@ -695,6 +705,7 @@ async def test_no_result_with_filtering(
695705 assert dict (state .attributes ) == {
696706 "friendly_name" : CALENDAR_NAME ,
697707 "offset_reached" : False ,
708+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
698709 }
699710
700711
@@ -749,6 +760,7 @@ async def test_all_day_event(
749760 "end_time" : "2017-11-28 00:00:00" ,
750761 "location" : "Hamburg" ,
751762 "description" : "What a beautiful day" ,
763+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
752764 }
753765
754766
@@ -773,6 +785,7 @@ async def test_event_rrule(
773785 "end_time" : "2017-11-27 22:30:00" ,
774786 "location" : "Hamburg" ,
775787 "description" : "Every day for a while" ,
788+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
776789 }
777790
778791
@@ -797,6 +810,7 @@ async def test_event_rrule_ongoing(
797810 "end_time" : "2017-11-27 22:30:00" ,
798811 "location" : "Hamburg" ,
799812 "description" : "Every day for a while" ,
813+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
800814 }
801815
802816
@@ -821,6 +835,7 @@ async def test_event_rrule_duration(
821835 "end_time" : "2017-11-27 23:30:00" ,
822836 "location" : "Hamburg" ,
823837 "description" : "Every day for a while as well" ,
838+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
824839 }
825840
826841
@@ -845,6 +860,7 @@ async def test_event_rrule_duration_ongoing(
845860 "end_time" : "2017-11-27 23:30:00" ,
846861 "location" : "Hamburg" ,
847862 "description" : "Every day for a while as well" ,
863+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
848864 }
849865
850866
@@ -869,6 +885,7 @@ async def test_event_rrule_endless(
869885 "end_time" : "2017-11-27 23:59:59" ,
870886 "location" : "Hamburg" ,
871887 "description" : "Every day forever" ,
888+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
872889 }
873890
874891
@@ -925,6 +942,7 @@ async def test_event_rrule_all_day_early(
925942 "end_time" : "2016-12-02 00:00:00" ,
926943 "location" : "Hamburg" ,
927944 "description" : "Groundhog Day" ,
945+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
928946 }
929947
930948
@@ -949,6 +967,7 @@ async def test_event_rrule_hourly_on_first(
949967 "end_time" : "2015-11-27 00:30:00" ,
950968 "location" : "Hamburg" ,
951969 "description" : "The bell tolls for thee" ,
970+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
952971 }
953972
954973
@@ -973,6 +992,7 @@ async def test_event_rrule_hourly_on_last(
973992 "end_time" : "2015-11-27 11:30:00" ,
974993 "location" : "Hamburg" ,
975994 "description" : "The bell tolls for thee" ,
995+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
976996 }
977997
978998
@@ -1105,6 +1125,7 @@ async def test_setup_config_entry(
11051125 "end_time" : "2017-11-28 00:00:00" ,
11061126 "location" : "Hamburg" ,
11071127 "description" : "What a beautiful day" ,
1128+ "supported_features" : CalendarEntityFeature .CREATE_EVENT ,
11081129 }
11091130
11101131
@@ -1140,3 +1161,103 @@ async def test_config_entry_supported_components(
11401161 # No entity created when no components exist
11411162 state = hass .states .get ("calendar.calendar_4" )
11421163 assert not state
1164+
1165+
1166+ @pytest .mark .parametrize ("tz" , [UTC ])
1167+ @pytest .mark .parametrize (
1168+ ("service_data" , "expected_ics_fields" ),
1169+ [
1170+ # Basic event with all fields
1171+ (
1172+ {
1173+ "summary" : "Test Event" ,
1174+ "start_date_time" : "2025-08-06T10:00:00+00:00" ,
1175+ "end_date_time" : "2025-08-06T11:00:00+00:00" ,
1176+ "description" : "Test Description" ,
1177+ "location" : "Test Location" ,
1178+ },
1179+ {
1180+ "description" : "Test Description" ,
1181+ "dtend" : datetime .datetime (
1182+ 2025 , 8 , 6 , 11 , 0 , tzinfo = zoneinfo .ZoneInfo (key = "UTC" )
1183+ ),
1184+ "dtstart" : datetime .datetime (
1185+ 2025 , 8 , 6 , 10 , 0 , tzinfo = zoneinfo .ZoneInfo (key = "UTC" )
1186+ ),
1187+ "location" : "Test Location" ,
1188+ "summary" : "Test Event" ,
1189+ },
1190+ ),
1191+ # Event with only required fields
1192+ (
1193+ {
1194+ "summary" : "Required Only" ,
1195+ "start_date_time" : "2025-08-07T09:00:00+00:00" ,
1196+ "end_date_time" : "2025-08-07T10:00:00+00:00" ,
1197+ },
1198+ {
1199+ "summary" : "Required Only" ,
1200+ "dtstart" : datetime .datetime (
1201+ 2025 , 8 , 7 , 9 , 0 , tzinfo = zoneinfo .ZoneInfo (key = "UTC" )
1202+ ),
1203+ "dtend" : datetime .datetime (
1204+ 2025 , 8 , 7 , 10 , 0 , tzinfo = zoneinfo .ZoneInfo (key = "UTC" )
1205+ ),
1206+ },
1207+ ),
1208+ # All-day event (date only)
1209+ (
1210+ {
1211+ "summary" : "All Day Event" ,
1212+ "start_date" : "2025-08-08" ,
1213+ "end_date" : "2025-08-09" ,
1214+ },
1215+ {
1216+ "summary" : "All Day Event" ,
1217+ "dtstart" : datetime .date (2025 , 8 , 8 ),
1218+ "dtend" : datetime .date (2025 , 8 , 9 ),
1219+ },
1220+ ),
1221+ # Event with different timezone
1222+ (
1223+ {
1224+ "summary" : "Different TZ" ,
1225+ "start_date_time" : "2025-08-07T09:00:00+02:00" ,
1226+ "end_date_time" : "2025-08-07T10:00:00+02:00" ,
1227+ },
1228+ {
1229+ "summary" : "Different TZ" ,
1230+ "dtstart" : datetime .datetime (
1231+ 2025 , 8 , 7 , 7 , 0 , tzinfo = zoneinfo .ZoneInfo (key = "UTC" )
1232+ ),
1233+ "dtend" : datetime .datetime (
1234+ 2025 , 8 , 7 , 8 , 0 , tzinfo = zoneinfo .ZoneInfo (key = "UTC" )
1235+ ),
1236+ },
1237+ ),
1238+ # Rrule is not supported in API (async_call) calls.
1239+ ],
1240+ )
1241+ async def test_add_vevent (
1242+ hass : HomeAssistant ,
1243+ setup_platform_cb : Callable [[], Awaitable [None ]],
1244+ calendars : list [Mock ],
1245+ service_data : dict ,
1246+ expected_ics_fields : dict ,
1247+ ) -> None :
1248+ """Test adding a VEVENT to the calendar."""
1249+ await setup_platform_cb ()
1250+
1251+ calendars [0 ].add_event = MagicMock (return_value = [])
1252+ await hass .services .async_call (
1253+ "calendar" ,
1254+ "create_event" ,
1255+ service_data ,
1256+ target = {"entity_id" : TEST_ENTITY },
1257+ blocking = True ,
1258+ )
1259+ await hass .async_block_till_done ()
1260+
1261+ calendars [0 ].add_event .assert_called_once ()
1262+ assert calendars [0 ].add_event .call_args
1263+ assert calendars [0 ].add_event .call_args [1 ] == expected_ics_fields
0 commit comments