1
0

test_base.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  1. # This file is part of Radicale Server - Calendar Server
  2. # Copyright © 2012-2016 Guillaume Ayoub
  3. #
  4. # This library is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This library is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. Radicale tests with simple requests.
  18. """
  19. import logging
  20. import os
  21. import posixpath
  22. import shutil
  23. import tempfile
  24. import pytest
  25. from radicale import Application, config
  26. from . import BaseTest
  27. from .helpers import get_file_content
  28. class BaseRequestsMixIn:
  29. """Tests with simple requests."""
  30. def test_root(self):
  31. """GET request at "/"."""
  32. status, headers, answer = self.request("GET", "/")
  33. assert status == 200
  34. assert "Radicale works!" in answer
  35. # Test the creation of the collection
  36. self.request("MKCOL", "/calendar.ics/")
  37. self.request(
  38. "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
  39. status, headers, answer = self.request("GET", "/calendar.ics/")
  40. assert "BEGIN:VCALENDAR" in answer
  41. assert "END:VCALENDAR" in answer
  42. def test_add_event(self):
  43. """Add an event."""
  44. self.request("MKCOL", "/calendar.ics/")
  45. self.request(
  46. "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
  47. event = get_file_content("event1.ics")
  48. path = "/calendar.ics/event1.ics"
  49. status, headers, answer = self.request("PUT", path, event)
  50. assert status == 201
  51. status, headers, answer = self.request("GET", path)
  52. assert "ETag" in headers.keys()
  53. assert status == 200
  54. assert "VEVENT" in answer
  55. assert "Event" in answer
  56. assert "UID:event" in answer
  57. def test_add_todo(self):
  58. """Add a todo."""
  59. self.request("MKCOL", "/calendar.ics/")
  60. self.request(
  61. "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
  62. todo = get_file_content("todo1.ics")
  63. path = "/calendar.ics/todo1.ics"
  64. status, headers, answer = self.request("PUT", path, todo)
  65. assert status == 201
  66. status, headers, answer = self.request("GET", path)
  67. assert "ETag" in headers.keys()
  68. assert "VTODO" in answer
  69. assert "Todo" in answer
  70. assert "UID:todo" in answer
  71. def test_update(self):
  72. """Update an event."""
  73. self.request("MKCOL", "/calendar.ics/")
  74. self.request(
  75. "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
  76. event = get_file_content("event1.ics")
  77. path = "/calendar.ics/event1.ics"
  78. status, headers, answer = self.request("PUT", path, event)
  79. assert status == 201
  80. status, headers, answer = self.request("GET", path)
  81. assert "ETag" in headers.keys()
  82. assert status == 200
  83. assert "VEVENT" in answer
  84. assert "Event" in answer
  85. assert "UID:event" in answer
  86. assert "DTSTART;TZID=Europe/Paris:20130901T180000" in answer
  87. assert "DTEND;TZID=Europe/Paris:20130901T190000" in answer
  88. # Then we send another PUT request
  89. event = get_file_content("event1-prime.ics")
  90. status, headers, answer = self.request("PUT", path, event)
  91. assert status == 201
  92. status, headers, answer = self.request("GET", "/calendar.ics/")
  93. assert answer.count("BEGIN:VEVENT") == 1
  94. status, headers, answer = self.request("GET", path)
  95. assert "ETag" in headers.keys()
  96. assert status == 200
  97. assert "VEVENT" in answer
  98. assert "Event" in answer
  99. assert "UID:event" in answer
  100. assert "DTSTART;TZID=Europe/Paris:20130901T180000" not in answer
  101. assert "DTEND;TZID=Europe/Paris:20130901T190000" not in answer
  102. assert "DTSTART;TZID=Europe/Paris:20140901T180000" in answer
  103. assert "DTEND;TZID=Europe/Paris:20140901T210000" in answer
  104. def test_put_whole_collection(self):
  105. """Create and overwrite a whole collection."""
  106. event = get_file_content("event1.ics")
  107. status, headers, answer = self.request("PUT", "/calendar.ics/", event)
  108. assert status == 201
  109. status, headers, answer = self.request(
  110. "PUT", "/calendar.ics/event1.ics", event)
  111. assert status == 201
  112. # Overwrite
  113. status, headers, answer = self.request("PUT", "/calendar.ics/", event)
  114. assert status == 201
  115. status, headers, answer = self.request(
  116. "GET", "/calendar.ics/event1.ics")
  117. assert status == 404
  118. def test_delete(self):
  119. """Delete an event."""
  120. self.request("MKCOL", "/calendar.ics/")
  121. self.request(
  122. "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
  123. event = get_file_content("event1.ics")
  124. path = "/calendar.ics/event1.ics"
  125. status, headers, answer = self.request("PUT", path, event)
  126. # Then we send a DELETE request
  127. status, headers, answer = self.request("DELETE", path)
  128. assert status == 200
  129. assert "href>%s</" % path in answer
  130. status, headers, answer = self.request("GET", "/calendar.ics/")
  131. assert "VEVENT" not in answer
  132. def test_mkcalendar(self):
  133. """Make a calendar."""
  134. self.request("MKCALENDAR", "/calendar.ics/")
  135. status, headers, answer = self.request("GET", "/calendar.ics/")
  136. assert status == 200
  137. def test_move(self):
  138. """Move a item."""
  139. self.request("MKCALENDAR", "/calendar.ics/")
  140. event = get_file_content("event1.ics")
  141. path1 = "/calendar.ics/event1.ics"
  142. path2 = "/calendar.ics/event2.ics"
  143. status, headers, answer = self.request("PUT", path1, event)
  144. status, headers, answer = self.request(
  145. "MOVE", path1, HTTP_DESTINATION=path2, HTTP_HOST="")
  146. assert status == 201
  147. status, headers, answer = self.request("GET", path1)
  148. assert status == 404
  149. status, headers, answer = self.request("GET", path2)
  150. assert status == 200
  151. def test_head(self):
  152. status, headers, answer = self.request("HEAD", "/")
  153. assert status == 200
  154. def test_options(self):
  155. status, headers, answer = self.request("OPTIONS", "/")
  156. assert status == 200
  157. assert "DAV" in headers
  158. def test_delete_collection(self):
  159. """Delete a collection."""
  160. self.request("MKCOL", "/calendar.ics/")
  161. event = get_file_content("event1.ics")
  162. self.request("PUT", "/calendar.ics/event1.ics", event)
  163. status, headers, answer = self.request("DELETE", "/calendar.ics/")
  164. assert status == 200
  165. assert "href>/calendar.ics/</" in answer
  166. status, headers, answer = self.request("GET", "/calendar.ics/")
  167. assert status == 404
  168. def test_delete_root_collection(self):
  169. """Delete the root collection."""
  170. self.request("MKCOL", "/calendar.ics/")
  171. event = get_file_content("event1.ics")
  172. self.request("PUT", "/event1.ics", event)
  173. self.request("PUT", "/calendar.ics/event1.ics", event)
  174. status, headers, answer = self.request("DELETE", "/")
  175. assert status == 200
  176. assert "href>/</" in answer
  177. status, headers, answer = self.request("GET", "/calendar.ics/")
  178. assert status == 404
  179. status, headers, answer = self.request("GET", "/event1.ics")
  180. assert status == 404
  181. def test_propfind(self):
  182. calendar_path = "/calendar.ics/"
  183. self.request("MKCALENDAR", calendar_path)
  184. event = get_file_content("event1.ics")
  185. event_path = posixpath.join(calendar_path, "event.ics")
  186. self.request("PUT", event_path, event)
  187. status, headers, answer = self.request("PROPFIND", "/", HTTP_DEPTH="1")
  188. assert status == 207
  189. assert "href>/</" in answer
  190. assert "href>%s</" % calendar_path in answer
  191. status, headers, answer = self.request("PROPFIND", calendar_path, HTTP_DEPTH="1")
  192. assert status == 207
  193. assert "href>%s</" % calendar_path in answer
  194. assert "href>%s</" % event_path in answer
  195. def test_proppatch(self):
  196. """Write a property and read it back."""
  197. self.request("MKCALENDAR", "/calendar.ics/")
  198. proppatch = get_file_content("proppatch1.xml")
  199. status, headers, answer = self.request(
  200. "PROPPATCH", "/calendar.ics/", proppatch)
  201. assert status == 207
  202. assert "calendar-color" in answer
  203. assert "200 OK</status" in answer
  204. # Read property back
  205. propfind = get_file_content("propfind1.xml")
  206. status, headers, answer = self.request(
  207. "PROPFIND", "/calendar.ics/", propfind)
  208. assert status == 207
  209. assert ":calendar-color>#BADA55</" in answer
  210. assert "200 OK</status" in answer
  211. def test_multiple_events_with_same_uid(self):
  212. """Add two events with the same UID."""
  213. self.request("MKCOL", "/calendar.ics/")
  214. self.request("PUT", "/calendar.ics/", get_file_content("event2.ics"))
  215. status, headers, answer = self.request(
  216. "REPORT", "/calendar.ics/",
  217. """<?xml version="1.0" encoding="utf-8" ?>
  218. <C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
  219. <D:prop xmlns:D="DAV:"><D:getetag/></D:prop>
  220. </C:calendar-query>""")
  221. assert answer.count("<getetag>") == 1
  222. status, headers, answer = self.request("GET", "/calendar.ics/")
  223. assert answer.count("BEGIN:VEVENT") == 2
  224. def _test_filter(self, filters, kind="event", items=1):
  225. filters_text = "".join(
  226. "<C:filter>%s</C:filter>" % filter_ for filter_ in filters)
  227. self.request("MKCOL", "/calendar.ics/")
  228. self.request(
  229. "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR")
  230. for i in range(items):
  231. filename = "{}{}.ics".format(kind, i + 1)
  232. event = get_file_content(filename)
  233. self.request("PUT", "/calendar.ics/{}".format(filename), event)
  234. status, headers, answer = self.request(
  235. "REPORT", "/calendar.ics",
  236. """<?xml version="1.0" encoding="utf-8" ?>
  237. <C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
  238. <D:prop xmlns:D="DAV:">
  239. <D:getetag/>
  240. </D:prop>
  241. %s
  242. </C:calendar-query>""" % filters_text)
  243. return answer
  244. def test_calendar_tag_filter(self):
  245. """Report request with tag-based filter on calendar."""
  246. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  247. <C:comp-filter name="VCALENDAR"></C:comp-filter>"""])
  248. def test_item_tag_filter(self):
  249. """Report request with tag-based filter on an item."""
  250. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  251. <C:comp-filter name="VCALENDAR">
  252. <C:comp-filter name="VEVENT"></C:comp-filter>
  253. </C:comp-filter>"""])
  254. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  255. <C:comp-filter name="VCALENDAR">
  256. <C:comp-filter name="VTODO"></C:comp-filter>
  257. </C:comp-filter>"""])
  258. def test_item_not_tag_filter(self):
  259. """Report request with tag-based is-not filter on an item."""
  260. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  261. <C:comp-filter name="VCALENDAR">
  262. <C:comp-filter name="VEVENT">
  263. <C:is-not-defined />
  264. </C:comp-filter>
  265. </C:comp-filter>"""])
  266. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  267. <C:comp-filter name="VCALENDAR">
  268. <C:comp-filter name="VTODO">
  269. <C:is-not-defined />
  270. </C:comp-filter>
  271. </C:comp-filter>"""])
  272. def test_item_prop_filter(self):
  273. """Report request with prop-based filter on an item."""
  274. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  275. <C:comp-filter name="VCALENDAR">
  276. <C:comp-filter name="VEVENT">
  277. <C:prop-filter name="SUMMARY"></C:prop-filter>
  278. </C:comp-filter>
  279. </C:comp-filter>"""])
  280. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  281. <C:comp-filter name="VCALENDAR">
  282. <C:comp-filter name="VEVENT">
  283. <C:prop-filter name="UNKNOWN"></C:prop-filter>
  284. </C:comp-filter>
  285. </C:comp-filter>"""])
  286. def test_item_not_prop_filter(self):
  287. """Report request with prop-based is-not filter on an item."""
  288. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  289. <C:comp-filter name="VCALENDAR">
  290. <C:comp-filter name="VEVENT">
  291. <C:prop-filter name="SUMMARY">
  292. <C:is-not-defined />
  293. </C:prop-filter>
  294. </C:comp-filter>
  295. </C:comp-filter>"""])
  296. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  297. <C:comp-filter name="VCALENDAR">
  298. <C:comp-filter name="VEVENT">
  299. <C:prop-filter name="UNKNOWN">
  300. <C:is-not-defined />
  301. </C:prop-filter>
  302. </C:comp-filter>
  303. </C:comp-filter>"""])
  304. def test_mutiple_filters(self):
  305. """Report request with multiple filters on an item."""
  306. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  307. <C:comp-filter name="VCALENDAR">
  308. <C:comp-filter name="VEVENT">
  309. <C:prop-filter name="SUMMARY">
  310. <C:is-not-defined />
  311. </C:prop-filter>
  312. </C:comp-filter>
  313. </C:comp-filter>""", """
  314. <C:comp-filter name="VCALENDAR">
  315. <C:comp-filter name="VEVENT">
  316. <C:prop-filter name="UNKNOWN">
  317. <C:is-not-defined />
  318. </C:prop-filter>
  319. </C:comp-filter>
  320. </C:comp-filter>"""])
  321. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  322. <C:comp-filter name="VCALENDAR">
  323. <C:comp-filter name="VEVENT">
  324. <C:prop-filter name="SUMMARY"></C:prop-filter>
  325. </C:comp-filter>
  326. </C:comp-filter>""", """
  327. <C:comp-filter name="VCALENDAR">
  328. <C:comp-filter name="VEVENT">
  329. <C:prop-filter name="UNKNOWN">
  330. <C:is-not-defined />
  331. </C:prop-filter>
  332. </C:comp-filter>
  333. </C:comp-filter>"""])
  334. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  335. <C:comp-filter name="VCALENDAR">
  336. <C:comp-filter name="VEVENT">
  337. <C:prop-filter name="SUMMARY"></C:prop-filter>
  338. <C:prop-filter name="UNKNOWN">
  339. <C:is-not-defined />
  340. </C:prop-filter>
  341. </C:comp-filter>
  342. </C:comp-filter>"""])
  343. def test_text_match_filter(self):
  344. """Report request with text-match filter on calendar."""
  345. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  346. <C:comp-filter name="VCALENDAR">
  347. <C:comp-filter name="VEVENT">
  348. <C:prop-filter name="SUMMARY">
  349. <C:text-match>event</C:text-match>
  350. </C:prop-filter>
  351. </C:comp-filter>
  352. </C:comp-filter>"""])
  353. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  354. <C:comp-filter name="VCALENDAR">
  355. <C:comp-filter name="VEVENT">
  356. <C:prop-filter name="UNKNOWN">
  357. <C:text-match>event</C:text-match>
  358. </C:prop-filter>
  359. </C:comp-filter>
  360. </C:comp-filter>"""])
  361. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  362. <C:comp-filter name="VCALENDAR">
  363. <C:comp-filter name="VEVENT">
  364. <C:prop-filter name="SUMMARY">
  365. <C:text-match>unknown</C:text-match>
  366. </C:prop-filter>
  367. </C:comp-filter>
  368. </C:comp-filter>"""])
  369. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  370. <C:comp-filter name="VCALENDAR">
  371. <C:comp-filter name="VEVENT">
  372. <C:prop-filter name="SUMMARY">
  373. <C:text-match negate-condition="yes">event</C:text-match>
  374. </C:prop-filter>
  375. </C:comp-filter>
  376. </C:comp-filter>"""])
  377. def test_param_filter(self):
  378. """Report request with param-filter on calendar."""
  379. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  380. <C:comp-filter name="VCALENDAR">
  381. <C:comp-filter name="VEVENT">
  382. <C:prop-filter name="ATTENDEE">
  383. <C:param-filter name="PARTSTAT">
  384. <C:text-match collation="i;ascii-casemap"
  385. >ACCEPTED</C:text-match>
  386. </C:param-filter>
  387. </C:prop-filter>
  388. </C:comp-filter>
  389. </C:comp-filter>"""])
  390. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  391. <C:comp-filter name="VCALENDAR">
  392. <C:comp-filter name="VEVENT">
  393. <C:prop-filter name="ATTENDEE">
  394. <C:param-filter name="PARTSTAT">
  395. <C:text-match collation="i;ascii-casemap"
  396. >UNKNOWN</C:text-match>
  397. </C:param-filter>
  398. </C:prop-filter>
  399. </C:comp-filter>
  400. </C:comp-filter>"""])
  401. assert "href>/calendar.ics/event1.ics</" not in self._test_filter(["""
  402. <C:comp-filter name="VCALENDAR">
  403. <C:comp-filter name="VEVENT">
  404. <C:prop-filter name="ATTENDEE">
  405. <C:param-filter name="PARTSTAT">
  406. <C:is-not-defined />
  407. </C:param-filter>
  408. </C:prop-filter>
  409. </C:comp-filter>
  410. </C:comp-filter>"""])
  411. assert "href>/calendar.ics/event1.ics</" in self._test_filter(["""
  412. <C:comp-filter name="VCALENDAR">
  413. <C:comp-filter name="VEVENT">
  414. <C:prop-filter name="ATTENDEE">
  415. <C:param-filter name="UNKNOWN">
  416. <C:is-not-defined />
  417. </C:param-filter>
  418. </C:prop-filter>
  419. </C:comp-filter>
  420. </C:comp-filter>"""])
  421. def test_time_range_filter_events(self):
  422. """Report request with time-range filter on events."""
  423. answer = self._test_filter(["""
  424. <C:comp-filter name="VCALENDAR">
  425. <C:comp-filter name="VEVENT">
  426. <C:time-range start="20130801T000000Z" end="20131001T000000Z"/>
  427. </C:comp-filter>
  428. </C:comp-filter>"""], "event", items=5)
  429. assert "href>/calendar.ics/event1.ics</" in answer
  430. assert "href>/calendar.ics/event2.ics</" in answer
  431. assert "href>/calendar.ics/event3.ics</" in answer
  432. assert "href>/calendar.ics/event4.ics</" in answer
  433. assert "href>/calendar.ics/event5.ics</" in answer
  434. answer = self._test_filter(["""
  435. <C:comp-filter name="VCALENDAR">
  436. <C:comp-filter name="VEVENT">
  437. <C:prop-filter name="ATTENDEE">
  438. <C:param-filter name="PARTSTAT">
  439. <C:is-not-defined />
  440. </C:param-filter>
  441. </C:prop-filter>
  442. <C:time-range start="20130801T000000Z" end="20131001T000000Z"/>
  443. </C:comp-filter>
  444. </C:comp-filter>"""], items=5)
  445. assert "href>/calendar.ics/event1.ics</" not in answer
  446. assert "href>/calendar.ics/event2.ics</" not in answer
  447. assert "href>/calendar.ics/event3.ics</" not in answer
  448. assert "href>/calendar.ics/event4.ics</" not in answer
  449. assert "href>/calendar.ics/event5.ics</" not in answer
  450. answer = self._test_filter(["""
  451. <C:comp-filter name="VCALENDAR">
  452. <C:comp-filter name="VEVENT">
  453. <C:time-range start="20130902T000000Z" end="20131001T000000Z"/>
  454. </C:comp-filter>
  455. </C:comp-filter>"""], items=5)
  456. assert "href>/calendar.ics/event1.ics</" not in answer
  457. assert "href>/calendar.ics/event2.ics</" in answer
  458. assert "href>/calendar.ics/event3.ics</" in answer
  459. assert "href>/calendar.ics/event4.ics</" in answer
  460. assert "href>/calendar.ics/event5.ics</" in answer
  461. answer = self._test_filter(["""
  462. <C:comp-filter name="VCALENDAR">
  463. <C:comp-filter name="VEVENT">
  464. <C:time-range start="20130903T000000Z" end="20130908T000000Z"/>
  465. </C:comp-filter>
  466. </C:comp-filter>"""], items=5)
  467. assert "href>/calendar.ics/event1.ics</" not in answer
  468. assert "href>/calendar.ics/event2.ics</" not in answer
  469. assert "href>/calendar.ics/event3.ics</" in answer
  470. assert "href>/calendar.ics/event4.ics</" in answer
  471. assert "href>/calendar.ics/event5.ics</" in answer
  472. answer = self._test_filter(["""
  473. <C:comp-filter name="VCALENDAR">
  474. <C:comp-filter name="VEVENT">
  475. <C:time-range start="20130903T000000Z" end="20130904T000000Z"/>
  476. </C:comp-filter>
  477. </C:comp-filter>"""], items=5)
  478. assert "href>/calendar.ics/event1.ics</" not in answer
  479. assert "href>/calendar.ics/event2.ics</" not in answer
  480. assert "href>/calendar.ics/event3.ics</" in answer
  481. assert "href>/calendar.ics/event4.ics</" not in answer
  482. assert "href>/calendar.ics/event5.ics</" not in answer
  483. answer = self._test_filter(["""
  484. <C:comp-filter name="VCALENDAR">
  485. <C:comp-filter name="VEVENT">
  486. <C:time-range start="20130805T000000Z" end="20130810T000000Z"/>
  487. </C:comp-filter>
  488. </C:comp-filter>"""], items=5)
  489. assert "href>/calendar.ics/event1.ics</" not in answer
  490. assert "href>/calendar.ics/event2.ics</" not in answer
  491. assert "href>/calendar.ics/event3.ics</" not in answer
  492. assert "href>/calendar.ics/event4.ics</" not in answer
  493. assert "href>/calendar.ics/event5.ics</" not in answer
  494. def test_time_range_filter_events_rrule(self):
  495. """Report request with time-range filter on events with rrules."""
  496. answer = self._test_filter(["""
  497. <C:comp-filter name="VCALENDAR">
  498. <C:comp-filter name="VEVENT">
  499. <C:time-range start="20130801T000000Z" end="20131001T000000Z"/>
  500. </C:comp-filter>
  501. </C:comp-filter>"""], "event", items=2)
  502. assert "href>/calendar.ics/event1.ics</" in answer
  503. assert "href>/calendar.ics/event2.ics</" in answer
  504. answer = self._test_filter(["""
  505. <C:comp-filter name="VCALENDAR">
  506. <C:comp-filter name="VEVENT">
  507. <C:time-range start="20140801T000000Z" end="20141001T000000Z"/>
  508. </C:comp-filter>
  509. </C:comp-filter>"""], "event", items=2)
  510. assert "href>/calendar.ics/event1.ics</" not in answer
  511. assert "href>/calendar.ics/event2.ics</" in answer
  512. answer = self._test_filter(["""
  513. <C:comp-filter name="VCALENDAR">
  514. <C:comp-filter name="VEVENT">
  515. <C:time-range start="20120801T000000Z" end="20121001T000000Z"/>
  516. </C:comp-filter>
  517. </C:comp-filter>"""], "event", items=2)
  518. assert "href>/calendar.ics/event1.ics</" not in answer
  519. assert "href>/calendar.ics/event2.ics</" not in answer
  520. answer = self._test_filter(["""
  521. <C:comp-filter name="VCALENDAR">
  522. <C:comp-filter name="VEVENT">
  523. <C:time-range start="20130903T000000Z" end="20130907T000000Z"/>
  524. </C:comp-filter>
  525. </C:comp-filter>"""], "event", items=2)
  526. assert "href>/calendar.ics/event1.ics</" not in answer
  527. assert "href>/calendar.ics/event2.ics</" not in answer
  528. def test_time_range_filter_todos(self):
  529. """Report request with time-range filter on todos."""
  530. answer = self._test_filter(["""
  531. <C:comp-filter name="VCALENDAR">
  532. <C:comp-filter name="VTODO">
  533. <C:time-range start="20130801T000000Z" end="20131001T000000Z"/>
  534. </C:comp-filter>
  535. </C:comp-filter>"""], "todo", items=8)
  536. assert "href>/calendar.ics/todo1.ics</" in answer
  537. assert "href>/calendar.ics/todo2.ics</" in answer
  538. assert "href>/calendar.ics/todo3.ics</" in answer
  539. assert "href>/calendar.ics/todo4.ics</" in answer
  540. assert "href>/calendar.ics/todo5.ics</" in answer
  541. assert "href>/calendar.ics/todo6.ics</" in answer
  542. assert "href>/calendar.ics/todo7.ics</" in answer
  543. assert "href>/calendar.ics/todo8.ics</" in answer
  544. answer = self._test_filter(["""
  545. <C:comp-filter name="VCALENDAR">
  546. <C:comp-filter name="VTODO">
  547. <C:time-range start="20130901T160000Z" end="20130901T183000Z"/>
  548. </C:comp-filter>
  549. </C:comp-filter>"""], "todo", items=8)
  550. assert "href>/calendar.ics/todo1.ics</" not in answer
  551. assert "href>/calendar.ics/todo2.ics</" in answer
  552. assert "href>/calendar.ics/todo3.ics</" in answer
  553. assert "href>/calendar.ics/todo4.ics</" not in answer
  554. assert "href>/calendar.ics/todo5.ics</" not in answer
  555. assert "href>/calendar.ics/todo6.ics</" not in answer
  556. assert "href>/calendar.ics/todo7.ics</" in answer
  557. assert "href>/calendar.ics/todo8.ics</" in answer
  558. answer = self._test_filter(["""
  559. <C:comp-filter name="VCALENDAR">
  560. <C:comp-filter name="VTODO">
  561. <C:time-range start="20130903T160000Z" end="20130901T183000Z"/>
  562. </C:comp-filter>
  563. </C:comp-filter>"""], "todo", items=8)
  564. assert "href>/calendar.ics/todo2.ics</" not in answer
  565. answer = self._test_filter(["""
  566. <C:comp-filter name="VCALENDAR">
  567. <C:comp-filter name="VTODO">
  568. <C:time-range start="20130903T160000Z" end="20130901T173000Z"/>
  569. </C:comp-filter>
  570. </C:comp-filter>"""], "todo", items=8)
  571. assert "href>/calendar.ics/todo2.ics</" not in answer
  572. answer = self._test_filter(["""
  573. <C:comp-filter name="VCALENDAR">
  574. <C:comp-filter name="VTODO">
  575. <C:time-range start="20130903T160000Z" end="20130903T173000Z"/>
  576. </C:comp-filter>
  577. </C:comp-filter>"""], "todo", items=8)
  578. assert "href>/calendar.ics/todo3.ics</" not in answer
  579. answer = self._test_filter(["""
  580. <C:comp-filter name="VCALENDAR">
  581. <C:comp-filter name="VTODO">
  582. <C:time-range start="20130903T160000Z" end="20130803T203000Z"/>
  583. </C:comp-filter>
  584. </C:comp-filter>"""], "todo", items=8)
  585. assert "href>/calendar.ics/todo7.ics</" in answer
  586. def test_time_range_filter_todos_rrule(self):
  587. """Report request with time-range filter on todos with rrules."""
  588. answer = self._test_filter(["""
  589. <C:comp-filter name="VCALENDAR">
  590. <C:comp-filter name="VTODO">
  591. <C:time-range start="20130801T000000Z" end="20131001T000000Z"/>
  592. </C:comp-filter>
  593. </C:comp-filter>"""], "todo", items=2)
  594. assert "href>/calendar.ics/todo1.ics</" in answer
  595. assert "href>/calendar.ics/todo2.ics</" in answer
  596. answer = self._test_filter(["""
  597. <C:comp-filter name="VCALENDAR">
  598. <C:comp-filter name="VTODO">
  599. <C:time-range start="20140801T000000Z" end="20141001T000000Z"/>
  600. </C:comp-filter>
  601. </C:comp-filter>"""], "todo", items=2)
  602. assert "href>/calendar.ics/todo1.ics</" not in answer
  603. assert "href>/calendar.ics/todo2.ics</" in answer
  604. answer = self._test_filter(["""
  605. <C:comp-filter name="VCALENDAR">
  606. <C:comp-filter name="VTODO">
  607. <C:time-range start="20140902T000000Z" end="20140903T000000Z"/>
  608. </C:comp-filter>
  609. </C:comp-filter>"""], "todo", items=2)
  610. assert "href>/calendar.ics/todo1.ics</" not in answer
  611. assert "href>/calendar.ics/todo2.ics</" in answer
  612. answer = self._test_filter(["""
  613. <C:comp-filter name="VCALENDAR">
  614. <C:comp-filter name="VTODO">
  615. <C:time-range start="20140904T000000Z" end="20140914T000000Z"/>
  616. </C:comp-filter>
  617. </C:comp-filter>"""], "todo", items=2)
  618. assert "href>/calendar.ics/todo1.ics</" not in answer
  619. assert "href>/calendar.ics/todo2.ics</" not in answer
  620. def test_time_range_filter_journals(self):
  621. """Report request with time-range filter on journals."""
  622. answer = self._test_filter(["""
  623. <C:comp-filter name="VCALENDAR">
  624. <C:comp-filter name="VJOURNAL">
  625. <C:time-range start="19991229T000000Z" end="20000202T000000Z"/>
  626. </C:comp-filter>
  627. </C:comp-filter>"""], "journal", items=3)
  628. assert "href>/calendar.ics/journal1.ics</" not in answer
  629. assert "href>/calendar.ics/journal2.ics</" in answer
  630. assert "href>/calendar.ics/journal3.ics</" in answer
  631. answer = self._test_filter(["""
  632. <C:comp-filter name="VCALENDAR">
  633. <C:comp-filter name="VJOURNAL">
  634. <C:time-range start="19991229T000000Z" end="20000202T000000Z"/>
  635. </C:comp-filter>
  636. </C:comp-filter>"""], "journal", items=3)
  637. assert "href>/calendar.ics/journal1.ics</" not in answer
  638. assert "href>/calendar.ics/journal2.ics</" in answer
  639. assert "href>/calendar.ics/journal3.ics</" in answer
  640. answer = self._test_filter(["""
  641. <C:comp-filter name="VCALENDAR">
  642. <C:comp-filter name="VJOURNAL">
  643. <C:time-range start="19981229T000000Z" end="19991012T000000Z"/>
  644. </C:comp-filter>
  645. </C:comp-filter>"""], "journal", items=3)
  646. assert "href>/calendar.ics/journal1.ics</" not in answer
  647. assert "href>/calendar.ics/journal2.ics</" not in answer
  648. assert "href>/calendar.ics/journal3.ics</" not in answer
  649. answer = self._test_filter(["""
  650. <C:comp-filter name="VCALENDAR">
  651. <C:comp-filter name="VJOURNAL">
  652. <C:time-range start="20131229T000000Z" end="21520202T000000Z"/>
  653. </C:comp-filter>
  654. </C:comp-filter>"""], "journal", items=3)
  655. assert "href>/calendar.ics/journal1.ics</" not in answer
  656. assert "href>/calendar.ics/journal2.ics</" in answer
  657. assert "href>/calendar.ics/journal3.ics</" not in answer
  658. answer = self._test_filter(["""
  659. <C:comp-filter name="VCALENDAR">
  660. <C:comp-filter name="VJOURNAL">
  661. <C:time-range start="20000101T000000Z" end="20000202T000000Z"/>
  662. </C:comp-filter>
  663. </C:comp-filter>"""], "journal", items=3)
  664. assert "href>/calendar.ics/journal1.ics</" not in answer
  665. assert "href>/calendar.ics/journal2.ics</" in answer
  666. assert "href>/calendar.ics/journal3.ics</" in answer
  667. def test_time_range_filter_journals_rrule(self):
  668. """Report request with time-range filter on journals with rrules."""
  669. answer = self._test_filter(["""
  670. <C:comp-filter name="VCALENDAR">
  671. <C:comp-filter name="VJOURNAL">
  672. <C:time-range start="19991229T000000Z" end="20000202T000000Z"/>
  673. </C:comp-filter>
  674. </C:comp-filter>"""], "journal", items=2)
  675. assert "href>/calendar.ics/journal1.ics</" not in answer
  676. assert "href>/calendar.ics/journal2.ics</" in answer
  677. answer = self._test_filter(["""
  678. <C:comp-filter name="VCALENDAR">
  679. <C:comp-filter name="VJOURNAL">
  680. <C:time-range start="20051229T000000Z" end="20060202T000000Z"/>
  681. </C:comp-filter>
  682. </C:comp-filter>"""], "journal", items=2)
  683. assert "href>/calendar.ics/journal1.ics</" not in answer
  684. assert "href>/calendar.ics/journal2.ics</" in answer
  685. answer = self._test_filter(["""
  686. <C:comp-filter name="VCALENDAR">
  687. <C:comp-filter name="VJOURNAL">
  688. <C:time-range start="20060102T000000Z" end="20060202T000000Z"/>
  689. </C:comp-filter>
  690. </C:comp-filter>"""], "journal", items=2)
  691. assert "href>/calendar.ics/journal1.ics</" not in answer
  692. assert "href>/calendar.ics/journal2.ics</" not in answer
  693. def test_principal_collection_creation(self):
  694. """Verify existence of the principal collection."""
  695. status, headers, answer = self.request(
  696. "GET", "/user/", REMOTE_USER="user")
  697. assert status == 200
  698. def test_existence_of_root_collections(self):
  699. """Verify that the root collection always exists."""
  700. # Use PROPFIND because GET returns message
  701. status, headers, answer = self.request("PROPFIND", "/")
  702. assert status == 207
  703. # it should still exist after deletion
  704. self.request("DELETE", "/")
  705. status, headers, answer = self.request("PROPFIND", "/")
  706. assert status == 207
  707. def test_fsync(self):
  708. """Create a directory and file with syncing enabled."""
  709. self.configuration.set("storage", "fsync", "True")
  710. status, headers, answer = self.request("MKCALENDAR", "/calendar.ics/")
  711. assert status == 201
  712. def test_hook(self):
  713. """Run hook."""
  714. self.configuration.set(
  715. "storage", "hook", "mkdir %s" % os.path.join("collection-root",
  716. "created_by_hook"))
  717. status, headers, answer = self.request("MKCOL", "/calendar.ics/")
  718. assert status == 201
  719. status, headers, answer = self.request("GET", "/created_by_hook/")
  720. assert status == 200
  721. def test_hook_read_access(self):
  722. """Verify that hook is not run for read accesses."""
  723. self.configuration.set(
  724. "storage", "hook", "mkdir %s" % os.path.join("collection-root",
  725. "created_by_hook"))
  726. status, headers, answer = self.request("GET", "/")
  727. assert status == 200
  728. status, headers, answer = self.request("GET", "/created_by_hook/")
  729. assert status == 404
  730. @pytest.mark.skipif(os.system("type flock") != 0,
  731. reason="flock command not found")
  732. def test_hook_storage_locked(self):
  733. """Verify that the storage is locked when the hook runs."""
  734. self.configuration.set(
  735. "storage", "hook", "flock -n .Radicale.lock || exit 0; exit 1")
  736. status, headers, answer = self.request("MKCOL", "/calendar.ics/")
  737. assert status == 201
  738. def test_hook_principal_collection_creation(self):
  739. """Verify that the hooks runs when a new user is created."""
  740. self.configuration.set(
  741. "storage", "hook", "mkdir %s" % os.path.join("collection-root",
  742. "created_by_hook"))
  743. status, headers, answer = self.request("GET", "/", REMOTE_USER="user")
  744. assert status == 200
  745. status, headers, answer = self.request("GET", "/created_by_hook/")
  746. assert status == 200
  747. def test_hook_fail(self):
  748. """Verify that a request fails if the hook fails."""
  749. self.configuration.set("storage", "hook", "exit 1")
  750. try:
  751. status, headers, answer = self.request("MKCOL", "/calendar.ics/")
  752. assert status != 201
  753. except Exception:
  754. pass
  755. class BaseFileSystemTest(BaseTest):
  756. """Base class for filesystem backend tests."""
  757. storage_type = None
  758. def setup(self):
  759. self.configuration = config.load()
  760. self.configuration.set("storage", "type", self.storage_type)
  761. self.logger = logging.getLogger("radicale_test")
  762. self.colpath = tempfile.mkdtemp()
  763. self.configuration.set("storage", "filesystem_folder", self.colpath)
  764. # Disable syncing to disk for better performance
  765. self.configuration.set("storage", "fsync", "False")
  766. self.application = Application(self.configuration, self.logger)
  767. def teardown(self):
  768. shutil.rmtree(self.colpath)
  769. class TestMultiFileSystem(BaseFileSystemTest, BaseRequestsMixIn):
  770. """Test BaseRequests on multifilesystem."""
  771. storage_type = "multifilesystem"
  772. class TestCustomStorageSystem(BaseFileSystemTest):
  773. """Test custom backend loading."""
  774. storage_type = "tests.custom.storage"
  775. def test_root(self):
  776. """A simple test to verify that the custom backend works."""
  777. BaseRequestsMixIn.test_root(self)