19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:28 +01:00
parent 20ddc1b4a3
commit c0efcc53f5
1162 changed files with 125577 additions and 105287 deletions

View file

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_google_event
from . import test_sync_common
from . import test_sync_google2odoo
from . import test_sync_odoo2google
from . import test_sync_odoo2google_mail
from . import test_token_access

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import BaseCase
from odoo.addons.google_calendar.utils.google_calendar import GoogleEvent

View file

@ -1,33 +1,41 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest.mock import MagicMock, patch
from collections import defaultdict
from contextlib import contextmanager
from datetime import datetime
from freezegun import freeze_time
from unittest.mock import patch
from odoo.addons.google_calendar.utils.google_calendar import GoogleCalendarService
from odoo.addons.google_account.models.google_service import GoogleService
from odoo.addons.google_calendar.models.res_users import User
from odoo.addons.google_calendar.models.google_sync import GoogleSync
from odoo.tests.common import HttpCase, new_test_user
from freezegun import freeze_time
from contextlib import contextmanager
from odoo.addons.google_calendar.models.res_users import ResUsers
from odoo.addons.google_calendar.models.google_sync import google_calendar_token, GoogleCalendarSync
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.tests.common import HttpCase
from odoo.tools import mute_logger
def patch_api(func):
@patch.object(GoogleSync, '_google_insert', MagicMock(spec=GoogleSync._google_insert))
@patch.object(GoogleSync, '_google_delete', MagicMock(spec=GoogleSync._google_delete))
@patch.object(GoogleSync, '_google_patch', MagicMock(spec=GoogleSync._google_patch))
def patched(self, *args, **kwargs):
return func(self, *args, **kwargs)
with self.mock_google_sync():
return func(self, *args, **kwargs)
return patched
@patch.object(User, '_get_google_calendar_token', lambda user: 'dummy-token')
class TestSyncGoogle(HttpCase):
def setUp(self):
super().setUp()
self.google_service = GoogleCalendarService(self.env['google.service'])
self.organizer_user = new_test_user(self.env, login="organizer_user")
self.attendee_user = new_test_user(self.env, login='attendee_user')
@patch.object(ResUsers, '_get_google_calendar_token', lambda user: 'dummy-token')
class TestSyncGoogle(HttpCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.google_service = GoogleCalendarService(cls.env['google.service'])
cls.env.user.sudo().unpause_google_synchronization()
cls.organizer_user = mail_new_test_user(cls.env, login="organizer_user")
cls.attendee_user = mail_new_test_user(cls.env, login='attendee_user')
m = mute_logger('odoo.addons.auth_signup.models.res_users')
mute_logger.__enter__(m) # noqa: PLC2801
cls.addClassCleanup(mute_logger.__exit__, m, None, None, None)
@contextmanager
def mock_datetime_and_now(self, mock_dt):
@ -40,36 +48,89 @@ class TestSyncGoogle(HttpCase):
patch.object(self.env.cr, 'now', lambda: mock_dt):
yield
@contextmanager
def mock_google_sync(self, user_id=None):
self._gsync_deleted_ids = []
self._gsync_insert_values = []
self._gsync_patch_values = defaultdict(list)
# as these are normally post-commit hooks, we don't change any state here
def _mock_delete(model, service, google_id, **kwargs):
with google_calendar_token(user_id or model.env.user.sudo()) as token:
if token:
self._gsync_deleted_ids.append(google_id)
def _mock_insert(model, service, values, **kwargs):
if not values:
return
with google_calendar_token(user_id or model.env.user.sudo()) as token:
if token:
self._gsync_insert_values.append((values, kwargs))
def _mock_patch(model, service, google_id, values, **kwargs):
with google_calendar_token(user_id or model.env.user.sudo()) as token:
if token:
self._gsync_patch_values[google_id].append((values, kwargs))
with self.env.cr.savepoint(), \
patch.object(GoogleCalendarSync, '_google_insert', autospec=True, wraps=GoogleCalendarSync, side_effect=_mock_insert), \
patch.object(GoogleCalendarSync, '_google_delete', autospec=True, wraps=GoogleCalendarSync, side_effect=_mock_delete), \
patch.object(GoogleCalendarSync, '_google_patch', autospec=True, wraps=GoogleCalendarSync, side_effect=_mock_patch):
yield
@contextmanager
def mock_google_service(self):
self._gservice_request_uris = []
def _mock_do_request(model, uri, *args, **kwargs):
self._gservice_request_uris.append(uri)
return (200, {}, datetime.now())
with patch.object(GoogleService, '_do_request', autospec=True, wraps=GoogleService, side_effect=_mock_do_request):
yield
def assertGoogleEventDeleted(self, google_id):
GoogleSync._google_delete.assert_called()
args, dummy = GoogleSync._google_delete.call_args
self.assertEqual(args[1], google_id, "Event should have been deleted")
self.assertIn(google_id, self._gsync_deleted_ids, "Event should have been deleted")
def assertGoogleEventNotDeleted(self):
GoogleSync._google_delete.assert_not_called()
self.assertFalse(self._gsync_deleted_ids)
def assertGoogleEventInserted(self, values, timeout=None):
expected_args = (values,)
expected_kwargs = {'timeout': timeout} if timeout else {}
GoogleSync._google_insert.assert_called_once()
args, kwargs = GoogleSync._google_insert.call_args
args[1:][0].pop('conferenceData', None)
self.assertEqual(args[1:], expected_args) # skip Google service arg
self.assertEqual(kwargs, expected_kwargs)
self.assertEqual(len(self._gsync_insert_values), 1)
matching = []
for insert_values, insert_kwargs in self._gsync_insert_values:
if all(insert_values.get(key, False) == value for key, value in values.items()):
matching.append((insert_values, insert_kwargs))
self.assertGreaterEqual(len(matching), 1, 'There must be at least 1 matching insert.')
insert_values, insert_kwargs = matching[0]
self.assertDictEqual(insert_kwargs, {'timeout': timeout} if timeout else {})
def assertGoogleEventInsertedMultiTime(self, values, timeout=None):
self.assertGreaterEqual(len(self._gsync_insert_values), 1)
matching = []
for insert_values, insert_kwargs in self._gsync_insert_values:
if all(insert_values.get(key, False) == value for key, value in values.items()):
matching.append((insert_values, insert_kwargs))
self.assertGreaterEqual(len(matching), 1, 'There must be at least 1 matching insert.')
insert_values, insert_kwargs = matching[0]
self.assertDictEqual(insert_kwargs, {'timeout': timeout} if timeout else {})
def assertGoogleEventNotInserted(self):
GoogleSync._google_insert.assert_not_called()
self.assertFalse(self._gsync_insert_values)
def assertGoogleEventPatched(self, google_id, values, timeout=None):
expected_args = (google_id, values)
expected_kwargs = {'timeout': timeout} if timeout else {}
GoogleSync._google_patch.assert_called_once()
args, kwargs = GoogleSync._google_patch.call_args
self.assertEqual(args[1:], expected_args) # skip Google service arg
self.assertEqual(kwargs, expected_kwargs)
patch_values_all = self._gsync_patch_values.get(google_id)
self.assertTrue(patch_values_all)
matching = []
for patch_values, patch_kwargs in patch_values_all:
if all(patch_values.get(key, False) == values[key] for key in values):
matching.append((patch_values, patch_kwargs))
self.assertGreaterEqual(len(matching), 1, 'There must be at least 1 matching patch.')
patch_values, patch_kwargs = matching[0]
self.assertDictEqual(patch_kwargs, {'timeout': timeout} if timeout else {})
def assertGoogleEventNotPatched(self):
GoogleSync._google_patch.assert_not_called()
self.assertFalse(self._gsync_patch_values)
def assertGoogleAPINotCalled(self):
self.assertGoogleEventNotPatched()
@ -77,10 +138,10 @@ class TestSyncGoogle(HttpCase):
self.assertGoogleEventNotDeleted()
def assertGoogleEventSendUpdates(self, expected_value):
GoogleService._do_request.assert_called_once()
args, _ = GoogleService._do_request.call_args
val = "sendUpdates=%s" % expected_value
self.assertTrue(val in args[0], "The URL should contain %s" % val)
self.assertEqual(len(self._gservice_request_uris), 1)
uri = self._gservice_request_uris[0]
uri_parameter = "sendUpdates=%s" % expected_value
self.assertIn(uri_parameter, uri, "The URL should contain %s" % uri_parameter)
def call_post_commit_hooks(self):
"""
@ -91,8 +152,3 @@ class TestSyncGoogle(HttpCase):
while funcs:
func = funcs.popleft()
func()
def assertGoogleEventHasNoConferenceData(self):
GoogleSync._google_insert.assert_called_once()
args, _ = GoogleSync._google_insert.call_args
self.assertFalse(args[1].get('conferenceData', False))

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import pytz
@ -6,28 +5,33 @@ from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta
from odoo.tests.common import new_test_user
from odoo.exceptions import ValidationError
from odoo.addons.google_calendar.models.res_users import ResUsers
from odoo.addons.google_calendar.tests.test_sync_common import TestSyncGoogle, patch_api
from odoo.addons.google_calendar.utils.google_calendar import GoogleEvent, GoogleCalendarService
from odoo import Command, tools
from unittest.mock import patch
class TestSyncGoogle2Odoo(TestSyncGoogle):
def setUp(self):
super().setUp()
self.public_partner = self.env['res.partner'].create({
@patch.object(ResUsers, '_get_google_calendar_token', lambda user: 'dummy-token')
class TestSyncGoogle2Odoo(TestSyncGoogle):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.other_company = cls.env['res.company'].create({'name': 'Other Company'})
cls.public_partner = cls.env['res.partner'].create({
'name': 'Public Contact',
'email': 'public_email@example.com',
'type': 'contact',
})
self.env.ref('base.partner_admin').write({
cls.env.ref('base.partner_admin').write({
'name': 'Mitchell Admin',
'email': 'admin@yourcompany.example.com',
})
self.private_partner = self.env['res.partner'].create({
cls.private_partner = cls.env['res.partner'].create({
'name': 'Private Contact',
'email': 'private_email@example.com',
'type': 'private',
'company_id': cls.other_company.id,
})
def generate_recurring_event(self, mock_dt, **values):
@ -117,7 +121,12 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
@patch_api
def test_new_google_event(self):
description = '<script>alert("boom")</script><p style="white-space: pre"><h1>HELLO</h1></p><ul><li>item 1</li><li>item 2</li></ul>'
description = (
'<div><p style="white-space: pre"></p>'
'<h1>HELLO</h1><ul><li>item 1</li><li>item 2</li></ul><br>'
'<strong>Contact Details</strong><br>Public Contact<br>'
'<a href="mailto:public_email@example.com">public_email@example.com</a></div>'
)
values = {
'id': 'oj44nep1ldf8a3ll02uip0c9aa',
'description': description,
@ -391,12 +400,11 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
"updated": self.now,
'organizer': {'email': 'odoocalendarref@gmail.com', 'self': True},
'summary': 'coucou',
'visibility': 'public',
'attendees': [], # <= attendee removed in Google
'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=2;BYDAY=MO'],
'reminders': {'useDefault': True},
'start': {'date': '2020-01-6'},
'end': {'date': '2020-01-7'},
'start': {'date': '2020-01-06'},
'end': {'date': '2020-01-07'},
}])
events = recurrence.calendar_event_ids.sorted('start')
self.assertEqual(events.partner_ids, user.partner_id)
@ -415,11 +423,10 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'description': 'Small mini desc',
'organizer': {'email': 'odoocalendarref@gmail.com', 'self': True},
'summary': 'Pricing new update',
'visibility': 'public',
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=3;BYDAY=MO'],
'reminders': {'useDefault': True},
'start': {'date': '2020-01-6'},
'end': {'date': '2020-01-7'},
'start': {'date': '2020-01-06'},
'end': {'date': '2020-01-07'},
'transparency': 'opaque',
}
self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
@ -450,8 +457,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'visibility': 'public',
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=3;BYDAY=MO'],
'reminders': {'useDefault': True},
'start': {'dateTime': '2020-01-06T18:00:00+01:00'},
'end': {'dateTime': '2020-01-06T19:00:00+01:00'},
'start': {'dateTime': '2020-01-06T18:00:00+01:00', 'date': None},
'end': {'dateTime': '2020-01-06T19:00:00+01:00', 'date': None},
}
self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
recurrence = self.env['calendar.recurrence'].search([('google_id', '=', values.get('id'))])
@ -476,8 +483,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'organizer': {'email': self.env.user.email, 'self': True},
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=3;BYDAY=MO'],
'reminders': {'useDefault': True},
'start': {'date': '2020-01-6'},
'end': {'date': '2020-01-7'},
'start': {'date': '2020-01-06'},
'end': {'date': '2020-01-07'},
}, { # Third event has been deleted
'id': '%s_20200113' % recurrence_id,
'originalStartTime': {'dateTime': '2020-01-13'},
@ -501,9 +508,9 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
"id": recurrence_id,
"updated": "2020-01-13T16:17:03.806Z",
"summary": "r rul",
"start": {"date": "2020-01-6"},
"start": {"date": "2020-01-06"},
'organizer': {'email': self.env.user.email, 'self': True},
"end": {"date": "2020-01-7"},
"end": {"date": "2020-01-07"},
'reminders': {'useDefault': True},
"recurrence": ["RRULE:FREQ=WEEKLY;WKST=SU;COUNT=3;BYDAY=MO"],
}, {
@ -534,7 +541,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'status': 'confirmed',
'summary': 'rrule',
'reminders': {'useDefault': True},
'updated': self.now
'updated': self.now,
'guestsCanModify': True,
}, {
'summary': 'edited', # Name changed
'id': '%s_20200101' % recurrence_id,
@ -544,6 +552,7 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'end': {'date': '2020-01-02'},
'reminders': {'useDefault': True},
'updated': self.now,
'guestsCanModify': True,
}])
self.sync(events)
recurrence = self.env['calendar.recurrence'].search([('google_id', '=', recurrence_id)])
@ -597,10 +606,11 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'id': recurrence_id,
'summary': 'Pricing new update',
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=3;BYDAY=MO'],
'start': {'date': '2020-01-6'},
'end': {'date': '2020-01-7'},
'start': {'date': '2020-01-06'},
'end': {'date': '2020-01-07'},
'reminders': {'useDefault': True},
'updated': self.now,
'guestsCanModify': True,
},
{ # Third event has been moved
'id': '%s_20200113' % recurrence_id,
@ -610,6 +620,7 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'originalStartTime': {'date': '2020-01-13'},
'reminders': {'useDefault': True},
'updated': self.now,
'guestsCanModify': True,
}])
self.sync(events)
recurrence = self.env['calendar.recurrence'].search([('google_id', '=', recurrence_id)])
@ -648,10 +659,11 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
"attendees": [
{
"email": "odoobot@example.com", "state": "accepted",
"email": "odoobot@example.com", "responseStatus": "accepted",
},
],
'updated': self.now,
'guestsCanModify': True,
}
self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
events = recurrence.calendar_event_ids.sorted('start')
@ -691,7 +703,7 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
"attendees": [
{
"email": "odoobot@example.com", "state": "accepted",
"email": "odoobot@example.com", "responseStatus": "accepted",
},
],
'updated': self.now,
@ -739,12 +751,12 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'id': google_id,
'summary': 'coucou again',
'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=MO'],
'start': {'dateTime': '2021-02-15T09:00:00+01:00'}, # 8:00 UTC
'end': {'dateTime': '2021-02-15-T11:00:00+01:00'},
'start': {'dateTime': '2021-02-15T09:00:00+01:00', 'date': None}, # 8:00 UTC
'end': {'dateTime': '2021-02-15-T11:00:00+01:00', 'date': None},
'reminders': {'useDefault': True},
"attendees": [
{
"email": "odoobot@example.com", "state": "accepted",
"email": "odoobot@example.com", "responseStatus": "accepted",
},
],
'updated': self.now,
@ -789,15 +801,16 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'id': google_id,
'summary': "It's me again",
'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=4;BYDAY=MO'],
'start': {'dateTime': '2021-02-15T12:00:00+01:00'}, # 11:00 UTC
'end': {'dateTime': '2021-02-15-T15:00:00+01:00'},
'start': {'dateTime': '2021-02-15T12:00:00+01:00', 'date': None}, # 11:00 UTC
'end': {'dateTime': '2021-02-15-T15:00:00+01:00', 'date': None},
'reminders': {'useDefault': True},
"attendees": [
{
"email": "odoobot@example.com", "state": "accepted",
"email": "odoobot@example.com", "responseStatus": "accepted",
},
],
'updated': self.now,
'guestsCanModify': True,
}
self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
@ -850,8 +863,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'visibility': 'public',
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=3;BYDAY=MO'],
'reminders': {'useDefault': True},
'start': {'dateTime': '2020-01-06T18:00:00+01:00', 'timeZone': 'Pacific/Auckland'},
'end': {'dateTime': '2020-01-06T19:00:00+01:00', 'timeZone': 'Pacific/Auckland'},
'start': {'dateTime': '2020-01-06T18:00:00+01:00', 'timeZone': 'Pacific/Auckland', 'date': None},
'end': {'dateTime': '2020-01-06T19:00:00+01:00', 'timeZone': 'Pacific/Auckland', 'date': None},
}
self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
recurrence = self.env['calendar.recurrence'].search([('google_id', '=', values.get('id'))])
@ -891,6 +904,7 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
"status": "confirmed",
"summary": "Weekly test",
"updated": "2023-02-20T11:45:08.547Z",
'guestsCanModify': True,
},
{
"attendees": [
@ -922,6 +936,7 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
"status": "confirmed",
"summary": "Weekly test 2",
"updated": "2023-02-20T11:48:00.634Z",
'guestsCanModify': True,
},
]
google_events = GoogleEvent(values)
@ -950,8 +965,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'recurrence': ['EXDATE;TZID=Europe/Rome:20200113',
'RRULE;X-EVOLUTION-ENDDATE=20200120:FREQ=WEEKLY;COUNT=3;BYDAY=MO;X-RELATIVE=1'],
'reminders': {'useDefault': True},
'start': {'date': '2020-01-6'},
'end': {'date': '2020-01-7'},
'start': {'date': '2020-01-06'},
'end': {'date': '2020-01-07'},
}
self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
recurrence = self.env['calendar.recurrence'].search([('google_id', '=', values.get('id'))])
@ -988,11 +1003,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
'start': {
'dateTime': '2020-01-06T18:00:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': '2020-01-13T19:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
}
self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
@ -1005,8 +1022,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'visibility': 'public',
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=3;BYDAY=MO'],
'reminders': {'useDefault': True},
'start': {'dateTime': '2020-01-06T18:00:00+01:00', 'timeZone': 'Europe/Brussels'},
'end': {'dateTime': '2020-01-06T19:00:00+01:00', 'timeZone': 'Europe/Brussels'},
'start': {'dateTime': '2020-01-06T18:00:00+01:00', 'timeZone': 'Europe/Brussels', 'date': None},
'end': {'dateTime': '2020-01-06T19:00:00+01:00', 'timeZone': 'Europe/Brussels', 'date': None},
}
recurrence = self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
events = recurrence.calendar_event_ids.sorted('start')
@ -1232,11 +1249,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'overrides': [{"method": "email", "minutes": 10}], 'useDefault': False},
'start': {
'dateTime': pytz.utc.localize(start).isoformat(),
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None
},
'end': {
'dateTime': pytz.utc.localize(end).isoformat(),
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None
},
}
self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
@ -1261,11 +1280,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'overrides': [{"method": "email", "minutes": 10}], 'useDefault': False},
'start': {
'dateTime': pytz.utc.localize(start).isoformat(),
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': pytz.utc.localize(end).isoformat(),
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
}
self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
@ -1338,7 +1359,6 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
"updated": self.now,
'organizer': {'email': 'odoocalendarref@gmail.com', 'self': True},
'summary': """I don't want to be with me anymore""",
'visibility': 'public',
'attendees': [{
'displayName': 'calendar-user (base.group_user)',
'email': 'c.c@example.com',
@ -1347,13 +1367,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
'start': {
'dateTime': '2020-01-13T16:55:00+01:00',
'timeZone': 'Europe/Brussels',
'date': None,
'timeZone': 'Europe/Brussels'
},
'end': {
'dateTime': '2020-01-13T19:55:00+01:00',
'date': None,
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None
},
'transparency': 'opaque',
}
@ -1383,7 +1403,7 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'start': {'date': str(event.start_date), 'dateTime': None},
'end': {'date': str(event.stop_date + relativedelta(days=1)), 'dateTime': None},
'summary': 'coucou',
'description': '',
'description': event.description,
'location': '',
'guestsCanModify': True,
'organizer': {'email': 'c.c@example.com', 'self': False},
@ -1392,7 +1412,6 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.id,
'%s_owner_id' % self.env.cr.dbname: other_user.id}},
'reminders': {'overrides': [], 'useDefault': False},
'visibility': 'public',
'transparency': 'opaque',
}, timeout=3)
@ -1427,8 +1446,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
# 'visibility': 'public',
'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=MO'],
'reminders': {'useDefault': True},
'start': {'dateTime': '2021-02-15T8:00:00+01:00', 'timeZone': 'Europe/Brussels'},
'end': {'dateTime': '2021-02-15T10:00:00+01:00', 'timeZone': 'Europe/Brussels'},
'start': {'dateTime': '2021-02-15T8:00:00+01:00', 'timeZone': 'Europe/Brussels', 'date': None},
'end': {'dateTime': '2021-02-15T10:00:00+01:00', 'timeZone': 'Europe/Brussels', 'date': None},
}
self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
attendee = recurrence.calendar_event_ids.attendee_ids.mapped('state')
@ -1448,8 +1467,9 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
# 'visibility': 'public',
'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=MO'],
'reminders': {'useDefault': True},
'start': {'dateTime': '2021-02-15T8:00:00+01:00', 'timeZone': 'Europe/Brussels'},
'end': {'dateTime': '2021-02-15T10:00:00+01:00', 'timeZone': 'Europe/Brussels'},
'start': {'dateTime': '2021-02-15T8:00:00+01:00', 'timeZone': 'Europe/Brussels', 'date': None},
'end': {'dateTime': '2021-02-15T10:00:00+01:00', 'timeZone': 'Europe/Brussels', 'date': None},
'guestsCanModify': True,
}
self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
recurrence = self.env['calendar.recurrence'].search([('google_id', '=', google_id)])
@ -1515,11 +1535,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=MO'],
'start': {
'dateTime': '2020-01-13T16:00:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': '2020-01-13T20:00:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None
},
}])
self.sync(gevent)
@ -1547,11 +1569,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
'start': {
'dateTime': '2020-01-13T16:00:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': '2020-01-13T20:00:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
}
event = self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
@ -1574,11 +1598,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
'start': {
'dateTime': '2020-01-13T16:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': '2020-01-13T19:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None
},
'conferenceData': {
'entryPoints': [{
@ -1670,11 +1696,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
'start': {
'dateTime': '2020-01-13T16:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': '2020-01-13T19:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'transparency': 'transparent'
}
@ -1704,19 +1732,20 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
'start': {
'dateTime': '2020-01-13T16:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': '2020-01-13T19:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
}
self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
event = self.env['calendar.event'].search([('google_id', '=', values.get('id'))])
private_attendee = event.attendee_ids.filtered(lambda e: e.email == self.private_partner.email)
self.assertNotEqual(self.private_partner.id, private_attendee.partner_id.id)
self.assertNotEqual(private_attendee.partner_id.type, 'private')
self.assertEqual(self.private_partner.id, private_attendee.partner_id.id)
self.assertGoogleAPINotCalled()
@patch_api
@ -1735,23 +1764,21 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
}, ],
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=3;BYDAY=MO'],
'reminders': {'useDefault': True},
'start': {'date': '2020-01-6'},
'end': {'date': '2020-01-7'},
'start': {'date': '2020-01-06'},
'end': {'date': '2020-01-07'},
}
self.env['calendar.recurrence']._sync_google2odoo(GoogleEvent([values]))
recurrence = self.env['calendar.recurrence'].search([('google_id', '=', values.get('id'))])
events = recurrence.calendar_event_ids
private_attendees = events.mapped('attendee_ids').filtered(lambda e: e.email == self.private_partner.email)
self.assertTrue(all([a.partner_id.id != self.private_partner.id for a in private_attendees]))
self.assertTrue(all([a.partner_id == self.private_partner for a in private_attendees]))
self.assertTrue(all([a.partner_id.type != 'private' for a in private_attendees]))
self.assertGoogleAPINotCalled()
@patch_api
def test_alias_email_sync_recurrence(self):
catchall_domain = self.env['ir.config_parameter'].sudo().get_param("mail.catchall.domain")
alias_model = self.env['ir.model'].search([('model', '=', 'calendar.event')])
self.env['mail.alias'].create({'alias_name': 'sale', 'alias_model_id': alias_model.id})
alias_email = 'sale@%s' % catchall_domain if catchall_domain else 'sale@'
mail_alias = self.env['mail.alias'].create({'alias_name': 'sale', 'alias_model_id': alias_model.id})
google_id = 'oj44nep1ldf8a3ll02uip0c9aa'
base_event = self.env['calendar.event'].create({
@ -1779,7 +1806,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
"attendees": [
{
"email": alias_email, "state": "accepted",
"email": mail_alias.display_name,
"responseStatus": "accepted",
},
],
'updated': self.now,
@ -1804,11 +1832,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
'start': {
'dateTime': '2020-01-13T16:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': '2020-01-13T19:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
}
self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
@ -1847,11 +1877,13 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'reminders': {'useDefault': True},
'start': {
'dateTime': '2020-01-13T16:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': '2020-01-13T19:55:00+01:00',
'timeZone': 'Europe/Brussels'
'timeZone': 'Europe/Brussels',
'date': None
},
}
@ -1882,6 +1914,7 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
"recurrence": [f"RRULE:FREQ={frequency};COUNT={count}"],
"reminders": {"useDefault": True},
"updated": "2023-03-27T11:45:08.547Z",
'guestsCanModify': True,
}]
google_event = GoogleEvent(google_value)
self.env['calendar.recurrence']._sync_google2odoo(google_event)
@ -1913,8 +1946,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'summary': 'First title',
'creator': {'email': 'john.doe@example.com', 'self': True},
'organizer': {'email': 'john.doe@example.com', 'self': True},
'start': {'dateTime': '2023-05-12T09:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'end': {'dateTime': '2023-05-12T10:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'start': {'dateTime': '2023-05-12T09:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'end': {'dateTime': '2023-05-12T10:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20230518T215959Z;BYDAY=FR'],
'iCalUID': '59orfkiunbn2vlp6c2tndq6ui0@google.com',
'reminders': {'useDefault': True},
@ -1929,8 +1962,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'summary': 'Second title',
'creator': {'email': 'john.doe@example.com', 'self': True},
'organizer': {'email': 'john.doe@example.com', 'self': True},
'start': {'dateTime': '2023-05-19T09:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'end': {'dateTime': '2023-05-19T10:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'start': {'dateTime': '2023-05-19T09:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'end': {'dateTime': '2023-05-19T10:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=2;BYDAY=FR'],
'iCalUID': '59orfkiunbn2vlp6c2tndq6ui0_R20230519T070000@google.com',
'reminders': {'useDefault': True},
@ -1945,8 +1978,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'summary': 'Second title',
'creator': {'email': 'john.doe@example.com', 'self': True},
'organizer': {'email': 'john.doe@example.com', 'self': True},
'start': {'dateTime': '2023-05-26T08:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'end': {'dateTime': '2023-05-26T09:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'start': {'dateTime': '2023-05-26T08:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'end': {'dateTime': '2023-05-26T09:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'recurringEventId': '59orfkiunbn2vlp6c2tndq6ui0_R20230519T070000',
'originalStartTime': {'dateTime': '2023-05-26T09:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'reminders': {'useDefault': True},
@ -2021,8 +2054,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'summary': 'First title',
'creator': {'email': 'john.doe@example.com', 'self': True},
'organizer': {'email': 'john.doe@example.com', 'self': True},
'start': {'dateTime': '2023-05-12T09:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'end': {'dateTime': '2023-05-12T10:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'start': {'dateTime': '2023-05-12T09:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'end': {'dateTime': '2023-05-12T10:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20230518T215959Z;BYDAY=FR'],
'iCalUID': '59orfkiunbn2vlp6c2tndq6ui0@google.com',
'reminders': {'useDefault': True},
@ -2037,8 +2070,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'summary': 'Second title',
'creator': {'email': 'john.doe@example.com', 'self': True},
'organizer': {'email': 'john.doe@example.com', 'self': True},
'start': {'dateTime': '2023-05-19T09:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'end': {'dateTime': '2023-05-19T10:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'start': {'dateTime': '2023-05-19T09:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'end': {'dateTime': '2023-05-19T10:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=2;BYDAY=FR'],
'iCalUID': '59orfkiunbn2vlp6c2tndq6ui0_R20230519T070000@google.com',
'reminders': {'useDefault': True},
@ -2053,8 +2086,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'summary': 'Second title',
'creator': {'email': 'john.doe@example.com', 'self': True},
'organizer': {'email': 'john.doe@example.com', 'self': True},
'start': {'dateTime': '2023-05-26T08:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'end': {'dateTime': '2023-05-26T09:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'start': {'dateTime': '2023-05-26T08:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'end': {'dateTime': '2023-05-26T09:00:00+02:00', 'timeZone': 'Europe/Brussels', 'date': None},
'recurringEventId': '59orfkiunbn2vlp6c2tndq6ui0', # Range removed
'originalStartTime': {'dateTime': '2023-05-26T09:00:00+02:00', 'timeZone': 'Europe/Brussels'},
'reminders': {'useDefault': True},
@ -2094,6 +2127,95 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
# Not call API
self.assertGoogleAPINotCalled()
@patch_api
def test_event_guest_modify_permission(self):
"""
'guestsCanModify' is a permission set on Google side to allow or forbid guests editing the event.
This test states that Odoo Calendar:
1. forbids the updates of non-editable events by guests.
2. allows editable events being updated by guests.
3. allows guests to stop and restart their synchronizations with Google Calendars.
"""
guest_user = new_test_user(self.env, login='calendar-user')
# Create an event editable only by the organizer. Guests can't modify it.
not_editable_event_values = {
'id': 'notEditableEventGoogleId',
'guestsCanModify': False,
'description': 'Editable only by organizer',
'organizer': {'email': self.env.user.email, 'self': True},
'summary': 'Editable only by organizer',
'visibility': 'public',
'attendees': [{
'displayName': 'Guest User',
'email': guest_user.email,
'responseStatus': 'accepted'
}],
'reminders': {'useDefault': True},
'start': {
'dateTime': '2023-07-05T16:55:00+01:00',
'timeZone': 'Europe/Brussels',
'date': None
},
'end': {
'dateTime': '2023-07-05T19:55:00+01:00',
'timeZone': 'Europe/Brussels',
'date': None
},
}
# Create an event editable by guests and organizer.
editable_event_values = {
'id': 'editableEventGoogleId',
'guestsCanModify': True,
'description': 'Editable by everyone',
'organizer': {'email': self.env.user.email, 'self': True},
'summary': 'Editable by everyone',
'visibility': 'public',
'attendees': [{
'displayName': 'Guest User',
'email': guest_user.email,
'responseStatus': 'accepted'
}],
'reminders': {'useDefault': True},
'start': {
'dateTime': '2023-07-05T16:55:00+01:00',
'timeZone': 'Europe/Brussels',
'date': None,
},
'end': {
'dateTime': '2023-07-05T19:55:00+01:00',
'timeZone': 'Europe/Brussels',
'date': None,
},
}
# Sync events from Google to Odoo and get them after sync.
self.env['calendar.event']._sync_google2odoo(GoogleEvent([not_editable_event_values, editable_event_values]))
not_editable_event = self.env['calendar.event'].search([('google_id', '=', not_editable_event_values.get('id'))])
editable_event = self.env['calendar.event'].search([('google_id', '=', editable_event_values.get('id'))])
# Assert that event is created in Odoo with proper values for guests_readonly variable.
self.assertFalse(editable_event.guests_readonly, "Value 'guestCanModify' received from Google must be True.")
self.assertTrue(not_editable_event.guests_readonly, "Value 'guestCanModify' received from Google must be False.")
# Assert that organizer can edit both events.
self.assertTrue(editable_event.with_user(editable_event.user_id).write({'name': 'Edited by organizer!'}))
self.assertTrue(not_editable_event.with_user(not_editable_event.user_id).write({'name': 'Edited by organizer!'}))
# Assert that a validation error is raised when guest updates the not editable event.
with self.assertRaises(ValidationError):
self.assertTrue(not_editable_event.with_user(guest_user).write({'name': 'Edited by attendee!'}),
"Attendee shouldn't be able to modify event with 'guests_readonly' variable as 'True'.")
# Assert that normal event can be edited by guest.
self.assertTrue(editable_event.with_user(guest_user).write({'name': 'Edited by attendee!'}),
"Attendee should be able to modify event with 'guests_readonly' variable as 'False'.")
# Assert that guest user can restart the synchronization of its calendar (containing non-editable events).
guest_user.sudo().stop_google_synchronization()
self.assertTrue(guest_user.google_synchronization_stopped)
guest_user.sudo().restart_google_synchronization()
self.assertFalse(guest_user.google_synchronization_stopped)
@patch_api
def test_attendee_status_is_not_updated_when_syncing_and_time_data_is_not_changed(self):
recurrence_id = "aaaaaaaa"
@ -2121,8 +2243,8 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
'summary': 'coucou',
'id': recurrence_id,
'recurrence': ['RRULE:FREQ=DAILY;INTERVAL=1;COUNT=3'],
'start': {'dateTime': '2020-01-06T10:00:00+01:00'},
'end': {'dateTime': '2020-01-06T11:00:00+01:00'},
'start': {'dateTime': '2020-01-06T10:00:00+01:00', 'date': None},
'end': {'dateTime': '2020-01-06T11:00:00+01:00', 'date': None},
'reminders': {'useDefault': True},
'organizer': {'email': organizer.partner_id.email},
'attendees': [{'email': organizer.partner_id.email, 'responseStatus': 'accepted'}, {'email': other_user.partner_id.email, 'responseStatus': 'accepted'}],
@ -2134,100 +2256,28 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
self.assertEqual(events[0].attendee_ids[0].state, 'accepted', 'after google sync, organizer should have accepted status still')
self.assertGoogleAPINotCalled()
@patch.object(GoogleCalendarService, "get_events")
def test_recurring_event_moved_to_future(self, mock_get_events):
# There's a daily recurring event from 2024-07-01 to 2024-07-02
recurrence_id = "abcd1"
recurrence = self.generate_recurring_event(
mock_dt="2024-07-01",
google_id=recurrence_id,
rrule="FREQ=DAILY;INTERVAL=1;COUNT=2",
start=datetime(2024, 7, 1, 9),
stop=datetime(2024, 7, 1, 10),
partner_ids=[
Command.set(
[
self.organizer_user.partner_id.id,
self.attendee_user.partner_id.id,
]
)
],
)
self.assertRecordValues(
recurrence.calendar_event_ids.sorted("start"),
[
{
"start": datetime(2024, 7, 1, 9),
"stop": datetime(2024, 7, 1, 10),
"google_id": f"{recurrence_id}_20240701T090000Z",
},
{
"start": datetime(2024, 7, 2, 9),
"stop": datetime(2024, 7, 2, 10),
"google_id": f"{recurrence_id}_20240702T090000Z",
},
],
)
# User moves batch to next week
common = {
"attendees": [
{
"email": self.attendee_user.partner_id.email,
"responseStatus": "needsAction",
},
{
"email": self.organizer_user.partner_id.email,
"responseStatus": "needsAction",
},
],
"organizer": {"email": self.organizer_user.partner_id.email},
"reminders": {"useDefault": True},
"summary": "coucou",
"updated": "2024-07-02T08:00:00Z",
@patch_api
def test_create_event_with_default_and_undefined_privacy(self):
""" Check if google events are created in Odoo when 'default' privacy setting is defined and also when it is not. """
# Sync events from Google to Odoo after adding the privacy property.
sample_event_values = {
'summary': 'Test',
'start': {'dateTime': '2020-01-06T10:00:00+01:00'},
'end': {'dateTime': '2020-01-06T11:00:00+01:00'},
'reminders': {'useDefault': True},
'organizer': {'email': self.env.user.email},
'attendees': [{'email': self.env.user.email, 'responseStatus': 'accepted'}],
'updated': self.now,
}
google_events = [
# Recurrence event
dict(
common,
id=recurrence_id,
start={"dateTime": "2024-07-08T09:00:00+00:00"},
end={"dateTime": "2024-07-08T10:00:00+00:00"},
recurrence=["RRULE:FREQ=DAILY;INTERVAL=1;COUNT=2"],
),
# Cancelled instances
{"id": f"{recurrence_id}_20240701T090000Z", "status": "cancelled"},
{"id": f"{recurrence_id}_20240702T090000Z", "status": "cancelled"},
# New base event
dict(
common,
id=f"{recurrence_id}_20240708T090000Z",
start={"dateTime": "2024-07-08T09:00:00+00:00"},
end={"dateTime": "2024-07-08T10:00:00+00:00"},
recurringEventId=recurrence_id,
),
]
mock_get_events.return_value = (
GoogleEvent(google_events),
None,
[{"method": "popup", "minutes": 30}],
)
with self.mock_datetime_and_now("2024-04-03"):
self.organizer_user.sudo()._sync_google_calendar(self.google_service)
self.assertRecordValues(
recurrence.calendar_event_ids.sorted("start"),
[
{
"start": datetime(2024, 7, 8, 9),
"stop": datetime(2024, 7, 8, 10),
"google_id": f"{recurrence_id}_20240708T090000Z",
},
{
"start": datetime(2024, 7, 9, 9),
"stop": datetime(2024, 7, 9, 10),
"google_id": f"{recurrence_id}_20240709T090000Z",
},
],
)
undefined_privacy_event = {'id': 100, **sample_event_values}
default_privacy_event = {'id': 200, 'privacy': 'default', **sample_event_values}
self.env['calendar.event']._sync_google2odoo(GoogleEvent([undefined_privacy_event, default_privacy_event]))
# Ensure that synced events have the correct privacy field in Odoo.
undefined_privacy_odoo_event = self.env['calendar.event'].search([('google_id', '=', 1)])
default_privacy_odoo_event = self.env['calendar.event'].search([('google_id', '=', 2)])
self.assertFalse(undefined_privacy_odoo_event.privacy, "Event with undefined privacy must have False value in privacy field.")
self.assertFalse(default_privacy_odoo_event.privacy, "Event with default privacy must have False value in privacy field.")
@patch.object(GoogleCalendarService, 'get_events')
def test_accepting_recurrent_event_with_this_event_option_synced_by_attendee(self, mock_get_events):

View file

@ -1,29 +1,28 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from dateutil.relativedelta import relativedelta
from unittest.mock import patch
from odoo.addons.google_calendar.utils.google_event import GoogleEvent
from odoo.addons.google_calendar.utils.google_calendar import GoogleCalendarService
from odoo.addons.google_account.models.google_service import GoogleService
from odoo.addons.google_calendar.models.res_users import User
from odoo.addons.google_calendar.models.res_users import ResUsers
from odoo.addons.google_calendar.tests.test_sync_common import TestSyncGoogle, patch_api
from odoo.tests.common import users, warmup
from odoo.tests import tagged
from odoo import tools
@tagged('odoo2google', 'is_query_count')
@patch.object(User, '_get_google_calendar_token', lambda user: 'dummy-token')
@tagged('odoo2google', 'calendar_performance', 'is_query_count')
@patch.object(ResUsers, '_get_google_calendar_token', lambda user: 'dummy-token')
class TestSyncOdoo2Google(TestSyncGoogle):
def setUp(self):
super().setUp()
self.env.user.partner_id.tz = "Europe/Brussels"
self.google_service = GoogleCalendarService(self.env['google.service'])
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.user.partner_id.tz = "Europe/Brussels"
cls.google_service = GoogleCalendarService(cls.env['google.service'])
# Make sure this test will work for the next 30 years
self.env['ir.config_parameter'].set_param('google_calendar.sync.range_days', 10000)
cls.env['ir.config_parameter'].set_param('google_calendar.sync.range_days', 10000)
@patch_api
def test_event_creation(self):
@ -51,7 +50,7 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'start': {'dateTime': '2020-01-15T08:00:00+00:00', 'date': None},
'end': {'dateTime': '2020-01-15T18:00:00+00:00', 'date': None},
'summary': 'Event',
'description': tools.html_sanitize(description),
'description': event.description,
'location': '',
'visibility': 'private',
'guestsCanModify': True,
@ -77,7 +76,7 @@ class TestSyncOdoo2Google(TestSyncGoogle):
})
partner_model = self.env.ref('base.model_res_partner')
partner = self.env['res.partner'].search([], limit=1)
with self.assertQueryCount(__system__=616):
with self.assertQueryCount(__system__=526):
events = self.env['calendar.event'].create([{
'name': "Event %s" % (i),
'start': datetime(2020, 1, 15, 8, 0),
@ -92,10 +91,9 @@ class TestSyncOdoo2Google(TestSyncGoogle):
events._sync_odoo2google(self.google_service)
with self.assertQueryCount(__system__=130):
with self.assertQueryCount(__system__=24):
events.unlink()
@patch_api
@users('__system__')
@warmup
@ -108,8 +106,7 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'duration': 18,
})
partner_model = self.env.ref('base.model_res_partner')
partner = self.env['res.partner'].search([], limit=1)
with self.assertQueryCount(__system__=72):
with self.assertQueryCount(__system__=105):
event = self.env['calendar.event'].create({
'name': "Event",
'start': datetime(2020, 1, 15, 8, 0),
@ -126,7 +123,7 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'res_id': partner.id,
})
with self.assertQueryCount(__system__=35):
with self.assertQueryCount(__system__=29):
event.unlink()
def test_event_without_user(self):
@ -164,7 +161,7 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'start': {'dateTime': '2020-01-15T08:00:00+00:00', 'date': None},
'end': {'dateTime': '2020-01-15T18:00:00+00:00', 'date': None},
'summary': 'Event',
'description': '',
'description': event.description,
'location': '',
'visibility': 'private',
'guestsCanModify': True,
@ -193,7 +190,6 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'summary': 'Event',
'description': '',
'location': '',
'visibility': 'public',
'guestsCanModify': True,
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': 'odoobot@example.com', 'self': True},
@ -255,7 +251,6 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'summary': 'Event',
'description': '',
'location': '',
'visibility': 'public',
'guestsCanModify': True,
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': 'odoobot@example.com', 'self': True},
@ -291,7 +286,6 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'summary': 'Event',
'description': '',
'location': '',
'visibility': 'public',
'guestsCanModify': True,
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': 'odoobot@example.com', 'self': True},
@ -345,7 +339,6 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'attendees': [{'email': 'odoobot@example.com', 'responseStatus': 'accepted'}],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.recurrence_id.id}},
'reminders': {'overrides': [], 'useDefault': False},
'visibility': 'public',
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=1;BYDAY=WE'],
'transparency': 'opaque',
}, timeout=3)
@ -390,9 +383,8 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'start': {'dateTime': '2020-01-15T08:00:00+00:00', 'date': None},
'end': {'dateTime': '2020-01-15T18:00:00+00:00', 'date': None},
'summary': 'Event',
'description': '',
'description': event.description,
'location': '',
'visibility': 'public',
'guestsCanModify': True,
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': 'jean-luc@opoo.com', 'self': True},
@ -436,7 +428,6 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=2;BYDAY=WE'],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: new_recurrence.id}},
'reminders': {'overrides': [], 'useDefault': False},
'visibility': 'public',
'transparency': 'opaque',
}, timeout=3)
@ -559,14 +550,13 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'start': {'date': str(event.start_date), 'dateTime': None},
'end': {'date': str(event.stop_date + relativedelta(days=1)), 'dateTime': None},
'summary': 'Event with attendees',
'description': '',
'description': event.description,
'location': '',
'guestsCanModify': True,
'organizer': {'email': 'odoobot@example.com', 'self': True},
'attendees': [{'email': 'jean-luc@opoo.com', 'responseStatus': 'declined'}],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.id}},
'reminders': {'overrides': [], 'useDefault': False},
'visibility': 'public',
'transparency': 'opaque',
})
@ -605,39 +595,9 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=2;BYDAY=WE'],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: new_recurrence.id}},
'reminders': {'overrides': [], 'useDefault': False},
'visibility': 'public',
'transparency': 'opaque',
}, timeout=3)
@patch.object(GoogleService, '_do_request')
def test_send_update_do_request(self, mock_do_request):
self.env.cr.postcommit.clear()
event = self.env['calendar.event'].create({
'name': "Event",
'allday': True,
'start': datetime(2020, 1, 15),
'stop': datetime(2020, 1, 15),
'need_sync': False,
})
event.with_context(send_updates=True)._sync_odoo2google(self.google_service)
self.call_post_commit_hooks()
self.assertGoogleEventSendUpdates('all')
@patch.object(GoogleService, '_do_request')
def test_not_send_update_do_request(self, mock_do_request):
event = self.env['calendar.event'].create({
'name': "Event",
'allday': True,
'start': datetime(2020, 1, 15),
'stop': datetime(2020, 1, 15),
'need_sync': False,
})
event.with_context(send_updates=False)._sync_odoo2google(self.google_service)
self.call_post_commit_hooks()
self.assertGoogleEventSendUpdates('none')
@patch_api
def test_recurrence_delete_single_events(self):
"""
@ -678,7 +638,6 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'attendees': [{'email': 'odoobot@example.com', 'responseStatus': 'accepted'}],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event_1.id}},
'reminders': {'overrides': [], 'useDefault': False},
'visibility': 'public',
'status': 'cancelled',
'transparency': 'opaque',
}, timeout=3)
@ -691,6 +650,92 @@ class TestSyncOdoo2Google(TestSyncGoogle):
self.assertFalse(event_2.active)
self.assertFalse(recurrence.active)
@patch_api
def test_create_event_with_sync_config_paused(self):
"""
Creates an event with the synchronization paused, its field 'need_sync'
must be True for later synchronizing it with Google Calendar.
"""
# Set synchronization as active and unpause the synchronization.
self.env.user.google_synchronization_stopped = False
self.env.user.sudo().pause_google_synchronization()
# Create record and call synchronization method.
record = self.env['calendar.event'].create({
'name': "Event",
'start': datetime(2023, 6, 29, 8, 0),
'stop': datetime(2023, 6, 29, 18, 0),
'need_sync': True
})
record._sync_odoo2google(self.google_service)
# Assert that synchronization is paused, insert wasn't called and record is waiting to be synced.
self.assertFalse(self.env.user.google_synchronization_stopped)
self.assertEqual(self.env.user._get_google_sync_status(), "sync_paused")
self.assertTrue(record.need_sync, "Sync variable must be true for updating event when sync re-activates")
self.assertGoogleEventNotInserted()
@patch_api
def test_update_synced_event_with_sync_config_paused(self):
"""
Updates a synced event with synchronization paused, event must be modified and have its
field 'need_sync' as True for later synchronizing it with Google Calendar.
"""
# Set synchronization as active and unpause it.
self.env.user.google_synchronization_stopped = False
self.env.user.sudo().unpause_google_synchronization()
# Setup synced record in Calendar.
record = self.env['calendar.event'].create({
'google_id': 'aaaaaaaaa',
'name': "Event",
'start': datetime(2023, 6, 29, 8, 0),
'stop': datetime(2023, 6, 29, 18, 0),
'need_sync': False
})
# Pause synchronization and update synced event. It will only update it locally.
self.env.user.sudo().pause_google_synchronization()
record.write({'name': "Updated Event"})
record._sync_odoo2google(self.google_service)
# Assert that synchronization is paused, patch wasn't called and record is waiting to be synced.
self.assertFalse(self.env.user.google_synchronization_stopped)
self.assertEqual(self.env.user._get_google_sync_status(), "sync_paused")
self.assertEqual(record.name, "Updated Event", "Assert that event name was updated in Odoo Calendar")
self.assertTrue(record.need_sync, "Sync variable must be true for updating event when sync re-activates")
self.assertGoogleEventNotPatched()
@patch_api
def test_delete_synced_event_with_sync_config_paused(self):
"""
Deletes a synced event with synchronization paused, event must be archived in Odoo and
have its field 'need_sync' as True for later synchronizing it with Google Calendar.
"""
# Set synchronization as active and then pause synchronization.
self.env.user.google_synchronization_stopped = False
self.env.user.sudo().unpause_google_synchronization()
# Setup synced record in Calendar.
record = self.env['calendar.event'].create({
'google_id': 'aaaaaaaaa',
'name': "Event",
'start': datetime(2023, 6, 29, 8, 0),
'stop': datetime(2023, 6, 29, 18, 0),
'need_sync': False
})
# Pause synchronization and delete synced event.
self.env.user.sudo().pause_google_synchronization()
record.unlink()
# Assert that synchronization is paused, delete wasn't called and record was archived in Odoo.
self.assertFalse(self.env.user.google_synchronization_stopped)
self.assertEqual(self.env.user._get_google_sync_status(), "sync_paused")
self.assertFalse(record.active, "Event must be archived in Odoo after unlinking it")
self.assertTrue(record.need_sync, "Sync variable must be true for updating event in Google when sync re-activates")
self.assertGoogleEventNotDeleted()
@patch_api
def test_videocall_location_on_location_set(self):
partner = self.env['res.partner'].create({'name': 'Jean-Luc', 'email': 'jean-luc@opoo.com'})
@ -703,7 +748,7 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'location' : 'Event Location'
})
event._sync_odoo2google(self.google_service)
self.assertGoogleEventHasNoConferenceData()
self.assertGoogleEventInserted({'conferenceData': False})
@patch_api
def test_event_available_privacy(self):
@ -723,7 +768,6 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'summary': 'Event',
'description': '',
'location': '',
'visibility': 'public',
'guestsCanModify': True,
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': 'odoobot@example.com', 'self': True},
@ -750,7 +794,6 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'summary': 'Event',
'description': '',
'location': '',
'visibility': 'public',
'guestsCanModify': True,
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': 'odoobot@example.com', 'self': True},
@ -759,6 +802,55 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'transparency': 'opaque',
})
@patch_api
@patch.object(ResUsers, '_sync_request')
def test_event_sync_after_pause_period(self, mock_sync_request):
""" Ensure that an event created during the paused synchronization period gets synchronized after resuming it. """
# Pause the synchronization and creates the local event.
self.organizer_user.google_synchronization_stopped = False
self.organizer_user.sudo().pause_google_synchronization()
record = self.env['calendar.event'].with_user(self.organizer_user).create({
'name': "Event",
'start': datetime(2023, 1, 15, 8, 0),
'stop': datetime(2023, 1, 15, 18, 0),
'partner_ids': [(4, self.organizer_user.partner_id.id), (4, self.attendee_user.partner_id.id)]
})
# Define mock return values for the '_sync_request' method.
mock_sync_request.return_value = {
'events': GoogleEvent([]),
'default_reminders': (),
'full_sync': False,
}
# With the synchronization paused, manually call the synchronization to simulate the page refresh.
self.organizer_user.sudo()._sync_google_calendar(self.google_service)
self.assertFalse(self.organizer_user.google_synchronization_stopped, "Synchronization should not be stopped, only paused.")
self.assertEqual(self.organizer_user._get_google_sync_status(), "sync_paused", "Synchronization must be paused since it wasn't resumed yet.")
self.assertTrue(record.need_sync, "Record must have its 'need_sync' variable as true for it to be synchronized when the synchronization is resumed.")
self.assertGoogleEventNotInserted()
# Unpause the synchronization and call the calendar synchronization. Ensure the event was inserted in Google side.
self.organizer_user.sudo().unpause_google_synchronization()
self.organizer_user.with_user(self.organizer_user).sudo()._sync_google_calendar(self.google_service)
self.assertGoogleEventInserted({
'id': False,
'start': {'dateTime': '2023-01-15T08:00:00+00:00', 'date': None},
'end': {'dateTime': '2023-01-15T18:00:00+00:00', 'date': None},
'summary': 'Event',
'description': record.description,
'location': '',
'guestsCanModify': True,
'transparency': 'opaque',
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': self.organizer_user.email, 'self': True},
'attendees': [
{'email': self.attendee_user.email, 'responseStatus': 'needsAction'},
{'email': self.organizer_user.email, 'responseStatus': 'accepted'}
],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: record.id}},
})
@patch_api
def test_allday_duplicated_first_event_in_recurrence(self):
""" Ensure that when creating recurrence with 'all day' events the first event won't get duplicated in Google. """
@ -788,9 +880,8 @@ class TestSyncOdoo2Google(TestSyncGoogle):
'start': {'date': '2024-01-17', 'dateTime': None},
'end': {'date': '2024-01-18', 'dateTime': None},
'summary': 'All Day Recurrent Event',
'description': '',
'description': event.description,
'location': '',
'visibility': 'public',
'guestsCanModify': True,
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': self.organizer_user.email, 'self': True},
@ -801,118 +892,227 @@ class TestSyncOdoo2Google(TestSyncGoogle):
}, timeout=3)
@patch_api
def test_partner_type_change(self):
"""Test syncing an event with a private address attendee using
a user without access to private addresses.
@patch.object(ResUsers, '_sync_request')
def test_skip_google_sync_for_non_synchronized_users_new_events(self, mock_sync_request):
"""
user = self.env['res.users'].create({
'name': 'user1',
'login': 'user1',
'email': 'user1@odoo.com',
})
private_partner = self.env['res.partner'].create({
'name': 'Private Contact',
'email': 'private_email@example.com',
'type': 'private',
})
event = self.env['calendar.event'].create({
'name': "Private Event",
'user_id': user.id,
'start': datetime(2020, 1, 13, 16, 55),
'stop': datetime(2020, 1, 13, 19, 55),
'partner_ids': [(4, private_partner.id)],
'privacy': 'private',
Skip the synchro of new events by attendees when the organizer is not synchronized with Google.
Otherwise, the event ownership will be lost to the attendee and it could generate duplicates in
Odoo, as well cause problems in the future the synchronization of that event for the original owner.
"""
with self.mock_datetime_and_now("2023-01-10"):
# Stop the synchronization for the organizer and leave the attendee synchronized.
# Then, create an event with the organizer and attendee. Assert that it was not inserted.
self.organizer_user.google_synchronization_stopped = True
self.attendee_user.google_synchronization_stopped = False
record = self.env['calendar.event'].with_user(self.organizer_user).create({
'name': "Event",
'start': datetime(2023, 1, 15, 8, 0),
'stop': datetime(2023, 1, 15, 18, 0),
'need_sync': True,
'partner_ids': [(4, self.organizer_user.partner_id.id), (4, self.attendee_user.partner_id.id)]
})
self.assertGoogleEventNotInserted()
# Define mock return values for the '_sync_request' method.
mock_sync_request.return_value = {
'events': GoogleEvent([]),
'default_reminders': (),
'full_sync': False,
}
# Synchronize the attendee, and ensure that the event was not inserted after it.
self.attendee_user.with_user(self.attendee_user).sudo()._sync_google_calendar(self.google_service)
self.assertGoogleAPINotCalled()
# Now, we synchronize the organizer and make sure the event got inserted by him.
self.organizer_user.with_user(self.organizer_user).restart_google_synchronization()
self.organizer_user.with_user(self.organizer_user).sudo()._sync_google_calendar(self.google_service)
self.assertGoogleEventInserted({
'id': False,
'start': {'dateTime': '2023-01-15T08:00:00+00:00', 'date': None},
'end': {'dateTime': '2023-01-15T18:00:00+00:00', 'date': None},
'summary': 'Event',
'description': ('<div><strong>Organized by</strong><br>'
'organizer_user (base.group_user)<br><a href="mailto:o.o@example.com">o.o@example.com</a><br><br>'
'<strong>Contact Details</strong><br>'
'attendee_user (base.group_user)<br><a href="mailto:a.a@example.com">a.a@example.com</a></div>'),
'location': '',
'guestsCanModify': True,
'transparency': 'opaque',
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': self.organizer_user.email, 'self': True},
'attendees': [
{'email': self.attendee_user.email, 'responseStatus': 'needsAction'},
{'email': self.organizer_user.email, 'responseStatus': 'accepted'}
],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: record.id}},
})
@patch_api
def test_event_duplication_allday_google_calendar(self):
event = self.env['calendar.event'].with_user(self.organizer_user).create({
'name': "Event",
'allday': True,
'partner_ids': [(4, self.organizer_user.partner_id.id), (4, self.attendee_user.partner_id.id)],
'start': datetime(2020, 1, 15),
'stop': datetime(2020, 1, 15),
'need_sync': False,
})
event = event.with_user(user)
event.env.invalidate_all()
event._sync_odoo2google(self.google_service)
event_response_data = {
'id': False,
'start': {'date': '2020-01-15', 'dateTime': None},
'end': {'date': '2020-01-16', 'dateTime': None},
'summary': 'Event',
'description': event.description,
'location': '',
'guestsCanModify': True,
'organizer': {'email': self.organizer_user.email, 'self': True},
'attendees': [
{'email': self.attendee_user.email, 'responseStatus': 'needsAction'},
{'email': self.organizer_user.email, 'responseStatus': 'accepted'}
],
'reminders': {'overrides': [], 'useDefault': False},
'transparency': 'opaque',
}
self.assertGoogleEventInsertedMultiTime({
**event_response_data,
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.id}},
})
event2 = event.copy()
event2._sync_odoo2google(self.google_service)
event_response_data['description'] = event2.description
self.assertGoogleEventInsertedMultiTime({
**event_response_data,
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event2.id}},
})
def test_event_over_send_updates(self):
"""Test that events that are over don't sent updates to attendees."""
with self.mock_datetime_and_now("2023-01-10"):
self.env.cr.postcommit.clear()
with self.mock_google_service():
past_event = self.env['calendar.event'].create({
'name': "Event",
'start': datetime(2020, 1, 15, 8, 0),
'stop': datetime(2020, 1, 15, 9, 0),
'need_sync': False,
})
past_event._sync_odoo2google(self.google_service)
self.call_post_commit_hooks()
self.assertTrue(past_event._is_event_over(), "Event should be considered over")
self.assertGoogleEventSendUpdates('none')
def test_event_not_over_send_updates(self):
"""Test that events that are not over send updates to attendees."""
with self.mock_datetime_and_now("2023-01-10"):
self.env.cr.postcommit.clear()
with self.mock_google_service():
future_date = datetime(2023, 1, 20, 8, 0) # Fixed date instead of datetime.now()
future_event = self.env['calendar.event'].create({
'name': "Future Event",
'start': future_date,
'stop': future_date + relativedelta(hours=1),
'need_sync': False,
})
# Sync the event and verify send_updates is set to 'all'
future_event._sync_odoo2google(self.google_service)
self.call_post_commit_hooks()
self.assertFalse(future_event._is_event_over(), "Future event should not be considered over")
self.assertGoogleEventSendUpdates('all')
def test_recurrence_over_send_updates(self):
"""Test that recurrences that are over don't send updates to attendees."""
with self.mock_datetime_and_now("2023-01-10"):
self.env.cr.postcommit.clear()
with self.mock_google_service():
past_event = self.env['calendar.event'].create({
'name': "Past Recurring Event",
'start': datetime(2020, 1, 15, 8, 0),
'stop': datetime(2020, 1, 15, 9, 0),
'need_sync': False,
})
past_recurrence = self.env['calendar.recurrence'].create({
'rrule': 'FREQ=WEEKLY;COUNT=2;BYDAY=WE',
'base_event_id': past_event.id,
'need_sync': False,
})
past_recurrence._apply_recurrence()
# Sync the recurrence and verify send_updates is set to 'none'
past_recurrence._sync_odoo2google(self.google_service)
self.call_post_commit_hooks()
self.assertTrue(past_recurrence._is_event_over(), "Past recurrence should be considered over")
self.assertGoogleEventSendUpdates('none')
def test_recurrence_not_over_send_updates(self):
"""Test that recurrences that are not over send updates to attendees."""
with self.mock_datetime_and_now("2023-01-10"):
self.env.cr.postcommit.clear()
with self.mock_google_service():
future_date = datetime(2023, 1, 20, 8, 0)
future_event = self.env['calendar.event'].create({
'name': "Future Recurring Event",
'start': future_date,
'stop': future_date + relativedelta(hours=1),
'need_sync': False,
})
future_recurrence = self.env['calendar.recurrence'].create(
{
'rrule': 'FREQ=WEEKLY;COUNT=2;BYDAY=WE',
'base_event_id': future_event.id,
'need_sync': False,
}
)
future_recurrence._apply_recurrence()
# Sync the recurrence and verify send_updates is set to 'all'
future_recurrence._sync_odoo2google(self.google_service)
self.call_post_commit_hooks()
self.assertFalse(future_recurrence._is_event_over(), "Future recurrence should not be considered over")
self.assertGoogleEventSendUpdates('all')
@patch_api
def test_eliminate_remote_meet_link_when_removed_locally(self):
partner = self.env['res.partner'].create({'name': 'Name', 'email': 'email@emailprovider.com'})
google_id = 'nicegoogleid'
event = self.env['calendar.event'].create({
'google_id': google_id,
'name': "Awesome event",
'start': datetime(2020, 1, 15, 8, 0),
'stop': datetime(2020, 1, 15, 18, 0),
'partner_ids': [(4, partner.id)],
'need_sync': False,
})
event.write({'videocall_location': False})
event._sync_odoo2google(self.google_service)
self.assertGoogleEventPatched(google_id, {'conferenceData': None}, timeout=3)
@patch_api
def test_event_creation_for_different_user(self):
"""
Test event is synchronized for organizer with active google synchronization if it is
created by a user with google synchronization stopped.
"""
self.attendee_user.google_synchronization_stopped = True
self.organizer_user.google_calendar_token = 'dummy-token'
event = self.env['calendar.event'].with_user(self.attendee_user).create({
'name': "Event",
'start': datetime(2020, 1, 15, 8, 0),
'stop': datetime(2020, 1, 15, 18, 0),
'user_id': self.organizer_user.id,
'partner_ids': [(4, self.organizer_user.partner_id.id), (4, self.attendee_user.partner_id.id)],
})
event._sync_odoo2google(self.google_service)
self.assertGoogleEventInserted({
'id': False,
'start': {'dateTime': '2020-01-13T16:55:00+00:00', 'date': None},
'end': {'dateTime': '2020-01-13T19:55:00+00:00', 'date': None},
'summary': 'Private Event',
'description': '',
'location': '',
'visibility': 'private',
'guestsCanModify': True,
'reminders': {'overrides': [], 'useDefault': False},
'organizer': {'email': 'user1@odoo.com', 'self': True},
'attendees': [{'email': 'private_email@example.com', 'responseStatus': 'needsAction'}],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.id}},
'transparency': 'opaque',
})
@patch_api
def test_update_allday_to_timed_event(self):
""" Ensure that updating in Odoo all-day events to timed events is reflected in Google. """
# Create an 'all-day' event synchronized with Google.
self.organizer_user.stop_google_synchronization()
event = self.env['calendar.event'].with_user(self.organizer_user).create({
'name': "AllDay",
'google_id': 'allDayEv',
'user_id': self.organizer_user.id,
'start': datetime(2024, 1, 17),
'stop': datetime(2024, 1, 17),
'allday': True,
'need_sync': False,
'recurrency': True,
'recurrence_id': False,
})
# In Odoo, update the event from 'all-day' to timed event.
# Ensure that it got successfully patched in Google side.
self.organizer_user.restart_google_synchronization()
event.with_user(self.organizer_user.id).write({"allday": False})
self.assertGoogleEventPatched(event.google_id, {
'id': event.google_id,
'start': {'date': None, 'dateTime': '2024-01-17T00:00:00+00:00'},
'end': {'date': None, 'dateTime': '2024-01-17T00:00:00+00:00'},
'summary': 'AllDay',
'description': '',
'start': {'dateTime': '2020-01-15T08:00:00+00:00', 'date': None},
'end': {'dateTime': '2020-01-15T18:00:00+00:00', 'date': None},
'summary': 'Event',
'location': '',
'guestsCanModify': True,
'organizer': {'email': 'o.o@example.com', 'self': True},
'attendees': [{'email': 'o.o@example.com', 'responseStatus': 'accepted'}],
'organizer': {'email': 'o.o@example.com', 'self': False},
'attendees': [{'email': 'a.a@example.com', 'responseStatus': 'accepted'}, {'email': 'o.o@example.com', 'responseStatus': 'needsAction'}],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.id}},
'reminders': {'overrides': [], 'useDefault': False},
'visibility': 'public',
'transparency': 'opaque',
}, timeout=3)
@patch_api
def test_update_timed_to_allday_event(self):
""" Ensure that updating in Odoo timed events to all-day events is reflected in Google. """
# Create a timed event synchronized with Google.
self.organizer_user.stop_google_synchronization()
event = self.env['calendar.event'].with_user(self.organizer_user).create({
'name': "TimedEvent",
'google_id': 'timedEvId',
'user_id': self.organizer_user.id,
'start': datetime(2024, 1, 17, 10, 00),
'stop': datetime(2024, 1, 17, 11, 00),
'allday': False,
'need_sync': False,
'recurrency': True,
'recurrence_id': False,
})
# In Odoo, update the event from timed to 'all-day'.
# Ensure that it got successfully patched in Google side.
self.organizer_user.restart_google_synchronization()
event.with_user(self.organizer_user.id).write({"allday": True})
self.assertGoogleEventPatched(event.google_id, {
'id': event.google_id,
'start': {'date': '2024-01-17', 'dateTime': None},
'end': {'date': '2024-01-18', 'dateTime': None},
'summary': 'TimedEvent',
'description': '',
'location': '',
'guestsCanModify': True,
'organizer': {'email': 'o.o@example.com', 'self': True},
'attendees': [{'email': 'o.o@example.com', 'responseStatus': 'accepted'}],
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.id}},
'reminders': {'overrides': [], 'useDefault': False},
'visibility': 'public',
'transparency': 'opaque',
}, timeout=3)

View file

@ -0,0 +1,56 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from freezegun import freeze_time
from unittest.mock import patch
from odoo.addons.google_calendar.models.res_users import ResUsers
from odoo.addons.google_calendar.tests.test_sync_common import TestSyncGoogle
from odoo.addons.mail.tests.common import MailCommon
from odoo.tests import tagged
from .test_token_access import TestTokenAccess
@tagged('odoo2google')
class TestSyncOdoo2GoogleMail(TestTokenAccess, TestSyncGoogle, MailCommon):
@patch.object(ResUsers, '_get_google_calendar_token', lambda user: user.google_calendar_token)
@freeze_time("2020-01-01")
def test_event_creation_for_user(self):
organizer1 = self.users[0]
organizer2 = self.users[1]
user_root = self.env.ref('base.user_root')
organizer1.google_calendar_token = 'abc'
organizer2.google_calendar_token = False
event_values = {
'name': "Event",
'start': datetime(2020, 1, 15, 8, 0),
'stop': datetime(2020, 1, 15, 18, 0),
}
partner = self.env['res.partner'].create({'name': 'Jean-Luc', 'email': 'jean-luc@opoo.com'})
for create_user, organizer, responsible, expect_mail, is_public in [
(user_root, organizer1, organizer1, False, True), (user_root, None, user_root, True, True),
(organizer1, None, organizer1, False, False), (organizer1, organizer2, organizer1, False, True)]:
with self.subTest(create_uid=create_user.name if create_user else None, user_id=organizer.name if organizer else None):
with self.mock_mail_gateway(), self.mock_google_sync(user_id=responsible):
self.env['calendar.event'].with_user(create_user).create({
**event_values,
'partner_ids': [(4, partner.id)],
'user_id': organizer.id if organizer else False,
})
if not expect_mail:
self.assertNotSentEmail()
self.assertGoogleEventInserted({
'attendees': [{'email': 'jean-luc@opoo.com', 'responseStatus': 'needsAction'}],
'id': False,
'start': {'dateTime': '2020-01-15T08:00:00+00:00', 'date': None},
'end': {'dateTime': '2020-01-15T18:00:00+00:00', 'date': None},
'guestsCanModify': is_public,
'organizer': {'email': organizer.email, 'self': False} if organizer else False,
'summary': 'Event',
'reminders': {'useDefault': False, 'overrides': []},
}, timeout=3)
else:
self.assertGoogleEventNotInserted()
self.assertMailMail(partner, 'sent', author=user_root.partner_id)

View file

@ -1,3 +1,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, Command
from odoo.exceptions import AccessError
from odoo.tests.common import TransactionCase
@ -10,17 +12,16 @@ class TestTokenAccess(TransactionCase):
cls.users = []
for u in ('user1', 'user2'):
credentials = cls.env['google.calendar.credentials'].create({
'calendar_rtoken': f'{u}_rtoken',
'calendar_token': f'{u}_token',
'calendar_token_validity': fields.Datetime.today(),
'calendar_sync_token': f'{u}_sync_token',
})
user = cls.env['res.users'].create({
'name': f'{u}',
'login': f'{u}',
'email': f'{u}@odoo.com',
'google_calendar_account_id': credentials.id,
})
user.res_users_settings_id.write({
'google_calendar_rtoken': f'{u}_rtoken',
'google_calendar_token': f'{u}_token',
'google_calendar_token_validity': fields.Datetime.today(),
'google_calendar_sync_token': f'{u}_sync_token',
})
cls.users += [user]
@ -28,39 +29,39 @@ class TestTokenAccess(TransactionCase):
'name': 'system_user',
'login': 'system_user',
'email': 'system_user@odoo.com',
'groups_id': [Command.link(cls.env.ref('base.group_system').id)],
'group_ids': [Command.link(cls.env.ref('base.group_system').id)],
})
def test_normal_user_should_be_able_to_reset_his_own_token(self):
user = self.users[0]
old_validity = user.google_calendar_account_id.calendar_token_validity
old_validity = user.res_users_settings_id.google_calendar_token_validity
user.with_user(user).google_calendar_account_id._set_auth_tokens('my_new_token', 'my_new_rtoken', 3600)
user.with_user(user).res_users_settings_id._set_google_auth_tokens('my_new_token', 'my_new_rtoken', 3600)
self.assertEqual(user.google_calendar_account_id.calendar_rtoken, 'my_new_rtoken')
self.assertEqual(user.google_calendar_account_id.calendar_token, 'my_new_token')
self.assertEqual(user.res_users_settings_id.google_calendar_rtoken, 'my_new_rtoken')
self.assertEqual(user.res_users_settings_id.google_calendar_token, 'my_new_token')
self.assertNotEqual(
user.google_calendar_account_id.calendar_token_validity,
user.res_users_settings_id.google_calendar_token_validity,
old_validity
)
def test_normal_user_should_not_be_able_to_reset_other_user_tokens(self):
user1, user2 = self.users
with self.assertRaises(AccessError):
user2.with_user(user1).google_calendar_account_id._set_auth_tokens(False, False, 0)
# Skip test: the access error will not be raised anymore since the access rules were deleted.
# with self.assertRaises(AccessError):
# user2.with_user(user1).res_users_settings_id._set_auth_tokens(False, False, 0)
def test_system_user_should_be_able_to_reset_any_tokens(self):
user = self.users[0]
old_validity = user.google_calendar_account_id.calendar_token_validity
old_validity = user.res_users_settings_id.google_calendar_token_validity
user.with_user(self.system_user).google_calendar_account_id._set_auth_tokens(
user.with_user(self.system_user).res_users_settings_id._set_google_auth_tokens(
'my_new_token', 'my_new_rtoken', 3600
)
self.assertEqual(user.google_calendar_account_id.calendar_rtoken, 'my_new_rtoken')
self.assertEqual(user.google_calendar_account_id.calendar_token, 'my_new_token')
self.assertEqual(user.res_users_settings_id.google_calendar_rtoken, 'my_new_rtoken')
self.assertEqual(user.res_users_settings_id.google_calendar_token, 'my_new_token')
self.assertNotEqual(
user.google_calendar_account_id.calendar_token_validity,
user.res_users_settings_id.google_calendar_token_validity,
old_validity
)