caldav: add rrule/until convertion to date (#88393)
gitea/passerelle/pipeline/head This commit looks good
Details
gitea/passerelle/pipeline/head This commit looks good
Details
This commit is contained in:
parent
1e13344dc0
commit
fd7a2d487d
|
@ -55,7 +55,7 @@ EVENT_SCHEMA_PART = {
|
|||
'CATEGORY': {'type': 'string'},
|
||||
'TRANSP': {
|
||||
'type': 'boolean',
|
||||
'description': 'Transparent if true else opaque (RFC 5545 3.8.2.7)',
|
||||
'description': _('Transparent if true else opaque (RFC 5545 3.8.2.7)'),
|
||||
},
|
||||
'RRULE': {
|
||||
'description': _('Recurrence rule (RFC 5545 3.8.5.3)'),
|
||||
|
@ -84,6 +84,10 @@ EVENT_SCHEMA_PART = {
|
|||
'type': 'integer',
|
||||
'minimum': 1,
|
||||
},
|
||||
'UNTIL': {
|
||||
'type': 'string',
|
||||
'description': _('Date or date and time indicating the end of recurrence'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -201,6 +205,7 @@ class CalDAV(BaseResource):
|
|||
vevent = vevent[0]
|
||||
# vevent.update(post_data) do not convert values as expected
|
||||
for k, v in post_data.items():
|
||||
vevent.pop(k)
|
||||
vevent.add(k, v)
|
||||
try:
|
||||
# do not use ical.save(no_create=True) : no_create fails on some calDAV
|
||||
|
@ -300,15 +305,33 @@ class CalDAV(BaseResource):
|
|||
if 'CATEGORY' in data:
|
||||
data['CATEGORIES'] = [data.pop('CATEGORY')]
|
||||
|
||||
for dt_field in ('DTSTART', 'DTEND'):
|
||||
value = data[dt_field]
|
||||
if 'RRULE' in data and 'UNTIL' in data['RRULE']:
|
||||
try:
|
||||
data[dt_field] = parse_date(value) or parse_datetime(value)
|
||||
data['RRULE']['UNTIL'] = self._parse_date_or_datetime(data['RRULE']['UNTIL'])
|
||||
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),
|
||||
_('Unable to convert field %s=%r: not a valid date nor date-time')
|
||||
% ('RRULE/UNTIL', data['RRULE']['UNTIL']),
|
||||
http_status=400,
|
||||
)
|
||||
|
||||
for dt_field in ('DTSTART', 'DTEND'):
|
||||
if dt_field not in data:
|
||||
continue
|
||||
try:
|
||||
data[dt_field] = self._parse_date_or_datetime(data[dt_field])
|
||||
except ValueError:
|
||||
raise APIError(
|
||||
_('Unable to convert field %s=%r: not a valid date nor date-time')
|
||||
% (dt_field, data[dt_field]),
|
||||
http_status=400,
|
||||
)
|
||||
|
||||
def _parse_date_or_datetime(self, value):
|
||||
try:
|
||||
ret = parse_date(value) or parse_datetime(value)
|
||||
except ValueError:
|
||||
ret = None
|
||||
if not ret:
|
||||
raise ValueError('Invalid value')
|
||||
return ret
|
||||
|
|
|
@ -41,6 +41,42 @@ PROPFIND_REPLY = '''<?xml version="1.0" encoding="utf-8"?>
|
|||
</D:multistatus>
|
||||
'''
|
||||
|
||||
FAKE_ICS = '''BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//EGroupware//NONSGML EGroupware Calendar 17.1.003//FR
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Paris
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTART;VALUE=DATE:20240227
|
||||
DTEND;VALUE=DATE:20240228
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
SUMMARY:Yann Weber off
|
||||
RRULE:FREQ=WEEKLY;BYDAY=TU
|
||||
TRANSP:TRANSPARENT
|
||||
CATEGORIES:Journées off
|
||||
UID:01234567-abcd
|
||||
STATUS:CONFIRMED
|
||||
CREATED:20240320T095450Z
|
||||
LAST-MODIFIED:20240320T095450Z
|
||||
DTSTAMP:20240320T095513Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
'''
|
||||
|
||||
SOME_EVENTS = [
|
||||
{'DTSTART': '2020-02-20T20:02', 'DTEND': '2020-02-22T22:43', 'SUMMARY': 'Test'},
|
||||
|
@ -308,18 +344,24 @@ def test_caldav_event_create_ok_net_err(app, caldav_conn, event):
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'dtstart,dtend',
|
||||
'dtstart,dtend,until',
|
||||
[
|
||||
('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'),
|
||||
('2020-13-13', '2021-01-01', '2021-12-01'),
|
||||
('2020-01-01T01:02:03', '2020-01-01T02:64:02', '2021-12-01'),
|
||||
('23-01-12', '2023-01-13', '2021-12-01'),
|
||||
('13:30', '2023-01-01T14:00', '2021-12-01'),
|
||||
('2023-10-02', '2023-10-03', '2024-13-12'),
|
||||
],
|
||||
)
|
||||
def test_caldav_event_create_bad_dates(app, caldav_conn, dtstart, dtend):
|
||||
def test_caldav_event_create_bad_dates(app, caldav_conn, dtstart, dtend, until):
|
||||
url = tests.utils.generic_endpoint_url('caldav', 'event/create')
|
||||
username = 'toto'
|
||||
event = {'DTSTART': dtstart, 'DTEND': dtend, 'SUMMARY': 'hello'}
|
||||
event = {
|
||||
'DTSTART': dtstart,
|
||||
'DTEND': dtend,
|
||||
'SUMMARY': 'hello',
|
||||
'RRULE': {'FREQ': 'WEEKLY', 'BYDAY': 'FR', 'UNTIL': until},
|
||||
}
|
||||
qs_params = {'username': username}
|
||||
|
||||
resp = app_post(app, url, qs_params, event, status=400)
|
||||
|
@ -331,6 +373,9 @@ def test_caldav_event_create_bad_dates(app, caldav_conn, dtstart, dtend):
|
|||
@pytest.mark.parametrize('transp', [True, False])
|
||||
@responses.activate
|
||||
def test_caldav_event_update_ok(app, transp, caldav_conn):
|
||||
"""This test mock de caldav lib in order to check if the
|
||||
icalendar lib raises error on event modifications
|
||||
"""
|
||||
url = tests.utils.generic_endpoint_url('caldav', 'event/update')
|
||||
username = 'toto'
|
||||
evt_id = '01234567-abcd'
|
||||
|
@ -342,7 +387,7 @@ def test_caldav_event_update_ok(app, transp, caldav_conn):
|
|||
'DTEND': '2020-03-30',
|
||||
'SUMMARY': 'foobar',
|
||||
'TRANSP': transp,
|
||||
'RRULE': {'FREQ': 'MONTHLY', 'BYDAY': ['FR']},
|
||||
'RRULE': {'FREQ': 'MONTHLY', 'BYDAY': ['FR'], 'UNTIL': '2020-10-01'},
|
||||
}
|
||||
qs_params = {'username': username, 'event_id': evt_id}
|
||||
|
||||
|
@ -365,13 +410,45 @@ def test_caldav_event_update_ok(app, transp, caldav_conn):
|
|||
elif k == 'RRULE':
|
||||
assert isinstance(evt[k], icalendar.vRecur)
|
||||
for rk, rv in event['RRULE'].items():
|
||||
assert evt[k][rk] == rv
|
||||
if rk == 'UNTIL':
|
||||
assert evt[k][rk] == datetime.date.fromisoformat(rv)
|
||||
else:
|
||||
assert evt[k][rk] == rv
|
||||
elif k in ('DTSTART', 'DTEND'):
|
||||
assert evt.decoded(k) == datetime.date.fromisoformat(v)
|
||||
else:
|
||||
assert str(evt[k]) == v
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_caldav_event_update_ok_nomock(app, caldav_conn):
|
||||
"""This test let the caldav lib think it is able to retrieve
|
||||
an event in order to modify it.
|
||||
This can trigger errors from icalendar lib when caldav
|
||||
attempt to save the event
|
||||
"""
|
||||
url = tests.utils.generic_endpoint_url('caldav', 'event/update')
|
||||
username = 'toto'
|
||||
evt_id = '01234567-abcd'
|
||||
evt_url = DAV_URL + get_event_path(caldav_conn, username, evt_id)
|
||||
|
||||
event = {
|
||||
'DTSTART': '2020-02-20',
|
||||
'DTEND': '2020-03-30',
|
||||
'SUMMARY': 'foobar',
|
||||
'TRANSP': True,
|
||||
'RRULE': {'FREQ': 'MONTHLY', 'BYDAY': ['FR'], 'UNTIL': '2020-10-01'},
|
||||
}
|
||||
qs_params = {'username': username, 'event_id': evt_id}
|
||||
|
||||
responses.add(responses.GET, evt_url, body=FAKE_ICS)
|
||||
responses.add(responses.PUT, evt_url, status=201)
|
||||
|
||||
resp = app_post(app, url, qs_params, event)
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data']['event_id'] == evt_id
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_caldav_event_update_notfound(app, caldav_conn):
|
||||
url = tests.utils.generic_endpoint_url('caldav', 'event/update')
|
||||
|
|
Loading…
Reference in New Issue