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,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):