mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
parent
ded1feb05a
commit
6aeeb90259
8 changed files with 99 additions and 6 deletions
12
.travis.yml
12
.travis.yml
|
|
@ -165,6 +165,18 @@
|
||||||
"env": "BUILD=test DAV_SERVER=davical REQUIREMENTS=minimal ",
|
"env": "BUILD=test DAV_SERVER=davical REQUIREMENTS=minimal ",
|
||||||
"python": "3.6"
|
"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 ",
|
"env": "BUILD=test DAV_SERVER=skip REQUIREMENTS=devel ",
|
||||||
"python": "pypy3"
|
"python": "pypy3"
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ matrix.append({
|
||||||
for python in python_versions:
|
for python in python_versions:
|
||||||
if python == latest_python:
|
if python == latest_python:
|
||||||
dav_servers = ("skip", "radicale", "owncloud", "nextcloud", "baikal",
|
dav_servers = ("skip", "radicale", "owncloud", "nextcloud", "baikal",
|
||||||
"davical")
|
"davical", "icloud")
|
||||||
rs_servers = ()
|
rs_servers = ()
|
||||||
else:
|
else:
|
||||||
dav_servers = ("skip", "radicale")
|
dav_servers = ("skip", "radicale")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
import uuid
|
||||||
|
|
||||||
import textwrap
|
import textwrap
|
||||||
from urllib.parse import quote as urlquote, unquote as urlunquote
|
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,
|
def test_create_collection(self, requires_collections, get_storage_args,
|
||||||
get_item):
|
get_item):
|
||||||
if getattr(self, 'dav_server', '') == 'radicale':
|
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 = get_storage_args(collection=None)
|
||||||
args['collection'] = 'test'
|
args['collection'] = 'test'
|
||||||
|
|
@ -233,8 +237,9 @@ class StorageTests(object):
|
||||||
if s.storage_name == 'filesystem':
|
if s.storage_name == 'filesystem':
|
||||||
pytest.skip('Behavior depends on the filesystem.')
|
pytest.skip('Behavior depends on the filesystem.')
|
||||||
|
|
||||||
s.upload(get_item(uid='A' * 42))
|
uid = str(uuid.uuid4())
|
||||||
s.upload(get_item(uid='a' * 42))
|
s.upload(get_item(uid=uid.upper()))
|
||||||
|
s.upload(get_item(uid=uid.lower()))
|
||||||
items = list(href for href, etag in s.list())
|
items = list(href for href, etag in s.list())
|
||||||
assert len(items) == 2
|
assert len(items) == 2
|
||||||
assert len(set(items)) == 2
|
assert len(set(items)) == 2
|
||||||
|
|
@ -242,7 +247,9 @@ class StorageTests(object):
|
||||||
def test_specialchars(self, monkeypatch, requires_collections,
|
def test_specialchars(self, monkeypatch, requires_collections,
|
||||||
get_storage_args, get_item):
|
get_storage_args, get_item):
|
||||||
if getattr(self, 'dav_server', '') == 'radicale':
|
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)
|
monkeypatch.setattr('vdirsyncer.utils.generate_href', lambda x: x)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,8 @@ class TestCalDAVStorage(DAVStorageTests):
|
||||||
list(s.list())
|
list(s.list())
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
@pytest.mark.skipif(dav_server == 'icloud',
|
||||||
|
reason='iCloud only accepts VEVENT')
|
||||||
def test_item_types_general(self, s):
|
def test_item_types_general(self, s):
|
||||||
event = s.upload(format_item(EVENT_TEMPLATE))[0]
|
event = s.upload(format_item(EVENT_TEMPLATE))[0]
|
||||||
task = s.upload(format_item(TASK_TEMPLATE))[0]
|
task = s.upload(format_item(TASK_TEMPLATE))[0]
|
||||||
|
|
|
||||||
56
tests/storage/servers/icloud/__init__.py
Normal file
56
tests/storage/servers/icloud/__init__.py
Normal 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
|
||||||
0
tests/storage/servers/icloud/install.sh
Normal file
0
tests/storage/servers/icloud/install.sh
Normal file
|
|
@ -243,4 +243,7 @@ class Storage(metaclass=StorageMeta):
|
||||||
|
|
||||||
|
|
||||||
def normalize_meta_value(value):
|
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()
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,8 @@ def normalize_item(item, ignore_props=IGNORE_PROPS):
|
||||||
if not isinstance(item, Item):
|
if not isinstance(item, Item):
|
||||||
item = Item(item)
|
item = Item(item)
|
||||||
|
|
||||||
|
item = _strip_timezones(item)
|
||||||
|
|
||||||
x = _Component('TEMP', item.raw.splitlines(), [])
|
x = _Component('TEMP', item.raw.splitlines(), [])
|
||||||
for prop in IGNORE_PROPS:
|
for prop in IGNORE_PROPS:
|
||||||
del x[prop]
|
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)))
|
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):
|
def hash_item(text):
|
||||||
return hashlib.sha256(normalize_item(text).encode('utf-8')).hexdigest()
|
return hashlib.sha256(normalize_item(text).encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue