test_base.py 40 KB

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