test_hook_email.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. # This file is part of Radicale - CalDAV and CardDAV server
  2. # Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
  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 related to hook 'email'
  18. """
  19. import logging
  20. import os
  21. import re
  22. from datetime import datetime, timedelta
  23. from radicale.tests import BaseTest
  24. from radicale.tests.helpers import get_file_content
  25. class TestHooks(BaseTest):
  26. """Tests with hooks."""
  27. def setup_method(self) -> None:
  28. BaseTest.setup_method(self)
  29. rights_file_path = os.path.join(self.colpath, "rights")
  30. with open(rights_file_path, "w") as f:
  31. f.write("""\
  32. [permit delete collection]
  33. user: .*
  34. collection: test-permit-delete
  35. permissions: RrWwD
  36. [forbid delete collection]
  37. user: .*
  38. collection: test-forbid-delete
  39. permissions: RrWwd
  40. [permit overwrite collection]
  41. user: .*
  42. collection: test-permit-overwrite
  43. permissions: RrWwO
  44. [forbid overwrite collection]
  45. user: .*
  46. collection: test-forbid-overwrite
  47. permissions: RrWwo
  48. [allow all]
  49. user: .*
  50. collection: .*
  51. permissions: RrWw""")
  52. self.configure({"rights": {"file": rights_file_path,
  53. "type": "from_file"}})
  54. self.configure({"hook": {"type": "email",
  55. "dryrun": "True"}})
  56. def _future_date_timestamp(self) -> str:
  57. """Return a date timestamp for a future date."""
  58. future_date = datetime.now() + timedelta(days=1)
  59. return future_date.strftime("%Y%m%dT%H%M%S")
  60. def _past_date_timestamp(self) -> str:
  61. past_date = datetime.now() - timedelta(days=1)
  62. return past_date.strftime("%Y%m%dT%H%M%S")
  63. def _replace_end_date_in_event(self, event: str, new_date: str) -> str:
  64. """Replace the end date in an event string."""
  65. return re.sub(r"DTEND;TZID=Europe/Paris:\d{8}T\d{6}",
  66. f"DTEND;TZID=Europe/Paris:{new_date}", event)
  67. def test_add_event_with_future_end_date(self, caplog) -> None:
  68. caplog.set_level(logging.WARNING)
  69. """Add an event."""
  70. self.mkcalendar("/calendar.ics/")
  71. event = get_file_content("event1.ics")
  72. event = self._replace_end_date_in_event(event, self._future_date_timestamp())
  73. path = "/calendar.ics/event1.ics"
  74. self.put(path, event)
  75. _, headers, answer = self.request("GET", path, check=200)
  76. assert "ETag" in headers
  77. assert headers["Content-Type"] == "text/calendar; charset=utf-8"
  78. assert "VEVENT" in answer
  79. assert "Event" in answer
  80. assert "UID:event" in answer
  81. logs = caplog.messages
  82. # Should have a log saying the notification item was received
  83. assert len([log for log in logs if "received notification_item: {'type': 'upsert'," in log]) == 1
  84. # Should NOT have a log saying that no email is sent (email won't actually be sent due to dryrun)
  85. assert len([log for log in logs if "skipping notification for event: event1" in log]) == 0
  86. def test_add_event_with_past_end_date(self, caplog) -> None:
  87. caplog.set_level(logging.WARNING)
  88. """Add an event."""
  89. self.mkcalendar("/calendar.ics/")
  90. event = get_file_content("event1.ics")
  91. event = self._replace_end_date_in_event(event, self._past_date_timestamp())
  92. path = "/calendar.ics/event1.ics"
  93. self.put(path, event)
  94. _, headers, answer = self.request("GET", path, check=200)
  95. assert "ETag" in headers
  96. assert headers["Content-Type"] == "text/calendar; charset=utf-8"
  97. assert "VEVENT" in answer
  98. assert "Event" in answer
  99. assert "UID:event" in answer
  100. logs = caplog.messages
  101. # Should have a log saying the notification item was received
  102. assert len([log for log in logs if "received notification_item: {'type': 'upsert'," in log]) == 1
  103. # Should have a log saying that no email is sent due to past end date
  104. assert len([log for log in logs if "Event end time is in the past, skipping notification for event: event1" in log]) == 1
  105. def test_delete_event_with_future_end_date(self, caplog) -> None:
  106. caplog.set_level(logging.WARNING)
  107. """Delete an event."""
  108. self.mkcalendar("/calendar.ics/")
  109. event = get_file_content("event1.ics")
  110. event = self._replace_end_date_in_event(event, self._future_date_timestamp())
  111. path = "/calendar.ics/event1.ics"
  112. self.put(path, event)
  113. _, responses = self.delete(path)
  114. assert responses[path] == 200
  115. _, answer = self.get("/calendar.ics/")
  116. assert "VEVENT" not in answer
  117. logs = caplog.messages
  118. # Should have a log saying the notification item was received
  119. assert len([log for log in logs if "received notification_item: {'type': 'delete'," in log]) == 1
  120. # Should NOT have a log saying that no email is sent (email won't actually be sent due to dryrun)
  121. assert len([log for log in logs if "skipping notification for event: event1" in log]) == 0
  122. def test_delete_event_with_past_end_date(self, caplog) -> None:
  123. caplog.set_level(logging.WARNING)
  124. """Delete an event."""
  125. self.mkcalendar("/calendar.ics/")
  126. event = get_file_content("event1.ics")
  127. event = self._replace_end_date_in_event(event, self._past_date_timestamp())
  128. path = "/calendar.ics/event1.ics"
  129. self.put(path, event)
  130. _, responses = self.delete(path)
  131. assert responses[path] == 200
  132. _, answer = self.get("/calendar.ics/")
  133. assert "VEVENT" not in answer
  134. logs = caplog.messages
  135. # Should have a log saying the notification item was received
  136. assert len([log for log in logs if "received notification_item: {'type': 'delete'," in log]) == 1
  137. # Should have a log saying that no email is sent due to past end date
  138. assert len([log for log in logs if "Event end time is in the past, skipping notification for event: event1" in log]) == 1