mirror of
https://github.com/Kozea/Radicale.git
synced 2025-09-15 20:36:55 +00:00
Merge pull request #1834 from metallerok/1812_test_specific_event
Fixed an issue where non-recurring events were not included in the response when requesting an expand report
This commit is contained in:
commit
a824ae6c14
4 changed files with 163 additions and 51 deletions
|
@ -363,12 +363,12 @@ def _expand(
|
||||||
# Split the vevents included in the component into one that contains the
|
# Split the vevents included in the component into one that contains the
|
||||||
# recurrence information and others that contain a recurrence id to
|
# recurrence information and others that contain a recurrence id to
|
||||||
# override instances.
|
# override instances.
|
||||||
vevent_recurrence, vevents_overridden = _split_overridden_vevents(vevent_component)
|
base_vevent, vevents_overridden = _split_overridden_vevents(vevent_component)
|
||||||
|
|
||||||
dt_format = DT_FORMAT_TIMESTAMP
|
dt_format = DT_FORMAT_TIMESTAMP
|
||||||
all_day_event = False
|
all_day_event = False
|
||||||
|
|
||||||
if type(vevent_recurrence.dtstart.value) is datetime.date:
|
if type(base_vevent.dtstart.value) is datetime.date:
|
||||||
# If an event comes to us with a dtstart specified as a date
|
# If an event comes to us with a dtstart specified as a date
|
||||||
# then in the response we return the date, not datetime
|
# then in the response we return the date, not datetime
|
||||||
dt_format = DT_FORMAT_DATE
|
dt_format = DT_FORMAT_DATE
|
||||||
|
@ -385,11 +385,11 @@ def _expand(
|
||||||
_strip_single_event(vevent, dt_format)
|
_strip_single_event(vevent, dt_format)
|
||||||
|
|
||||||
duration = None
|
duration = None
|
||||||
if hasattr(vevent_recurrence, "dtend"):
|
if hasattr(base_vevent, "dtend"):
|
||||||
duration = vevent_recurrence.dtend.value - vevent_recurrence.dtstart.value
|
duration = base_vevent.dtend.value - base_vevent.dtstart.value
|
||||||
elif hasattr(vevent_recurrence, "duration"):
|
elif hasattr(base_vevent, "duration"):
|
||||||
try:
|
try:
|
||||||
duration = vevent_recurrence.duration.value
|
duration = base_vevent.duration.value
|
||||||
if duration.total_seconds() <= 0:
|
if duration.total_seconds() <= 0:
|
||||||
logger.warning("Invalid DURATION: %s", duration)
|
logger.warning("Invalid DURATION: %s", duration)
|
||||||
duration = None
|
duration = None
|
||||||
|
@ -399,8 +399,8 @@ def _expand(
|
||||||
|
|
||||||
# Generate EXDATE to remove from expansion range
|
# Generate EXDATE to remove from expansion range
|
||||||
exdates_set: set[datetime.datetime] = set()
|
exdates_set: set[datetime.datetime] = set()
|
||||||
if hasattr(vevent_recurrence, 'exdate'):
|
if hasattr(base_vevent, 'exdate'):
|
||||||
exdates = vevent_recurrence.exdate.value
|
exdates = base_vevent.exdate.value
|
||||||
if not isinstance(exdates, list):
|
if not isinstance(exdates, list):
|
||||||
exdates = [exdates]
|
exdates = [exdates]
|
||||||
|
|
||||||
|
@ -412,9 +412,14 @@ def _expand(
|
||||||
|
|
||||||
logger.debug("EXDATE values: %s", exdates_set)
|
logger.debug("EXDATE values: %s", exdates_set)
|
||||||
|
|
||||||
|
events_for_filtering = vevents_overridden
|
||||||
|
|
||||||
rruleset = None
|
rruleset = None
|
||||||
if hasattr(vevent_recurrence, 'rrule'):
|
if hasattr(base_vevent, 'rrule'):
|
||||||
rruleset = vevent_recurrence.getrruleset()
|
rruleset = base_vevent.getrruleset()
|
||||||
|
else:
|
||||||
|
# if event does not have rrule, only include base event
|
||||||
|
events_for_filtering = [base_vevent]
|
||||||
|
|
||||||
filtered_vevents = []
|
filtered_vevents = []
|
||||||
if rruleset:
|
if rruleset:
|
||||||
|
@ -439,7 +444,7 @@ def _expand(
|
||||||
.format(max_occurrence))
|
.format(max_occurrence))
|
||||||
|
|
||||||
_strip_component(vevent_component)
|
_strip_component(vevent_component)
|
||||||
_strip_single_event(vevent_recurrence, dt_format)
|
_strip_single_event(base_vevent, dt_format)
|
||||||
|
|
||||||
i_overridden = 0
|
i_overridden = 0
|
||||||
|
|
||||||
|
@ -466,7 +471,7 @@ def _expand(
|
||||||
|
|
||||||
if not vevent:
|
if not vevent:
|
||||||
# Create new instance from recurrence
|
# Create new instance from recurrence
|
||||||
vevent = copy.deepcopy(vevent_recurrence)
|
vevent = copy.deepcopy(base_vevent)
|
||||||
|
|
||||||
# For all day events, the system timezone may influence the
|
# For all day events, the system timezone may influence the
|
||||||
# results, so use recurrence_dt
|
# results, so use recurrence_dt
|
||||||
|
@ -496,9 +501,9 @@ def _expand(
|
||||||
|
|
||||||
filtered_vevents.append(vevent)
|
filtered_vevents.append(vevent)
|
||||||
|
|
||||||
# Filter overridden and recurrence base events
|
# Filter overridden and non-recurring events
|
||||||
if time_range_start is not None and time_range_end is not None:
|
if time_range_start is not None and time_range_end is not None:
|
||||||
for vevent in vevents_overridden:
|
for vevent in events_for_filtering:
|
||||||
dtstart = vevent.dtstart.value
|
dtstart = vevent.dtstart.value
|
||||||
|
|
||||||
# Handle string values for DTSTART/DTEND
|
# Handle string values for DTSTART/DTEND
|
||||||
|
|
31
radicale/tests/static/event_issue1812_2.ics
Normal file
31
radicale/tests/static/event_issue1812_2.ics
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//algoo.fr//NONSGML Open Calendar v0.9//EN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Paris
|
||||||
|
LAST-MODIFIED:20250523T094234Z
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:19701025T030000Z
|
||||||
|
RRULE:BYDAY=-1SU;BYMONTH=10;FREQ=YEARLY
|
||||||
|
TZNAME:CET
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:19700329T020000Z
|
||||||
|
RRULE:BYDAY=-1SU;BYMONTH=3;FREQ=YEARLY
|
||||||
|
TZNAME:CEST
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:a07cfa8b-0ce6-4956-800d-c0bfe1f0730a
|
||||||
|
DTSTART;TZID=Europe/Paris;VALUE=DATE:20250716
|
||||||
|
DTEND;TZID=Europe/Paris;VALUE=DATE:20250718
|
||||||
|
DTSTAMP;VALUE=DATE-TIME:20250721T075355Z
|
||||||
|
RRULE:FREQ=WEEKLY
|
||||||
|
SEQUENCE:1
|
||||||
|
SUMMARY:bla
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
31
radicale/tests/static/event_issue1812_3.ics
Normal file
31
radicale/tests/static/event_issue1812_3.ics
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//algoo.fr//NONSGML Open Calendar v0.9//EN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Paris
|
||||||
|
LAST-MODIFIED:20250523T094234Z
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:19701025T030000Z
|
||||||
|
RRULE:BYDAY=-1SU;BYMONTH=10;FREQ=YEARLY
|
||||||
|
TZNAME:CET
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:19700329T020000Z
|
||||||
|
RRULE:BYDAY=-1SU;BYMONTH=3;FREQ=YEARLY
|
||||||
|
TZNAME:CEST
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:c6be8b2c-3d72-453c-b698-4f25cdf1569e
|
||||||
|
DTSTART;TZID=Europe/Paris;VALUE=DATE-TIME:20250716T110000
|
||||||
|
DTEND;TZID=Europe/Paris;VALUE=DATE-TIME:20250716T120000
|
||||||
|
ATTENDEE;CN=Corentin;ROLE=REQ-PARTICIPANT:MAILTO:corentin.jeanne@algoo.fr
|
||||||
|
DTSTAMP:20250718T151312Z
|
||||||
|
ORGANIZER;CN=Sigma:MAILTO:lambda@lambda.lambda
|
||||||
|
SUMMARY:Test mail notifications 2
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -132,7 +132,7 @@ permissions: RrWw""")
|
||||||
self._req_without_expand(expected_uid, start, end))
|
self._req_without_expand(expected_uid, start, end))
|
||||||
assert len(responses) == 1
|
assert len(responses) == 1
|
||||||
response_without_expand = responses[f'/calendar.ics/{expected_uid}.ics']
|
response_without_expand = responses[f'/calendar.ics/{expected_uid}.ics']
|
||||||
assert not isinstance(response_without_expand, int)
|
assert isinstance(response_without_expand, dict)
|
||||||
status, element = response_without_expand["C:calendar-data"]
|
status, element = response_without_expand["C:calendar-data"]
|
||||||
|
|
||||||
assert status == 200 and element.text
|
assert status == 200 and element.text
|
||||||
|
@ -158,7 +158,7 @@ permissions: RrWw""")
|
||||||
assert len(responses) == 1
|
assert len(responses) == 1
|
||||||
|
|
||||||
response_with_expand = responses[f'/calendar.ics/{expected_uid}.ics']
|
response_with_expand = responses[f'/calendar.ics/{expected_uid}.ics']
|
||||||
assert not isinstance(response_with_expand, int)
|
assert isinstance(response_with_expand, dict)
|
||||||
status, element = response_with_expand["C:calendar-data"]
|
status, element = response_with_expand["C:calendar-data"]
|
||||||
|
|
||||||
logger.debug("lbt: element is %s",
|
logger.debug("lbt: element is %s",
|
||||||
|
@ -196,7 +196,7 @@ permissions: RrWw""")
|
||||||
self._req_without_expand(expected_uid, start, end))
|
self._req_without_expand(expected_uid, start, end))
|
||||||
assert len(responses) == 1
|
assert len(responses) == 1
|
||||||
response_without_expand = responses[f'/calendar.ics/{expected_uid}.ics']
|
response_without_expand = responses[f'/calendar.ics/{expected_uid}.ics']
|
||||||
assert not isinstance(response_without_expand, int)
|
assert isinstance(response_without_expand, dict)
|
||||||
status, element = response_without_expand["C:calendar-data"]
|
status, element = response_without_expand["C:calendar-data"]
|
||||||
|
|
||||||
assert status == 200 and element.text
|
assert status == 200 and element.text
|
||||||
|
@ -467,3 +467,48 @@ permissions: RrWw""")
|
||||||
assert "BEGIN:VEVENT" in element.text
|
assert "BEGIN:VEVENT" in element.text
|
||||||
assert "RECURRENCE-ID:20060103T170000Z" in element.text
|
assert "RECURRENCE-ID:20060103T170000Z" in element.text
|
||||||
assert "DTSTART:20060103T170000Z" in element.text
|
assert "DTSTART:20060103T170000Z" in element.text
|
||||||
|
|
||||||
|
def test_expand_report_for_recurring_and_non_recurring_events(self) -> None:
|
||||||
|
"""Test calendar-query with time-range filter, matches one VEVENT."""
|
||||||
|
self.mkcalendar("/test/")
|
||||||
|
self.put("/test/event.ics/", get_file_content("event_issue1812_2.ics"))
|
||||||
|
self.put("/test/event2.ics/", get_file_content("event_issue1812_3.ics"))
|
||||||
|
|
||||||
|
request = """
|
||||||
|
<c:calendar-query xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:d="DAV:">
|
||||||
|
<d:prop>
|
||||||
|
<d:getetag/>
|
||||||
|
<c:calendar-data>
|
||||||
|
<c:expand start="20250629T220000Z" end="20250803T220000Z"/>
|
||||||
|
</c:calendar-data>
|
||||||
|
</d:prop>
|
||||||
|
<c:filter>
|
||||||
|
<c:comp-filter name="VCALENDAR">
|
||||||
|
<c:comp-filter name="VEVENT">
|
||||||
|
<c:time-range start="20250629T220000Z" end="20250803T220000Z"/>
|
||||||
|
</c:comp-filter>
|
||||||
|
</c:comp-filter>
|
||||||
|
</c:filter>
|
||||||
|
</c:calendar-query>
|
||||||
|
"""
|
||||||
|
status, responses = self.report("/test", request)
|
||||||
|
assert status == 207
|
||||||
|
assert len(responses) == 2
|
||||||
|
assert isinstance(responses, dict)
|
||||||
|
assert "/test/event.ics" in responses
|
||||||
|
assert "/test/event2.ics" in responses
|
||||||
|
assert isinstance(responses["/test/event.ics"], dict)
|
||||||
|
assert isinstance(responses["/test/event2.ics"], dict)
|
||||||
|
|
||||||
|
assert "C:calendar-data" in responses["/test/event.ics"]
|
||||||
|
status, event1_calendar_data = responses["/test/event.ics"]["C:calendar-data"]
|
||||||
|
assert event1_calendar_data.text
|
||||||
|
assert "UID:a07cfa8b-0ce6-4956-800d-c0bfe1f0730a" in event1_calendar_data.text
|
||||||
|
assert "RECURRENCE-ID:20250716" in event1_calendar_data.text
|
||||||
|
assert "RECURRENCE-ID:20250723" in event1_calendar_data.text
|
||||||
|
assert "RECURRENCE-ID:20250730" in event1_calendar_data.text
|
||||||
|
|
||||||
|
assert "C:calendar-data" in responses["/test/event2.ics"]
|
||||||
|
status, event2_calendar_data = responses["/test/event2.ics"]["C:calendar-data"]
|
||||||
|
assert event2_calendar_data.text
|
||||||
|
assert "UID:c6be8b2c-3d72-453c-b698-4f25cdf1569e" in event2_calendar_data.text
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue