From f32e50bc9dae630ffac3dfd21c39a5f98fc5f915 Mon Sep 17 00:00:00 2001 From: Nate Harris Date: Thu, 14 Aug 2025 00:06:55 -0600 Subject: [PATCH] - Add unit tests to confirm emails not triggered when adding/deleting event with past end date --- radicale/storage/__init__.py | 3 +- radicale/tests/test_hook_email.py | 56 +++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/radicale/storage/__init__.py b/radicale/storage/__init__.py index 78f9a7d4..fb17f834 100644 --- a/radicale/storage/__init__.py +++ b/radicale/storage/__init__.py @@ -300,8 +300,7 @@ class BaseStorage: def discover( self, path: str, depth: str = "0", - child_context_manager: Optional[ - Callable[[str, Optional[str]], ContextManager[None]]] = None, + child_context_manager: Optional[Callable[[str, Optional[str]], ContextManager[None]]] = None, user_groups: Set[str] = set([])) -> Iterable["types.CollectionOrItem"]: """Discover a list of collections under the given ``path``. diff --git a/radicale/tests/test_hook_email.py b/radicale/tests/test_hook_email.py index 74674589..af012a6c 100644 --- a/radicale/tests/test_hook_email.py +++ b/radicale/tests/test_hook_email.py @@ -21,6 +21,8 @@ Radicale tests related to hook 'email' import logging import os +import re +from datetime import datetime, timedelta from radicale.tests import BaseTest from radicale.tests.helpers import get_file_content @@ -63,11 +65,26 @@ permissions: RrWw""") self.configure({"hook": {"type": "email", "dryrun": "True"}}) - def test_add_event(self, caplog) -> None: + def _future_date_timestamp(self) -> str: + """Return a date timestamp for a future date.""" + future_date = datetime.now() + timedelta(days=1) + return future_date.strftime("%Y%m%dT%H%M%S") + + def _past_date_timestamp(self) -> str: + past_date = datetime.now() - timedelta(days=1) + return past_date.strftime("%Y%m%dT%H%M%S") + + def _replace_end_date_in_event(self, event: str, new_date: str) -> str: + """Replace the end date in an event string.""" + return re.sub(r"DTEND;TZID=Europe/Paris:\d{8}T\d{6}", + f"DTEND;TZID=Europe/Paris:{new_date}", event) + + def test_add_event_with_future_end_date(self, caplog) -> None: caplog.set_level(logging.WARNING) """Add an event.""" self.mkcalendar("/calendar.ics/") event = get_file_content("event1.ics") + event = self._replace_end_date_in_event(event, self._future_date_timestamp()) path = "/calendar.ics/event1.ics" self.put(path, event) _, headers, answer = self.request("GET", path, check=200) @@ -87,11 +104,30 @@ permissions: RrWw""") if (found != 7): raise ValueError("Logging misses expected log lines, found=%d", found) - def test_delete_event(self, caplog) -> None: + def test_add_event_with_past_end_date(self, caplog) -> None: + caplog.set_level(logging.WARNING) + """Add an event.""" + self.mkcalendar("/calendar.ics/") + event = get_file_content("event1.ics") + event = self._replace_end_date_in_event(event, self._past_date_timestamp()) + path = "/calendar.ics/event1.ics" + self.put(path, event) + _, headers, answer = self.request("GET", path, check=200) + assert "ETag" in headers + assert headers["Content-Type"] == "text/calendar; charset=utf-8" + assert "VEVENT" in answer + assert "Event" in answer + assert "UID:event" in answer + + # Should not trigger an email + assert len(caplog.messages) == 0 + + def test_delete_event_with_future_end_date(self, caplog) -> None: caplog.set_level(logging.WARNING) """Delete an event.""" self.mkcalendar("/calendar.ics/") event = get_file_content("event1.ics") + event = self._replace_end_date_in_event(event, self._future_date_timestamp()) path = "/calendar.ics/event1.ics" self.put(path, event) _, responses = self.delete(path) @@ -108,3 +144,19 @@ permissions: RrWw""") found = found | 4 if (found != 7): raise ValueError("Logging misses expected log lines, found=%d", found) + + def test_delete_event_with_past_end_date(self, caplog) -> None: + caplog.set_level(logging.WARNING) + """Delete an event.""" + self.mkcalendar("/calendar.ics/") + event = get_file_content("event1.ics") + event = self._replace_end_date_in_event(event, self._past_date_timestamp()) + path = "/calendar.ics/event1.ics" + self.put(path, event) + _, responses = self.delete(path) + assert responses[path] == 200 + _, answer = self.get("/calendar.ics/") + assert "VEVENT" not in answer + + # Should not trigger an email + assert len(caplog.messages) == 0