From 7f58704c0ea5983e6ac79e3af4ca8a441e95b010 Mon Sep 17 00:00:00 2001 From: Test User Date: Wed, 4 Mar 2026 18:06:08 +0530 Subject: [PATCH] test: add real-world platform calendar fixtures and cross-platform merge tests --- tests/calendars/android_etar.ics | 39 ++++++++++++ tests/calendars/google_with_vtimezone.ics | 60 +++++++++++++++++++ tests/calendars/google_x_location.ics | 48 +++++++++++++++ tests/calendars/thunderbird.ics | 43 ++++++++++++++ tests/test_platform_calendars.py | 72 +++++++++++++++++++++++ 5 files changed, 262 insertions(+) create mode 100644 tests/calendars/android_etar.ics create mode 100644 tests/calendars/google_with_vtimezone.ics create mode 100644 tests/calendars/google_x_location.ics create mode 100644 tests/calendars/thunderbird.ics create mode 100644 tests/test_platform_calendars.py diff --git a/tests/calendars/android_etar.ics b/tests/calendars/android_etar.ics new file mode 100644 index 0000000..28fcf43 --- /dev/null +++ b/tests/calendars/android_etar.ics @@ -0,0 +1,39 @@ +BEGIN:VCALENDAR +PRODID:-//Offline Calendar//iCal Import/Export 2.8.1//EN +VERSION:2.0 +METHOD:PUBLISH +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/London +TZURL:http://tzurl.org/zoneinfo/Europe/London +X-LIC-LOCATION:Europe/London +BEGIN:DAYLIGHT +TZOFFSETFROM:+0000 +TZOFFSETTO:+0100 +TZNAME:BST +DTSTART:19810329T010000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0100 +TZOFFSETTO:+0000 +TZNAME:GMT +DTSTART:19961027T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20241005T112701Z +UID:android-etar-event-1@test.mergecal.org +SUMMARY:Morning Run +STATUS:CONFIRMED +DTSTART;TZID=Europe/London:20241005T070000 +DTEND;TZID=Europe/London:20241005T080000 +LAST-MODIFIED:20241005T112701Z +BEGIN:VALARM +TRIGGER:-PT30M +ACTION:DISPLAY +DESCRIPTION:Morning Run +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/tests/calendars/google_with_vtimezone.ics b/tests/calendars/google_with_vtimezone.ics new file mode 100644 index 0000000..11786bc --- /dev/null +++ b/tests/calendars/google_with_vtimezone.ics @@ -0,0 +1,60 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:Test Calendar +X-WR-TIMEZONE:Europe/London +BEGIN:VTIMEZONE +TZID:Europe/Berlin +X-LIC-LOCATION:Europe/Berlin +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:GMT+2 +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:GMT+1 +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART:20241004T181500Z +DTEND:20241004T190000Z +DTSTAMP:20241004T175945Z +UID:google-event-1@test.mergecal.org +CREATED:20241004T175920Z +LAST-MODIFIED:20241004T175928Z +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Team Meeting +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P0DT0H10M0S +DESCRIPTION:This is an event reminder +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P0DT0H14M0S +DESCRIPTION:This is an event reminder +END:VALARM +BEGIN:VALARM +ACTION:EMAIL +ATTENDEE:mailto:attendee@example.com +TRIGGER:-P0DT0H15M0S +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P0DT0H15M0S +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/tests/calendars/google_x_location.ics b/tests/calendars/google_x_location.ics new file mode 100644 index 0000000..1bbdf8d --- /dev/null +++ b/tests/calendars/google_x_location.ics @@ -0,0 +1,48 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:Work Calendar +X-WR-TIMEZONE:Europe/Zurich +X-WR-CALDESC:Work Bookings +BEGIN:VTIMEZONE +TZID:Europe/Zurich +X-LIC-LOCATION:Europe/Zurich +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=Europe/Zurich:20161028T140000 +DTEND;TZID=Europe/Zurich:20161028T143000 +RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR +DTSTAMP:20161031T192828Z +UID:google-recurring-event-1@test.mergecal.org +CREATED:20161029T121229Z +DESCRIPTION:Some Description +LAST-MODIFIED:20161029T121229Z +LOCATION:Roadstar 16\n12764 Happyville\nDenmark +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Daily Sync +TRANSP:OPAQUE +X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS="Röadstar 16\n12764 Happyvi + lle\nDenmark";X-APPLE-MAPKIT-HANDLE=CAESARoSCWYTYFhHQBEGfw4hQCIBDQoHRGVubW + FyaxJES0hhcHB5dmlsbGUqSGFwcHl2aWxsZTIHSGFwcHl2aWxsZToEMTI3NjRCDQpSb2Fkc3Rh + cloCMTZiUm9hZHN0YXIgMTYBEU1vcmRvcgENCk1vcmRvcioSUm9hZHN0YXIgMTYyUm9hZHN0YX + IgMTYxMjc2NCBIYXBweXZpbGxlMgdEZW5tYXJrOThA=;X-APPLE-RADIUS=49.91305866584698 + ;X-APPLE-REFERENCEFRAME=1;X-TITLE=:geo:52.382762,7.528319 +END:VEVENT +END:VCALENDAR diff --git a/tests/calendars/thunderbird.ics b/tests/calendars/thunderbird.ics new file mode 100644 index 0000000..e476b9f --- /dev/null +++ b/tests/calendars/thunderbird.ics @@ -0,0 +1,43 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/London +X-TZINFO:Europe/London[2024a] +BEGIN:STANDARD +TZOFFSETTO:+000000 +TZOFFSETFROM:+010000 +TZNAME:GMT +DTSTART:19701025T020000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETTO:+010000 +TZOFFSETFROM:+000000 +TZNAME:BST +DTSTART:19700329T010000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20241023T131035Z +LAST-MODIFIED:20241023T131141Z +DTSTAMP:20241023T131141Z +UID:thunderbird-event-1@test.mergecal.org +SUMMARY:Team Standup +DTSTART;TZID=Europe/London:20241023T150000 +DTEND;TZID=Europe/London:20241023T160000 +TRANSP:OPAQUE +X-MOZ-GENERATION:2 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-PT15M +DESCRIPTION:Mozilla Standardbeschreibung +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-PT45M +DESCRIPTION:Mozilla Standardbeschreibung +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/tests/test_platform_calendars.py b/tests/test_platform_calendars.py new file mode 100644 index 0000000..b9d4f0b --- /dev/null +++ b/tests/test_platform_calendars.py @@ -0,0 +1,72 @@ +"""Tests for merging calendars from various real-world platforms.""" + +import pytest + +from mergecal import merge_calendars + +PLATFORM_PAIRS = [ + ("google_with_vtimezone", "google_x_location"), + ("google_with_vtimezone", "thunderbird"), + ("google_x_location", "thunderbird"), + ("google_with_vtimezone", "android_etar"), + ("thunderbird", "android_etar"), +] + + +@pytest.mark.parametrize("cal_a,cal_b", PLATFORM_PAIRS) +def test_merging_platform_calendars_is_reproducible(calendars, cal_a, cal_b): + """Each platform pair merges to the same result on repeated calls.""" + cals = [getattr(calendars, cal_a), getattr(calendars, cal_b)] + assert merge_calendars(cals) == merge_calendars(cals) + + +@pytest.mark.parametrize("cal_a,cal_b", PLATFORM_PAIRS) +def test_merging_platform_calendar_pair_is_idempotent(calendars, cal_a, cal_b): + """Merging a platform pair twice produces the same result as merging it once.""" + cals = [getattr(calendars, cal_a), getattr(calendars, cal_b)] + assert merge_calendars(cals) == merge_calendars(cals * 2) + + +def test_merging_all_platforms_produces_events(calendars): + """All platform calendars can be merged together into a single output.""" + platforms = [ + calendars.google_with_vtimezone, + calendars.google_x_location, + calendars.thunderbird, + calendars.android_etar, + ] + result = merge_calendars(platforms) + assert len(list(result.events)) == 4 + + +def test_google_alarm_email_attendee_survives_merge(calendars): + """Google Calendar EMAIL alarm with ATTENDEE is preserved after merging.""" + result = merge_calendars([calendars.google_with_vtimezone]) + events = list(result.events) + alarms = [c for e in events for c in e.subcomponents if c.name == "VALARM"] + email_alarms = [a for a in alarms if a.get("action") == "EMAIL"] + assert len(email_alarms) == 1 + + +def test_google_recurring_with_x_apple_location_survives_merge(calendars): + """Google recurring event with X-APPLE-STRUCTURED-LOCATION merges cleanly.""" + result = merge_calendars([calendars.google_x_location]) + events = list(result.events) + assert len(events) == 1 + assert "X-APPLE-STRUCTURED-LOCATION" in events[0] + + +def test_thunderbird_x_moz_generation_survives_merge(calendars): + """Thunderbird X-MOZ-GENERATION property is preserved after merging.""" + result = merge_calendars([calendars.thunderbird]) + events = list(result.events) + assert events[0].get("x-moz-generation") is not None + + +def test_cross_platform_dedup_by_uid(calendars): + """Events with the same UID from two feeds are deduplicated.""" + result = merge_calendars( + [calendars.google_with_vtimezone, calendars.google_with_vtimezone] + ) + uids = [e.uid for e in result.events] + assert len(uids) == len(set(uids))