test_base.py 39 KB

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