@@ -64,6 +64,8 @@ def events_responses() -> None:
6464 "value" : [
6565 {
6666 "id" : "singular_id" ,
67+ "iCalUId" : "singular_ical_id" ,
68+ "createdDateTime" : "2025-07-07T08:41:36.5027961Z" ,
6769 "lastModifiedDateTime" : "2022-09-07T08:41:36.5027961Z" ,
6870 "originalStartTimeZone" : "Eastern Standard Time" ,
6971 "originalEndTimeZone" : "Eastern Standard Time" ,
@@ -106,6 +108,8 @@ def events_responses() -> None:
106108 },
107109 {
108110 "id" : "recurrence_id" ,
111+ "iCalUId" : "recurrence_ical_id" ,
112+ "createdDateTime" : "2022-09-07T08:41:36.5027961Z" ,
109113 "lastModifiedDateTime" : "2022-09-27T14:41:23.1042764Z" ,
110114 "originalStartTimeZone" : "Pacific Standard Time" ,
111115 "originalEndTimeZone" : "Pacific Standard Time" ,
@@ -352,6 +356,8 @@ def exception_override_response() -> None:
352356 },
353357 {
354358 "id" : "recurrence_id_exception" ,
359+ "iCalUId" : "recurrence_ical_id_exception" ,
360+ "createdDateTime" : "2022-09-27T14:41:23.1042764Z" ,
355361 "lastModifiedDateTime" : "2022-09-27T14:41:23.1042764Z" ,
356362 "originalStartTimeZone" : "Pacific Standard Time" ,
357363 "originalEndTimeZone" : "Pacific Standard Time" ,
@@ -474,10 +480,195 @@ def test_sync_events(provider) -> None:
474480
475481 assert isinstance (events_by_title ["Singular" ], Event )
476482 assert events_by_title ["Singular" ].description == "Singular"
483+ assert events_by_title ["Singular" ].uid == "singular_id"
477484 assert isinstance (events_by_title ["Recurring" ], RecurringEvent )
478485 assert events_by_title ["Recurring" ].description == "Hello world!"
479486
480487
488+ @pytest .fixture
489+ def events_with_cutoff_response () -> None :
490+ responses .get (
491+ BASE_URL + "/me/calendars/fake_calendar_id/events" ,
492+ json = {
493+ "value" : [
494+ {
495+ "id" : "before_cutoff_id" ,
496+ "iCalUId" : "before_cutoff_ical_id" ,
497+ "createdDateTime" : "2025-07-29T12:00:00.0000000Z" , # Before cutoff
498+ "lastModifiedDateTime" : "2025-07-29T12:00:00.0000000Z" ,
499+ "originalStartTimeZone" : "Eastern Standard Time" ,
500+ "originalEndTimeZone" : "Eastern Standard Time" ,
501+ "subject" : "Event Before Cutoff" ,
502+ "importance" : "normal" ,
503+ "sensitivity" : "normal" ,
504+ "isAllDay" : False ,
505+ "isCancelled" : False ,
506+ "isOrganizer" : True ,
507+ "showAs" : "busy" ,
508+ "type" : "singleInstance" ,
509+ "recurrence" : None ,
510+ "body" : {
511+ "contentType" : "html" ,
512+ "content" : "Before cutoff" ,
513+ },
514+ "start" : {
515+ "dateTime" : "2025-08-01T12:00:00.0000000" ,
516+ "timeZone" : "UTC" ,
517+ },
518+ "end" : {
519+ "dateTime" : "2025-08-01T13:00:00.0000000" ,
520+ "timeZone" : "UTC" ,
521+ },
522+ "locations" : [],
523+ "attendees" : [],
524+ "organizer" : None ,
525+ },
526+ {
527+ "id" : "after_cutoff_id" ,
528+ "iCalUId" : "after_cutoff_ical_id" ,
529+ "createdDateTime" : "2025-08-01T12:00:00.0000000Z" , # After cutoff
530+ "lastModifiedDateTime" : "2025-08-01T12:00:00.0000000Z" ,
531+ "originalStartTimeZone" : "Eastern Standard Time" ,
532+ "originalEndTimeZone" : "Eastern Standard Time" ,
533+ "subject" : "Event After Cutoff" ,
534+ "importance" : "normal" ,
535+ "sensitivity" : "normal" ,
536+ "isAllDay" : False ,
537+ "isCancelled" : False ,
538+ "isOrganizer" : True ,
539+ "showAs" : "busy" ,
540+ "type" : "singleInstance" ,
541+ "recurrence" : None ,
542+ "body" : {"contentType" : "html" , "content" : "After cutoff" },
543+ "start" : {
544+ "dateTime" : "2025-08-01T14:00:00.0000000" ,
545+ "timeZone" : "UTC" ,
546+ },
547+ "end" : {
548+ "dateTime" : "2025-08-01T15:00:00.0000000" ,
549+ "timeZone" : "UTC" ,
550+ },
551+ "locations" : [],
552+ "attendees" : [],
553+ "organizer" : None ,
554+ },
555+ {
556+ "id" : "recurring_after_cutoff_id" ,
557+ "iCalUId" : "recurring_after_cutoff_ical_id" ,
558+ "createdDateTime" : "2025-08-05T10:00:00.0000000Z" , # After cutoff
559+ "lastModifiedDateTime" : "2025-08-05T10:00:00.0000000Z" ,
560+ "originalStartTimeZone" : "Pacific Standard Time" ,
561+ "originalEndTimeZone" : "Pacific Standard Time" ,
562+ "subject" : "Recurring After Cutoff" ,
563+ "importance" : "normal" ,
564+ "sensitivity" : "normal" ,
565+ "isAllDay" : False ,
566+ "isCancelled" : False ,
567+ "isOrganizer" : True ,
568+ "showAs" : "busy" ,
569+ "type" : "seriesMaster" ,
570+ "body" : {
571+ "contentType" : "html" ,
572+ "content" : "Recurring after cutoff" ,
573+ },
574+ "start" : {
575+ "dateTime" : "2025-08-10T15:00:00.0000000" ,
576+ "timeZone" : "UTC" ,
577+ },
578+ "end" : {
579+ "dateTime" : "2025-08-10T15:30:00.0000000" ,
580+ "timeZone" : "UTC" ,
581+ },
582+ "locations" : [],
583+ "recurrence" : {
584+ "pattern" : {
585+ "type" : "daily" ,
586+ "interval" : 1 ,
587+ "month" : 0 ,
588+ "dayOfMonth" : 0 ,
589+ "firstDayOfWeek" : "sunday" ,
590+ "index" : "first" ,
591+ },
592+ "range" : {
593+ "type" : "endDate" ,
594+ "startDate" : "2025-08-10" ,
595+ "endDate" : "2025-08-12" ,
596+ "recurrenceTimeZone" : "Pacific Standard Time" ,
597+ "numberOfOccurrences" : 0 ,
598+ },
599+ },
600+ "attendees" : [],
601+ "organizer" : {
602+ "emailAddress" : {
603+ "name" : "Test" ,
604+ "address" : "test@example.com" ,
605+ }
606+ },
607+ },
608+ ]
609+ },
610+ )
611+
612+
613+ @pytest .fixture
614+ def recurring_instances_response () -> None :
615+ responses .get (
616+ BASE_URL + "/me/events/recurring_after_cutoff_id/instances" ,
617+ json = {
618+ "value" : [
619+ {
620+ "type" : "occurrence" ,
621+ "start" : {
622+ "dateTime" : "2025-08-10T15:00:00.0000000" ,
623+ "timeZone" : "UTC" ,
624+ },
625+ "originalStart" : "2025-08-10T15:00:00Z" ,
626+ },
627+ {
628+ "type" : "occurrence" ,
629+ "start" : {
630+ "dateTime" : "2025-08-11T15:00:00.0000000" ,
631+ "timeZone" : "UTC" ,
632+ },
633+ "originalStart" : "2025-08-11T15:00:00Z" ,
634+ },
635+ {
636+ "type" : "occurrence" ,
637+ "start" : {
638+ "dateTime" : "2025-08-12T15:00:00.0000000" ,
639+ "timeZone" : "UTC" ,
640+ },
641+ "originalStart" : "2025-08-12T15:00:00Z" ,
642+ },
643+ ]
644+ },
645+ )
646+
647+
648+ @responses .activate
649+ @pytest .mark .usefixtures (
650+ "events_with_cutoff_response" , "recurring_instances_response"
651+ )
652+ def test_sync_events_respects_ical_uid_cutoff (provider ) -> None :
653+ events = provider .sync_events ("fake_calendar_id" )
654+ events_by_title = {event .title : event for event in events }
655+
656+ # Event created before cutoff should use MS Graph ID
657+ assert events_by_title ["Event Before Cutoff" ].uid == "before_cutoff_id"
658+
659+ # Event created after cutoff should use iCalUID
660+ assert events_by_title ["Event After Cutoff" ].uid == "after_cutoff_ical_id"
661+
662+ # Recurring event created after cutoff should use iCalUID
663+ assert isinstance (
664+ events_by_title ["Recurring After Cutoff" ], RecurringEvent
665+ )
666+ assert (
667+ events_by_title ["Recurring After Cutoff" ].uid
668+ == "recurring_after_cutoff_ical_id"
669+ )
670+
671+
481672@responses .activate
482673@pytest .mark .usefixtures ("events_responses" , "cancellation_override_response" )
483674def test_sync_events_cancellation (provider ) -> None :
0 commit comments