@@ -566,6 +566,183 @@ async def test_add_organizer_no_arg(self, async_client: Any, async_calendar: Any
566566 f"ORGANIZER { org !r} should match the principal's address { expected_vcal !r} "
567567 )
568568
569+ # ==================== Group A – Core CRUD ====================
570+
571+ @pytest .mark .asyncio
572+ async def test_get_supported_components (self , async_calendar : Any ) -> None :
573+ """get_supported_components() must include VEVENT."""
574+ components = await async_calendar .get_supported_components ()
575+ assert components
576+ assert "VEVENT" in components
577+
578+ @pytest .mark .asyncio
579+ async def test_lookup_event (self , async_calendar : Any ) -> None :
580+ """Add an event and look it up by URL, by UID, and via Event(url=…).load()."""
581+ from caldav import Event
582+ from caldav .lib import error
583+
584+ self .skip_unless_support ("save-load.event" )
585+ c = async_calendar
586+
587+ e1 = await c .add_event (ev1_static )
588+ assert e1 .url is not None
589+
590+ # look up by UID
591+ e3 = await c .get_event_by_uid ("20010712T182145Z-123401@example.com" )
592+ assert str (e3 .icalendar_component ["uid" ]) == "20010712T182145Z-123401@example.com"
593+ assert e3 .url == e1 .url
594+
595+ # load directly from URL without going through the calendar object
596+ e4 = Event (client = c .client , url = e1 .url )
597+ await e4 .load ()
598+ assert str (e4 .icalendar_component ["uid" ]) == "20010712T182145Z-123401@example.com"
599+
600+ with pytest .raises (error .NotFoundError ):
601+ await c .get_event_by_uid ("nonexistent-uid-000" )
602+
603+ @pytest .mark .asyncio
604+ async def test_create_overwrite_delete_event (self , async_calendar : Any ) -> None :
605+ """no_create/no_overwrite flags, same-UID overwrite, and delete."""
606+ from caldav .lib import error
607+
608+ self .skip_unless_support ("save-load.event" )
609+ c = async_calendar
610+
611+ # attempting to update a non-existing event must raise ConsistencyError
612+ with pytest .raises (error .ConsistencyError ):
613+ await c .add_event (ev1_static , no_create = True )
614+
615+ # no_create + no_overwrite is always an error
616+ with pytest .raises (error .ConsistencyError ):
617+ await c .add_event (ev1_static , no_create = True , no_overwrite = True )
618+
619+ e1 = await c .add_event (ev1_static )
620+ assert e1 .url is not None
621+
622+ # same UID again → overwrite (unless server forbids it)
623+ if not self .is_supported ("no-overwrite" ):
624+ e2 = await c .add_event (ev1_static )
625+
626+ # no_create on an existing event must succeed
627+ e2 = await c .add_event (ev1_static , no_create = True )
628+
629+ # modify and save with no_create
630+ e2 .icalendar_component ["summary" ] = "Bastille Day Party!"
631+ await e2 .save (no_create = True )
632+
633+ e3 = await c .event_by_url (e1 .url )
634+ assert e3 .icalendar_component ["summary" ] == "Bastille Day Party!"
635+
636+ # no_overwrite on an existing event must raise ConsistencyError
637+ with pytest .raises (error .ConsistencyError ):
638+ await c .add_event (ev1_static , no_overwrite = True )
639+
640+ await e1 .delete ()
641+
642+ with pytest .raises (error .NotFoundError ):
643+ await c .event_by_url (e1 .url )
644+ with pytest .raises (error .NotFoundError ):
645+ await c .get_event_by_uid ("20010712T182145Z-123401@example.com" )
646+
647+ @pytest .mark .asyncio
648+ async def test_object_by_uid (self , async_task_list : Any ) -> None :
649+ """Add a TODO with a known UID and retrieve it via get_object_by_uid()."""
650+ from caldav .lib import error
651+
652+ c = async_task_list
653+ await c .add_todo (summary = "Some test task with a well-known uid" , uid = "well_known_1" )
654+
655+ foo = await c .get_object_by_uid ("well_known_1" )
656+ assert str (foo .icalendar_component ["summary" ]) == "Some test task with a well-known uid"
657+
658+ # prefix match must NOT succeed
659+ with pytest .raises (error .NotFoundError ):
660+ await c .get_object_by_uid ("well_known" )
661+
662+ # suffix match must NOT succeed
663+ with pytest .raises (error .NotFoundError ):
664+ await c .get_object_by_uid ("well_known_10" )
665+
666+ @pytest .mark .asyncio
667+ async def test_load_event (self , async_calendar : Any , async_calendar2 : Any ) -> None :
668+ """add_event() returns an object; load() must populate it."""
669+ self .skip_unless_support ("save-load.event" )
670+ self .skip_unless_support ("create-calendar" )
671+
672+ c1 = async_calendar
673+
674+ e1_ = await c1 .add_event (ev1_static )
675+ await e1_ .load () # load the object returned by add_event
676+
677+ events = await c1 .get_events ()
678+ assert len (events ) >= 1
679+ e1 = events [0 ]
680+ await e1 .load () # load a freshly fetched handle
681+ assert e1 .url == e1_ .url
682+
683+ @pytest .mark .asyncio
684+ async def test_copy_event (self , async_calendar : Any , async_calendar2 : Any ) -> None :
685+ """copy() within same calendar and cross-calendar."""
686+ self .skip_unless_support ("save-load.event" )
687+ self .skip_unless_support ("create-calendar" )
688+
689+ c1 = async_calendar
690+ c2 = async_calendar2
691+
692+ e1_ = await c1 .add_event (ev1_static )
693+ events = await c1 .get_events ()
694+ e1 = events [0 ]
695+
696+ # duplicate in same calendar with a new UID
697+ e1_dup = e1 .copy ()
698+ await e1_dup .save ()
699+ assert len (await c1 .get_events ()) == 2
700+
701+ # copy cross-calendar keeping the same UID
702+ if self .is_supported ("save.duplicate-uid.cross-calendar" ):
703+ e1_in_c2 = e1 .copy (new_parent = c2 , keep_uid = True )
704+ await e1_in_c2 .save ()
705+ assert len (await c2 .get_events ()) == 1
706+
707+ # modifying the copy in c2 must not affect c1's event
708+ e1_in_c2 .icalendar_component ["summary" ] = "asdf"
709+ await e1_in_c2 .save ()
710+ await e1 .load ()
711+ assert str (e1 .icalendar_component ["summary" ]) == "Bastille Day Party"
712+
713+ # copy in same calendar keeping UID — same-UID PUT is a no-op / overwrite
714+ e1_dup2 = e1 .copy (keep_uid = True )
715+ await e1_dup2 .save ()
716+ # count should still be 2 (not 3) because same UID overwrites
717+ assert len (await c1 .get_events ()) == 2
718+
719+ @pytest .mark .asyncio
720+ async def test_multi_get (self , async_calendar : Any ) -> None :
721+ """calendar_multiget() retrieves multiple events in one request."""
722+ self .skip_unless_support ("save-load.event" )
723+
724+ c = async_calendar
725+
726+ event1 = await c .add_event (
727+ uid = "test-multiget-1" ,
728+ dtstart = datetime (2015 , 1 , 1 , 8 , 0 , 0 ),
729+ dtend = datetime (2015 , 1 , 1 , 9 , 0 , 0 ),
730+ summary = "test-multiget-1" ,
731+ )
732+ event2 = await c .add_event (
733+ uid = "test-multiget-2" ,
734+ dtstart = datetime (2015 , 1 , 1 , 8 , 0 , 0 ),
735+ dtend = datetime (2015 , 1 , 1 , 9 , 0 , 0 ),
736+ summary = "test-multiget-2" ,
737+ )
738+
739+ results = await c .calendar_multiget ([event1 .url , event2 .url ])
740+ assert len (results ) == 2
741+ uids = {str (r .icalendar_component ["uid" ]) for r in results }
742+ assert uids == {"test-multiget-1" , "test-multiget-2" }
743+
744+ await event1 .load_by_multiget ()
745+
569746
570747class _AsyncTestSchedulingBase :
571748 """
0 commit comments