Implement time-range filters for events

This commit is contained in:
Guillaume Ayoub 2016-05-30 14:53:20 +02:00
parent 656680d998
commit 248fc7e9e3
7 changed files with 294 additions and 41 deletions

View File

@ -29,6 +29,7 @@ import posixpath
import re import re
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime, timedelta, timezone
from urllib.parse import unquote, urlparse from urllib.parse import unquote, urlparse
import vobject import vobject
@ -146,9 +147,9 @@ def _comp_match(item, filter_, scope="collection"):
return filter_.get("name") != tag return filter_.get("name") != tag
if filter_[0].tag == _tag("C", "time-range"): if filter_[0].tag == _tag("C", "time-range"):
# Point #3 of rfc4791-9.7.1 # Point #3 of rfc4791-9.7.1
if not _time_range_match(item, filter_): if not _time_range_match(item.item, filter_[0], tag):
return False return False
filter_.remove(filter_[0]) filter_ = filter_[1:]
# Point #4 of rfc4791-9.7.1 # Point #4 of rfc4791-9.7.1
return all( return all(
_prop_match(item, child) if child.tag == _tag("C", "prop-filter") _prop_match(item, child) if child.tag == _tag("C", "prop-filter")
@ -180,26 +181,81 @@ def _prop_match(item, filter_):
return name not in vobject_item.contents return name not in vobject_item.contents
if filter_[0].tag == _tag("C", "time-range"): if filter_[0].tag == _tag("C", "time-range"):
# Point #3 of rfc4791-9.7.2 # Point #3 of rfc4791-9.7.2
if not _time_range_match(item, filter_[0]): if not _time_range_match(vobject_item, filter_[0], name):
return False return False
filter_.remove(filter_[0]) filter_ = filter_[1:]
elif filter_[0].tag == _tag("C", "text-match"): elif filter_[0].tag == _tag("C", "text-match"):
# Point #4 of rfc4791-9.7.2 # Point #4 of rfc4791-9.7.2
if not _text_match(vobject_item, filter_[0], name): if not _text_match(vobject_item, filter_[0], name):
return False return False
filter_.remove(filter_[0]) filter_ = filter_[1:]
return all( return all(
_param_filter_match(vobject_item, param_filter, name) _param_filter_match(vobject_item, param_filter, name)
for param_filter in filter_) for param_filter in filter_)
def _time_range_match(item, filter_): def _time_range_match(vobject_item, filter_, child_name):
"""Check whether the ``item`` matches the time-range ``filter_``. """Check whether the ``item`` matches the time-range ``filter_``.
See rfc4791-9.9. See rfc4791-9.9.
""" """
start = filter_.get("start")
end = filter_.get("end")
if not start and not end:
return False
if start:
start = datetime.strptime(start, "%Y%m%dT%H%M%SZ")
else:
start = datetime.datetime.min
if end:
end = datetime.strptime(end, "%Y%m%dT%H%M%SZ")
else:
end = datetime.datetime.max
start = start.replace(tzinfo=timezone.utc)
end = end.replace(tzinfo=timezone.utc)
child = getattr(vobject_item, child_name.lower())
# Comments give the lines in the tables of the specification
if child_name == "VEVENT":
# TODO: check if there's a timezone
dtstart = child.dtstart.value
if not isinstance(dtstart, datetime):
dtstart_is_datetime = False
# TODO: changing dates to datetimes may be wrong because of tz
dtstart = datetime.combine(dtstart, datetime.min.time()).replace(
tzinfo=timezone.utc)
else:
dtstart_is_datetime = True
dtend = getattr(child, "dtend", None)
duration = getattr(child, "duration", None)
if dtend is not None:
# Line 1
dtend = dtend.value
if not isinstance(dtend, datetime):
dtend = datetime.combine(dtend, datetime.min.time()).replace(
tzinfo=timezone.utc)
return start < dtend and end > dtstart
elif duration is not None:
duration = duration.value
if duration.seconds > 0:
# Line 2
return start < dtstart + duration and end > dtstart
else:
# Line 3
return start <= dtstart and end > dtstart
elif dtstart_is_datetime:
# Line 4
return start <= dtstart and end > dtstart
else:
# Line 5
return start < dtstart + timedelta(days=1) and end > dtstart
elif child_name == "VTODO":
# TODO: implement this # TODO: implement this
pass
elif child_name == "VJOURNAL":
# TODO: implement this
pass
return True return True
@ -491,11 +547,11 @@ def _propfind_response(path, item, props, user, write=False):
for href, _ in item.list(): for href, _ in item.list():
event = item.get(href) event = item.get(href)
if "vtimezone" in event.contents: if "vtimezone" in event.contents:
for timezone in event.vtimezone_list: for timezone_ in event.vtimezone_list:
timezones.add(timezone) timezones.add(timezone_)
collection = vobject.iCalendar() collection = vobject.iCalendar()
for timezone in timezones: for timezone_ in timezones:
collection.add(timezone) collection.add(timezone_)
element.text = collection.serialize() element.text = collection.serialize()
elif tag == _tag("D", "displayname"): elif tag == _tag("D", "displayname"):
element.text = item.get_meta("D:displayname") or item.path element.text = item.get_meta("D:displayname") or item.path

View File

@ -23,12 +23,12 @@ BEGIN:VEVENT
CREATED:20130902T150157Z CREATED:20130902T150157Z
LAST-MODIFIED:20130902T150158Z LAST-MODIFIED:20130902T150158Z
DTSTAMP:20130902T150158Z DTSTAMP:20130902T150158Z
UID:event UID:event1
SUMMARY:Event SUMMARY:Event
ORGANIZER:mailto:unclesam@example.com ORGANIZER:mailto:unclesam@example.com
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Jane Doe:MAILTO:janedoe@example.com ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Jane Doe:MAILTO:janedoe@example.com
ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="MAILTO:bob@host.com";PARTSTAT=ACCEPTED;CN=John Doe:MAILTO:johndoe@example.com ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="MAILTO:bob@host.com";PARTSTAT=ACCEPTED;CN=John Doe:MAILTO:johndoe@example.com
DTSTART;TZID=Europe/Paris:20130902T180000 DTSTART;TZID=Europe/Paris:20130901T180000
DTEND;TZID=Europe/Paris:20130902T190000 DTEND;TZID=Europe/Paris:20130901T190000
END:VEVENT END:VEVENT
END:VCALENDAR END:VCALENDAR

31
tests/static/event2.ics Normal file
View File

@ -0,0 +1,31 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20130902T150157Z
LAST-MODIFIED:20130902T150158Z
DTSTAMP:20130902T150158Z
UID:event2
SUMMARY:Event2
DTSTART;TZID=Europe/Paris:20130902T180000
DTEND;TZID=Europe/Paris:20130902T190000
END:VEVENT
END:VCALENDAR

31
tests/static/event3.ics Normal file
View File

@ -0,0 +1,31 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20130902T150157Z
LAST-MODIFIED:20130902T150158Z
DTSTAMP:20130902T150158Z
UID:event3
SUMMARY:Event3
DTSTART;TZID=Europe/Paris:20130903
DURATION:PT1H
END:VEVENT
END:VCALENDAR

30
tests/static/event4.ics Normal file
View File

@ -0,0 +1,30 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20130902T150157Z
LAST-MODIFIED:20130902T150158Z
DTSTAMP:20130902T150158Z
UID:event4
SUMMARY:Event4
DTSTART;TZID=Europe/Paris:20130904T180000
END:VEVENT
END:VCALENDAR

30
tests/static/event5.ics Normal file
View File

@ -0,0 +1,30 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20130902T150157Z
LAST-MODIFIED:20130902T150158Z
DTSTAMP:20130902T150158Z
UID:event5
SUMMARY:Event5
DTSTART;TZID=Europe/Paris:20130905
END:VEVENT
END:VCALENDAR

View File

@ -54,8 +54,8 @@ class BaseRequests:
"""Add an event.""" """Add an event."""
self.request( self.request(
"PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR") "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
event = get_file_content("event.ics") event = get_file_content("event1.ics")
path = "/calendar.ics/event.ics" path = "/calendar.ics/event1.ics"
status, headers, answer = self.request("PUT", path, event) status, headers, answer = self.request("PUT", path, event)
assert status == 201 assert status == 201
status, headers, answer = self.request("GET", path) status, headers, answer = self.request("GET", path)
@ -83,8 +83,8 @@ class BaseRequests:
"""Delete an event.""" """Delete an event."""
self.request( self.request(
"PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR") "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
event = get_file_content("event.ics") event = get_file_content("event1.ics")
path = "/calendar.ics/event.ics" path = "/calendar.ics/event1.ics"
status, headers, answer = self.request("PUT", path, event) status, headers, answer = self.request("PUT", path, event)
# Then we send a DELETE request # Then we send a DELETE request
status, headers, answer = self.request("DELETE", path) status, headers, answer = self.request("DELETE", path)
@ -93,13 +93,14 @@ class BaseRequests:
status, headers, answer = self.request("GET", "/calendar.ics/") status, headers, answer = self.request("GET", "/calendar.ics/")
assert "VEVENT" not in answer assert "VEVENT" not in answer
def _test_filter(self, filters): def _test_filter(self, filters, events=1):
filters_text = "".join( filters_text = "".join(
"<C:filter>%s</C:filter>" % filter_ for filter_ in filters) "<C:filter>%s</C:filter>" % filter_ for filter_ in filters)
self.request( self.request(
"PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR") "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
event = get_file_content("event.ics") for i in range(events):
self.request("PUT", "/calendar.ics/event.ics", event) event = get_file_content("event%i.ics" % (i + 1))
self.request("PUT", "/calendar.ics/event%i.ics" % (i + 1), event)
status, headers, answer = self.request( status, headers, answer = self.request(
"REPORT", "/calendar.ics", "REPORT", "/calendar.ics",
"""<?xml version="1.0" encoding="utf-8" ?> """<?xml version="1.0" encoding="utf-8" ?>
@ -113,29 +114,29 @@ class BaseRequests:
def test_calendar_tag_filter(self): def test_calendar_tag_filter(self):
"""Report request with tag-based filter on calendar.""" """Report request with tag-based filter on calendar."""
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"></C:comp-filter>"""]) <C:comp-filter name="VCALENDAR"></C:comp-filter>"""])
def test_item_tag_filter(self): def test_item_tag_filter(self):
"""Report request with tag-based filter on an item.""" """Report request with tag-based filter on an item."""
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"></C:comp-filter> <C:comp-filter name="VEVENT"></C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VTODO"></C:comp-filter> <C:comp-filter name="VTODO"></C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
def test_item_not_tag_filter(self): def test_item_not_tag_filter(self):
"""Report request with tag-based is-not filter on an item.""" """Report request with tag-based is-not filter on an item."""
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:is-not-defined /> <C:is-not-defined />
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VTODO"> <C:comp-filter name="VTODO">
<C:is-not-defined /> <C:is-not-defined />
@ -144,13 +145,13 @@ class BaseRequests:
def test_item_prop_filter(self): def test_item_prop_filter(self):
"""Report request with prop-based filter on an item.""" """Report request with prop-based filter on an item."""
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="SUMMARY"></C:prop-filter> <C:prop-filter name="SUMMARY"></C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="UNKNOWN"></C:prop-filter> <C:prop-filter name="UNKNOWN"></C:prop-filter>
@ -159,7 +160,7 @@ class BaseRequests:
def test_item_not_prop_filter(self): def test_item_not_prop_filter(self):
"""Report request with prop-based is-not filter on an item.""" """Report request with prop-based is-not filter on an item."""
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="SUMMARY"> <C:prop-filter name="SUMMARY">
@ -167,7 +168,7 @@ class BaseRequests:
</C:prop-filter> </C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="UNKNOWN"> <C:prop-filter name="UNKNOWN">
@ -178,7 +179,7 @@ class BaseRequests:
def test_mutiple_filters(self): def test_mutiple_filters(self):
"""Report request with multiple filters on an item.""" """Report request with multiple filters on an item."""
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="SUMMARY"> <C:prop-filter name="SUMMARY">
@ -193,7 +194,7 @@ class BaseRequests:
</C:prop-filter> </C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="SUMMARY"></C:prop-filter> <C:prop-filter name="SUMMARY"></C:prop-filter>
@ -206,7 +207,7 @@ class BaseRequests:
</C:prop-filter> </C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="SUMMARY"></C:prop-filter> <C:prop-filter name="SUMMARY"></C:prop-filter>
@ -218,7 +219,7 @@ class BaseRequests:
def test_text_match_filter(self): def test_text_match_filter(self):
"""Report request with text-match filter on calendar.""" """Report request with text-match filter on calendar."""
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="SUMMARY"> <C:prop-filter name="SUMMARY">
@ -226,7 +227,7 @@ class BaseRequests:
</C:prop-filter> </C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="UNKNOWN"> <C:prop-filter name="UNKNOWN">
@ -234,7 +235,7 @@ class BaseRequests:
</C:prop-filter> </C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="SUMMARY"> <C:prop-filter name="SUMMARY">
@ -242,7 +243,7 @@ class BaseRequests:
</C:prop-filter> </C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="SUMMARY"> <C:prop-filter name="SUMMARY">
@ -253,7 +254,7 @@ class BaseRequests:
def test_param_filter(self): def test_param_filter(self):
"""Report request with param-filter on calendar.""" """Report request with param-filter on calendar."""
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="ATTENDEE"> <C:prop-filter name="ATTENDEE">
@ -264,7 +265,7 @@ class BaseRequests:
</C:prop-filter> </C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="ATTENDEE"> <C:prop-filter name="ATTENDEE">
@ -275,7 +276,7 @@ class BaseRequests:
</C:prop-filter> </C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" not in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="ATTENDEE"> <C:prop-filter name="ATTENDEE">
@ -285,7 +286,7 @@ class BaseRequests:
</C:prop-filter> </C:prop-filter>
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
assert "href>/calendar.ics/event.ics</" in self._test_filter([""" assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
<C:comp-filter name="VCALENDAR"> <C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT"> <C:comp-filter name="VEVENT">
<C:prop-filter name="ATTENDEE"> <C:prop-filter name="ATTENDEE">
@ -296,6 +297,80 @@ class BaseRequests:
</C:comp-filter> </C:comp-filter>
</C:comp-filter>"""]) </C:comp-filter>"""])
def test_time_range_filter(self):
"""Report request with time-range filter on calendar."""
answer = self._test_filter(["""
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range start="20130801T000000Z" end="20131001T000000Z"/>
</C:comp-filter>
</C:comp-filter>"""], events=5)
assert "href>/calendar.ics/event1.ics</" in answer
assert "href>/calendar.ics/event2.ics</" in answer
assert "href>/calendar.ics/event3.ics</" in answer
assert "href>/calendar.ics/event4.ics</" in answer
assert "href>/calendar.ics/event5.ics</" in answer
answer = self._test_filter(["""
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:prop-filter name="ATTENDEE">
<C:param-filter name="PARTSTAT">
<C:is-not-defined />
</C:param-filter>
</C:prop-filter>
<C:time-range start="20130801T000000Z" end="20131001T000000Z"/>
</C:comp-filter>
</C:comp-filter>"""], events=5)
assert "href>/calendar.ics/event1.ics</" not in answer
assert "href>/calendar.ics/event2.ics</" not in answer
assert "href>/calendar.ics/event3.ics</" not in answer
assert "href>/calendar.ics/event4.ics</" not in answer
assert "href>/calendar.ics/event5.ics</" not in answer
answer = self._test_filter(["""
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range start="20130902T000000Z" end="20131001T000000Z"/>
</C:comp-filter>
</C:comp-filter>"""], events=5)
assert "href>/calendar.ics/event1.ics</" not in answer
assert "href>/calendar.ics/event2.ics</" in answer
assert "href>/calendar.ics/event3.ics</" in answer
assert "href>/calendar.ics/event4.ics</" in answer
assert "href>/calendar.ics/event5.ics</" in answer
answer = self._test_filter(["""
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range start="20130903T000000Z" end="20131001T000000Z"/>
</C:comp-filter>
</C:comp-filter>"""], events=5)
assert "href>/calendar.ics/event1.ics</" not in answer
assert "href>/calendar.ics/event2.ics</" not in answer
assert "href>/calendar.ics/event3.ics</" in answer
assert "href>/calendar.ics/event4.ics</" in answer
assert "href>/calendar.ics/event5.ics</" in answer
answer = self._test_filter(["""
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range start="20130903T000000Z" end="20130904T000000Z"/>
</C:comp-filter>
</C:comp-filter>"""], events=5)
assert "href>/calendar.ics/event1.ics</" not in answer
assert "href>/calendar.ics/event2.ics</" not in answer
assert "href>/calendar.ics/event3.ics</" in answer
assert "href>/calendar.ics/event4.ics</" not in answer
assert "href>/calendar.ics/event5.ics</" not in answer
answer = self._test_filter(["""
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range start="20130805T000000Z" end="20130810T000000Z"/>
</C:comp-filter>
</C:comp-filter>"""], events=5)
assert "href>/calendar.ics/event1.ics</" not in answer
assert "href>/calendar.ics/event2.ics</" not in answer
assert "href>/calendar.ics/event3.ics</" not in answer
assert "href>/calendar.ics/event4.ics</" not in answer
assert "href>/calendar.ics/event5.ics</" not in answer
class TestMultiFileSystem(BaseRequests, BaseTest): class TestMultiFileSystem(BaseRequests, BaseTest):
"""Base class for filesystem tests.""" """Base class for filesystem tests."""