caldav: add event creation/update/deletion (#87227)
gitea/passerelle/pipeline/head This commit looks good Details

This commit is contained in:
Yann Weber 2024-02-21 18:40:00 +01:00
parent 5f86102711
commit 665c16bca2
8 changed files with 793 additions and 0 deletions

1
debian/control vendored
View File

@ -16,6 +16,7 @@ Architecture: all
Depends: ghostscript,
pdftk,
poppler-utils,
python3-caldav,
python3-cmislib,
python3-cryptography,
python3-dateutil,

View File

View File

@ -0,0 +1,47 @@
# Generated by Django 3.2.18 on 2024-02-20 15:41
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('base', '0030_resourcelog_base_resour_appname_298cbc_idx'),
]
operations = [
migrations.CreateModel(
name='CalDAV',
fields=[
(
'id',
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
('title', models.CharField(max_length=50, verbose_name='Title')),
('slug', models.SlugField(unique=True, verbose_name='Identifier')),
('description', models.TextField(verbose_name='Description')),
(
'dav_url',
models.URLField(
help_text='DAV root URL (such as https://test.egw/groupdav.php/)',
verbose_name='DAV root URL',
),
),
('dav_login', models.CharField(max_length=128, verbose_name='DAV username')),
('dav_password', models.CharField(max_length=512, verbose_name='DAV password')),
(
'users',
models.ManyToManyField(
blank=True,
related_name='_caldav_caldav_users_+',
related_query_name='+',
to='base.ApiUser',
),
),
],
options={
'verbose_name': 'CalDAV',
},
),
]

View File

@ -0,0 +1,305 @@
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2024 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import functools
import urllib.parse
import caldav
import requests
from django.db import models
from django.utils.dateparse import parse_date, parse_datetime
from django.utils.translation import gettext_lazy as _
from passerelle.base.models import BaseResource
from passerelle.utils.api import endpoint
from passerelle.utils.conversion import exception_to_text
from passerelle.utils.jsonresponse import APIError
EVENT_SCHEMA_PART = {
'type': 'object',
'description': _('Ical event properties ( VEVENT RFC 5545 3.6.1 )'),
'required': ['DTSTART', 'DTEND'],
'properties': {
'DTSTART': {
'type': 'string',
'description': _('Event start (included) ISO-8601 date-time or date (for allday event)'),
},
'DTEND': {
'type': 'string',
'description': _('Event end (excluded) ISO-8601 date-time or date (for allday event)'),
},
'SUMMARY': {
'type': 'string',
'description': 'RFC 5545 3.8.1.12',
},
'DESCRIPTION': {
'type': 'string',
'description': 'RFC 5545 3.8.2.5',
},
'LOCATION': {
'type': 'string',
'description': 'RFC 5545 3.8.1.7',
},
'TRANSP': {
'type': 'boolean',
'description': 'Transparent if true else opaque (RFC 5545 3.8.2.7)',
},
'RRULE': {
'description': _('Recurrence rule (RFC 5545 3.8.5.3)'),
'type': 'object',
'properties': {
'FREQ': {
'type': 'string',
'enum': ['WEEKLY', 'MONTHLY', 'YEARLY'],
},
'BYDAY': {
'type': 'array',
'items': {
'type': 'string',
'enum': ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'],
},
},
'BYMONTH': {
'type': 'array',
'items': {
'type': 'integer',
'minimum': 1,
'maximum': 12,
},
},
'COUNT': {
'type': 'integer',
'minimum': 1,
},
},
},
},
}
USERNAME_PARAM = {
'description': _('The calendar\'s owner username'),
'type': 'string',
}
EVENT_UID_PARAM = {
'description': _('An event UID'),
'type': 'string',
}
# Action's request body schema
EVENT_SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': _('Event description schema'),
'unflatten': True,
**EVENT_SCHEMA_PART,
}
class CalDAV(BaseResource):
dav_url = models.URLField(
blank=False,
verbose_name=_('DAV root URL'),
help_text=_('DAV root URL (such as https://test.egw/groupdav.php/)'),
)
dav_login = models.CharField(max_length=128, verbose_name=_('DAV username'), blank=False)
dav_password = models.CharField(max_length=512, verbose_name=_('DAV password'), blank=False)
category = _('Misc')
class Meta:
verbose_name = _('CalDAV')
@functools.cached_property
def dav_client(self):
'''Instanciate a caldav.DAVCLient and return the instance'''
return caldav.DAVClient(self.dav_url, username=self.dav_login, password=self.dav_password)
def check_status(self):
'''Attempt a propfind on DAV root URL'''
try:
rep = self.dav_client.propfind()
rep.find_objects_and_props()
except caldav.lib.error.AuthorizationError:
raise Exception(_('Not authorized: bad login/password ?'))
@endpoint(
name='event',
pattern='^create$',
example_pattern='create',
methods=['post'],
post={'request_body': {'schema': {'application/json': EVENT_SCHEMA}}},
parameters={
'username': USERNAME_PARAM,
},
)
def create_event(self, request, username, post_data):
'''Event creation endpoint'''
cal = self.get_calendar(username)
self._process_event_properties(post_data)
try:
evt = cal.save_event(**post_data)
except requests.exceptions.RequestException as expt:
raise APIError(
_('Error sending creation request to caldav server'),
data={
'expt_class': str(type(expt)),
'expt': str(expt),
'username': username,
},
)
return {'data': {'event_id': evt.id}}
# Patch do not support request_body validation, using post instead
@endpoint(
name='event',
pattern='^update$',
example_pattern='update',
methods=['post'],
post={'request_body': {'schema': {'application/json': EVENT_SCHEMA}}},
parameters={
'username': USERNAME_PARAM,
'event_id': EVENT_UID_PARAM,
},
)
def update_event(self, request, username, event_id, post_data):
'''Event update endpoint'''
self._process_event_properties(post_data)
ical = self.get_event(username, event_id)
vevent = ical.icalendar_instance.walk('VEVENT')
if not len(vevent) == 1:
raise APIError(
_('Given event (user:%r uid:%r) do not contains VEVENT component') % (username, event_id),
data={
'username': username,
'event_uid': event_id,
'VEVENT': str(vevent),
},
)
vevent = vevent[0]
# vevent.update(post_data) do not convert values as expected
for k, v in post_data.items():
vevent[k] = v
try:
# do not use ical.save(no_create=True) : no_create fails on some calDAV
ical.save()
except requests.exceptions.RequestException as expt:
raise APIError(
_('Error sending update request to caldav server'),
data={
'expt_class': str(type(expt)),
'expt': str(expt),
'username': username,
'event_id': event_id,
},
)
return {'data': {'event_id': ical.id}}
@endpoint(
name='event',
pattern='^delete$',
example_pattern='delete',
methods=['delete'],
parameters={
'username': USERNAME_PARAM,
'event_id': EVENT_UID_PARAM,
},
)
def delete_event(self, request, username, event_id):
ical = self.get_event(username, event_id)
try:
ical.delete()
except requests.exceptions.RequestException as expt:
raise APIError(
_('Error sending deletion request to caldav server'),
data={
'expt_class': str(type(expt)),
'expt': str(expt),
'username': username,
'event_id': event_id,
},
)
return {}
def get_event(self, username, event_uid):
'''Fetch an event given a username and an event_uid
Arguments:
- username: Calendar owner's username
- event_uid: The event's UID
Returns an caldav.Event instance
'''
event_path = '%s/calendar/%s.ics' % (urllib.parse.quote(username), urllib.parse.quote(str(event_uid)))
cal = self.get_calendar(username)
try:
ical = cal.event_by_url(event_path)
except caldav.lib.error.DAVError as expt:
raise APIError(
_('Unable to get event %r in calendar owned by %r') % (event_uid, username),
data={
'expt': exception_to_text(expt),
'expt_cls': str(type(expt)),
'username': username,
'event_uid': event_uid,
},
)
except requests.exceptions.RequestException as expt:
raise APIError(
_('Unable to communicate with caldav server while fetching event'),
data={
'expt': exception_to_text(expt),
'expt_class': str(type(expt)),
'username': username,
'event_uid': event_uid,
},
)
return ical
def get_calendar(self, username):
'''Given a username returns the associated calendar set
Arguments:
- username: Calendar owner's username
Returns A caldav.Calendar instance
Note: do not raise any caldav exception before a method trying to make
a request is called
'''
path = '%s/calendar' % urllib.parse.quote(username)
calendar = caldav.Calendar(client=self.dav_client, url=path)
return calendar
def _process_event_properties(self, data):
'''Handles verification & convertion of event properties
@note Modify given data dict inplace
'''
if 'TRANSP' in data:
data['TRANSP'] = 'TRANSPARENT' if data['TRANSP'] else 'OPAQUE'
for dt_field in ('DTSTART', 'DTEND'):
value = data[dt_field]
try:
data[dt_field] = parse_date(value) or parse_datetime(value)
except ValueError:
data[dt_field] = None
if not data[dt_field]:
raise APIError(
_('Unable to convert field %s=%r: not a valid date nor date-time') % (dt_field, value),
http_status=400,
)

View File

@ -143,6 +143,7 @@ INSTALLED_APPS = (
'passerelle.apps.base_adresse',
'passerelle.apps.bbb',
'passerelle.apps.bdp',
'passerelle.apps.caldav',
'passerelle.apps.cartads_cs',
'passerelle.apps.choosit',
'passerelle.apps.cityweb',

View File

@ -138,6 +138,7 @@ setup(
scripts=['manage.py'],
include_package_data=True,
install_requires=[
'caldav',
'django >= 3.2, <3.3',
'django-model-utils<4.3',
'requests',

438
tests/test_caldav.py Normal file
View File

@ -0,0 +1,438 @@
import datetime
import urllib
from unittest.mock import Mock, patch
import caldav
import icalendar
import pytest
import responses
from django.contrib.contenttypes.models import ContentType
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
import tests.utils
from passerelle.apps.caldav.models import CalDAV
from passerelle.base.models import AccessRight, ApiUser
DAV_URL = 'http://test.caldav.notld/somedav/'
EVENT_PATH_FMT = '%(username)s/calendar/%(uid)s.ics'
PROPFIND_REPLY = '''<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response xmlns:ns0="urn:uuid:02481234-abcd-4321-abcd-00aa00bb00cc/" xmlns:ns2="urn:ietf:params:xml:ns:carddav">
<D:href>/somedave/</D:href>
<D:propstat>
<D:prop>
<D:displayname>Test Caldav</D:displayname>
<D:owner/>
<D:resourcetype><D:collection /></D:resourcetype>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
<D:current-user-principal><D:href>/somedav/principals/users/apiaccess/</D:href></D:current-user-principal>
<D:principal-collection-set><D:href>/somedav/principals/</D:href></D:principal-collection-set>
<D:getetag>"none"</D:getetag>
<D:getcontentlength/>
<D:getlastmodified/>
<ns2:addressbook-home-set><D:href>/somedav/apiaccess/</D:href></ns2:addressbook-home-set>
<ns2:principal-address/>
<ns2:directory-gateway><D:href>/somedav/addressbook/</D:href></ns2:directory-gateway>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
'''
SOME_EVENTS = [
{'DTSTART': '2020-02-20T20:02', 'DTEND': '2020-02-22T22:43', 'SUMMARY': 'Test'},
{
'DTSTART': '2020-02-20',
'DTEND': '2020-02-22',
'SUMMARY': 'Test',
'RRULE': {'FREQ': 'MONTHLY', 'BYDAY': ['FR', 'MO']},
},
{
'DTSTART': '2020-02-20',
'DTEND': '2020-02-22',
'SUMMARY': 'Test',
'LOCATION': 'Foo bar',
'TRANSP': True,
'DESCRIPTION': 'test',
},
]
@pytest.fixture()
def caldav_conn(db):
user = ApiUser.objects.create(username='all', keytype='', key='')
cdav = CalDAV.objects.create(dav_url=DAV_URL, slug='test', dav_login='foo', dav_password='hackmeplz')
content_type = ContentType.objects.get_for_model(cdav)
AccessRight.objects.create(
codename='can_access', apiuser=user, resource_type=content_type, resource_pk=cdav.pk
)
return cdav
def get_fake_event(caldav_conn):
vevent = icalendar.Event()
for k, v in {
'DTSTART': datetime.date(2023, 1, 1),
'DTEND': datetime.date(2023, 2, 1),
'SUMMARY': 'Test',
'UID': '12345678-abcd',
}.items():
vevent.add(k, v)
ical = icalendar.Calendar()
ical.add_component(vevent)
event = caldav.Event(client=caldav_conn.dav_client, data=ical)
event.url = caldav.lib.url.URL(DAV_URL + 'foo/calendar/12345678-abcd.ics')
calendar = caldav.Calendar(client=caldav_conn.dav_client)
calendar.url = caldav.lib.url.URL(DAV_URL + 'foo/calendar')
event.parent = calendar
return event
def get_calendar_mock(event_path, event_uid):
'''Return a mock configured to be usable as a Calendar instance in
the caldav connector
'''
event_url = DAV_URL + event_path
url = caldav.lib.url.URL(event_url)
evt_conf = {'url': url, 'id': event_uid}
mock_event = Mock(**evt_conf)
ical = icalendar.Calendar()
event = icalendar.Event()
conf = {
'url': DAV_URL,
'id': '1337',
'props': {
'dtstart': datetime.date.fromisoformat('2023-02-02'),
'dtend': datetime.date.fromisoformat('2023-02-28'),
'summary': 'test',
},
}
for k, v in conf.items():
event.add(k, v)
ical.add_component(event)
get_evt_conf = {
'url': url,
'id': event_uid,
'save': Mock(),
'delete': Mock(),
'icalendar_instance': ical,
}
mock_get_event = Mock(**get_evt_conf)
cal_conf = {'save_event.return_value': mock_event, 'event_by_url.return_value': mock_get_event}
return Mock(**cal_conf)
def qs_url(url, params):
'''Append query string to URL'''
return url + '?' + urllib.parse.urlencode(params)
def app_post(app, url, params, data, status=None):
'''Allows to post data on an URL with query string params and JSON data'''
return app.post_json(qs_url(url, params), params=data, status=status)
def get_event_path(caldav_conn, username, uid):
return EVENT_PATH_FMT % {
'username': urllib.parse.quote(username),
'uid': urllib.parse.quote(str(uid)),
}
#
# Tests
#
@responses.activate
def test_caldav_check_status_ok(app, caldav_conn):
responses.add('PROPFIND', DAV_URL, headers={'Content-Type': 'text/xml'}, body=PROPFIND_REPLY)
caldav_conn.check_status()
assert len(responses.calls) == 1
assert responses.calls[0].request.method == 'PROPFIND'
assert responses.calls[0].request.url == DAV_URL
@responses.activate
def test_caldav_check_status_login_failed(app, caldav_conn):
responses.add(
'PROPFIND',
DAV_URL,
headers={'Content-Type': 'text/xml'},
body=PROPFIND_REPLY,
status=401,
)
with pytest.raises(Exception) as expt:
caldav_conn.check_status()
assert str(expt) == 'Not authorized: bad login/password ?'
assert len(responses.calls) == 1
assert responses.calls[0].request.method == 'PROPFIND'
assert responses.calls[0].request.url == DAV_URL
@responses.activate
def test_caldav_check_status_fails(app, caldav_conn):
responses.add('PROPFIND', DAV_URL, status=500)
with pytest.raises(Exception):
caldav_conn.check_status()
assert len(responses.calls) == 1
assert responses.calls[0].request.method == 'PROPFIND'
assert responses.calls[0].request.url == DAV_URL
@pytest.mark.parametrize('requests_expt', (ConnectTimeout, ReadTimeout, ConnectionError))
@responses.activate
def test_caldav_check_status_timeout(app, caldav_conn, requests_expt):
propfind_mock = responses.add( # pylint: disable=assignment-from-none
'PROPFIND', DAV_URL, body=requests_expt('Do not work as expected')
)
get_mock = responses.add( # pylint: disable=assignment-from-none
'GET', DAV_URL, body=requests_expt('Do not work as expected')
)
with pytest.raises(Exception):
caldav_conn.check_status()
assert len(responses.calls) == 2
assert len(propfind_mock.calls) == 1
assert len(get_mock.calls) == 1
@responses.activate
def test_caldav_event_create_allday_ok(app, caldav_conn):
url = tests.utils.generic_endpoint_url('caldav', 'event/create')
username = 'foo'
evt_id = 42
evt_path = get_event_path(caldav_conn, username, evt_id)
cal_mock = get_calendar_mock(evt_path, evt_id)
event = {'DTSTART': '2020-02-20', 'DTEND': '2020-03-30', 'SUMMARY': 'foobar'}
qs_params = {'username': username}
with patch('caldav.Calendar', return_value=cal_mock):
resp = app_post(app, url, qs_params, event)
assert resp.json['err'] == 0
assert resp.json['data']['event_id'] == evt_id
event_conv = {
k: v if k not in ('DTSTART', 'DTEND') else datetime.date.fromisoformat(v)
for k, v in event.items()
}
cal_mock.save_event.assert_called_once_with(**event_conv)
@pytest.mark.parametrize(
'event',
SOME_EVENTS,
)
@responses.activate
def test_caldav_event_create_ok(app, caldav_conn, event):
url = tests.utils.generic_endpoint_url('caldav', 'event/create')
username = 'foo'
evt_id = '12345678-abcd'
evt_path = get_event_path(caldav_conn, username, evt_id)
cal_mock = get_calendar_mock(evt_path, evt_id)
qs_params = {'username': username}
with patch('caldav.Calendar', return_value=cal_mock):
resp = app_post(app, url, qs_params, event)
assert resp.json['err'] == 0
assert resp.json['data']['event_id'] == evt_id
save_args = event.copy()
caldav_conn._process_event_properties(save_args)
cal_mock.save_event.assert_called_once_with(**save_args)
@pytest.mark.parametrize(
'event',
SOME_EVENTS,
)
@responses.activate
def test_caldav_event_create_ok_nomock(app, caldav_conn, event):
'''Testing that caldav is able to create the event instance before sending it'''
url = tests.utils.generic_endpoint_url('caldav', 'event/create')
username = 'toto'
qs_params = {'username': username}
resp = app_post(app, url, qs_params, event)
assert resp.json['err'] != 0
assert resp.json['err_desc'] == 'Error sending creation request to caldav server'
@pytest.mark.parametrize(
'event',
SOME_EVENTS,
)
@responses.activate
def test_caldav_event_create_ok_net_err(app, caldav_conn, event):
with patch('passerelle.apps.caldav.models.CalDAV.get_event', return_value=get_fake_event(caldav_conn)):
url = tests.utils.generic_endpoint_url('caldav', 'event/create')
username = 'toto'
qs_params = {'username': username}
resp = app_post(app, url, qs_params, event)
assert resp.json['err'] != 0
assert resp.json['err_desc'] == 'Error sending creation request to caldav server'
@pytest.mark.parametrize(
'dtstart,dtend',
[
('2020-13-13', '2021-01-01'),
('2020-01-01T01:02:03', '2020-01-01T02:64:02'),
('23-01-12', '2023-01-13'),
('13:30', '2023-01-01T14:00'),
],
)
def test_caldav_event_create_bad_dates(app, caldav_conn, dtstart, dtend):
url = tests.utils.generic_endpoint_url('caldav', 'event/create')
username = 'toto'
event = {'DTSTART': dtstart, 'DTEND': dtend, 'SUMMARY': 'hello'}
qs_params = {'username': username}
resp = app_post(app, url, qs_params, event, status=400)
assert resp.status_code == 400
assert resp.json['err'] != 0
@pytest.mark.parametrize('transp', [True, False])
@responses.activate
def test_caldav_event_update_ok(app, transp, caldav_conn):
url = tests.utils.generic_endpoint_url('caldav', 'event/update')
username = 'toto'
evt_id = '01234567-abcd'
evt_path = get_event_path(caldav_conn, username, evt_id)
cal_mock = get_calendar_mock(evt_path, evt_id)
event = {'DTSTART': '2020-02-20', 'DTEND': '2020-03-30', 'SUMMARY': 'foobar', 'TRANSP': transp}
qs_params = {'username': username, 'event_id': evt_id}
with patch('caldav.Calendar', return_value=cal_mock):
resp = app_post(app, url, qs_params, event)
assert resp.json['err'] == 0
assert resp.json['data']['event_id'] == evt_id
cal_mock.event_by_url.assert_called_once_with(evt_path)
cal_mock.event_by_url().save.assert_called_once_with()
ical = cal_mock.event_by_url().icalendar_instance
evt = ical.walk('VEVENT')[0]
for k, v in event.items():
if k == 'TRANSP':
if v:
assert str(evt[k]) == 'TRANSPARENT'
else:
assert str(evt[k]) == 'OPAQUE'
else:
assert str(evt[k]) == v
@responses.activate
def test_caldav_event_update_notfound(app, caldav_conn):
url = tests.utils.generic_endpoint_url('caldav', 'event/update')
username = 'foo-user'
evt_id = '42'
evt_path = get_event_path(caldav_conn, username, evt_id)
cal_mock = get_calendar_mock(evt_path, evt_id)
cal_mock.event_by_url().icalendar_instance = icalendar.Calendar() # empty evt
event = {'DTSTART': '2020-02-20', 'DTEND': '2020-03-30', 'SUMMARY': 'foobar'}
qs_params = {'username': username, 'event_id': evt_id}
with patch('caldav.Calendar', return_value=cal_mock):
resp = app_post(app, url, qs_params, event)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'Given event (user:%r uid:%r) do not contains VEVENT component' % (
username,
evt_id,
)
@responses.activate
def test_caldav_event_update_bad_evt(app, caldav_conn):
url = tests.utils.generic_endpoint_url('caldav', 'event/update')
username = 'toto'
evt_id = '012345678'
evt_path = get_event_path(caldav_conn, username, evt_id)
cal_mock = get_calendar_mock(evt_path, evt_id)
cal_mock.event_by_url.side_effect = caldav.lib.error.NotFoundError('NotFoundError at \'404 Not Found')
event = {'DTSTART': '2020-02-20', 'DTEND': '2020-03-30', 'SUMMARY': 'foobar'}
qs_params = {'username': username, 'event_id': evt_id}
with patch('caldav.Calendar', return_value=cal_mock):
resp = app_post(app, url, qs_params, event)
assert resp.json['err'] == 1
cal_mock.event_by_url.assert_called_once_with(evt_path)
assert cal_mock.event_by_url.save.call_count == 0
@responses.activate
def test_caldav_event_update_net_err(app, caldav_conn):
with patch('passerelle.apps.caldav.models.CalDAV.get_event', return_value=get_fake_event(caldav_conn)):
url = tests.utils.generic_endpoint_url('caldav', 'event/update')
username = 'toto'
evt_id = '012345678'
event = {'DTSTART': '2020-02-20', 'DTEND': '2020-03-30', 'SUMMARY': 'foobar'}
qs_params = {'username': username, 'event_id': evt_id}
resp = app_post(app, url, qs_params, event)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'Error sending update request to caldav server'
@responses.activate
def test_caldav_event_delete_ok(app, caldav_conn):
url = tests.utils.generic_endpoint_url('caldav', 'event/delete')
username = 'toto'
evt_id = '012345678-dcba'
evt_path = get_event_path(caldav_conn, username, evt_id)
cal_mock = get_calendar_mock(evt_path, evt_id)
qs_params = {'username': username, 'event_id': evt_id}
with patch('caldav.Calendar', return_value=cal_mock):
resp = app.delete(qs_url(url, qs_params))
assert resp.json['err'] == 0
cal_mock.event_by_url.assert_called_once_with(evt_path)
cal_mock.event_by_url().delete.assert_called_once_with()
@responses.activate
def test_caldav_event_delete_net_err(app, caldav_conn):
with patch('passerelle.apps.caldav.models.CalDAV.get_event', return_value=get_fake_event(caldav_conn)):
url = tests.utils.generic_endpoint_url('caldav', 'event/delete')
username = 'toto'
evt_id = '012345678-dcba'
qs_params = {'username': username, 'event_id': evt_id}
resp = app.delete(qs_url(url, qs_params))
assert resp.json['err'] != 0
assert resp.json['err_desc'] == 'Error sending deletion request to caldav server'
@responses.activate
def test_caldav_event_delete_get_event_fail(app, caldav_conn):
url = tests.utils.generic_endpoint_url('caldav', 'event/delete')
username = 'toto'
evt_id = '012345678-dcba'
qs_params = {'username': username, 'event_id': evt_id}
resp = app.delete(qs_url(url, qs_params))
assert resp.json['err'] != 0
assert resp.json['err_desc'] == 'Unable to communicate with caldav server while fetching event'