test_expand.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. # This file is part of Radicale - CalDAV and CardDAV server
  2. # Copyright © 2012-2017 Guillaume Ayoub
  3. # Copyright © 2017-2019 Unrud <unrud@outlook.com>
  4. #
  5. # This library is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This library is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  17. """
  18. Radicale tests with expand requests.
  19. """
  20. import os
  21. from typing import ClassVar, List
  22. from xml.etree import ElementTree
  23. from radicale.log import logger
  24. from radicale.tests import BaseTest
  25. from radicale.tests.helpers import get_file_content
  26. ONLY_DATES = True
  27. CONTAINS_TIMES = False
  28. class TestExpandRequests(BaseTest):
  29. """Tests with expand requests."""
  30. # Allow skipping sync-token tests, when not fully supported by the backend
  31. full_sync_token_support: ClassVar[bool] = True
  32. def setup_method(self) -> None:
  33. BaseTest.setup_method(self)
  34. rights_file_path = os.path.join(self.colpath, "rights")
  35. with open(rights_file_path, "w") as f:
  36. f.write("""\
  37. [permit delete collection]
  38. user: .*
  39. collection: test-permit-delete
  40. permissions: RrWwD
  41. [forbid delete collection]
  42. user: .*
  43. collection: test-forbid-delete
  44. permissions: RrWwd
  45. [permit overwrite collection]
  46. user: .*
  47. collection: test-permit-overwrite
  48. permissions: RrWwO
  49. [forbid overwrite collection]
  50. user: .*
  51. collection: test-forbid-overwrite
  52. permissions: RrWwo
  53. [allow all]
  54. user: .*
  55. collection: .*
  56. permissions: RrWw""")
  57. self.configure({"rights": {"file": rights_file_path,
  58. "type": "from_file"}})
  59. def _test_expand(self,
  60. expected_uid: str,
  61. start: str,
  62. end: str,
  63. expected_recurrence_ids: List[str],
  64. expected_start_times: List[str],
  65. expected_end_times: List[str],
  66. only_dates: bool,
  67. nr_uids: int) -> None:
  68. self.put("/calendar.ics/", get_file_content(f"{expected_uid}.ics"))
  69. req_body_without_expand = \
  70. f"""<?xml version="1.0" encoding="utf-8" ?>
  71. <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
  72. <D:prop>
  73. <C:calendar-data>
  74. </C:calendar-data>
  75. </D:prop>
  76. <C:filter>
  77. <C:comp-filter name="VCALENDAR">
  78. <C:comp-filter name="VEVENT">
  79. <C:time-range start="{start}" end="{end}"/>
  80. </C:comp-filter>
  81. </C:comp-filter>
  82. </C:filter>
  83. </C:calendar-query>
  84. """
  85. _, responses = self.report("/calendar.ics/", req_body_without_expand)
  86. assert len(responses) == 1
  87. response_without_expand = responses[f'/calendar.ics/{expected_uid}.ics']
  88. assert not isinstance(response_without_expand, int)
  89. status, element = response_without_expand["C:calendar-data"]
  90. assert status == 200 and element.text
  91. assert "RRULE" in element.text
  92. if not only_dates:
  93. assert "BEGIN:VTIMEZONE" in element.text
  94. if nr_uids == 1:
  95. assert "RECURRENCE-ID" not in element.text
  96. uids: List[str] = []
  97. for line in element.text.split("\n"):
  98. if line.startswith("UID:"):
  99. uid = line[len("UID:"):]
  100. assert uid == expected_uid
  101. uids.append(uid)
  102. assert len(uids) == nr_uids
  103. req_body_with_expand = \
  104. f"""<?xml version="1.0" encoding="utf-8" ?>
  105. <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
  106. <D:prop>
  107. <C:calendar-data>
  108. <C:expand start="{start}" end="{end}"/>
  109. </C:calendar-data>
  110. </D:prop>
  111. <C:filter>
  112. <C:comp-filter name="VCALENDAR">
  113. <C:comp-filter name="VEVENT">
  114. <C:time-range start="{start}" end="{end}"/>
  115. </C:comp-filter>
  116. </C:comp-filter>
  117. </C:filter>
  118. </C:calendar-query>
  119. """
  120. _, responses = self.report("/calendar.ics/", req_body_with_expand)
  121. assert len(responses) == 1
  122. response_with_expand = responses[f'/calendar.ics/{expected_uid}.ics']
  123. assert not isinstance(response_with_expand, int)
  124. status, element = response_with_expand["C:calendar-data"]
  125. logger.debug("lbt: element is %s",
  126. ElementTree.tostring(element, encoding='unicode'))
  127. assert status == 200 and element.text
  128. assert "RRULE" not in element.text
  129. assert "BEGIN:VTIMEZONE" not in element.text
  130. uids = []
  131. recurrence_ids = []
  132. for line in element.text.split("\n"):
  133. if line.startswith("UID:"):
  134. assert line == f"UID:{expected_uid}"
  135. uids.append(line)
  136. if line.startswith("RECURRENCE-ID:"):
  137. assert line in expected_recurrence_ids
  138. recurrence_ids.append(line)
  139. if line.startswith("DTSTART:"):
  140. assert line in expected_start_times
  141. if line.startswith("DTEND:"):
  142. assert line in expected_end_times
  143. assert len(uids) == len(expected_recurrence_ids)
  144. assert len(set(recurrence_ids)) == len(expected_recurrence_ids)
  145. def test_report_with_expand_property(self) -> None:
  146. """Test report with expand property"""
  147. self._test_expand(
  148. "event_daily_rrule",
  149. "20060103T000000Z",
  150. "20060105T000000Z",
  151. ["RECURRENCE-ID:20060103T170000Z", "RECURRENCE-ID:20060104T170000Z"],
  152. ["DTSTART:20060103T170000Z", "DTSTART:20060104T170000Z"],
  153. [],
  154. CONTAINS_TIMES,
  155. 1
  156. )
  157. def test_report_with_expand_property_start_inside(self) -> None:
  158. """Test report with expand property start inside"""
  159. self._test_expand(
  160. "event_daily_rrule",
  161. "20060103T171500Z",
  162. "20060105T000000Z",
  163. ["RECURRENCE-ID:20060103T170000Z", "RECURRENCE-ID:20060104T170000Z"],
  164. ["DTSTART:20060103T170000Z", "DTSTART:20060104T170000Z"],
  165. [],
  166. CONTAINS_TIMES,
  167. 1
  168. )
  169. def test_report_with_expand_property_just_inside(self) -> None:
  170. """Test report with expand property start and end inside event"""
  171. self._test_expand(
  172. "event_daily_rrule",
  173. "20060103T171500Z",
  174. "20060103T171501Z",
  175. ["RECURRENCE-ID:20060103T170000Z"],
  176. ["DTSTART:20060103T170000Z"],
  177. [],
  178. CONTAINS_TIMES,
  179. 1
  180. )
  181. def test_report_with_expand_property_issue1812(self) -> None:
  182. """Test report with expand property for issue 1812"""
  183. self._test_expand(
  184. "event_issue1812",
  185. "20250127T183000Z",
  186. "20250127T183001Z",
  187. ["RECURRENCE-ID:20250127T180000Z"],
  188. ["DTSTART:20250127T180000Z"],
  189. ["DTEND:20250127T233000Z"],
  190. CONTAINS_TIMES,
  191. 11
  192. )
  193. def test_report_with_expand_property_issue1812_DS(self) -> None:
  194. """Test report with expand property for issue 1812 - DS active"""
  195. self._test_expand(
  196. "event_issue1812",
  197. "20250627T183000Z",
  198. "20250627T183001Z",
  199. ["RECURRENCE-ID:20250627T170000Z"],
  200. ["DTSTART:20250627T170000Z"],
  201. ["DTEND:20250627T223000Z"],
  202. CONTAINS_TIMES,
  203. 11
  204. )
  205. def test_report_with_expand_property_all_day_event(self) -> None:
  206. """Test report with expand property for all day events"""
  207. self._test_expand(
  208. "event_full_day_rrule",
  209. "20060103T000000Z",
  210. "20060105T000000Z",
  211. ["RECURRENCE-ID:20060103", "RECURRENCE-ID:20060104", "RECURRENCE-ID:20060105"],
  212. ["DTSTART:20060103", "DTSTART:20060104", "DTSTART:20060105"],
  213. ["DTEND:20060104", "DTEND:20060105", "DTEND:20060106"],
  214. ONLY_DATES,
  215. 1
  216. )
  217. def test_report_with_expand_property_overridden(self) -> None:
  218. """Test report with expand property with overridden events"""
  219. self._test_expand(
  220. "event_daily_rrule_overridden",
  221. "20060103T000000Z",
  222. "20060105T000000Z",
  223. ["RECURRENCE-ID:20060103T170000Z", "RECURRENCE-ID:20060104T170000Z"],
  224. ["DTSTART:20060103T170000Z", "DTSTART:20060104T190000Z"],
  225. [],
  226. CONTAINS_TIMES,
  227. 2
  228. )
  229. def test_report_with_expand_property_timezone(self):
  230. self._test_expand(
  231. "event_weekly_rrule",
  232. "20060320T000000Z",
  233. "20060414T000000Z",
  234. [
  235. "RECURRENCE-ID:20060321T200000Z",
  236. "RECURRENCE-ID:20060328T200000Z",
  237. "RECURRENCE-ID:20060404T190000Z",
  238. "RECURRENCE-ID:20060411T190000Z",
  239. ],
  240. [
  241. "DTSTART:20060321T200000Z",
  242. "DTSTART:20060328T200000Z",
  243. "DTSTART:20060404T190000Z",
  244. "DTSTART:20060411T190000Z",
  245. ],
  246. [],
  247. CONTAINS_TIMES,
  248. 1
  249. )