bunch of new test cases and code mods

This commit is contained in:
Marc Durepos 2024-10-07 17:13:52 -04:00
parent b660e4d533
commit 9243fad2e6
4 changed files with 207 additions and 24 deletions

View file

@ -59,9 +59,9 @@ class CalendarEvent(models.Model):
@api.depends("user_id", "partner_ids", "partner_ids.user_id")
def _compute_caldav_users(self):
for rec in self:
rec.caldav_user_ids = (
rec.user_id | rec.partner_ids.mapped("user_id")
).filtered(lambda user: user.is_caldav_enabled)
rec.caldav_user_ids = (rec.user_id | rec.partner_ids.user_ids).filtered(
"is_caldav_enabled"
)
@api.model_create_multi
def create(self, vals_list):
@ -249,7 +249,8 @@ class CalendarEvent(models.Model):
"""Match the fields from calendar.event (recurring fields) to the fields specified in RRULE at
https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html"""
rrule = component.get("rrule")
rrule = [item[1] for item in component.property_items() if item[0] == "RRULE"]
rrule = rrule[0] if rrule else None
if not rrule:
if not self.recurrency:
# No change, this was already not a recurring event
@ -268,7 +269,7 @@ class CalendarEvent(models.Model):
return {"recurrence_update": "all_events", "recurrency": False}
rrule_str = rrule.to_ical().decode("utf-8")
sequence = component.get("sequence")
if sequence and sequence != 0:
if sequence and sequence != 1:
# This is not the base event so we can't change recurrence properties
return {}
@ -315,15 +316,19 @@ class CalendarEvent(models.Model):
partner_ids = self._get_attendee_partners(component, current_user_email)
existing_instance = self._get_existing_instance(uid, recurrence_id)
outdated = False
last_modified = component.decoded("last-modified")
if existing_instance and last_modified:
last_modified = last_modified.astimezone(utc).replace(tzinfo=None)
if last_modified < existing_instance.write_date:
_logger.info(
f"Last modified date {last_modified} is before most recent "
f"write date {existing_instance.write_date}. Skipping."
)
continue
# _logger.info(
# f"Last modified date {last_modified} is before most recent "
# f"write date {existing_instance.write_date}. Skipping."
# )
outdated = True
owned = (
existing_instance and existing_instance.partner_id == user.partner_id
)
values, recurrency_vals = (
self._get_vals_recurrency_vals_from_ical_component(
partner_ids, component, user
@ -333,6 +338,14 @@ class CalendarEvent(models.Model):
if not existing_instance:
_logger.info(f"Creating with vals: {values}")
self.with_context(caldav_no_sync=True).create(values)
elif outdated or not owned:
_logger.info(
f"Event {existing_instance.caldav_uid} "
f"{'outdated ' if outdated else ''}"
f"{'not owned by user ' + user.name if not owned else ''}."
f" Skipping."
)
pass # Do nothing, it's not this user's event to modify or it's outdated
else:
# Don't update partner_ids if no change
if partner_ids != existing_instance.partner_ids:
@ -473,13 +486,3 @@ class CalendarEvent(models.Model):
"tentative": "TENTATIVE",
}
return mapping.get(state, "NEEDS-ACTION")
@staticmethod
def _map_ical_status(ical_status):
mapping = {
"NEEDS-ACTION": "needsAction",
"ACCEPTED": "accepted",
"DECLINED": "declined",
"TENTATIVE": "tentative",
}
return mapping.get(ical_status, "needsAction")

View file

@ -6,12 +6,15 @@ class CaldavTestCommon:
def _generate_user(
cls, name, caldav_username=None, caldav_password=None, caldav_url=None
):
groups_ids = cls.env.ref("base.group_user") | cls.env.ref(
"base.group_partner_manager"
)
vals = {
"name": name,
"login": name,
"password": name,
"email": name + "@example.com",
"groups_id": [Command.set(cls.env.ref("base.group_user").ids)],
"groups_id": [Command.set(groups_ids.ids)],
}
if caldav_username:
vals.update(caldav_username=caldav_username)

View file

@ -0,0 +1,38 @@
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
X-WR-CALNAME:test recurring
METHOD:PUBLISH
PRODID:-//Apple Inc.//macOS 14.5//EN
BEGIN:VTIMEZONE
TZID:America/Toronto
BEGIN:DAYLIGHT
TZOFFSETFROM:-0500
DTSTART:20070311T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
TZNAME:EDT
TZOFFSETTO:-0400
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0400
DTSTART:20071104T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
TZNAME:EST
TZOFFSETTO:-0500
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
TRANSP:OPAQUE
DTEND;TZID=America/Toronto:20241008T040000
X-APPLE-CREATOR-TEAM-IDENTITY:0000000000
UID:F777C16F-2F14-4326-9681-69C6043DB8C0
DTSTAMP:20241007T195255Z
X-APPLE-CREATOR-IDENTITY:com.apple.calendar
SEQUENCE:1
SUMMARY:test recurring
DTSTART;TZID=America/Toronto:20241008T030000
LAST-MODIFIED:20241007T195252Z
CREATED:20241007T195223Z
RRULE:FREQ=WEEKLY;COUNT=10
END:VEVENT
END:VCALENDAR

View file

@ -8,6 +8,16 @@ from .common import CaldavTestCommon
from contextlib import contextmanager
from datetime import datetime, UTC, timedelta
WEEKDAY_MAP = {
0: "SUN",
1: "MON",
2: "TUE",
3: "WED",
4: "THU",
5: "FRI",
6: "SAT",
}
def _get_ics_path(filename):
return Path(__file__).parent / "data" / filename
@ -23,10 +33,13 @@ def _patch_caldav_with_events_from_ics(ics_paths, user, last_modified=None):
mock_client = MockDAVClient.return_value
mock_calendar = MockCalendar.return_value
mock_client.calendar = mock_calendar
mock_calendars = {}
def calendar_side_effect(url):
if url not in mock_calendars:
mock_calendars[url] = MockCalendar()
if url == user.caldav_calendar_url:
return mock_calendar
return mock_calendars[url]
raise Exception("Calendar does not exist.")
mock_calendar.side_effect = calendar_side_effect
@ -109,6 +122,16 @@ class TestCalendarEvent(TransactionCase, CaldavTestCommon):
event = self.env["calendar.event"].search([("user_id", "=", user.id)])
self.assertFalse(event)
def test_recurring_from_server_create(self):
user = self.user_1
ics_path = _get_ics_path("test_recurring.ics")
with _patch_caldav_with_events_from_ics(ics_path, user):
self.env["calendar.event"].poll_caldav_server()
events = self.env["calendar.event"].search(
[("partner_id", "=", user.partner_id.id)]
)
self.assertEqual(len(events), 10)
def test_multiple_attendees_event_from_server_create(self):
user = self.user_1
ics_path = _get_ics_path("test_multi_attendee.ics")
@ -154,9 +177,125 @@ class TestCalendarEvent(TransactionCase, CaldavTestCommon):
ics_path = _get_ics_path("test_multi_user.ics")
with _patch_caldav_with_events_from_ics(ics_path, user1):
self.env["calendar.event"].poll_caldav_server()
with _patch_caldav_with_events_from_ics(ics_path, user2):
self.env["calendar.event"].poll_caldav_server()
with _patch_caldav_with_events_from_ics(ics_path, user3):
self.env["calendar.event"].poll_caldav_server()
event = self.env["calendar.event"].search(
[("caldav_uid", "=", "2495546B-5C9A-4632-AAD3-A179EF83CF20")]
)
self.assertEqual(len(event), 1)
# Make sure the event wasn't duplicated all over the place
other_user_events = self.env["calendar.event"].search(
[("user_id", "in", [user2.id, user3.id])]
)
self.assertFalse(other_user_events)
self.assertIn(user2.partner_id, event.partner_ids)
self.assertIn(user3.partner_id, event.partner_ids)
def test_multiple_user_attendees_event_from_server_update(self):
pass
"""Test event has (as in above test):
Organizer: user1 (test1@example.com)
Attendees: user2 and user3 (test2@example.com, test3@example.com)
"""
user1 = self.user_1
user2 = self.user_2
user3 = self.user_3
ics_path = _get_ics_path("test_multi_user.ics")
with _patch_caldav_with_events_from_ics(ics_path, user1):
self.env["calendar.event"].poll_caldav_server()
with _patch_caldav_with_events_from_ics(ics_path, user2):
self.env["calendar.event"].poll_caldav_server()
with _patch_caldav_with_events_from_ics(ics_path, user3):
self.env["calendar.event"].poll_caldav_server()
# Now update it to remove one attendee
# Shuffle the user polling order just to test more robustly
ics_path = _get_ics_path("test_multi_user_update.ics")
with _patch_caldav_with_events_from_ics(
ics_path, user2, last_modified=datetime.now(UTC)
):
self.env["calendar.event"].poll_caldav_server()
with _patch_caldav_with_events_from_ics(
ics_path, user3, last_modified=datetime.now(UTC)
):
self.env["calendar.event"].poll_caldav_server()
with _patch_caldav_with_events_from_ics(
ics_path, user1, last_modified=datetime.now(UTC)
):
self.env["calendar.event"].poll_caldav_server()
event = self.env["calendar.event"].search(
[("caldav_uid", "=", "2495546B-5C9A-4632-AAD3-A179EF83CF20")]
)
self.assertIn(user3.partner_id, event.partner_ids)
self.assertNotIn(user2.partner_id, event.partner_ids)
self.assertEqual(len(event.attendee_ids), 2)
def _create_multi_user_test_event(self):
return (
self.env["calendar.event"]
.with_user(self.user_1)
.create(
{
"name": "Test event",
"partner_ids": [
Command.set(
[
self.user_2.partner_id.id,
self.user_3.partner_id.id,
self.user_1.partner_id.id,
]
),
Command.create(
{
"name": "Test partner",
"email": "testpartner@example.com",
}
),
],
"start": datetime.now() + timedelta(days=2),
"stop": datetime.now() + timedelta(days=2, hours=1),
}
)
)
def test_multiple_user_attendees_event_to_server_create(self):
pass
with self._patch_all_3_users_davclients() as (_, mock_calendar):
self._create_multi_user_test_event()
self.assertEqual(mock_calendar.add_event.call_count, 3)
def test_event_to_server_delete(self):
with self._patch_all_3_users_davclients() as (_, mock_calendar):
self._create_multi_user_test_event().unlink()
self.assertEqual(
mock_calendar.event_by_uid.return_value.delete.call_count, 3
)
def test_event_to_server_update(self):
with self._patch_all_3_users_davclients() as (_, mock_calendar):
self._create_multi_user_test_event().write(
{"start": datetime.now() + timedelta(days=14)}
)
self.assertEqual(mock_calendar.save_event.call_count, 3)
def test_recurrent_event_to_server(self):
with self._patch_all_3_users_davclients() as (_, mock_calendar):
self._create_multi_user_test_event().write(
{
"recurrency": True,
}
)
args = mock_calendar.save_event.call_args
self.assertEqual(mock_calendar.save_event.call_count, 3)
@contextmanager
def _patch_all_3_users_davclients(self):
with patch("caldav.DAVClient") as MockDAVClient:
(self.user_1 | self.user_2 | self.user_3)._compute_is_caldav_enabled()
mock_client = MockDAVClient.return_value
mock_calendar = MagicMock()
mock_event_by_uid = MagicMock()
mock_client.calendar.return_value = mock_calendar
mock_calendar.events.return_value = []
mock_calendar.event_by_uid.return_value = mock_event_by_uid
yield mock_client, mock_calendar