icalendar/src/icalendar/tests/test_icalendar.py

324 wiersze
12 KiB
Python

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import icalendar
import os
import textwrap
import unittest
class IcalendarTestCase (unittest.TestCase):
def test_long_lines(self):
from ..parser import Contentlines, Contentline
c = Contentlines([Contentline('BEGIN:VEVENT')])
c.append(Contentline(''.join('123456789 ' * 10)))
self.assertEqual(
c.to_ical(),
b'BEGIN:VEVENT\r\n123456789 123456789 123456789 123456789 '
b'123456789 123456789 123456789 1234\r\n 56789 123456789 '
b'123456789 \r\n'
)
# from doctests
# Notice that there is an extra empty string in the end of the content
# lines. That is so they can be easily joined with:
# '\r\n'.join(contentlines))
self.assertEqual(Contentlines.from_ical('A short line\r\n'),
['A short line', ''])
self.assertEqual(Contentlines.from_ical('A faked\r\n long line\r\n'),
['A faked long line', ''])
self.assertEqual(
Contentlines.from_ical('A faked\r\n long line\r\nAnd another '
'lin\r\n\te that is folded\r\n'),
['A faked long line', 'And another line that is folded', '']
)
def test_contentline_class(self):
from ..parser import Contentline, Parameters
from ..prop import vText
self.assertEqual(
Contentline('Si meliora dies, ut vina, poemata reddit').to_ical(),
b'Si meliora dies, ut vina, poemata reddit'
)
# A long line gets folded
c = Contentline(''.join(['123456789 '] * 10)).to_ical()
self.assertEqual(
c,
(b'123456789 123456789 123456789 123456789 123456789 123456789 '
b'123456789 1234\r\n 56789 123456789 123456789 ')
)
# A folded line gets unfolded
self.assertEqual(
Contentline.from_ical(c),
('123456789 123456789 123456789 123456789 123456789 123456789 '
'123456789 123456789 123456789 123456789 ')
)
# https://tools.ietf.org/html/rfc5545#section-3.3.11
# An intentional formatted text line break MUST only be included in
# a "TEXT" property value by representing the line break with the
# character sequence of BACKSLASH, followed by a LATIN SMALL LETTER
# N or a LATIN CAPITAL LETTER N, that is "\n" or "\N".
# Newlines are not allwoed in content lines
self.assertRaises(AssertionError, Contentline, b'1234\r\n\r\n1234')
self.assertEqual(
Contentline('1234\\n\\n1234').to_ical(),
b'1234\\n\\n1234'
)
# We do not fold within a UTF-8 character
c = Contentline(b'This line has a UTF-8 character where it should be '
b'folded. Make sure it g\xc3\xabts folded before that '
b'character.')
self.assertIn(b'\xc3\xab', c.to_ical())
# Another test of the above
c = Contentline(b'x' * 73 + b'\xc3\xab' + b'\\n ' + b'y' * 10)
self.assertEqual(c.to_ical().count(b'\xc3'), 1)
# Don't fail if we fold a line that is exactly X times 74 characters
# long
c = Contentline(''.join(['x'] * 148)).to_ical()
# It can parse itself into parts,
# which is a tuple of (name, params, vals)
self.assertEqual(
Contentline('dtstart:20050101T120000').parts(),
('dtstart', Parameters({}), '20050101T120000')
)
self.assertEqual(
Contentline('dtstart;value=datetime:20050101T120000').parts(),
('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000')
)
c = Contentline('ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:'
'MAILTO:maxm@example.com')
self.assertEqual(
c.parts(),
('ATTENDEE',
Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}),
'MAILTO:maxm@example.com')
)
self.assertEqual(
c.to_ical().decode('utf-8'),
'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:'
'MAILTO:maxm@example.com'
)
# and back again
# NOTE: we are quoting property values with spaces in it.
parts = ('ATTENDEE',
Parameters({'ROLE': 'REQ-PARTICIPANT',
'CN': 'Max Rasmussen'}),
'MAILTO:maxm@example.com')
self.assertEqual(
Contentline.from_parts(*parts),
'ATTENDEE;CN="Max Rasmussen";ROLE=REQ-PARTICIPANT:'
'MAILTO:maxm@example.com'
)
# and again
parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com')
self.assertEqual(
Contentline.from_parts(*parts),
'ATTENDEE:MAILTO:maxm@example.com'
)
# A value can also be any of the types defined in PropertyValues
parts = ('ATTENDEE', Parameters(), vText('MAILTO:test@example.com'))
self.assertEqual(
Contentline.from_parts(*parts),
'ATTENDEE:MAILTO:test@example.com'
)
# A value in UTF-8
parts = ('SUMMARY', Parameters(), vText('INternational char æ ø å'))
self.assertEqual(
Contentline.from_parts(*parts),
'SUMMARY:INternational char æ ø å'
)
# A value can also be unicode
parts = ('SUMMARY', Parameters(), vText('INternational char æ ø å'))
self.assertEqual(
Contentline.from_parts(*parts),
'SUMMARY:INternational char æ ø å'
)
# Traversing could look like this.
name, params, vals = c.parts()
self.assertEqual(name, 'ATTENDEE')
self.assertEqual(vals, 'MAILTO:maxm@example.com')
self.assertEqual(
sorted(params.items()),
sorted([('ROLE', 'REQ-PARTICIPANT'), ('CN', 'Max Rasmussen')])
)
# And the traditional failure
with self.assertRaisesRegexp(
ValueError,
'Content line could not be parsed into parts'
):
Contentline('ATTENDEE;maxm@example.com').parts()
# Another failure:
with self.assertRaisesRegexp(
ValueError,
'Content line could not be parsed into parts'
):
Contentline(':maxm@example.com').parts()
self.assertEqual(
Contentline('key;param=:value').parts(),
('key', Parameters({'PARAM': ''}), 'value')
)
self.assertEqual(
Contentline('key;param="pvalue":value').parts(),
('key', Parameters({'PARAM': 'pvalue'}), 'value')
)
# Should bomb on missing param:
with self.assertRaisesRegexp(
ValueError,
'Content line could not be parsed into parts'
):
Contentline.from_ical("k;:no param").parts()
self.assertEqual(
Contentline('key;param=pvalue:value', strict=False).parts(),
('key', Parameters({'PARAM': 'pvalue'}), 'value')
)
# If strict is set to True, uppercase param values that are not
# double-quoted, this is because the spec says non-quoted params are
# case-insensitive.
self.assertEqual(
Contentline('key;param=pvalue:value', strict=True).parts(),
('key', Parameters({'PARAM': 'PVALUE'}), 'value')
)
self.assertEqual(
Contentline('key;param="pValue":value', strict=True).parts(),
('key', Parameters({'PARAM': 'pValue'}), 'value')
)
contains_base64 = (
'X-APPLE-STRUCTURED-LOCATION;'
'VALUE=URI;X-ADDRESS="Kaiserliche Hofburg, 1010 Wien";'
'X-APPLE-MAPKIT-HANDLE=CAESxQEZgr3QZXJyZWljaA==;'
'X-APPLE-RADIUS=328.7978217977285;X-APPLE-REFERENCEFRAME=1;'
'X-TITLE=Heldenplatz:geo:48.206686,16.363235'
).encode('utf-8')
self.assertEqual(
Contentline(contains_base64, strict=True).parts(),
('X-APPLE-STRUCTURED-LOCATION',
Parameters({
'X-APPLE-RADIUS': '328.7978217977285',
'X-ADDRESS': 'Kaiserliche Hofburg, 1010 Wien',
'X-APPLE-REFERENCEFRAME': '1',
'X-TITLE': 'HELDENPLATZ',
'X-APPLE-MAPKIT-HANDLE':
'CAESXQEZGR3QZXJYZWLJAA==',
'VALUE': 'URI',
}),
'geo:48.206686,16.363235'
)
)
def test_fold_line(self):
from ..parser import foldline
self.assertEqual(foldline('foo'), 'foo')
self.assertEqual(
foldline("Lorem ipsum dolor sit amet, consectetur adipiscing "
"elit. Vestibulum convallis imperdiet dui posuere."),
('Lorem ipsum dolor sit amet, consectetur adipiscing elit. '
'Vestibulum conval\r\n lis imperdiet dui posuere.')
)
# I don't really get this test
# at least just but bytes in there
# porting it to "run" under python 2 & 3 makes it not much better
with self.assertRaises(AssertionError):
foldline('привет'.encode('utf-8'), limit=3)
self.assertEqual(foldline('foobar', limit=4), 'foo\r\n bar')
self.assertEqual(
foldline('Lorem ipsum dolor sit amet, consectetur adipiscing elit'
'. Vestibulum convallis imperdiet dui posuere.'),
('Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
' Vestibulum conval\r\n lis imperdiet dui posuere.')
)
self.assertEqual(
foldline('DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ'),
'DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭ\r\n ЮЯ'
)
def test_value_double_quoting(self):
from ..parser import dquote
self.assertEqual(dquote('Max'), 'Max')
self.assertEqual(dquote('Rasmussen, Max'), '"Rasmussen, Max"')
self.assertEqual(dquote('name:value'), '"name:value"')
def test_q_split(self):
from ..parser import q_split
self.assertEqual(q_split('Max,Moller,"Rasmussen, Max"'),
['Max', 'Moller', '"Rasmussen, Max"'])
def test_q_split_bin(self):
from ..parser import q_split
for s in ('X-SOMETHING=ABCDE==', ',,,'):
for maxsplit in range(-1, 3):
self.assertEqual(q_split(s, '=', maxsplit=maxsplit),
s.split('=', maxsplit))
def test_q_join(self):
from ..parser import q_join
self.assertEqual(q_join(['Max', 'Moller', 'Rasmussen, Max']),
'Max,Moller,"Rasmussen, Max"')
class TestEncoding(unittest.TestCase):
def test_broken_property(self):
"""
Test if error messages are encode properly.
"""
broken_ical = textwrap.dedent("""
BEGIN:VCALENDAR
BEGIN:VEVENT
SUMMARY:An Event with too many semicolons
DTSTART;;VALUE=DATE-TIME:20140409T093000
UID:abc
END:VEVENT
END:VCALENDAR
""")
cal = icalendar.Calendar.from_ical(broken_ical)
for event in cal.walk('vevent'):
self.assertEqual(len(event.errors), 1, 'Not the right amount of errors.')
error = event.errors[0][1]
self.assertTrue(error.startswith('Content line could not be parsed into parts'))
def test_apple_xlocation(self):
"""
Test if we support base64 encoded binary data in parameter values.
"""
directory = os.path.dirname(__file__)
with open(os.path.join(directory, 'x_location.ics'), 'rb') as fp:
data = fp.read()
cal = icalendar.Calendar.from_ical(data)
for event in cal.walk('vevent'):
self.assertEqual(len(event.errors), 0, 'Got too many errors')