Test iCloud (#567)

* Test iCloud

Fix #149
This commit is contained in:
Markus Unterwaditzer 2017-02-27 19:52:24 +01:00 committed by GitHub
parent ded1feb05a
commit 6aeeb90259
8 changed files with 99 additions and 6 deletions

View file

@ -165,6 +165,18 @@
"env": "BUILD=test DAV_SERVER=davical REQUIREMENTS=minimal ",
"python": "3.6"
},
{
"env": "BUILD=test DAV_SERVER=icloud REQUIREMENTS=devel ",
"python": "3.6"
},
{
"env": "BUILD=test DAV_SERVER=icloud REQUIREMENTS=release ",
"python": "3.6"
},
{
"env": "BUILD=test DAV_SERVER=icloud REQUIREMENTS=minimal ",
"python": "3.6"
},
{
"env": "BUILD=test DAV_SERVER=skip REQUIREMENTS=devel ",
"python": "pypy3"

View file

@ -41,7 +41,7 @@ matrix.append({
for python in python_versions:
if python == latest_python:
dav_servers = ("skip", "radicale", "owncloud", "nextcloud", "baikal",
"davical")
"davical", "icloud")
rs_servers = ()
else:
dav_servers = ("skip", "radicale")

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import random
import uuid
import textwrap
from urllib.parse import quote as urlquote, unquote as urlunquote
@ -199,7 +200,10 @@ class StorageTests(object):
def test_create_collection(self, requires_collections, get_storage_args,
get_item):
if getattr(self, 'dav_server', '') == 'radicale':
pytest.xfail('MKCOL is broken under Radicale 1.x')
pytest.skip('MKCOL is broken under Radicale 1.x')
if getattr(self, 'dav_server', '') == 'icloud':
pytest.skip('iCloud requires a minimum-length for collection name')
args = get_storage_args(collection=None)
args['collection'] = 'test'
@ -233,8 +237,9 @@ class StorageTests(object):
if s.storage_name == 'filesystem':
pytest.skip('Behavior depends on the filesystem.')
s.upload(get_item(uid='A' * 42))
s.upload(get_item(uid='a' * 42))
uid = str(uuid.uuid4())
s.upload(get_item(uid=uid.upper()))
s.upload(get_item(uid=uid.lower()))
items = list(href for href, etag in s.list())
assert len(items) == 2
assert len(set(items)) == 2
@ -242,7 +247,9 @@ class StorageTests(object):
def test_specialchars(self, monkeypatch, requires_collections,
get_storage_args, get_item):
if getattr(self, 'dav_server', '') == 'radicale':
pytest.xfail('Radicale is fundamentally broken.')
pytest.skip('Radicale is fundamentally broken.')
if getattr(self, 'dav_server', '') == 'icloud':
pytest.skip('iCloud rejects uploads.')
monkeypatch.setattr('vdirsyncer.utils.generate_href', lambda x: x)

View file

@ -133,6 +133,8 @@ class TestCalDAVStorage(DAVStorageTests):
list(s.list())
assert len(calls) == 1
@pytest.mark.skipif(dav_server == 'icloud',
reason='iCloud only accepts VEVENT')
def test_item_types_general(self, s):
event = s.upload(format_item(EVENT_TEMPLATE))[0]
task = s.upload(format_item(TASK_TEMPLATE))[0]

View file

@ -0,0 +1,56 @@
import os
import uuid
import pytest
def _clear_collection(s):
for href, etag in s.list():
s.delete(href, etag)
class ServerMixin(object):
@pytest.fixture
def get_storage_args(self, item_type, request):
# We need to properly clean up because otherwise we will run into
# iCloud's storage limit.
collections_to_delete = []
def delete_collections():
for s in collections_to_delete:
s.session.request('DELETE', '')
request.addfinalizer(delete_collections)
if item_type != 'VEVENT':
# For some reason the collections created by vdirsyncer are not
# usable as task lists.
pytest.skip('iCloud doesn\'t support anything else than VEVENT')
def inner(collection='test'):
args = {
'username': os.environ['ICLOUD_USERNAME'],
'password': os.environ['ICLOUD_PASSWORD']
}
if self.storage_class.fileext == '.ics':
args['url'] = 'https://caldav.icloud.com/'
elif self.storage_class.fileext == '.vcf':
args['url'] = 'https://contacts.icloud.com/'
else:
raise RuntimeError()
if collection is not None:
assert collection.startswith('test')
# iCloud requires a minimum length for collection names
collection += '-vdirsyncer-ci-' + str(uuid.uuid4())
args = self.storage_class.create_collection(collection,
**args)
s = self.storage_class(**args)
_clear_collection(s)
assert not list(s.list())
collections_to_delete.append(s)
return args
return inner

View file

View file

@ -243,4 +243,7 @@ class Storage(metaclass=StorageMeta):
def normalize_meta_value(value):
return (value or u'').strip()
# `None` is returned by iCloud for empty properties.
if not value or value == 'None':
value = ''
return value.strip()

View file

@ -107,6 +107,8 @@ def normalize_item(item, ignore_props=IGNORE_PROPS):
if not isinstance(item, Item):
item = Item(item)
item = _strip_timezones(item)
x = _Component('TEMP', item.raw.splitlines(), [])
for prop in IGNORE_PROPS:
del x[prop]
@ -115,6 +117,17 @@ def normalize_item(item, ignore_props=IGNORE_PROPS):
return u'\r\n'.join(filter(bool, (line.strip() for line in x.props)))
def _strip_timezones(item):
parsed = item.parsed
if not parsed or parsed.name != 'VCALENDAR':
return item
parsed.subcomponents = [c for c in parsed.subcomponents
if c.name != 'VTIMEZONE']
return Item('\r\n'.join(parsed.dump_lines()))
def hash_item(text):
return hashlib.sha256(normalize_item(text).encode('utf-8')).hexdigest()