From a59ce3c7b790b203933d896bc94c5b9da6183f75 Mon Sep 17 00:00:00 2001 From: Nicco Kunzmann Date: Mon, 18 Mar 2024 15:28:03 +0000 Subject: [PATCH] Refactor calendar tests for use with pytest --- .../tests/calendars/parsing_error.ics | 21 + .../calendars/parsing_error_in_UTC_offset.ics | 11 + src/icalendar/tests/conftest.py | 43 ++ src/icalendar/tests/test_unit_cal.py | 542 +++++++++--------- 4 files changed, 340 insertions(+), 277 deletions(-) create mode 100644 src/icalendar/tests/calendars/parsing_error.ics create mode 100644 src/icalendar/tests/calendars/parsing_error_in_UTC_offset.ics diff --git a/src/icalendar/tests/calendars/parsing_error.ics b/src/icalendar/tests/calendars/parsing_error.ics new file mode 100644 index 0000000..08d03b6 --- /dev/null +++ b/src/icalendar/tests/calendars/parsing_error.ics @@ -0,0 +1,21 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +BEGIN:VEVENT +DESCRIPTION:Perfectly OK event +DTSTART;VALUE=DATE:20080303 +DTEND;VALUE=DATE:20080304 +RRULE:FREQ=DAILY;UNTIL=20080323T235959Z +EXDATE;VALUE=DATE:20080311 +END:VEVENT +BEGIN:VEVENT +DESCRIPTION:Wrong event +DTSTART;VALUE=DATE:20080303 +DTEND;VALUE=DATE:20080304 +RRULE:FREQ=DAILY;UNTIL=20080323T235959Z +EXDATE;VALUE=DATE:20080311 +EXDATE;VALUE=DATE: +END:VEVENT +END:VCALENDAR diff --git a/src/icalendar/tests/calendars/parsing_error_in_UTC_offset.ics b/src/icalendar/tests/calendars/parsing_error_in_UTC_offset.ics new file mode 100644 index 0000000..46eef74 --- /dev/null +++ b/src/icalendar/tests/calendars/parsing_error_in_UTC_offset.ics @@ -0,0 +1,11 @@ +BEGIN:VCALENDAR +BEGIN:VTIMEZONE +TZID:Europe/Prague +BEGIN:STANDARD +DTSTART:18500101T000000 +TZNAME:PMT +TZOFFSETFROM:+5744 +TZOFFSETTO:+5744 +END:STANDARD +END:VTIMEZONE +END:VCALENDAR diff --git a/src/icalendar/tests/conftest.py b/src/icalendar/tests/conftest.py index 6583d23..95a24b2 100644 --- a/src/icalendar/tests/conftest.py +++ b/src/icalendar/tests/conftest.py @@ -8,6 +8,8 @@ try: import zoneinfo except ModuleNotFoundError: from backports import zoneinfo +from icalendar.cal import Component, Calendar, Event, ComponentFactory + class DataSource: '''A collection of parsed ICS elements (e.g calendars, timezones, events)''' @@ -118,3 +120,44 @@ def vUTCOffset_ignore_exceptions(): icalendar.vUTCOffset.ignore_exceptions = True yield icalendar.vUTCOffset.ignore_exceptions = False + + +@pytest.fixture() +def event_component(): + """Return an event component.""" + c = Component() + c.name = 'VEVENT' + return c + + +@pytest.fixture() +def c(): + """Return an empty component.""" + c = Component() + return c +comp = c + +@pytest.fixture() +def calendar_component(): + """Return an empty component.""" + c = Component() + c.name = 'VCALENDAR' + return c + + +@pytest.fixture() +def filled_event_component(c, calendar_component): + """Return an event with some values and add it to calendar_component.""" + e = Component(summary='A brief history of time') + e.name = 'VEVENT' + e.add('dtend', '20000102T000000', encode=0) + e.add('dtstart', '20000101T000000', encode=0) + calendar_component.add_component(e) + return e + + +@pytest.fixture() +def calendar_with_resources(): + c = Calendar() + c['resources'] = 'Chair, Table, "Room: 42"' + return c diff --git a/src/icalendar/tests/test_unit_cal.py b/src/icalendar/tests/test_unit_cal.py index c2d5c69..1a05244 100644 --- a/src/icalendar/tests/test_unit_cal.py +++ b/src/icalendar/tests/test_unit_cal.py @@ -12,343 +12,331 @@ from icalendar.cal import Component, Calendar, Event, ComponentFactory from icalendar import prop, cal +def test_cal_Component(calendar_component): + """A component is like a dictionary with extra methods and attributes.""" + assert calendar_component + assert calendar_component.is_empty() -class TestCalComponent(unittest.TestCase): - def test_cal_Component(self): +def test_nonempty_calendar_component(calendar_component): + """Every key defines a property.A property can consist of either a + single item. This can be set with a single value... + """ + calendar_component['prodid'] = '-//max m//icalendar.mxm.dk/' + assert not calendar_component.is_empty() + assert calendar_component == Calendar({'PRODID': '-//max m//icalendar.mxm.dk/'}) - # A component is like a dictionary with extra methods and attributes. - c = Component() - c.name = 'VCALENDAR' + # or with a list + calendar_component['ATTENDEE'] = ['Max M', 'Rasmussen'] + assert calendar_component == Calendar( + {'ATTENDEE': ['Max M', 'Rasmussen'], + 'PRODID': '-//max m//icalendar.mxm.dk/'}) - self.assertTrue(c) - self.assertTrue(c.is_empty()) - # Every key defines a property.A property can consist of either a - # single item. This can be set with a single value... - c['prodid'] = '-//max m//icalendar.mxm.dk/' - self.assertFalse(c.is_empty()) +def test_add_multiple_values(event_component): + """add multiple values to a property. - self.assertEqual( - c, - Calendar({'PRODID': '-//max m//icalendar.mxm.dk/'}) - ) + If you use the add method you don't have to considder if a value is + a list or not. + """ + # add multiple values at once + event_component.add('attendee', + ['test@test.com', 'test2@test.com']) - # or with a list - c['ATTENDEE'] = ['Max M', 'Rasmussen'] - self.assertEqual( - c, - Calendar({'ATTENDEE': ['Max M', 'Rasmussen'], - 'PRODID': '-//max m//icalendar.mxm.dk/'}) - ) + # or add one per line + event_component.add('attendee', 'maxm@mxm.dk') + event_component.add('attendee', 'test@example.dk') - # ## ADD MULTIPLE VALUES TO A PROPERTY + # add again multiple values at once to very concatenaton of lists + event_component.add('attendee', + ['test3@test.com', 'test4@test.com']) - # if you use the add method you don't have to considder if a value is - # a list or not. - c = Component() - c.name = 'VEVENT' + assert event_component == Event({'ATTENDEE': [ + prop.vCalAddress('test@test.com'), + prop.vCalAddress('test2@test.com'), + prop.vCalAddress('maxm@mxm.dk'), + prop.vCalAddress('test@example.dk'), + prop.vCalAddress('test3@test.com'), + prop.vCalAddress('test4@test.com') + ]}) - # add multiple values at once - c.add('attendee', - ['test@test.com', 'test2@test.com']) - # or add one per line - c.add('attendee', 'maxm@mxm.dk') - c.add('attendee', 'test@example.dk') +def test_get_content_directly(c): + """You can get the values back directly ...""" + c.add('prodid', '-//my product//') + assert c['prodid'] == prop.vText('-//my product//') + # ... or decoded to a python type + assert c.decoded('prodid') == b'-//my product//' - # add again multiple values at once to very concatenaton of lists - c.add('attendee', - ['test3@test.com', 'test4@test.com']) - self.assertEqual( - c, - Event({'ATTENDEE': [ - prop.vCalAddress('test@test.com'), - prop.vCalAddress('test2@test.com'), - prop.vCalAddress('maxm@mxm.dk'), - prop.vCalAddress('test@example.dk'), - prop.vCalAddress('test3@test.com'), - prop.vCalAddress('test4@test.com') - ]}) - ) +def test_get_default_value(c): + """With default values for non existing properties""" + assert c.decoded('version', 'No Version') == 'No Version' - ### - # You can get the values back directly ... - c.add('prodid', '-//my product//') - self.assertEqual(c['prodid'], prop.vText('-//my product//')) +def test_default_list_example(c): + c.add('rdate', [datetime(2013, 3, 28), datetime(2013, 3, 27)]) + assert isinstance(c.decoded('rdate'), prop.vDDDLists) - # ... or decoded to a python type - self.assertEqual(c.decoded('prodid'), b'-//my product//') - # With default values for non existing properties - self.assertEqual(c.decoded('version', 'No Version'), 'No Version') +def test_render_component(calendar_component): + """The component can render itself in the RFC 2445 format.""" + calendar_component.add('attendee', 'Max M') + assert calendar_component.to_ical() == b'BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n' - c.add('rdate', [datetime(2013, 3, 28), datetime(2013, 3, 27)]) - self.assertTrue(isinstance(c.decoded('rdate'), prop.vDDDLists)) - # The component can render itself in the RFC 2445 format. - c = Component() - c.name = 'VCALENDAR' - c.add('attendee', 'Max M') - self.assertEqual( - c.to_ical(), - b'BEGIN:VCALENDAR\r\nATTENDEE:Max M\r\nEND:VCALENDAR\r\n' - ) +def test_nested_component_event_ics(filled_event_component): + """Check the ical string of the event component.""" + assert filled_event_component.to_ical() == ( + b'BEGIN:VEVENT\r\nDTEND:20000102T000000\r\n' + + b'DTSTART:20000101T000000\r\nSUMMARY:A brief history of time\r' + + b'\nEND:VEVENT\r\n' + ) - # Components can be nested, so You can add a subcomponent. Eg a calendar - # holds events. - e = Component(summary='A brief history of time') - e.name = 'VEVENT' - e.add('dtend', '20000102T000000', encode=0) - e.add('dtstart', '20000101T000000', encode=0) - self.assertEqual( - e.to_ical(), - b'BEGIN:VEVENT\r\nDTEND:20000102T000000\r\n' - + b'DTSTART:20000101T000000\r\nSUMMARY:A brief history of time\r' - + b'\nEND:VEVENT\r\n' - ) - c.add_component(e) - self.assertEqual( - c.subcomponents, - [Event({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000', - 'SUMMARY': 'A brief history of time'})] - ) +def test_nested_components(calendar_component, filled_event_component): + """Components can be nested, so You can add a subcomponent. Eg a calendar + holds events.""" + self.assertEqual( + calendar_component.subcomponents, + [Event({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000', + 'SUMMARY': 'A brief history of time'})] + ) - # We can walk over nested componentes with the walk method. - self.assertEqual([i.name for i in c.walk()], ['VCALENDAR', 'VEVENT']) - # We can also just walk over specific component types, by filtering - # them on their name. - self.assertEqual([i.name for i in c.walk('VEVENT')], ['VEVENT']) +def test_walk_filled_calendar_component(calendar_component, filled_event_component): + """We can walk over nested componentes with the walk method.""" + assert [i.name for i in calendar_component.walk()] == ['VCALENDAR', 'VEVENT'] - self.assertEqual( - [i['dtstart'] for i in c.walk('VEVENT')], - ['20000101T000000'] - ) - # We can enumerate property items recursively with the property_items - # method. - self.assertEqual( - c.property_items(), - [('BEGIN', b'VCALENDAR'), ('ATTENDEE', prop.vCalAddress('Max M')), - ('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'), - ('DTSTART', '20000101T000000'), - ('SUMMARY', 'A brief history of time'), ('END', b'VEVENT'), - ('END', b'VCALENDAR')] - ) +def test_filter_walk(calendar_component, filled_event_component): + """We can also just walk over specific component types, by filtering + them on their name.""" + assert [i.name for i in calendar_component.walk('VEVENT')] == ['VEVENT'] + assert [i['dtstart'] for i in calendar_component.walk('VEVENT')] == ['20000101T000000'] - # We can also enumerate property items just under the component. - self.assertEqual( - c.property_items(recursive=False), - [('BEGIN', b'VCALENDAR'), - ('ATTENDEE', prop.vCalAddress('Max M')), - ('END', b'VCALENDAR')] - ) - sc = c.subcomponents[0] - self.assertEqual( - sc.property_items(recursive=False), - [('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'), - ('DTSTART', '20000101T000000'), - ('SUMMARY', 'A brief history of time'), ('END', b'VEVENT')] - ) +def test_recursive_property_items(calendar_component, filled_event_component): + """We can enumerate property items recursively with the property_items + method.""" + calendar_component.add('attendee', 'Max M') + assert calendar_component.property_items() == [ + ('BEGIN', b'VCALENDAR'), ('ATTENDEE', prop.vCalAddress('Max M')), + ('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'), + ('DTSTART', '20000101T000000'), + ('SUMMARY', 'A brief history of time'), ('END', b'VEVENT'), + ('END', b'VCALENDAR')] - # Text fields which span multiple mulitple lines require proper - # indenting - c = Calendar() - c['description'] = 'Paragraph one\n\nParagraph two' - self.assertEqual( - c.to_ical(), - b'BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two' - + b'\r\nEND:VCALENDAR\r\n' - ) - # INLINE properties have their values on one property line. Note the - # double quoting of the value with a colon in it. - c = Calendar() - c['resources'] = 'Chair, Table, "Room: 42"' - self.assertEqual( - c, - Calendar({'RESOURCES': 'Chair, Table, "Room: 42"'}) - ) +def test_flat_property_items(calendar_component, filled_event_component): + """We can also enumerate property items just under the component.""" + assert calendar_component.property_items(recursive=False) == [ + ('BEGIN', b'VCALENDAR'), + ('ATTENDEE', prop.vCalAddress('Max M')), + ('END', b'VCALENDAR')] - self.assertEqual( - c.to_ical(), - b'BEGIN:VCALENDAR\r\nRESOURCES:Chair\\, Table\\, "Room: 42"\r\n' - + b'END:VCALENDAR\r\n' - ) - # The inline values must be handled by the get_inline() and - # set_inline() methods. - self.assertEqual( - c.get_inline('resources', decode=0), - ['Chair', 'Table', 'Room: 42'] - ) +def test_flat_property_items(filled_event_component): + """Flat enumeration on the event.""" + assert filled_event_component.property_items(recursive=False) == [ + ('BEGIN', b'VEVENT'), ('DTEND', '20000102T000000'), + ('DTSTART', '20000101T000000'), + ('SUMMARY', 'A brief history of time'), ('END', b'VEVENT')] - # These can also be decoded - self.assertEqual( - c.get_inline('resources', decode=1), - [b'Chair', b'Table', b'Room: 42'] - ) - # You can set them directly ... - c.set_inline('resources', ['A', 'List', 'of', 'some, recources'], - encode=1) - self.assertEqual(c['resources'], 'A,List,of,"some, recources"') +def test_indent(): + """Text fields which span multiple mulitple lines require proper indenting""" + c = Calendar() + c['description'] = 'Paragraph one\n\nParagraph two' + assert c.to_ical() == ( + b'BEGIN:VCALENDAR\r\nDESCRIPTION:Paragraph one\\n\\nParagraph two' + + b'\r\nEND:VCALENDAR\r\n' + ) - # ... and back again - self.assertEqual( - c.get_inline('resources', decode=0), - ['A', 'List', 'of', 'some, recources'] - ) - c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,'\ - + '19970308T230000Z/19970309T000000Z' - self.assertEqual( - c.get_inline('freebusy', decode=0), - ['19970308T160000Z/PT3H', '19970308T200000Z/PT1H', - '19970308T230000Z/19970309T000000Z'] - ) +def test_INLINE_properties(calendar_with_resources): + """INLINE properties have their values on one property line. Note the + double quoting of the value with a colon in it. + """ + assert calendar_with_resources == Calendar({'RESOURCES': 'Chair, Table, "Room: 42"'}) + assert calendar_with_resources.to_ical() == ( + b'BEGIN:VCALENDAR\r\nRESOURCES:Chair\\, Table\\, "Room: 42"\r\n' + + b'END:VCALENDAR\r\n' + ) - freebusy = c.get_inline('freebusy', decode=1) - self.assertTrue(isinstance(freebusy[0][0], datetime)) - self.assertTrue(isinstance(freebusy[0][1], timedelta)) - def test_cal_Component_add(self): - # Test the for timezone correctness: dtstart should preserve it's - # timezone, created, dtstamp and last-modified must be in UTC. - Component = icalendar.cal.Component - comp = Component() - vienna = pytz.timezone("Europe/Vienna") - comp.add('dtstart', vienna.localize(datetime(2010, 10, 10, 10, 0, 0))) - comp.add('created', datetime(2010, 10, 10, 12, 0, 0)) - comp.add('dtstamp', vienna.localize(datetime(2010, 10, 10, 14, 0, 0))) - comp.add('last-modified', pytz.utc.localize( - datetime(2010, 10, 10, 16, 0, 0))) +def test_get_inline(calendar_with_resources): + """The inline values must be handled by the get_inline() and + set_inline() methods. + """ + assert calendar_with_resources.get_inline('resources', decode=0) == \ + ['Chair', 'Table', 'Room: 42'] - lines = comp.to_ical().splitlines() - self.assertTrue( - b"DTSTART;TZID=Europe/Vienna:20101010T100000" - in lines) - self.assertTrue(b"CREATED:20101010T120000Z" in lines) - self.assertTrue(b"DTSTAMP:20101010T120000Z" in lines) - self.assertTrue( - b"LAST-MODIFIED:20101010T160000Z" in lines - ) - def test_cal_Component_add_no_reencode(self): - """Already encoded values should not be re-encoded. - """ - comp = cal.Component() - comp.add('ATTACH', 'me') +def test_get_inline_decoded(calendar_with_resources): + """These can also be decoded""" + assert calendar_with_resources.get_inline('resources', decode=1) == \ + [b'Chair', b'Table', b'Room: 42'] - comp.add('ATTACH', 'you', encode=False) - binary = prop.vBinary('us') - comp.add('ATTACH', binary) - self.assertEqual(comp['ATTACH'], ['me', 'you', binary]) +def test_set_inline(calendar_with_resources): + """You can set them directly ...""" + calendar_with_resources.set_inline('resources', ['A', 'List', 'of', 'some, recources'], + encode=1) + assert calendar_with_resources['resources'] == 'A,List,of,"some, recources"' + assert calendar_with_resources.get_inline('resources', decode=0) == ['A', 'List', 'of', 'some, recources'] - def test_cal_Component_add_property_parameter(self): - # Test the for timezone correctness: dtstart should preserve it's - # timezone, crated, dtstamp and last-modified must be in UTC. - comp = Component() - comp.add('X-TEST-PROP', 'tryout.', - parameters={'prop1': 'val1', 'prop2': 'val2'}) - lines = comp.to_ical().splitlines() - self.assertTrue(b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines) - def test_cal_Component_from_ical(self): - # Check for proper handling of TZID parameter of datetime properties - for component_name, property_name in ( - ('VEVENT', 'DTSTART'), - ('VEVENT', 'DTEND'), - ('VEVENT', 'RECURRENCE-ID'), - ('VTODO', 'DUE') - ): - component_str = 'BEGIN:' + component_name + '\n' - component_str += property_name + ';TZID=America/Denver:' - component_str += '20120404T073000\nEND:' + component_name - component = Component.from_ical(component_str) - self.assertEqual(str(component[property_name].dt.tzinfo.zone), - "America/Denver") +def test_inline_free_busy_inline(c): + c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,'\ + + '19970308T230000Z/19970309T000000Z' + assert c.get_inline('freebusy', decode=0) == \ + ['19970308T160000Z/PT3H', '19970308T200000Z/PT1H', + '19970308T230000Z/19970309T000000Z'] - component_str = 'BEGIN:' + component_name + '\n' - component_str += property_name + ':' - component_str += '20120404T073000\nEND:' + component_name - component = Component.from_ical(component_str) - self.assertEqual(component[property_name].dt.tzinfo, - None) + freebusy = c.get_inline('freebusy', decode=1) + assert isinstance(freebusy[0][0], datetime) + assert isinstance(freebusy[0][1], timedelta) - def test_cal_Component_to_ical_property_order(self): - component_str = [b'BEGIN:VEVENT', - b'DTSTART:19970714T170000Z', - b'DTEND:19970715T035959Z', - b'SUMMARY:Bastille Day Party', - b'END:VEVENT'] - component = Component.from_ical(b'\r\n'.join(component_str)) - sorted_str = component.to_ical().splitlines() - assert sorted_str != component_str - assert set(sorted_str) == set(component_str) +def test_cal_Component_add(comp): + """Test the for timezone correctness: dtstart should preserve it's + timezone, created, dtstamp and last-modified must be in UTC. + """ + vienna = pytz.timezone("Europe/Vienna") + comp.add('dtstart', vienna.localize(datetime(2010, 10, 10, 10, 0, 0))) + comp.add('created', datetime(2010, 10, 10, 12, 0, 0)) + comp.add('dtstamp', vienna.localize(datetime(2010, 10, 10, 14, 0, 0))) + comp.add('last-modified', pytz.utc.localize( + datetime(2010, 10, 10, 16, 0, 0))) - preserved_str = component.to_ical(sorted=False).splitlines() - assert preserved_str == component_str + lines = comp.to_ical().splitlines() + assert b"DTSTART;TZID=Europe/Vienna:20101010T100000" in lines + assert b"CREATED:20101010T120000Z" in lines + assert b"DTSTAMP:20101010T120000Z" in lines + assert b"LAST-MODIFIED:20101010T160000Z" in lines - def test_cal_Component_to_ical_parameter_order(self): - component_str = [b'BEGIN:VEVENT', - b'X-FOOBAR;C=one;A=two;B=three:helloworld.', - b'END:VEVENT'] - component = Component.from_ical(b'\r\n'.join(component_str)) - sorted_str = component.to_ical().splitlines() - assert sorted_str[0] == component_str[0] - assert sorted_str[1] == b'X-FOOBAR;A=two;B=three;C=one:helloworld.' - assert sorted_str[2] == component_str[2] +def test_cal_Component_add_no_reencode(comp): + """Already encoded values should not be re-encoded. + """ + comp.add('ATTACH', 'me') + comp.add('ATTACH', 'you', encode=False) + binary = prop.vBinary('us') + comp.add('ATTACH', binary) - preserved_str = component.to_ical(sorted=False).splitlines() - assert preserved_str == component_str + assert comp['ATTACH'] == ['me', 'you', binary] - def test_repr(self): - """Test correct class representation. - """ - component = Component() + +def test_cal_Component_add_property_parameter(comp): + """Test the for timezone correctness: dtstart should preserve it's + timezone, crated, dtstamp and last-modified must be in UTC. + """ + comp.add('X-TEST-PROP', 'tryout.', + parameters={'prop1': 'val1', 'prop2': 'val2'}) + lines = comp.to_ical().splitlines() + assert b"X-TEST-PROP;PROP1=val1;PROP2=val2:tryout." in lines + + +comp_prop = pytest.mark.parametrize( + "component_name, property_name", + [ + ('VEVENT', 'DTSTART'), + ('VEVENT', 'DTEND'), + ('VEVENT', 'RECURRENCE-ID'), + ('VTODO', 'DUE') + ] +) + + +@comp_prop +def test_cal_Component_from_ical(component_name, property_name): + """Check for proper handling of TZID parameter of datetime properties""" + component_str = 'BEGIN:' + component_name + '\n' + component_str += property_name + ';TZID=America/Denver:' + component_str += '20120404T073000\nEND:' + component_name + component = Component.from_ical(component_str) + assert str(component[property_name].dt.tzinfo.zone) == "America/Denver" + + +@comp_prop +def test_cal_Component_from_ical_2(component_name, property_name): + """Check for proper handling of TZID parameter of datetime properties""" + component_str = 'BEGIN:' + component_name + '\n' + component_str += property_name + ':' + component_str += '20120404T073000\nEND:' + component_name + component = Component.from_ical(component_str) + assert component[property_name].dt.tzinfo == None + + +def test_cal_Component_to_ical_property_order(): + component_str = [b'BEGIN:VEVENT', + b'DTSTART:19970714T170000Z', + b'DTEND:19970715T035959Z', + b'SUMMARY:Bastille Day Party', + b'END:VEVENT'] + component = Component.from_ical(b'\r\n'.join(component_str)) + + sorted_str = component.to_ical().splitlines() + assert sorted_str != component_str + assert set(sorted_str) == set(component_str) + + preserved_str = component.to_ical(sorted=False).splitlines() + assert preserved_str == component_str + + +def test_cal_Component_to_ical_parameter_order(): + component_str = [b'BEGIN:VEVENT', + b'X-FOOBAR;C=one;A=two;B=three:helloworld.', + b'END:VEVENT'] + component = Component.from_ical(b'\r\n'.join(component_str)) + + sorted_str = component.to_ical().splitlines() + assert sorted_str[0] == component_str[0] + assert sorted_str[1] == b'X-FOOBAR;A=two;B=three;C=one:helloworld.' + assert sorted_str[2] == component_str[2] + + preserved_str = component.to_ical(sorted=False).splitlines() + assert preserved_str == component_str + + +@pytest.fixture() +def repr_example(c): + class ReprExample: + component = c component['key1'] = 'value1' - - self.assertTrue( - re.match(r"Component\({u?'KEY1': u?'value1'}\)", str(component)) - ) - calendar = Calendar() calendar['key1'] = 'value1' - - self.assertTrue( - re.match(r"VCALENDAR\({u?'KEY1': u?'value1'}\)", str(calendar)) - ) - event = Event() event['key1'] = 'value1' - - self.assertTrue( - re.match(r"VEVENT\({u?'KEY1': u?'value1'}\)", str(event)) - ) - - # Representation of nested Components nested = Component(key1='VALUE1') nested.add_component(component) - calendar.add_component(event) nested.add_component(calendar) + return ReprExample - self.assertTrue( - re.match( - r"Component\({u?'KEY1': u?'VALUE1'}, " - r"Component\({u?'KEY1': u?'value1'}\), " - r"VCALENDAR\({u?'KEY1': u?'value1'}, " - r"VEVENT\({u?'KEY1': u?'value1'}\)\)\)", - str(nested) - ) +def test_repr_component(repr_example): + """Test correct class representation. + """ + assert re.match(r"Component\({u?'KEY1': u?'value1'}\)", str(repr_example.component)) + +def test_repr_calendar(repr_example): + assert re.match(r"VCALENDAR\({u?'KEY1': u?'value1'}\)", str(repr_example.calendar)) + + +def test_repr_event(repr_example): + assert re.match(r"VEVENT\({u?'KEY1': u?'value1'}\)", str(repr_example.event)) + + +def test_nested_components(repr_example): + """Representation of nested Components""" + repr_example.calendar.add_component(repr_example.event) + print(repr_example.nested) + assert re.match( + r"Component\({u?'KEY1': u?'VALUE1'}, " + r"Component\({u?'KEY1': u?'value1'}\), " + r"VCALENDAR\({u?'KEY1': u?'value1'}, " + r"VEVENT\({u?'KEY1': u?'value1'}\)\)\)", + str(repr_example.nested) )