Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions tests/calendars/android_etar.ics
Original file line number Diff line number Diff line change
@@ -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
60 changes: 60 additions & 0 deletions tests/calendars/google_with_vtimezone.ics
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions tests/calendars/google_x_location.ics
Original file line number Diff line number Diff line change
@@ -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
43 changes: 43 additions & 0 deletions tests/calendars/thunderbird.ics
Original file line number Diff line number Diff line change
@@ -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
72 changes: 72 additions & 0 deletions tests/test_platform_calendars.py
Original file line number Diff line number Diff line change
@@ -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))
Loading