diff --git a/src/icalendar/tests/calendars/issue_104_broken_calendar.ics b/src/icalendar/tests/calendars/issue_104_broken_calendar.ics new file mode 100644 index 0000000..117afdf --- /dev/null +++ b/src/icalendar/tests/calendars/issue_104_broken_calendar.ics @@ -0,0 +1,14 @@ +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +BEGIN:VEVENT +DTSTART:20140401T000000Z +DTEND:20140401T010000Z +DTSTAMP:20140401T000000Z +SUMMARY:Broken Eevnt +CLASS:PUBLIC +STATUS:CONFIRMED +TRANSP:OPAQUE +END:VEVENT +X +END:VCALENDAR diff --git a/src/icalendar/tests/conftest.py b/src/icalendar/tests/conftest.py index 6176d98..efc0b07 100644 --- a/src/icalendar/tests/conftest.py +++ b/src/icalendar/tests/conftest.py @@ -1,10 +1,15 @@ import os import logging - import pytest import icalendar +import pytz +from datetime import datetime +from dateutil import tz +try: + import zoneinfo +except ModuleNotFoundError: + from backports import zoneinfo -LOGGER = logging.getLogger(__name__) class DataSource: '''A collection of parsed ICS elements (e.g calendars, timezones, events)''' @@ -13,18 +18,18 @@ class DataSource: self._data_source_folder = data_source_folder def __getattr__(self, attribute): - if not attribute in self.__dict__: - source_file = attribute.replace('-', '_') + '.ics' - source_path = os.path.join(self.__dict__['_data_source_folder'], source_file) - with open(source_path, 'rb') as f: - try: - raw_ics = f.read() - source = self.__dict__['_parser'](raw_ics) - source.raw_ics = raw_ics - self.__dict__[attribute] = source - except ValueError as error: - LOGGER.error(f'Could not load {source_file} due to {error}') - return self.__dict__[attribute] + """Parse a file and return the result stored in the attribute.""" + source_file = attribute.replace('-', '_') + '.ics' + source_path = os.path.join(self._data_source_folder, source_file) + with open(source_path, 'rb') as f: + raw_ics = f.read() + source = self._parser(raw_ics) + source.raw_ics = raw_ics + self.__dict__[attribute] = source + return source + + def __getitem__(self, key): + return getattr(self, key) def __repr__(self): return repr(self.__dict__) @@ -42,7 +47,15 @@ def timezones(): def events(): return DataSource(EVENTS_FOLDER, icalendar.Event.from_ical) +@pytest.fixture(params=[ + pytz.utc, + zoneinfo.ZoneInfo('UTC'), + pytz.timezone('UTC'), + tz.UTC, + tz.gettz('UTC')]) +def utc(request): + return request.param + @pytest.fixture def calendars(): return DataSource(CALENDARS_FOLDER, icalendar.Calendar.from_ical) - diff --git a/src/icalendar/tests/events/issue_100_transformed_doctests_into_unittests.ics b/src/icalendar/tests/events/issue_100_transformed_doctests_into_unittests.ics new file mode 100644 index 0000000..803f567 --- /dev/null +++ b/src/icalendar/tests/events/issue_100_transformed_doctests_into_unittests.ics @@ -0,0 +1,3 @@ +BEGIN:VEVENT +SUMMARY;LANGUAGE=ru:te +END:VEVENT diff --git a/src/icalendar/tests/events/issue_101_icalendar_chokes_on_umlauts_in_organizer.ics b/src/icalendar/tests/events/issue_101_icalendar_chokes_on_umlauts_in_organizer.ics new file mode 100644 index 0000000..27a6d09 --- /dev/null +++ b/src/icalendar/tests/events/issue_101_icalendar_chokes_on_umlauts_in_organizer.ics @@ -0,0 +1,14 @@ +BEGIN:VEVENT +SUMMARY:wichtiger termin 1 +DTSTART:20130416T100000Z +DTEND:20130416T110000Z +DTSTAMP:20130416T092616Z +UID:20130416112341.10064jz0k4j7uem8@acmenet.de +CLASS:PUBLIC +CREATED:20130416T092341Z +LAST-MODIFIED:20130416T092341Z +LOCATION:im büro +ORGANIZER;CN="acme, ädmin":mailto:adm-acme@mydomain.de +STATUS:CONFIRMED +TRANSP:OPAQUE +END:VEVENT diff --git a/src/icalendar/tests/events/issue_104_mark_events_broken.ics b/src/icalendar/tests/events/issue_104_mark_events_broken.ics new file mode 100644 index 0000000..a3ecea2 --- /dev/null +++ b/src/icalendar/tests/events/issue_104_mark_events_broken.ics @@ -0,0 +1,10 @@ +BEGIN:VEVENT +DTSTART:20140401T000000Z +DTEND:20140401T010000Z +DTSTAMP:20140401T000000Z +SUMMARY:Broken Eevnt +CLASS:PUBLIC +STATUS:CONFIRMED +TRANSP:OPAQUE +X +END:VEVENT diff --git a/src/icalendar/tests/issue_112_missing_tzinfo_on_exdate.ics b/src/icalendar/tests/events/issue_112_missing_tzinfo_on_exdate.ics similarity index 54% rename from src/icalendar/tests/issue_112_missing_tzinfo_on_exdate.ics rename to src/icalendar/tests/events/issue_112_missing_tzinfo_on_exdate.ics index 2356cdc..7363ab0 100644 --- a/src/icalendar/tests/issue_112_missing_tzinfo_on_exdate.ics +++ b/src/icalendar/tests/events/issue_112_missing_tzinfo_on_exdate.ics @@ -1,30 +1,3 @@ -BEGIN:VCALENDAR -PRODID:-//Google Inc//Google Calendar 70.9054//EN -VERSION:2.0 -CALSCALE:GREGORIAN -METHOD:PUBLISH -X-WR-CALNAME:Market East -X-WR-TIMEZONE:America/New_York -X-WR-CALDESC: -BEGIN:VTIMEZONE -TZID:America/New_York -X-LIC-LOCATION:America/New_York -BEGIN:DAYLIGHT -TZOFFSETFROM:-0500 -TZOFFSETTO:-0400 -TZNAME:EDT -DTSTART:19700308T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU -END:DAYLIGHT -BEGIN:STANDARD -TZOFFSETFROM:-0400 -TZOFFSETTO:-0500 -TZNAME:EST -DTSTART:19701101T020000 -RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU -END:STANDARD -END:VTIMEZONE - BEGIN:VEVENT DTSTART;TZID=America/New_York:20130907T120000 DTEND;TZID=America/New_York:20130907T170000 @@ -45,4 +18,3 @@ SUMMARY:Market East Live! TRANSP:OPAQUE END:VEVENT -END:VCALENDAR diff --git a/src/icalendar/tests/events/issue_157_removes_trailing_semicolon.ics b/src/icalendar/tests/events/issue_157_removes_trailing_semicolon.ics new file mode 100644 index 0000000..766b56f --- /dev/null +++ b/src/icalendar/tests/events/issue_157_removes_trailing_semicolon.ics @@ -0,0 +1,4 @@ +BEGIN:VEVENT +DTSTART:20150325T101010 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU; +END:VEVENT diff --git a/src/icalendar/tests/events/issue_184_broken_representation_of_period.ics b/src/icalendar/tests/events/issue_184_broken_representation_of_period.ics new file mode 100644 index 0000000..8b6ed13 --- /dev/null +++ b/src/icalendar/tests/events/issue_184_broken_representation_of_period.ics @@ -0,0 +1,6 @@ +BEGIN:VEVENT +DTSTART:20150219T133000 +DTSTAMP:20150219T133000 +UID:1234567 +RDATE;VALUE=PERIOD:20150219T133000/PT10H +END:VEVENT diff --git a/src/icalendar/tests/events/issue_53_description_parsed_properly.ics b/src/icalendar/tests/events/issue_53_description_parsed_properly.ics new file mode 100644 index 0000000..9edc183 --- /dev/null +++ b/src/icalendar/tests/events/issue_53_description_parsed_properly.ics @@ -0,0 +1,19 @@ +BEGIN:VEVENT +DTSTAMP:20120605T003759Z +DTSTART;TZID=America/New_York:20120712T183000 +DTEND;TZID=America/New_York:20120712T213000 +STATUS:CONFIRMED +SUMMARY:DevOps DC Meetup +DESCRIPTION:DevOpsDC\nThursday\, July 12 at 6:30 PM\n\nThis will be a joi + nt meetup / hack night with the DC jQuery Users Group. The idea behind + the hack night: Small teams consisting of at least 1 member...\n\nDeta + ils: http://www.meetup.com/DevOpsDC/events/47635522/ +CLASS:PUBLIC +CREATED:20120111T120339Z +GEO:38.90;-77.01 +LOCATION:Fathom Creative\, Inc. (1333 14th Street Northwest\, Washington + D.C.\, DC 20005) +URL:http://www.meetup.com/DevOpsDC/events/47635522/ +LAST-MODIFIED:20120522T174406Z +UID:event_qtkfrcyqkbnb@meetup.com +END:VEVENT diff --git a/src/icalendar/tests/events/issue_64_event_with_non_unicode_summary.ics b/src/icalendar/tests/events/issue_64_event_with_non_unicode_summary.ics new file mode 100644 index 0000000..e7cdbf4 --- /dev/null +++ b/src/icalendar/tests/events/issue_64_event_with_non_unicode_summary.ics @@ -0,0 +1,3 @@ +BEGIN:VEVENT +SUMMARY:abcdef +END:VEVENT diff --git a/src/icalendar/tests/events/issue_64_event_with_unicode_summary.ics b/src/icalendar/tests/events/issue_64_event_with_unicode_summary.ics new file mode 100644 index 0000000..bed239a --- /dev/null +++ b/src/icalendar/tests/events/issue_64_event_with_unicode_summary.ics @@ -0,0 +1,3 @@ +BEGIN:VEVENT +SUMMARY:åäö +END:VEVENT diff --git a/src/icalendar/tests/events/issue_70_rrule_causes_attribute_error.ics b/src/icalendar/tests/events/issue_70_rrule_causes_attribute_error.ics new file mode 100644 index 0000000..1e062e4 --- /dev/null +++ b/src/icalendar/tests/events/issue_70_rrule_causes_attribute_error.ics @@ -0,0 +1,12 @@ +BEGIN:VEVENT +CREATED:20081114T072804Z +UID:D449CA84-00A3-4E55-83E1-34B58268853B +DTEND:20070220T180000 +RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20070619T225959 +TRANSP:OPAQUE +SUMMARY:Esb mellon phone conf +DTSTART:20070220T170000 +DTSTAMP:20070221T095412Z +SEQUENCE:0 +END:VEVENT + diff --git a/src/icalendar/tests/test_encoding.py b/src/icalendar/tests/test_encoding.py index d1d70d3..9c00c08 100644 --- a/src/icalendar/tests/test_encoding.py +++ b/src/icalendar/tests/test_encoding.py @@ -1,5 +1,7 @@ import unittest +import pytest + import datetime import icalendar import os @@ -88,3 +90,22 @@ class TestEncoding(unittest.TestCase): + b'\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f\xc3\x84\xc3\x96\xc3\x9c\r\n' + b'END:VEVENT\r\nEND:VCALENDAR\r\n' ) + +@pytest.mark.parametrize('event_name', [ + # Non-unicode characters in summary + 'issue_64_event_with_non_unicode_summary', + # Unicode characters in summary + 'issue_64_event_with_unicode_summary', + # chokes on umlauts in ORGANIZER + 'issue_101_icalendar_chokes_on_umlauts_in_organizer' +]) +def test_events_unicoded(events, event_name): + '''Issue #64 - Event.to_ical() fails for unicode strings + Issue #101 - icalendar is choking on umlauts in ORGANIZER + + https://github.com/collective/icalendar/issues/64 + https://github.com/collective/icalendar/issues/101 + ''' + event = getattr(events, event_name) + assert event.to_ical() == event.raw_ics + diff --git a/src/icalendar/tests/test_fixed_issues.py b/src/icalendar/tests/test_fixed_issues.py index 66be7bf..acab64e 100644 --- a/src/icalendar/tests/test_fixed_issues.py +++ b/src/icalendar/tests/test_fixed_issues.py @@ -1,4 +1,3 @@ -from icalendar.parser_tools import to_unicode import unittest import datetime @@ -15,120 +14,6 @@ except ModuleNotFoundError: class TestIssues(unittest.TestCase): - def test_issue_53(self): - """Issue #53 - Parsing failure on some descriptions? - https://github.com/collective/icalendar/issues/53 - """ - - directory = os.path.dirname(__file__) - ics = open(os.path.join(directory, 'issue_53_parsing_failure.ics'), - 'rb') - cal = icalendar.Calendar.from_ical(ics.read()) - ics.close() - - event = cal.walk('VEVENT')[0] - desc = event.get('DESCRIPTION') - self.assertTrue(b'July 12 at 6:30 PM' in desc.to_ical()) - - timezones = cal.walk('VTIMEZONE') - self.assertEqual(len(timezones), 1) - tz = timezones[0] - self.assertEqual(tz['tzid'].to_ical(), b"America/New_York") - - def test_issue_55(self): - """Issue #55 - Parse error on utc-offset with seconds value - https://github.com/collective/icalendar/issues/55 - """ - ical_str = """BEGIN:VTIMEZONE -TZID:America/Los Angeles -BEGIN:STANDARD -DTSTART:18831118T120702 -RDATE:18831118T120702 -TZNAME:PST -TZOFFSETFROM:-075258 -TZOFFSETTO:-0800 -END:STANDARD -END:VTIMEZONE""" - - tz = icalendar.Timezone.from_ical(ical_str) - self.assertEqual( - tz.to_ical(), - b'BEGIN:VTIMEZONE\r\nTZID:America/Los Angeles\r\n' - b'BEGIN:STANDARD\r\n' - b'DTSTART:18831118T120702\r\nRDATE:18831118T120702\r\nTZNAME:PST' - b'\r\nTZOFFSETFROM:-075258\r\nTZOFFSETTO:-0800\r\n' - b'END:STANDARD\r\n' - b'END:VTIMEZONE\r\n') - - def test_issue_58(self): - """Issue #58 - TZID on UTC DATE-TIMEs - https://github.com/collective/icalendar/issues/58 - """ - - # According to RFC 2445: "The TZID property parameter MUST NOT be - # applied to DATE-TIME or TIME properties whose time values are - # specified in UTC." - - event = icalendar.Event() - dt = pytz.utc.localize(datetime.datetime(2012, 7, 16, 0, 0, 0)) - event.add('dtstart', dt) - self.assertEqual( - event.to_ical(), - b"BEGIN:VEVENT\r\n" - b"DTSTART;VALUE=DATE-TIME:20120716T000000Z\r\n" - b"END:VEVENT\r\n" - ) - - def test_issue_64(self): - """Issue #64 - Event.to_ical() fails for unicode strings - https://github.com/collective/icalendar/issues/64 - """ - - # Non-unicode characters - event = icalendar.Event() - event.add("dtstart", datetime.datetime(2012, 9, 3, 0, 0, 0)) - event.add("summary", "abcdef") - self.assertEqual( - event.to_ical(), - b"BEGIN:VEVENT\r\nSUMMARY:abcdef\r\nDTSTART;VALUE=DATE-TIME:" - b"20120903T000000\r\nEND:VEVENT\r\n" - ) - - # Unicode characters - event = icalendar.Event() - event.add("dtstart", datetime.datetime(2012, 9, 3, 0, 0, 0)) - event.add("summary", "åäö") - self.assertEqual( - event.to_ical(), - b"BEGIN:VEVENT\r\nSUMMARY:\xc3\xa5\xc3\xa4\xc3\xb6\r\n" - b"DTSTART;VALUE=DATE-TIME:20120903T000000\r\nEND:VEVENT\r\n" - ) - - def test_issue_70(self): - """Issue #70 - e.decode("RRULE") causes Attribute Error - https://github.com/collective/icalendar/issues/70 - """ - - ical_str = """BEGIN:VEVENT -CREATED:20081114T072804Z -UID:D449CA84-00A3-4E55-83E1-34B58268853B -DTEND:20070220T180000 -RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20070619T225959 -TRANSP:OPAQUE -SUMMARY:Esb mellon phone conf -DTSTART:20070220T170000 -DTSTAMP:20070221T095412Z -SEQUENCE:0 -END:VEVENT""" - - cal = icalendar.Calendar.from_ical(ical_str) - recur = cal.decoded("RRULE") - self.assertIsInstance(recur, icalendar.vRecur) - self.assertEqual( - recur.to_ical(), - b'FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1' - ) - def test_issue_82(self): """Issue #82 - vBinary __repr__ called rather than to_ical from container types @@ -146,119 +31,6 @@ END:VEVENT""" b"VALUE=BINARY:dGV4dA==\r\nEND:VEVENT\r\n" ) - def test_issue_100(self): - """Issue #100 - Transformed doctests into unittests, Test fixes and - cleanup. - https://github.com/collective/icalendar/pull/100 - """ - - ical_content = "BEGIN:VEVENT\r\nSUMMARY;LANGUAGE=ru:te\r\nEND:VEVENT" - icalendar.Event.from_ical(ical_content).to_ical() - - def test_issue_101(self): - """Issue #101 - icalendar is choking on umlauts in ORGANIZER - - https://github.com/collective/icalendar/issues/101 - """ - ical_str = r"""BEGIN:VCALENDAR -VERSION:2.0 -X-WR-CALNAME:Kalender von acme\, admin -PRODID:-//The Horde Project//Horde_iCalendar Library\, Horde 3.3.5//EN -METHOD:PUBLISH -BEGIN:VEVENT -DTSTART:20130416T100000Z -DTEND:20130416T110000Z -DTSTAMP:20130416T092616Z -UID:20130416112341.10064jz0k4j7uem8@acmenet.de -CREATED:20130416T092341Z -LAST-MODIFIED:20130416T092341Z -SUMMARY:wichtiger termin 1 -ORGANIZER;CN="acme, ädmin":mailto:adm-acme@mydomain.de -LOCATION:im büro -CLASS:PUBLIC -STATUS:CONFIRMED -TRANSP:OPAQUE -END:VEVENT -END:VCALENDAR""" - - cal = icalendar.Calendar.from_ical(ical_str) - org_cn = cal.walk('VEVENT')[0]['ORGANIZER'].params['CN'] - self.assertEqual(org_cn, 'acme, ädmin') - - def test_issue_104__ignore_exceptions(self): - """ - Issue #104 - line parsing error in a VEVENT - (which has ignore_exceptions). Should mark the event broken - but not raise an exception. - https://github.com/collective/icalendar/issues/104 - """ - ical_str = """ -BEGIN:VEVENT -DTSTART:20140401T000000Z -DTEND:20140401T010000Z -DTSTAMP:20140401T000000Z -SUMMARY:Broken Eevnt -CLASS:PUBLIC -STATUS:CONFIRMED -TRANSP:OPAQUE -X -END:VEVENT""" - event = icalendar.Calendar.from_ical(ical_str) - self.assertTrue(isinstance(event, icalendar.Event)) - self.assertTrue(event.is_broken) # REMOVE FOR NEXT MAJOR RELEASE - self.assertEqual( - event.errors, - [(None, "Content line could not be parsed into parts: 'X': Invalid content line")] # noqa - ) - - def test_issue_104__no_ignore_exceptions(self): - """ - Issue #104 - line parsing error in a VCALENDAR - (which doesn't have ignore_exceptions). Should raise an exception. - """ - ical_str = """BEGIN:VCALENDAR -VERSION:2.0 -METHOD:PUBLISH -BEGIN:VEVENT -DTSTART:20140401T000000Z -DTEND:20140401T010000Z -DTSTAMP:20140401T000000Z -SUMMARY:Broken Eevnt -CLASS:PUBLIC -STATUS:CONFIRMED -TRANSP:OPAQUE -END:VEVENT -X -END:VCALENDAR""" - with self.assertRaises(ValueError): - icalendar.Calendar.from_ical(ical_str) - - def test_issue_112(self): - """Issue #112 - No timezone info on EXDATE - https://github.com/collective/icalendar/issues/112 - """ - directory = os.path.dirname(__file__) - path = os.path.join(directory, - 'issue_112_missing_tzinfo_on_exdate.ics') - with open(path, 'rb') as ics: - cal = icalendar.Calendar.from_ical(ics.read()) - event = cal.walk('VEVENT')[0] - - event_ical = to_unicode(event.to_ical()) # Py3 str type doesn't - # support buffer API - # General timezone aware dates in ical string - self.assertTrue('DTSTART;TZID=America/New_York:20130907T120000' - in event_ical) - self.assertTrue('DTEND;TZID=America/New_York:20130907T170000' - in event_ical) - # Specific timezone aware exdates in ical string - self.assertTrue('EXDATE;TZID=America/New_York:20131012T120000' - in event_ical) - self.assertTrue('EXDATE;TZID=America/New_York:20131011T120000' - in event_ical) - - self.assertEqual(event['exdate'][0].dts[0].dt.tzname(), 'EDT') - def test_issue_116(self): """Issue #116/#117 - How to add 'X-APPLE-STRUCTURED-LOCATION' https://github.com/collective/icalendar/issues/116 @@ -289,70 +61,6 @@ END:VCALENDAR""" icalendar.Event.from_ical(event.to_ical()).to_ical() ) - def test_issue_142(self): - """Issue #142 - Multivalued parameters - This is needed for VCard 3.0. - https://github.com/collective/icalendar/pull/142 - """ - from icalendar.parser import Contentline, Parameters - - ctl = Contentline.from_ical("TEL;TYPE=HOME,VOICE:000000000") - - self.assertEqual( - ctl.parts(), - ('TEL', Parameters({'TYPE': ['HOME', 'VOICE']}), '000000000'), - ) - - def test_issue_143(self): - """Issue #143 - Allow dots in property names. - Another vCard related issue. - https://github.com/collective/icalendar/pull/143 - """ - from icalendar.parser import Contentline, Parameters - - ctl = Contentline.from_ical("ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR:;;This is the Adress 08; Some City;;12345;Germany") # nopep8 - self.assertEqual( - ctl.parts(), - ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR', - Parameters(), - ';;This is the Adress 08; Some City;;12345;Germany'), - ) - - ctl2 = Contentline.from_ical("ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL:") # nopep8 - self.assertEqual( - ctl2.parts(), - ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL', - Parameters(), - ''), - ) - - def test_issue_157(self): - """Issue #157 - Recurring rules and trailing semicolons - https://github.com/collective/icalendar/pull/157 - """ - # The trailing semicolon caused a problem - ical_str = """BEGIN:VEVENT -DTSTART:20150325T101010 -RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU; -END:VEVENT""" - - cal = icalendar.Calendar.from_ical(ical_str) - recur = cal.decoded("RRULE") - self.assertIsInstance(recur, icalendar.vRecur) - self.assertEqual( - recur.to_ical(), - b'FREQ=YEARLY;BYDAY=1SU;BYMONTH=11' - ) - - def test_index_error_issue(self): - """Found an issue where from_ical() would raise IndexError for - properties without parent components. - https://github.com/collective/icalendar/pull/179 - """ - - with self.assertRaises(ValueError): - icalendar.Calendar.from_ical('VERSION:2.0') - def test_issue_178(self): """Issue #178 - A component with an unknown/invalid name is represented as one of the known components, the information about the original @@ -395,26 +103,6 @@ END:VEVENT""" b'BEGIN:VEVENT\r\nDTSTART:20150122\r\nUID:12345\r\n' b'END:VEVENT\r\nEND:MYCOMPTOO\r\n') - def test_issue_184(self): - """Issue #184 - Previous changes in code broke already broken - representation of PERIOD values - in a new way""" - - ical_str = ['BEGIN:VEVENT', - 'DTSTAMP:20150219T133000', - 'DTSTART:20150219T133000', - 'UID:1234567', - 'RDATE;VALUE=PERIOD:20150219T133000/PT10H', - 'END:VEVENT'] - - event = icalendar.Event.from_ical('\r\n'.join(ical_str)) - self.assertEqual(event.errors, []) - self.assertEqual(event.to_ical(), - b'BEGIN:VEVENT\r\nDTSTART:20150219T133000\r\n' - b'DTSTAMP:20150219T133000\r\nUID:1234567\r\n' - b'RDATE;VALUE=PERIOD:20150219T133000/PT10H\r\n' - b'END:VEVENT\r\n' - ) - def test_issue_237(self): """Issue #237 - Fail to parse timezone with non-ascii TZID""" @@ -458,27 +146,3 @@ END:VEVENT""" expected_tzname = 'Brasília standard'.encode('ascii', 'replace') self.assertEqual(dtstart.tzinfo.zone, expected_zone) self.assertEqual(dtstart.tzname(), expected_tzname) - - def test_issue_345(self): - """Issue #345 - Why is tools.UIDGenerator a class (that must be instantiated) instead of a module? """ - uid1 = icalendar.tools.UIDGenerator.uid() - uid2 = icalendar.tools.UIDGenerator.uid('test.test') - uid3 = icalendar.tools.UIDGenerator.uid(unique='123') - uid4 = icalendar.tools.UIDGenerator.uid('test.test', '123') - - self.assertEqual(uid1.split('@')[1], 'example.com') - self.assertEqual(uid2.split('@')[1], 'test.test') - self.assertEqual(uid3.split('-')[1], '123@example.com') - self.assertEqual(uid4.split('-')[1], '123@test.test') - -@pytest.mark.parametrize("zone", [ - pytz.utc, - zoneinfo.ZoneInfo('UTC'), - pytz.timezone('UTC'), - tz.UTC, - tz.gettz('UTC')]) -def test_issue_335_identify_UTC(zone): - myevent = icalendar.Event() - dt = datetime.datetime(2021, 11, 17, 15, 9, 15) - myevent.add('dtstart', dt.astimezone(zone)) - assert 'DTSTART;VALUE=DATE-TIME:20211117T150915Z' in myevent.to_ical().decode('ASCII') diff --git a/src/icalendar/tests/test_issue_104_mark_events_broken.py b/src/icalendar/tests/test_issue_104_mark_events_broken.py new file mode 100644 index 0000000..cbeace0 --- /dev/null +++ b/src/icalendar/tests/test_issue_104_mark_events_broken.py @@ -0,0 +1,20 @@ +import pytest + +from icalendar import Event, Calendar + +def test_ignore_exceptions_on_broken_events_issue_104(events): + ''' Issue #104 - line parsing error in a VEVENT + (which has ignore_exceptions). Should mark the event broken + but not raise an exception. + + https://github.com/collective/icalendar/issues/104 + ''' + assert events.issue_104_mark_events_broken.is_broken # TODO: REMOVE FOR NEXT MAJOR RELEASE + assert events.issue_104_mark_events_broken.errors == [(None, "Content line could not be parsed into parts: 'X': Invalid content line")] + +def test_dont_ignore_exceptions_on_broken_calendars_issue_104(calendars): + '''Issue #104 - line parsing error in a VCALENDAR + (which doesn't have ignore_exceptions). Should raise an exception. + ''' + with pytest.raises(ValueError): + calendars.issue_104_broken_calendar diff --git a/src/icalendar/tests/test_parsing.py b/src/icalendar/tests/test_parsing.py new file mode 100644 index 0000000..a9d6372 --- /dev/null +++ b/src/icalendar/tests/test_parsing.py @@ -0,0 +1,121 @@ +'''Tests checking that parsing works''' +import pytest +from icalendar import Calendar, vRecur, vBinary, Event +from datetime import datetime +from icalendar.parser import Contentline, Parameters + + +@pytest.mark.parametrize('raw_content_line, expected_output', [ + # Issue #142 - Multivalued parameters. This is needed for VCard 3.0. + # see https://github.com/collective/icalendar/pull/142 + ('TEL;TYPE=HOME,VOICE:000000000', ('TEL', Parameters({'TYPE': ['HOME', 'VOICE']}), '000000000')), + # Issue #143 - Allow dots in property names. Another vCard related issue. + # see https://github.com/collective/icalendar/pull/143 + ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR:;;This is the Adress 08; Some City;;12345;Germany', \ + ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.ADR', \ + Parameters(),\ + ';;This is the Adress 08; Some City;;12345;Germany')), + ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL:', \ + ('ITEMADRNULLTHISISTHEADRESS08158SOMECITY12345.X-ABLABEL', \ + Parameters(), \ + '')) +]) +def test_content_lines_parsed_properly(raw_content_line, expected_output): + assert Contentline.from_ical(raw_content_line).parts() == expected_output + + +@pytest.mark.parametrize('timezone_info', [ + # General timezone aware dates in ical string + (b'DTSTART;TZID=America/New_York:20130907T120000'), + (b'DTEND;TZID=America/New_York:20130907T170000'), + # Specific timezone aware exdates in ical string + (b'EXDATE;TZID=America/New_York:20131012T120000'), + (b'EXDATE;TZID=America/New_York:20131011T120000') +]) +def test_timezone_info_present_in_ical_issue_112(events, timezone_info): + '''Issue #112 - No timezone info on EXDATE + + https://github.com/collective/icalendar/issues/112 + ''' + timezone_info in events.issue_112_missing_tzinfo_on_exdate.to_ical() + +def test_timezone_name_parsed_issue_112(events): + '''Issue #112 - No timezone info on EXDATE + + https://github.com/collective/icalendar/issues/112 + ''' + assert events.issue_112_missing_tzinfo_on_exdate['exdate'][0].dts[0].dt.tzname() == 'EDT' + +def test_issue_157_removes_trailing_semicolon(events): + '''Issue #157 - Recurring rules and trailing semicolons + + https://github.com/collective/icalendar/pull/157 + ''' + recur = events.issue_157_removes_trailing_semicolon.decoded("RRULE") + assert isinstance(recur, vRecur) + assert recur.to_ical() == b'FREQ=YEARLY;BYDAY=1SU;BYMONTH=11' + +@pytest.mark.parametrize('event_name', [ + # https://github.com/collective/icalendar/pull/100 + ('issue_100_transformed_doctests_into_unittests'), + ('issue_184_broken_representation_of_period'), +]) +def test_event_to_ical_is_inverse_of_from_ical(events, event_name): + """Make sure that an event's ICS is equal to the ICS it was made from.""" + event = events[event_name] + assert event.to_ical() == event.raw_ics + +def test_decode_rrule_attribute_error_issue_70(events): + # Issue #70 - e.decode("RRULE") causes Attribute Error + # see https://github.com/collective/icalendar/issues/70 + recur = events.issue_70_rrule_causes_attribute_error.decoded('RRULE') + assert isinstance(recur, vRecur) + assert recur.to_ical() == b'FREQ=WEEKLY;UNTIL=20070619T225959;INTERVAL=1' + +def test_description_parsed_properly_issue_53(events): + '''Issue #53 - Parsing failure on some descriptions? + + https://github.com/collective/icalendar/issues/53 + ''' + assert b'July 12 at 6:30 PM' in events.issue_53_description_parsed_properly['DESCRIPTION'].to_ical() + +def test_raises_value_error_for_properties_without_parent_pull_179(): + '''Found an issue where from_ical() would raise IndexError for + properties without parent components. + + https://github.com/collective/icalendar/pull/179 + ''' + with pytest.raises(ValueError): + Calendar.from_ical('VERSION:2.0') + +def test_tzid_parsed_properly_issue_53(timezones): + '''Issue #53 - Parsing failure on some descriptions? + + https://github.com/collective/icalendar/issues/53 + ''' + assert timezones.issue_53_tzid_parsed_properly['tzid'].to_ical() == b'America/New_York' + +def test_timezones_to_ical_is_inverse_of_from_ical(timezones): + '''Issue #55 - Parse error on utc-offset with seconds value + see https://github.com/collective/icalendar/issues/55''' + timezone = timezones['issue_55_parse_error_on_utc_offset_with_seconds'] + assert timezone.to_ical() == timezone.raw_ics + +@pytest.mark.parametrize('date, expected_output', [ + (datetime(2012, 7, 16, 0, 0, 0), b'DTSTART;VALUE=DATE-TIME:20120716T000000Z'), + (datetime(2021, 11, 17, 15, 9, 15), b'DTSTART;VALUE=DATE-TIME:20211117T150915Z') +]) +def test_no_tzid_when_utc(utc, date, expected_output): + '''Issue #58 - TZID on UTC DATE-TIMEs + Issue #335 - UTC timezone identification is broken + + https://github.com/collective/icalendar/issues/58 + https://github.com/collective/icalendar/issues/335 + ''' + # According to RFC 2445: "The TZID property parameter MUST NOT be + # applied to DATE-TIME or TIME properties whose time values are + # specified in UTC. + date = date.replace(tzinfo=utc) + event = Event() + event.add('dtstart', date) + assert expected_output in event.to_ical() diff --git a/src/icalendar/tests/test_unit_tools.py b/src/icalendar/tests/test_unit_tools.py index 6dd4976..f88e605 100644 --- a/src/icalendar/tests/test_unit_tools.py +++ b/src/icalendar/tests/test_unit_tools.py @@ -1,6 +1,7 @@ +import pytest import unittest -from icalendar.tools import UIDGenerator +from icalendar.tools import UIDGenerator class TestTools(unittest.TestCase): @@ -26,3 +27,28 @@ class TestTools(unittest.TestCase): txt = uid.to_ical() self.assertTrue(len(txt) == length) self.assertTrue(b'-/path/to/content@Example.ORG' in txt) + + +@pytest.mark.parametrize('split,expected,args,kw', [ + # default argument host_name + ("@", "example.com", (), {},), + ("@", "example.com", ("example.com",), {}), + ("@", "example.com", (), {"host_name":"example.com"}), + # replaced host_name + ("@", "test.test", ("test.test",), {}), + ("@", "test.test", (), {"host_name":"test.test"}), + # replace unique + ("-", "123@example.com", (), {"unique": "123"},), + ("-", "abc@example.com", (), {"unique": "abc"},), + # replace host_name and unique + ("-", "1234@test.icalendar", (), {"unique": "1234", "host_name":"test.icalendar"},), + ("-", "abc@test.example.com", ("test.example.com", "abc"), {},), + +]) +def test_uid_generator_issue_345(args, kw, split, expected): + '''Issue #345 - Why is tools.UIDGenerator a class (that must be instantiated) instead of a module? + + see https://github.com/collective/icalendar/issues/345 + ''' + uid = UIDGenerator.uid(*args, **kw) + assert uid.split(split)[1] == expected diff --git a/src/icalendar/tests/timezones/issue_53_tzid_parsed_properly.ics b/src/icalendar/tests/timezones/issue_53_tzid_parsed_properly.ics new file mode 100644 index 0000000..17b9a72 --- /dev/null +++ b/src/icalendar/tests/timezones/issue_53_tzid_parsed_properly.ics @@ -0,0 +1,19 @@ +BEGIN:VTIMEZONE +TZID:America/New_York +TZURL:http://tzurl.org/zoneinfo-outlook/America/New_York +X-LIC-LOCATION:America/New_York +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE diff --git a/src/icalendar/tests/timezones/issue_55_parse_error_on_utc_offset_with_seconds.ics b/src/icalendar/tests/timezones/issue_55_parse_error_on_utc_offset_with_seconds.ics new file mode 100644 index 0000000..09d639c --- /dev/null +++ b/src/icalendar/tests/timezones/issue_55_parse_error_on_utc_offset_with_seconds.ics @@ -0,0 +1,10 @@ +BEGIN:VTIMEZONE +TZID:America/Los Angeles +BEGIN:STANDARD +DTSTART:18831118T120702 +RDATE:18831118T120702 +TZNAME:PST +TZOFFSETFROM:-075258 +TZOFFSETTO:-0800 +END:STANDARD +END:VTIMEZONE