mirror of
https://github.com/bringout/oca-ocb-security.git
synced 2026-04-22 04:52:02 +02:00
19.0 vanilla
This commit is contained in:
parent
20ddc1b4a3
commit
c0efcc53f5
1162 changed files with 125577 additions and 105287 deletions
|
|
@ -6,4 +6,5 @@ from . import test_microsoft_service
|
|||
from . import test_create_events
|
||||
from . import test_update_events
|
||||
from . import test_delete_events
|
||||
from . import test_sync_odoo2microsoft_mail
|
||||
from . import test_answer_events
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import pytz
|
||||
from datetime import datetime, timedelta
|
||||
from markupsafe import Markup
|
||||
|
|
@ -8,9 +10,10 @@ from freezegun import freeze_time
|
|||
from odoo import fields
|
||||
|
||||
from odoo.tests.common import HttpCase
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.microsoft_calendar.models.microsoft_sync import MicrosoftCalendarSync
|
||||
|
||||
from odoo.addons.microsoft_calendar.models.microsoft_sync import MicrosoftSync
|
||||
from odoo.addons.microsoft_calendar.utils.event_id_storage import combine_ids
|
||||
|
||||
def mock_get_token(user):
|
||||
return f"TOKEN_FOR_USER_{user.id}"
|
||||
|
|
@ -23,14 +26,14 @@ def _modified_date_in_the_future(event):
|
|||
return (event.write_date + timedelta(seconds=5)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
def patch_api(func):
|
||||
@patch.object(MicrosoftSync, '_microsoft_insert', MagicMock())
|
||||
@patch.object(MicrosoftSync, '_microsoft_delete', MagicMock())
|
||||
@patch.object(MicrosoftSync, '_microsoft_patch', MagicMock())
|
||||
@patch.object(MicrosoftCalendarSync, '_microsoft_insert', MagicMock())
|
||||
@patch.object(MicrosoftCalendarSync, '_microsoft_delete', MagicMock())
|
||||
@patch.object(MicrosoftCalendarSync, '_microsoft_patch', MagicMock())
|
||||
def patched(self, *args, **kwargs):
|
||||
return func(self, *args, **kwargs)
|
||||
return patched
|
||||
|
||||
# By inheriting from TransactionCase, postcommit hooks (so methods tagged with `@after_commit` in MicrosoftSync),
|
||||
# By inheriting from TransactionCase, postcommit hooks (so methods tagged with `@after_commit` in MicrosoftCalendarSync),
|
||||
# are not called because no commit is done.
|
||||
# To be able to manually call these postcommit hooks, we need to inherit from HttpCase.
|
||||
# Note: as postcommit hooks are called separately, do not forget to invalidate cache for records read during the test.
|
||||
|
|
@ -39,6 +42,11 @@ class TestCommon(HttpCase):
|
|||
@patch_api
|
||||
def setUp(self):
|
||||
super(TestCommon, self).setUp()
|
||||
m = mute_logger('odoo.addons.auth_signup.models.res_users')
|
||||
mute_logger.__enter__(m) # noqa: PLC2801
|
||||
self.addCleanup(mute_logger.__exit__, m, None, None, None)
|
||||
|
||||
self.env.user.unpause_microsoft_synchronization()
|
||||
|
||||
# prepare users
|
||||
self.organizer_user = self.env["res.users"].search([("name", "=", "Mike Organizer")])
|
||||
|
|
@ -249,7 +257,8 @@ class TestCommon(HttpCase):
|
|||
"start": self.start_date,
|
||||
"stop": self.end_date,
|
||||
"user_id": self.organizer_user,
|
||||
"microsoft_id": combine_ids("123", "456"),
|
||||
"microsoft_id": "123",
|
||||
"ms_universal_event_id": "456",
|
||||
"partner_ids": [self.organizer_user.partner_id.id, self.attendee_user.partner_id.id],
|
||||
}
|
||||
self.expected_odoo_recurrency_from_outlook = {
|
||||
|
|
@ -266,7 +275,8 @@ class TestCommon(HttpCase):
|
|||
'fri': False,
|
||||
'interval': self.recurrent_event_interval,
|
||||
'month_by': 'date',
|
||||
'microsoft_id': combine_ids('REC123', 'REC456'),
|
||||
"microsoft_id": "REC123",
|
||||
"ms_universal_event_id": "REC456",
|
||||
'name': "Every %s Days until %s" % (
|
||||
self.recurrent_event_interval, self.recurrence_end_date.strftime("%Y-%m-%d")
|
||||
),
|
||||
|
|
@ -405,7 +415,8 @@ class TestCommon(HttpCase):
|
|||
"stop": self.end_date + timedelta(days=i * self.recurrent_event_interval),
|
||||
"until": self.recurrence_end_date.date(),
|
||||
"microsoft_recurrence_master_id": "REC123",
|
||||
'microsoft_id': combine_ids(f"REC123_EVENT_{i+1}", f"REC456_EVENT_{i+1}"),
|
||||
"microsoft_id": f"REC123_EVENT_{i+1}",
|
||||
"ms_universal_event_id": f"REC456_EVENT_{i+1}",
|
||||
"recurrency": True,
|
||||
"follow_recurrence": True,
|
||||
"active": True,
|
||||
|
|
@ -445,7 +456,8 @@ class TestCommon(HttpCase):
|
|||
self.simple_event = self.env["calendar.event"].with_user(self.organizer_user).create(
|
||||
dict(
|
||||
self.simple_event_values,
|
||||
microsoft_id=combine_ids("123", "456"),
|
||||
microsoft_id="123",
|
||||
ms_universal_event_id="456",
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -456,7 +468,8 @@ class TestCommon(HttpCase):
|
|||
dict(
|
||||
self.simple_event_values,
|
||||
name=f"event{i}",
|
||||
microsoft_id=combine_ids(f"e{i}", f"u{i}"),
|
||||
microsoft_id=f"e{i}",
|
||||
ms_universal_event_id=f"u{i}"
|
||||
)
|
||||
for i in range(1, 4)
|
||||
])
|
||||
|
|
@ -483,11 +496,13 @@ class TestCommon(HttpCase):
|
|||
# set ids set by Outlook
|
||||
if not already_created:
|
||||
self.recurrence.with_context(dont_notify=True).write({
|
||||
"microsoft_id": combine_ids("REC123", "REC456"),
|
||||
"microsoft_id": "REC123",
|
||||
"ms_universal_event_id": "REC456"
|
||||
})
|
||||
for i, e in enumerate(self.recurrence.calendar_event_ids.sorted(key=lambda r: r.start)):
|
||||
e.with_context(dont_notify=True).write({
|
||||
"microsoft_id": combine_ids(f"REC123_EVENT_{i+1}", f"REC456_EVENT_{i+1}"),
|
||||
"microsoft_id": f"REC123_EVENT_{i+1}",
|
||||
"ms_universal_event_id": f"REC456_EVENT_{i+1}",
|
||||
"microsoft_recurrence_master_id": "REC123",
|
||||
})
|
||||
self.recurrence.invalidate_recordset()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch, ANY
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import MicrosoftCalendarService
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_event import MicrosoftEvent
|
||||
from odoo.addons.microsoft_calendar.models.res_users import User
|
||||
from odoo.addons.microsoft_calendar.utils.event_id_storage import combine_ids
|
||||
from odoo.addons.microsoft_calendar.models.res_users import ResUsers
|
||||
from odoo.addons.microsoft_calendar.tests.common import TestCommon, mock_get_token, _modified_date_in_the_future, patch_api
|
||||
from odoo.tests import users
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ import json
|
|||
from freezegun import freeze_time
|
||||
|
||||
|
||||
@patch.object(User, '_get_microsoft_calendar_token', mock_get_token)
|
||||
@patch.object(ResUsers, '_get_microsoft_calendar_token', mock_get_token)
|
||||
class TestAnswerEvents(TestCommon):
|
||||
|
||||
@patch_api
|
||||
|
|
@ -26,7 +26,8 @@ class TestAnswerEvents(TestCommon):
|
|||
self.simple_event = self.env["calendar.event"].with_user(self.organizer_user).create(
|
||||
dict(
|
||||
self.simple_event_values,
|
||||
microsoft_id=combine_ids("123", "456"),
|
||||
microsoft_id="123",
|
||||
ms_universal_event_id="456",
|
||||
)
|
||||
)
|
||||
(self.organizer_user | self.attendee_user).microsoft_calendar_token_validity = datetime.now() + timedelta(hours=1)
|
||||
|
|
@ -38,14 +39,14 @@ class TestAnswerEvents(TestCommon):
|
|||
('event_id', '=', self.simple_event.id),
|
||||
('partner_id', '=', self.attendee_user.partner_id.id)
|
||||
])
|
||||
attendee_ms_organizer_event_id = 100
|
||||
mock_get_single_event.return_value = (True, {'value': [{'id': attendee_ms_organizer_event_id}]})
|
||||
|
||||
mock_get_single_event.return_value = (True, {'value': [{'id': attendee.event_id.microsoft_id}]})
|
||||
attendee.with_user(self.attendee_user).do_accept()
|
||||
self.call_post_commit_hooks()
|
||||
self.simple_event.invalidate_recordset()
|
||||
|
||||
mock_answer.assert_called_once_with(
|
||||
attendee_ms_organizer_event_id,
|
||||
self.simple_event.microsoft_id,
|
||||
'accept',
|
||||
{"comment": "", "sendResponse": True},
|
||||
token=mock_get_token(self.attendee_user),
|
||||
|
|
@ -59,13 +60,12 @@ class TestAnswerEvents(TestCommon):
|
|||
('event_id', '=', self.simple_event.id),
|
||||
('partner_id', '=', self.attendee_user.partner_id.id)
|
||||
])
|
||||
attendee_ms_organizer_event_id = 100
|
||||
mock_get_single_event.return_value = (True, {'value': [{'id': attendee_ms_organizer_event_id}]})
|
||||
mock_get_single_event.return_value = (True, {'value': [{'id': attendee.event_id.microsoft_id}]})
|
||||
attendee.with_user(self.attendee_user).do_decline()
|
||||
self.call_post_commit_hooks()
|
||||
self.simple_event.invalidate_recordset()
|
||||
mock_answer.assert_called_once_with(
|
||||
attendee_ms_organizer_event_id,
|
||||
self.simple_event.microsoft_id,
|
||||
'decline',
|
||||
{"comment": "", "sendResponse": True},
|
||||
token=mock_get_token(self.attendee_user),
|
||||
|
|
|
|||
|
|
@ -1,15 +1,22 @@
|
|||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch, call
|
||||
from datetime import timedelta, datetime
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import Command, fields
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import MicrosoftCalendarService
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_event import MicrosoftEvent
|
||||
from odoo.addons.microsoft_calendar.models.res_users import User
|
||||
from odoo.addons.microsoft_calendar.tests.common import TestCommon, mock_get_token
|
||||
from odoo.addons.microsoft_calendar.models.res_users import ResUsers
|
||||
from odoo.addons.microsoft_calendar.tests.common import TestCommon, mock_get_token, _modified_date_in_the_future
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
@patch.object(User, '_get_microsoft_calendar_token', mock_get_token)
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
@patch.object(ResUsers, '_get_microsoft_calendar_token', mock_get_token)
|
||||
class TestCreateEvents(TestCommon):
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
|
|
@ -193,7 +200,7 @@ class TestCreateEvents(TestCommon):
|
|||
recurrence.invalidate_recordset()
|
||||
|
||||
# assert
|
||||
self.assertEqual(recurrence.ms_organizer_event_id, event_id)
|
||||
self.assertEqual(recurrence.microsoft_id, event_id)
|
||||
self.assertEqual(recurrence.ms_universal_event_id, event_iCalUId)
|
||||
self.assertEqual(recurrence.need_sync_m, False)
|
||||
|
||||
|
|
@ -239,7 +246,7 @@ class TestCreateEvents(TestCommon):
|
|||
# assert
|
||||
mock_insert.assert_not_called()
|
||||
|
||||
self.assertEqual(recurrence.ms_organizer_event_id, False)
|
||||
self.assertEqual(recurrence.microsoft_id, False)
|
||||
self.assertEqual(recurrence.ms_universal_event_id, False)
|
||||
self.assertEqual(recurrence.need_sync_m, False)
|
||||
|
||||
|
|
@ -307,6 +314,101 @@ class TestCreateEvents(TestCommon):
|
|||
# Assert that no insert call was made.
|
||||
mock_insert.assert_not_called()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
def test_create_event_with_sync_config_paused(self, mock_insert):
|
||||
"""
|
||||
Creates an event with the synchronization paused, the event must have its field 'need_sync_m' as True
|
||||
for later synchronizing it with Outlook Calendar.
|
||||
"""
|
||||
# Set user sync configuration as active and then pause the synchronization.
|
||||
self.organizer_user.microsoft_synchronization_stopped = False
|
||||
self.organizer_user.pause_microsoft_synchronization()
|
||||
|
||||
# Try to create a simple event in Odoo Calendar.
|
||||
record = self.env["calendar.event"].with_user(self.organizer_user).create(self.simple_event_values)
|
||||
self.call_post_commit_hooks()
|
||||
record.invalidate_recordset()
|
||||
|
||||
# Ensure that synchronization is paused, insert wasn't called and record is waiting to be synced.
|
||||
self.assertFalse(self.organizer_user.microsoft_synchronization_stopped)
|
||||
self.assertEqual(self.organizer_user._get_microsoft_sync_status(), "sync_paused")
|
||||
self.assertTrue(record.need_sync_m, "Sync variable must be true for updating event when sync re-activates")
|
||||
mock_insert.assert_not_called()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
def test_sync_create_update_single_event(self, mock_insert, mock_get_events):
|
||||
"""
|
||||
If the synchronization with Outlook is stopped, then records (events and recurrences) created or updated
|
||||
should not be synced. They must be synced only when created or updated having the synchronization active.
|
||||
In this test, the synchronization is stopped and an event is created locally. After this, the synchronization
|
||||
is restarted and the event is updated (this way, syncing it with Outlook Calendar).
|
||||
"""
|
||||
# Set last synchronization date for allowing synchronizing events created after this date.
|
||||
self.organizer_user._set_ICP_first_synchronization_date(fields.Datetime.now())
|
||||
|
||||
# Stop the synchronization for clearing the last_sync_date.
|
||||
self.organizer_user.with_user(self.organizer_user).sudo().stop_microsoft_synchronization()
|
||||
self.assertEqual(self.organizer_user.microsoft_last_sync_date, False,
|
||||
"Variable last_sync_date must be False due to sync stop.")
|
||||
|
||||
# Create a not synced event (local).
|
||||
simple_event_values_updated = self.simple_event_values
|
||||
for date_field in ['start', 'stop']:
|
||||
simple_event_values_updated[date_field] = simple_event_values_updated[date_field].replace(year=datetime.now().year)
|
||||
event = self.env["calendar.event"].with_user(self.organizer_user).create(simple_event_values_updated)
|
||||
|
||||
# Assert that insert was not called and prepare mock for the synchronization restart.
|
||||
mock_insert.assert_not_called()
|
||||
mock_get_events.return_value = ([], None)
|
||||
|
||||
ten_minutes_after_creation = event.write_date + timedelta(minutes=10)
|
||||
with freeze_time(ten_minutes_after_creation):
|
||||
# Restart the synchronization with Outlook Calendar.
|
||||
self.organizer_user.with_user(self.organizer_user).sudo().restart_microsoft_synchronization()
|
||||
# Sync microsoft calendar, considering that ten minutes were passed after the event creation.
|
||||
self.organizer_user.with_user(self.organizer_user).sudo()._sync_microsoft_calendar()
|
||||
self.call_post_commit_hooks()
|
||||
event.invalidate_recordset()
|
||||
|
||||
# Assert that insert function was not called and check last_sync_date variable value.
|
||||
mock_insert.assert_not_called()
|
||||
|
||||
self.assertNotEqual(self.organizer_user.microsoft_last_sync_date, False,
|
||||
"Variable last_sync_date must not be empty after sync.")
|
||||
self.assertLessEqual(event.write_date, self.organizer_user.microsoft_last_sync_date,
|
||||
"Event creation must happen before last_sync_date")
|
||||
|
||||
# Assert that the local event did not get synced after synchronization restart.
|
||||
self.assertEqual(event.microsoft_id, False,
|
||||
"Event should not be synchronized while sync is paused.")
|
||||
self.assertEqual(event.ms_universal_event_id, False,
|
||||
"Event should not be synchronized while sync is paused.")
|
||||
|
||||
# Update local event information.
|
||||
event.write({
|
||||
"name": "New event name"
|
||||
})
|
||||
self.call_post_commit_hooks()
|
||||
|
||||
# Prepare mock for new synchronization.
|
||||
event_id = "123"
|
||||
event_iCalUId = "456"
|
||||
mock_insert.return_value = (event_id, event_iCalUId)
|
||||
mock_get_events.return_value = ([], None)
|
||||
|
||||
# Synchronize local event with Outlook after updating it locally.
|
||||
self.organizer_user.with_user(self.organizer_user).sudo()._sync_microsoft_calendar()
|
||||
self.call_post_commit_hooks()
|
||||
event.invalidate_recordset()
|
||||
|
||||
# Assert that the event got synchronized with Microsoft (through mock).
|
||||
self.assertEqual(event.microsoft_id, "123")
|
||||
self.assertEqual(event.ms_universal_event_id, "456")
|
||||
|
||||
# Assert that the Microsoft Insert was called once.
|
||||
mock_insert.assert_called_once()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
def test_create_event_for_another_user(self, mock_insert, mock_get_events):
|
||||
|
|
@ -357,7 +459,7 @@ class TestCreateEvents(TestCommon):
|
|||
'login': 'portal@user',
|
||||
'email': 'portal@user',
|
||||
'name': 'PortalUser',
|
||||
'groups_id': [Command.set([portal_group.id])],
|
||||
'group_ids': [Command.set([portal_group.id])],
|
||||
})
|
||||
|
||||
# Mock event from Microsoft and sync event with Odoo through self.attendee_user (synced user).
|
||||
|
|
@ -397,6 +499,203 @@ class TestCreateEvents(TestCommon):
|
|||
self.assertEqual(len(new_records), 1)
|
||||
self.assert_odoo_event(new_records, expected_event)
|
||||
|
||||
def test_create_event_with_default_and_undefined_sensitivity(self):
|
||||
""" Check if microsoft events are created in Odoo when 'None' sensitivity setting is defined and also when it is not. """
|
||||
# Sync events from Microsoft to Odoo after adding the sensitivity (privacy) property.
|
||||
self.simple_event_from_outlook_organizer.pop('sensitivity')
|
||||
undefined_privacy_event = {'id': 100, 'iCalUId': 2, **self.simple_event_from_outlook_organizer}
|
||||
default_privacy_event = {'id': 200, 'iCalUId': 4, 'sensitivity': None, **self.simple_event_from_outlook_organizer}
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent([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([('microsoft_id', '=', 100)])
|
||||
default_privacy_odoo_event = self.env['calendar.event'].search([('microsoft_id', '=', 200)])
|
||||
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 custom privacy must have False value in privacy field.")
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
def test_create_videocall_sync_microsoft_calendar(self, mock_insert, mock_get_events):
|
||||
"""
|
||||
Test syncing an event from Odoo to Microsoft Calendar.
|
||||
Ensures that meeting details are correctly updated after syncing from Microsoft.
|
||||
"""
|
||||
record = self.env["calendar.event"].with_user(self.organizer_user).create(self.simple_event_values)
|
||||
self.assertEqual(record.name, "simple_event", "Event name should be same as simple_event")
|
||||
|
||||
# Mock values to simulate Microsoft event creation
|
||||
event_id = "123"
|
||||
event_iCalUId = "456"
|
||||
mock_insert.return_value = (event_id, event_iCalUId)
|
||||
|
||||
# Prepare the mock event response from Microsoft
|
||||
self.response_from_outlook_organizer = {
|
||||
**self.simple_event_from_outlook_organizer,
|
||||
'_odoo_id': record.id,
|
||||
'onlineMeeting': {
|
||||
'joinUrl': 'https://teams.microsoft.com/l/meetup-join/test',
|
||||
'conferenceId': '275984951',
|
||||
'tollNumber': '+1 323-555-0166',
|
||||
},
|
||||
'lastModifiedDateTime': _modified_date_in_the_future(record),
|
||||
'isOnlineMeeting': True,
|
||||
'onlineMeetingProvider': 'teamsForBusiness',
|
||||
}
|
||||
mock_get_events.return_value = (MicrosoftEvent([self.response_from_outlook_organizer]), None)
|
||||
self.organizer_user.with_user(self.organizer_user).sudo()._sync_microsoft_calendar()
|
||||
self.call_post_commit_hooks()
|
||||
record.invalidate_recordset()
|
||||
|
||||
# Check that Microsoft insert was called exactly once
|
||||
mock_insert.assert_called_once()
|
||||
self.assertEqual(record.microsoft_id, event_id, "The Microsoft ID should be assigned to the event.")
|
||||
self.assertEqual(record.ms_universal_event_id, event_iCalUId)
|
||||
self.assertEqual(mock_insert.call_args[0][0].get('isOnlineMeeting'), True,
|
||||
"The event should be marked as an online meeting.")
|
||||
self.assertEqual(mock_insert.call_args[0][0].get('onlineMeetingProvider'), 'teamsForBusiness',
|
||||
"The event's online meeting provider should be set to Microsoft Teams.")
|
||||
self.assertEqual(record.need_sync_m, False)
|
||||
|
||||
# Verify the event's videocall_location is updated in Odoo
|
||||
event = self.env['calendar.event'].search([('name', '=', self.response_from_outlook_organizer.get('subject'))])
|
||||
self.assertTrue(event, "The event should exist in the calendar after sync.")
|
||||
self.assertEqual(event.videocall_location, 'https://teams.microsoft.com/l/meetup-join/test', "The meeting URL should match.")
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
def test_no_videocall_hr_holidays(self, mock_insert, mock_get_events):
|
||||
"""
|
||||
Test HR holidays synchronization with Microsoft Calendar, ensuring no online meetings
|
||||
are generated for leave requests.
|
||||
"""
|
||||
# Skip test if HR Holidays module isn't installed
|
||||
if self.env['ir.module.module']._get('hr_holidays').state not in ['installed', 'to upgrade']:
|
||||
self.skipTest("The 'hr_holidays' module must be installed to run this test.")
|
||||
|
||||
self.user_hrmanager = mail_new_test_user(self.env, login='bastien', groups='base.group_user,hr_holidays.group_hr_holidays_manager')
|
||||
self.user_employee = mail_new_test_user(self.env, login='enguerran', password='enguerran', groups='base.group_user')
|
||||
self.rd_dept = self.env['hr.department'].with_context(tracking_disable=True).create({
|
||||
'name': 'Research and Development',
|
||||
})
|
||||
self.employee_emp = self.env['hr.employee'].create({
|
||||
'name': 'Marc Demo',
|
||||
'user_id': self.user_employee.id,
|
||||
'department_id': self.rd_dept.id,
|
||||
})
|
||||
self.hr_leave_type = self.env['hr.leave.type'].with_user(self.user_hrmanager).create({
|
||||
'name': 'Time Off Type',
|
||||
'requires_allocation': False,
|
||||
})
|
||||
self.holiday = self.env['hr.leave'].with_context(mail_create_nolog=True, mail_notrack=True).with_user(self.user_employee).create({
|
||||
'name': 'Time Off Employee',
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.hr_leave_type.id,
|
||||
'request_date_from': datetime(2020, 1, 15),
|
||||
'request_date_to': datetime(2020, 1, 15),
|
||||
})
|
||||
self.holiday.with_user(self.user_hrmanager).action_approve()
|
||||
|
||||
# Ensure the event exists in the calendar and is correctly linked to the time off
|
||||
search_domain = [
|
||||
('name', 'like', self.holiday.employee_id.name),
|
||||
('start_date', '>=', self.holiday.request_date_from),
|
||||
('stop_date', '<=', self.holiday.request_date_to),
|
||||
]
|
||||
record = self.env['calendar.event'].search(search_domain)
|
||||
self.assertTrue(record, "The time off event should exist.")
|
||||
self.assertEqual(record.name, "Marc Demo on Time Off : 1 days",
|
||||
"The event name should match the employee's time off description.")
|
||||
self.assertEqual(record.start_date, datetime(2020, 1, 15).date(),
|
||||
"The start date should match the time off request.")
|
||||
self.assertEqual(record.stop_date, datetime(2020, 1, 15).date(),
|
||||
"The end date should match the time off request.")
|
||||
|
||||
# Mock Microsoft API response for event creation
|
||||
event_id = "123"
|
||||
event_iCalUId = "456"
|
||||
mock_insert.return_value = (event_id, event_iCalUId)
|
||||
mock_get_events.return_value = ([], None)
|
||||
|
||||
# Sync calendar with Microsoft
|
||||
self.user_employee.with_user(self.user_employee).sudo()._sync_microsoft_calendar()
|
||||
self.call_post_commit_hooks()
|
||||
record.invalidate_recordset()
|
||||
mock_insert.assert_called_once()
|
||||
|
||||
self.assertEqual(record.microsoft_id, event_id, "The Microsoft ID should be assigned correctly.")
|
||||
self.assertEqual(record.ms_universal_event_id, event_iCalUId, "The iCalUID should be assigned correctly.")
|
||||
self.assertEqual(record.need_sync_m, False, "The event should no longer need synchronization.")
|
||||
self.assertEqual(mock_insert.call_args[0][0].get('isOnlineMeeting'), False,
|
||||
"Time off events should not be marked as an online meeting.")
|
||||
self.assertFalse(mock_insert.call_args[0][0].get('onlineMeetingProvider', False))
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
def test_skip_sync_for_non_synchronized_users_new_events(self, mock_insert):
|
||||
"""
|
||||
Skip the synchro of new events by attendees when the organizer is not synchronized with Outlook.
|
||||
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('2021-09-20 10:00:00'):
|
||||
# Ensure that the calendar synchronization of the attendee is active. Deactivate organizer's synchronization.
|
||||
self.attendee_user.microsoft_calendar_token_validity = datetime.now() + timedelta(minutes=60)
|
||||
self.assertTrue(self.env['calendar.event'].with_user(self.attendee_user)._check_microsoft_sync_status())
|
||||
self.organizer_user.microsoft_synchronization_stopped = True
|
||||
|
||||
# Create an event with the organizer not synchronized and invite the synchronized attendee.
|
||||
self.simple_event_values['user_id'] = self.organizer_user.id
|
||||
self.simple_event_values['partner_ids'] = [Command.set([self.organizer_user.partner_id.id, self.attendee_user.partner_id.id])]
|
||||
event = self.env['calendar.event'].with_user(self.organizer_user).create(self.simple_event_values)
|
||||
self.assertTrue(event, "The event for the not synchronized owner must be created in Odoo.")
|
||||
|
||||
# Synchronize the attendee's calendar, then make sure insert was not called.
|
||||
event.with_user(self.attendee_user).sudo()._sync_odoo2microsoft()
|
||||
mock_insert.assert_not_called()
|
||||
|
||||
# Prepare mock return for the insertion.
|
||||
event_id = "123"
|
||||
event_iCalUId = "456"
|
||||
mock_insert.return_value = (event_id, event_iCalUId)
|
||||
|
||||
# Activate the synchronization of the organizer and ensure that the event is now inserted.
|
||||
self.organizer_user.microsoft_synchronization_stopped = False
|
||||
self.organizer_user.microsoft_calendar_token_validity = datetime.now() + timedelta(minutes=60)
|
||||
self.organizer_user.with_user(self.organizer_user).restart_microsoft_synchronization()
|
||||
event.with_user(self.organizer_user).sudo()._sync_odoo2microsoft()
|
||||
self.call_post_commit_hooks()
|
||||
mock_insert.assert_called()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
def test_create_duplicate_event_microsoft_calendar(self, mock_insert, mock_get_events):
|
||||
"""
|
||||
Test syncing an event from Odoo to Microsoft Calendar.
|
||||
"""
|
||||
record = self.env["calendar.event"].with_user(self.organizer_user).create(self.simple_event_values)
|
||||
|
||||
# Mock values to simulate Microsoft event creation
|
||||
event_id = "123"
|
||||
event_iCalUId = "456"
|
||||
mock_insert.return_value = (event_id, event_iCalUId)
|
||||
record2 = record.copy()
|
||||
# Prepare the mock event response from Microsoft
|
||||
self.response_from_outlook_organizer = {
|
||||
**self.simple_event_from_outlook_organizer,
|
||||
'_odoo_id': record.id,
|
||||
}
|
||||
self.response_from_outlook_organizer_1 = {
|
||||
**self.simple_event_from_outlook_organizer,
|
||||
'_odoo_id': record2.id,
|
||||
}
|
||||
mock_get_events.return_value = (MicrosoftEvent([self.response_from_outlook_organizer, self.response_from_outlook_organizer_1]), None)
|
||||
self.organizer_user.with_user(self.organizer_user).sudo()._sync_microsoft_calendar()
|
||||
self.call_post_commit_hooks()
|
||||
record.invalidate_recordset()
|
||||
record2.invalidate_recordset()
|
||||
|
||||
# Check that Microsoft insert was called exactly once
|
||||
mock_insert.assert_called()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
def test_new_db_skip_odoo2microsoft_sync_previously_created_events(self, mock_insert, mock_get_events):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch, ANY, call
|
||||
from datetime import timedelta
|
||||
|
||||
|
|
@ -7,7 +8,7 @@ from odoo import fields
|
|||
from odoo.exceptions import UserError
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import MicrosoftCalendarService
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_event import MicrosoftEvent
|
||||
from odoo.addons.microsoft_calendar.models.res_users import User
|
||||
from odoo.addons.microsoft_calendar.models.res_users import ResUsers
|
||||
from odoo.addons.microsoft_calendar.tests.common import (
|
||||
TestCommon,
|
||||
mock_get_token,
|
||||
|
|
@ -15,7 +16,8 @@ from odoo.addons.microsoft_calendar.tests.common import (
|
|||
patch_api
|
||||
)
|
||||
|
||||
@patch.object(User, '_get_microsoft_calendar_token', mock_get_token)
|
||||
|
||||
@patch.object(ResUsers, '_get_microsoft_calendar_token', mock_get_token)
|
||||
class TestDeleteEvents(TestCommon):
|
||||
|
||||
@patch_api
|
||||
|
|
@ -25,7 +27,7 @@ class TestDeleteEvents(TestCommon):
|
|||
|
||||
@patch.object(MicrosoftCalendarService, 'delete')
|
||||
def test_delete_simple_event_from_odoo_organizer_calendar(self, mock_delete):
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
|
||||
self.simple_event.with_user(self.organizer_user).unlink()
|
||||
self.call_post_commit_hooks()
|
||||
|
|
@ -40,7 +42,7 @@ class TestDeleteEvents(TestCommon):
|
|||
|
||||
@patch.object(MicrosoftCalendarService, 'delete')
|
||||
def test_delete_simple_event_from_odoo_attendee_calendar(self, mock_delete):
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
|
||||
self.simple_event.with_user(self.attendee_user).unlink()
|
||||
self.call_post_commit_hooks()
|
||||
|
|
@ -55,7 +57,7 @@ class TestDeleteEvents(TestCommon):
|
|||
|
||||
@patch.object(MicrosoftCalendarService, 'delete')
|
||||
def test_archive_simple_event_from_odoo_organizer_calendar(self, mock_delete):
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
|
||||
self.simple_event.with_user(self.organizer_user).write({'active': False})
|
||||
self.call_post_commit_hooks()
|
||||
|
|
@ -71,7 +73,7 @@ class TestDeleteEvents(TestCommon):
|
|||
|
||||
@patch.object(MicrosoftCalendarService, 'delete')
|
||||
def test_archive_simple_event_from_odoo_attendee_calendar(self, mock_delete):
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
|
||||
self.simple_event.with_user(self.attendee_user).write({'active': False})
|
||||
self.call_post_commit_hooks()
|
||||
|
|
@ -101,7 +103,7 @@ class TestDeleteEvents(TestCommon):
|
|||
self.assertFalse(all(e.active for e in several_simple_events))
|
||||
|
||||
mock_delete.assert_has_calls([
|
||||
call(e.ms_organizer_event_id, token=ANY, timeout=ANY)
|
||||
call(e.microsoft_id, token=ANY, timeout=ANY)
|
||||
for e in several_simple_events
|
||||
])
|
||||
|
||||
|
|
@ -110,7 +112,7 @@ class TestDeleteEvents(TestCommon):
|
|||
"""
|
||||
In his Outlook calendar, the organizer cannot delete the event, he can only cancel it.
|
||||
"""
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
mock_get_events.return_value = (
|
||||
MicrosoftEvent([{
|
||||
"id": event_id,
|
||||
|
|
@ -142,7 +144,7 @@ class TestDeleteEvents(TestCommon):
|
|||
return
|
||||
# arrange
|
||||
idx = 2
|
||||
event_id = self.recurrent_events[idx].ms_organizer_event_id
|
||||
event_id = self.recurrent_events[idx].microsoft_id
|
||||
|
||||
# act
|
||||
self.recurrent_events[idx].with_user(self.organizer_user).unlink()
|
||||
|
|
@ -163,7 +165,7 @@ class TestDeleteEvents(TestCommon):
|
|||
return
|
||||
# arrange
|
||||
idx = 0
|
||||
event_id = self.recurrent_events[idx].ms_organizer_event_id
|
||||
event_id = self.recurrent_events[idx].microsoft_id
|
||||
|
||||
# act
|
||||
self.recurrent_events[idx].with_user(self.organizer_user).unlink()
|
||||
|
|
@ -261,7 +263,7 @@ class TestDeleteEvents(TestCommon):
|
|||
# arrange
|
||||
mock_get_events.return_value = (
|
||||
MicrosoftEvent([{
|
||||
"id": self.recurrence.ms_organizer_event_id,
|
||||
"id": self.recurrence.microsoft_id,
|
||||
"@removed": {"reason": "deleted"}
|
||||
}]),
|
||||
None
|
||||
|
|
@ -289,7 +291,7 @@ class TestDeleteEvents(TestCommon):
|
|||
return
|
||||
# arrange
|
||||
idx = 0
|
||||
event_id = self.recurrent_events[idx].ms_organizer_event_id
|
||||
event_id = self.recurrent_events[idx].microsoft_id
|
||||
|
||||
# act
|
||||
self.recurrent_events[idx].with_user(self.organizer_user).action_mass_archive('self_only')
|
||||
|
|
@ -305,6 +307,26 @@ class TestDeleteEvents(TestCommon):
|
|||
timeout=ANY
|
||||
)
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'delete')
|
||||
def test_delete_synced_event_with_sync_config_paused(self, mock_delete):
|
||||
"""
|
||||
Deletes an event with the Outlook Calendar synchronization paused, the event must be archived completely.
|
||||
"""
|
||||
# Set user synchronization configuration as active and pause it.
|
||||
self.organizer_user.microsoft_synchronization_stopped = False
|
||||
self.organizer_user.pause_microsoft_synchronization()
|
||||
|
||||
# Try to delete a simple event in Odoo Calendar.
|
||||
self.simple_event.with_user(self.organizer_user).unlink()
|
||||
self.call_post_commit_hooks()
|
||||
self.simple_event.invalidate_recordset()
|
||||
|
||||
# Ensure that synchronization is paused, delete wasn't called and record doesn't exist anymore.
|
||||
self.assertFalse(self.organizer_user.microsoft_synchronization_stopped)
|
||||
self.assertEqual(self.organizer_user._get_microsoft_sync_status(), "sync_paused")
|
||||
self.assertFalse(self.simple_event.exists(), "Event must be deleted from Odoo even though sync configuration is off")
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'delete')
|
||||
def test_delete_recurrence_previously_synced(self, mock_delete):
|
||||
# Arrange: select recurrent event and update token validity to simulate an active sync environment.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from pytz import UTC
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_event import MicrosoftEvent
|
||||
from odoo.addons.microsoft_calendar.tests.common import TestCommon, patch_api
|
||||
|
||||
|
|
@ -15,7 +18,7 @@ class TestMicrosoftEvent(TestCommon):
|
|||
def test_already_mapped_events(self):
|
||||
|
||||
# arrange
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
event_uid = self.simple_event.ms_universal_event_id
|
||||
events = MicrosoftEvent([{
|
||||
"type": "singleInstance",
|
||||
|
|
@ -31,10 +34,69 @@ class TestMicrosoftEvent(TestCommon):
|
|||
self.assertEqual(len(mapped._events), 1)
|
||||
self.assertEqual(mapped._events[event_id]["_odoo_id"], self.simple_event.id)
|
||||
|
||||
def test_map_an_event_using_global_id(self):
|
||||
def test_forbid_edit_outlook_recurring_event(self):
|
||||
"""
|
||||
Test that no user can edit a recurring event imported from Outlook
|
||||
(identified by microsoft_recurrence_master_id), but that the sync
|
||||
mechanism itself can still write through via dont_notify context.
|
||||
"""
|
||||
# Give the organizer user a Microsoft token
|
||||
self.organizer_user.microsoft_calendar_token = "fake_token"
|
||||
|
||||
outlook_recurring_event, recurring_event = self.env['calendar.event'].with_context(dont_notify=True).create([
|
||||
{
|
||||
'name': 'Outlook Recurring Event',
|
||||
'start': datetime(2023, 9, 25, 10, 0),
|
||||
'stop': datetime(2023, 9, 25, 11, 0),
|
||||
'microsoft_id': 'AAA123:BBB456',
|
||||
'microsoft_recurrence_master_id': 'MASTER123',
|
||||
'user_id': self.organizer_user.id,
|
||||
},
|
||||
{
|
||||
'name': 'Regular Recurring Event',
|
||||
'start': datetime(2023, 9, 25, 10, 0),
|
||||
'stop': datetime(2023, 9, 25, 11, 0),
|
||||
'user_id': self.organizer_user.id,
|
||||
'partner_ids': [(4, self.organizer_user.partner_id.id)],
|
||||
'recurrency': True,
|
||||
'follow_recurrence': True,
|
||||
'rrule_type': 'daily',
|
||||
'interval': 1,
|
||||
'end_type': 'count',
|
||||
'count': 3,
|
||||
},
|
||||
])
|
||||
|
||||
# No user should be able to edit the Outlook event through Odoo
|
||||
for user in [self.attendee_user, self.organizer_user]:
|
||||
with self.assertRaises(UserError):
|
||||
outlook_recurring_event.with_user(user).with_context(dont_notify=False).write({
|
||||
'name': 'Trying to change name',
|
||||
'recurrence_update': 'future_events',
|
||||
})
|
||||
self.assertEqual(outlook_recurring_event.name, 'Outlook Recurring Event')
|
||||
|
||||
# But changes from Microsoft sync itself should still work
|
||||
outlook_recurring_event.with_context(dont_notify=True).write({
|
||||
'name': 'Updated from Outlook',
|
||||
'recurrence_update': 'future_events'
|
||||
})
|
||||
self.assertEqual(outlook_recurring_event.name, 'Updated from Outlook')
|
||||
|
||||
# Remove token: organizer is no longer synced to Outlook
|
||||
self.organizer_user.microsoft_calendar_token = False
|
||||
|
||||
# Any user should be able to edit a non-Outlook recurring event
|
||||
for user in [self.attendee_user, self.organizer_user]:
|
||||
recurring_event.with_user(user).with_context(dont_notify=False).write({
|
||||
'name': f'Changed by {user.name}',
|
||||
'recurrence_update': 'future_events',
|
||||
})
|
||||
self.assertEqual(recurring_event.name, f'Changed by {user.name}')
|
||||
|
||||
def test_map_an_event_using_global_id(self):
|
||||
# arrange
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
event_uid = self.simple_event.ms_universal_event_id
|
||||
events = MicrosoftEvent([{
|
||||
"type": "singleInstance",
|
||||
|
|
@ -55,7 +117,7 @@ class TestMicrosoftEvent(TestCommon):
|
|||
Here, the Odoo event has an uid but the Outlook event has not.
|
||||
"""
|
||||
# arrange
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
events = MicrosoftEvent([{
|
||||
"type": "singleInstance",
|
||||
"_odoo_id": False,
|
||||
|
|
@ -76,7 +138,7 @@ class TestMicrosoftEvent(TestCommon):
|
|||
"""
|
||||
|
||||
# arrange
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
event_uid = self.simple_event.ms_universal_event_id
|
||||
self.simple_event.ms_universal_event_id = False
|
||||
events = MicrosoftEvent([{
|
||||
|
|
@ -100,7 +162,7 @@ class TestMicrosoftEvent(TestCommon):
|
|||
"""
|
||||
|
||||
# arrange
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
self.simple_event.ms_universal_event_id = False
|
||||
events = MicrosoftEvent([{
|
||||
"type": "singleInstance",
|
||||
|
|
@ -120,7 +182,7 @@ class TestMicrosoftEvent(TestCommon):
|
|||
def test_map_a_recurrence_using_global_id(self):
|
||||
|
||||
# arrange
|
||||
rec_id = self.recurrence.ms_organizer_event_id
|
||||
rec_id = self.recurrence.microsoft_id
|
||||
rec_uid = self.recurrence.ms_universal_event_id
|
||||
events = MicrosoftEvent([{
|
||||
"type": "seriesMaster",
|
||||
|
|
@ -139,7 +201,7 @@ class TestMicrosoftEvent(TestCommon):
|
|||
def test_map_a_recurrence_using_instance_id(self):
|
||||
|
||||
# arrange
|
||||
rec_id = self.recurrence.ms_organizer_event_id
|
||||
rec_id = self.recurrence.microsoft_id
|
||||
events = MicrosoftEvent([{
|
||||
"type": "seriesMaster",
|
||||
"_odoo_id": False,
|
||||
|
|
@ -157,9 +219,9 @@ class TestMicrosoftEvent(TestCommon):
|
|||
def test_try_to_map_mixed_of_single_events_and_recurrences(self):
|
||||
|
||||
# arrange
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
event_uid = self.simple_event.ms_universal_event_id
|
||||
rec_id = self.recurrence.ms_organizer_event_id
|
||||
rec_id = self.recurrence.microsoft_id
|
||||
rec_uid = self.recurrence.ms_universal_event_id
|
||||
|
||||
events = MicrosoftEvent([
|
||||
|
|
@ -184,7 +246,7 @@ class TestMicrosoftEvent(TestCommon):
|
|||
def test_match_event_only(self):
|
||||
|
||||
# arrange
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
event_uid = self.simple_event.ms_universal_event_id
|
||||
events = MicrosoftEvent([{
|
||||
"type": "singleInstance",
|
||||
|
|
@ -203,7 +265,7 @@ class TestMicrosoftEvent(TestCommon):
|
|||
def test_match_recurrence_only(self):
|
||||
|
||||
# arrange
|
||||
rec_id = self.recurrence.ms_organizer_event_id
|
||||
rec_id = self.recurrence.microsoft_id
|
||||
rec_uid = self.recurrence.ms_universal_event_id
|
||||
events = MicrosoftEvent([{
|
||||
"type": "seriesMaster",
|
||||
|
|
@ -226,7 +288,7 @@ class TestMicrosoftEvent(TestCommon):
|
|||
recurrence.
|
||||
"""
|
||||
# arrange
|
||||
rec_id = self.recurrence.ms_organizer_event_id
|
||||
rec_id = self.recurrence.microsoft_id
|
||||
rec_uid = self.recurrence.ms_universal_event_id
|
||||
events = MicrosoftEvent([{
|
||||
"@removed": {
|
||||
|
|
@ -247,9 +309,9 @@ class TestMicrosoftEvent(TestCommon):
|
|||
def test_match_mix_of_events_and_recurrences(self):
|
||||
|
||||
# arrange
|
||||
event_id = self.simple_event.ms_organizer_event_id
|
||||
event_id = self.simple_event.microsoft_id
|
||||
event_uid = self.simple_event.ms_universal_event_id
|
||||
rec_id = self.recurrence.ms_organizer_event_id
|
||||
rec_id = self.recurrence.microsoft_id
|
||||
rec_uid = self.recurrence.ms_universal_event_id
|
||||
|
||||
events = MicrosoftEvent([
|
||||
|
|
@ -296,11 +358,10 @@ class TestMicrosoftEvent(TestCommon):
|
|||
def test_search_set_ms_universal_event_id(self):
|
||||
not_synced_events = self.env['calendar.event'].search([('ms_universal_event_id', '=', False)])
|
||||
synced_events = self.env['calendar.event'].search([('ms_universal_event_id', '!=', False)])
|
||||
|
||||
self.assertIn(self.simple_event, synced_events)
|
||||
self.assertNotIn(self.simple_event, not_synced_events)
|
||||
|
||||
self.simple_event.ms_universal_event_id = ''
|
||||
self.simple_event.ms_universal_event_id = False
|
||||
not_synced_events = self.env['calendar.event'].search([('ms_universal_event_id', '=', False)])
|
||||
synced_events = self.env['calendar.event'].search([('ms_universal_event_id', '!=', False)])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
import requests
|
||||
from unittest.mock import patch, call, MagicMock
|
||||
|
|
@ -5,7 +7,7 @@ from unittest.mock import patch, call, MagicMock
|
|||
from odoo import fields
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import MicrosoftCalendarService
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_event import MicrosoftEvent
|
||||
from odoo.addons.microsoft_account.models.microsoft_service import MicrosoftService
|
||||
from odoo.addons.microsoft_account.models.microsoft_service import MicrosoftService, DEFAULT_MICROSOFT_TOKEN_ENDPOINT
|
||||
from odoo.tests import TransactionCase
|
||||
|
||||
|
||||
|
|
@ -28,7 +30,10 @@ class TestMicrosoftService(TransactionCase):
|
|||
self.fake_next_sync_token_url = f"https://graph.microsoft.com/v1.0/me/calendarView/delta?$deltatoken={self.fake_next_sync_token}"
|
||||
|
||||
self.header_prefer = 'outlook.body-content-type="html", odata.maxpagesize=50'
|
||||
self.header = {'Content-type': 'application/json', 'Authorization': 'Bearer %s' % self.fake_token}
|
||||
self.delete_header = {
|
||||
'Authorization': 'Bearer %s' % self.fake_token,
|
||||
}
|
||||
self.header = {'Content-type': 'application/json', **self.delete_header}
|
||||
self.call_with_sync_token = call(
|
||||
"/v1.0/me/calendarView/delta",
|
||||
{"$deltatoken": self.fake_sync_token},
|
||||
|
|
@ -365,7 +370,7 @@ class TestMicrosoftService(TransactionCase):
|
|||
self.assertFalse(res)
|
||||
mock_do_request.assert_called_with(
|
||||
f"/v1.0/me/calendar/events/{event_id}",
|
||||
{}, headers={'Authorization': 'Bearer %s' % self.fake_token}, method="DELETE", timeout=DEFAULT_TIMEOUT
|
||||
{}, headers=self.delete_header, method="DELETE", timeout=DEFAULT_TIMEOUT
|
||||
)
|
||||
|
||||
@patch.object(MicrosoftService, "_do_request")
|
||||
|
|
@ -384,7 +389,7 @@ class TestMicrosoftService(TransactionCase):
|
|||
self.assertTrue(res)
|
||||
mock_do_request.assert_called_with(
|
||||
f"/v1.0/me/calendar/events/{event_id}",
|
||||
{}, headers={'Authorization': 'Bearer %s' % self.fake_token}, method="DELETE", timeout=DEFAULT_TIMEOUT
|
||||
{}, headers=self.delete_header, method="DELETE", timeout=DEFAULT_TIMEOUT
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -398,7 +403,7 @@ class TestMicrosoftService(TransactionCase):
|
|||
self.assertTrue(res)
|
||||
mock_do_request.assert_called_with(
|
||||
f"/v1.0/me/calendar/events/{event_id}",
|
||||
{}, headers={'Authorization': 'Bearer %s' % self.fake_token}, method="DELETE", timeout=DEFAULT_TIMEOUT
|
||||
{}, headers=self.delete_header, method="DELETE", timeout=DEFAULT_TIMEOUT
|
||||
)
|
||||
|
||||
def test_answer_token_error(self):
|
||||
|
|
@ -461,3 +466,40 @@ class TestMicrosoftService(TransactionCase):
|
|||
self.call_with_sync_token,
|
||||
self.call_without_sync_token
|
||||
])
|
||||
|
||||
@patch.object(MicrosoftService, "_do_request")
|
||||
def test_refresh_microsoft_calendar_token_uses_correct_endpoint(self, mock_do_request):
|
||||
# Ensure we use the correct endpoint (useful for single/multi-tenant deployments).
|
||||
mock_do_request.return_value = self._do_request_result(
|
||||
{
|
||||
"access_token": "dummy_access_token",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3599,
|
||||
"scope": "Mail.Read User.Read",
|
||||
"refresh_token": "dummy_refresh_token",
|
||||
}
|
||||
)
|
||||
IrParameter = self.env["ir.config_parameter"].sudo()
|
||||
IrParameter.set_param("microsoft_calendar_client_id", "dummy_client_id")
|
||||
IrParameter.set_param("microsoft_calendar_client_secret", "dummy_client_secret")
|
||||
|
||||
self.env.user._refresh_microsoft_calendar_token()
|
||||
|
||||
custom_token_endpoint = "https://login.microsoftonline.com/dummy_tenant_id/oauth2/v2.0/token"
|
||||
IrParameter.set_param("microsoft_account.token_endpoint", custom_token_endpoint)
|
||||
self.env.user._refresh_microsoft_calendar_token()
|
||||
|
||||
kwargs = {
|
||||
"params": {
|
||||
"client_id": "dummy_client_id",
|
||||
"client_secret": "dummy_client_secret",
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": False,
|
||||
},
|
||||
"headers": {"Content-type": "application/x-www-form-urlencoded"},
|
||||
"method": "POST",
|
||||
"preuri": "",
|
||||
}
|
||||
first_call = call(DEFAULT_MICROSOFT_TOKEN_ENDPOINT, **kwargs)
|
||||
second_call = call(custom_token_endpoint, **kwargs)
|
||||
mock_do_request.assert_has_calls([first_call, second_call])
|
||||
|
|
|
|||
|
|
@ -1,374 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import MicrosoftCalendarService, MicrosoftEvent
|
||||
from odoo.exceptions import ValidationError
|
||||
import pytz
|
||||
from datetime import datetime, date
|
||||
from odoo.tests.common import TransactionCase
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class TestSyncMicrosoft2Odoo(TransactionCase):
|
||||
|
||||
@property
|
||||
def now(self):
|
||||
return pytz.utc.localize(datetime.now()).isoformat()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.recurrence_id = 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA'
|
||||
values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAACyq4xQ=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': '2020-05-06T07:00:00Z', 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAACyq4xQ==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000D848B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAALKrjF"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX7vTsS0AARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAALKrjF"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA=', 'start': {'dateTime': '2020-05-04T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-04T16:00:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAALKrjF"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8IdBHsAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-05T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-05T16:00:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
self.single_event = [
|
||||
{
|
||||
'@odata.type': '#microsoft.graph.event',
|
||||
'@odata.etag': 'W/"AAAAA"',
|
||||
'type': 'singleInstance',
|
||||
'id': "CCCCC",
|
||||
'start': {
|
||||
'dateTime': '2020-05-05T14:30:00.0000000',
|
||||
'timeZone': 'UTC'
|
||||
},
|
||||
'end': {
|
||||
'dateTime': '2020-05-05T16:00:00.0000000',
|
||||
'timeZone': 'UTC'
|
||||
},
|
||||
'location': {
|
||||
'displayName': "a meeting room at Odoo"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
|
||||
self.datetime_future = pytz.utc.localize(datetime.now() + relativedelta(days=1)).isoformat()
|
||||
|
||||
def sync(self, events):
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(events)
|
||||
|
||||
def test_new_microsoft_recurrence(self):
|
||||
|
||||
recurrence = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
|
||||
events = recurrence.calendar_event_ids
|
||||
self.assertTrue(recurrence, "It should have created an recurrence")
|
||||
self.assertEqual(len(events), 3, "It should have created 3 events")
|
||||
self.assertEqual(recurrence.base_event_id, events[0])
|
||||
self.assertEqual(events.mapped('name'), ['My recurrent event', 'My recurrent event', 'My recurrent event'])
|
||||
self.assertFalse(events[0].allday)
|
||||
self.assertEqual(events[0].start, datetime(2020, 5, 3, 14, 30))
|
||||
self.assertEqual(events[0].stop, datetime(2020, 5, 3, 16, 00))
|
||||
self.assertEqual(events[1].start, datetime(2020, 5, 4, 14, 30))
|
||||
self.assertEqual(events[1].stop, datetime(2020, 5, 4, 16, 00))
|
||||
self.assertEqual(events[2].start, datetime(2020, 5, 5, 14, 30))
|
||||
self.assertEqual(events[2].stop, datetime(2020, 5, 5, 16, 00))
|
||||
|
||||
def test_microsoft_recurrence_delete_one_event(self):
|
||||
values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMhpkp"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX7vTsS0AARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMhpkp"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8IdBHsAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-05T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-05T16:00:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
|
||||
recurrence = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
|
||||
events = self.env['calendar.event'].search([('recurrence_id', '=', recurrence.id)], order='start asc')
|
||||
self.assertTrue(recurrence, "It should keep the recurrence")
|
||||
self.assertEqual(len(events), 2, "It should keep 2 events")
|
||||
self.assertEqual(recurrence.base_event_id, events[0])
|
||||
self.assertEqual(events.mapped('name'), ['My recurrent event', 'My recurrent event'])
|
||||
|
||||
def test_microsoft_recurrence_change_name_one_event(self):
|
||||
values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMhpkp"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX7vTsS0AARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ=="', 'createdDateTime': '2020-05-06T08:01:32.4884797Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00807E40504874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event 2', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'originalStart': '2020-05-04T14:30:00Z', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'showAs': 'busy', 'type': 'exception', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA%3D&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA=', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-04T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-04T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMhpkp"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8IdBHsAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-05T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-05T16:00:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
|
||||
recurrence = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
|
||||
events = self.env['calendar.event'].search([('recurrence_id', '=', recurrence.id)], order='start asc')
|
||||
self.assertTrue(recurrence, "It should have created an recurrence")
|
||||
self.assertEqual(len(events), 3, "It should have created 3 events")
|
||||
self.assertEqual(recurrence.base_event_id, events[0])
|
||||
self.assertEqual(events.mapped('name'), ['My recurrent event', 'My recurrent event 2', 'My recurrent event'])
|
||||
|
||||
def test_microsoft_recurrence_change_name_all_event(self):
|
||||
values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event 2', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMhpkp"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX7vTsS0AARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMhpkp"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA=', 'start': {'dateTime': '2020-05-04T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-04T16:00:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMhpkp"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8IdBHsAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-05T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-05T16:00:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
|
||||
recurrence = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
|
||||
events = self.env['calendar.event'].search([('recurrence_id', '=', recurrence.id)], order='start asc')
|
||||
self.assertTrue(recurrence, "It should keep the recurrence")
|
||||
self.assertEqual(len(events), 3, "It should keep the 3 events")
|
||||
self.assertEqual(recurrence.base_event_id, events[0])
|
||||
self.assertEqual(events.mapped('name'), ['My recurrent event 2', 'My recurrent event 2', 'My recurrent event 2'])
|
||||
|
||||
def test_microsoft_recurrence_change_date_one_event(self):
|
||||
values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZPA=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZPA==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMhpk8"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX7vTsS0AARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZPA=="', 'createdDateTime': '2020-05-06T08:41:52.1067613Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZPA==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00807E40504874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'originalStart': '2020-05-04T14:30:00Z', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'showAs': 'busy', 'type': 'exception', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA%3D&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA=', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-04T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-04T17:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMhpk8"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8IdBHsAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-05T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-05T16:00:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
|
||||
recurrence = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
|
||||
events = self.env['calendar.event'].search([('recurrence_id', '=', recurrence.id)], order='start asc')
|
||||
special_event = self.env['calendar.event'].search([('microsoft_id', '=', 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA=')])
|
||||
self.assertTrue(recurrence, "It should have created an recurrence")
|
||||
self.assertTrue(special_event, "It should have created an special event")
|
||||
self.assertEqual(len(events), 3, "It should have created 3 events")
|
||||
self.assertTrue(special_event in events)
|
||||
self.assertEqual(recurrence.base_event_id, events[0])
|
||||
self.assertEqual(events.mapped('name'), ['My recurrent event', 'My recurrent event', 'My recurrent event'])
|
||||
event_not_special = events - special_event
|
||||
self.assertEqual(event_not_special[0].start, datetime(2020, 5, 3, 14, 30))
|
||||
self.assertEqual(event_not_special[0].stop, datetime(2020, 5, 3, 16, 00))
|
||||
self.assertEqual(event_not_special[1].start, datetime(2020, 5, 5, 14, 30))
|
||||
self.assertEqual(event_not_special[1].stop, datetime(2020, 5, 5, 16, 00))
|
||||
self.assertEqual(special_event.start, datetime(2020, 5, 4, 14, 30))
|
||||
self.assertEqual(special_event.stop, datetime(2020, 5, 4, 17, 00))
|
||||
|
||||
def test_microsoft_recurrence_delete_first_event(self):
|
||||
values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADI/Bnw=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADI/Bnw==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMj8Gf"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA=', 'start': {'dateTime': '2020-05-04T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-04T16:00:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMj8Gf"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8IdBHsAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-05T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-05T16:00:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
|
||||
recurrence = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
|
||||
events = self.env['calendar.event'].search([('recurrence_id', '=', recurrence.id)], order='start asc')
|
||||
self.assertTrue(recurrence, "It should have created an recurrence")
|
||||
self.assertEqual(len(events), 2, "It should left 2 events")
|
||||
self.assertEqual(recurrence.base_event_id, events[0])
|
||||
self.assertEqual(events[0].start, datetime(2020, 5, 4, 14, 30))
|
||||
self.assertEqual(events[0].stop, datetime(2020, 5, 4, 16, 00))
|
||||
self.assertEqual(events[1].start, datetime(2020, 5, 5, 14, 30))
|
||||
self.assertEqual(events[1].stop, datetime(2020, 5, 5, 16, 00))
|
||||
|
||||
# Now we delete lastest event in Outlook.
|
||||
values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADI/Bpg=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADI/Bpg==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMj8Gm"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA=', 'start': {'dateTime': '2020-05-04T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-04T16:00:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
|
||||
events = self.env['calendar.event'].search([('recurrence_id', '=', recurrence.id)], order='start asc')
|
||||
self.assertEqual(len(events), 1, "It should have created 1 events")
|
||||
self.assertEqual(recurrence.base_event_id, events[0])
|
||||
|
||||
# Now, we change end datetime of recurrence in Outlook, so all recurrence is recreated (even deleted events)
|
||||
values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADI/Bqg=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADI/Bqg==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:30:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMj8Gq"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX7vTsS0AARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:30:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMj8Gq"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAACyy0xAAAABA=', 'start': {'dateTime': '2020-05-04T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-04T16:30:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMj8Gq"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8IdBHsAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-05T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-05T16:30:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
|
||||
events = self.env['calendar.event'].search([('recurrence_id', '=', recurrence.id)], order='start asc')
|
||||
self.assertEqual(len(events), 3, "It should have created 3 events")
|
||||
self.assertEqual(recurrence.base_event_id, events[0])
|
||||
self.assertEqual(events.mapped('name'), ['My recurrent event', 'My recurrent event', 'My recurrent event'])
|
||||
self.assertEqual(events[0].start, datetime(2020, 5, 3, 14, 30))
|
||||
self.assertEqual(events[0].stop, datetime(2020, 5, 3, 16, 30))
|
||||
self.assertEqual(events[1].start, datetime(2020, 5, 4, 14, 30))
|
||||
self.assertEqual(events[1].stop, datetime(2020, 5, 4, 16, 30))
|
||||
self.assertEqual(events[2].start, datetime(2020, 5, 5, 14, 30))
|
||||
self.assertEqual(events[2].stop, datetime(2020, 5, 5, 16, 30))
|
||||
|
||||
def test_microsoft_recurrence_split_recurrence(self):
|
||||
values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADI/Dig=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADI/Dig==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:30:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-03', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADI/Dkw=="', 'createdDateTime': '2020-05-06T13:24:10.0507138Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADI/Dkw==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E008000000001A4457A0A923D601000000000000000010000000476AE6084FD718418262DA1AE3E41411', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAAMkgQrAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAAMkgQrAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-04T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-04T17:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-04', 'endDate': '2020-05-06', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMj8OK"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX7vTsS0AARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAAEA==', 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:30:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMj8OT"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAAMkgQrAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX774WtQAAAEYAAAJAcu19N72jSr9Rp1mE2xWABwBlLa4RUBXJToExnebpwea2AAACAQ0AAABlLa4RUBXJToExnebpwea2AAAADJIEKwAAABA=', 'start': {'dateTime': '2020-05-04T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-04T17:00:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADI/Dkw=="', 'createdDateTime': '2020-05-06T13:25:05.9240043Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADI/Dkw==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00807E405051A4457A0A923D601000000000000000010000000476AE6084FD718418262DA1AE3E41411', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event 2', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'originalStart': '2020-05-05T14:30:00Z', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAAMkgQrAAAA', 'showAs': 'busy', 'type': 'exception', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8IdBHsAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAAMkgQrAAAAEA%3D%3D&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8IdBHsAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAAMkgQrAAAAEA==', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-05T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-05T17:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAABlLa4RUBXJToExnebpwea2AAAMj8OT"', 'seriesMasterId': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAAMkgQrAAAA', 'type': 'occurrence', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoBUQAICADX8VBriIAARgAAAkBy7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAAMkgQrAAAAEA==', 'start': {'dateTime': '2020-05-06T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-06T17:00:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
recurrence_1 = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
|
||||
recurrence_2 = self.env['calendar.recurrence'].search([('microsoft_id', '=', 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAAMkgQrAAAA')])
|
||||
|
||||
events_1 = self.env['calendar.event'].search([('recurrence_id', '=', recurrence_1.id)], order='start asc')
|
||||
events_2 = self.env['calendar.event'].search([('recurrence_id', '=', recurrence_2.id)], order='start asc')
|
||||
self.assertTrue(recurrence_1, "It should have created an recurrence")
|
||||
self.assertTrue(recurrence_2, "It should have created an recurrence")
|
||||
self.assertEqual(len(events_1), 1, "It should left 1 event")
|
||||
self.assertEqual(len(events_2), 3, "It should have created 3 events")
|
||||
self.assertEqual(recurrence_1.base_event_id, events_1[0])
|
||||
self.assertEqual(recurrence_2.base_event_id, events_2[0])
|
||||
self.assertEqual(events_1.mapped('name'), ['My recurrent event'])
|
||||
self.assertEqual(events_2.mapped('name'), ['My recurrent event', 'My recurrent event 2', 'My recurrent event'])
|
||||
self.assertEqual(events_1[0].start, datetime(2020, 5, 3, 14, 30))
|
||||
self.assertEqual(events_1[0].stop, datetime(2020, 5, 3, 16, 30))
|
||||
self.assertEqual(events_2[0].start, datetime(2020, 5, 4, 14, 30))
|
||||
self.assertEqual(events_2[0].stop, datetime(2020, 5, 4, 17, 00))
|
||||
self.assertEqual(events_2[1].start, datetime(2020, 5, 5, 14, 30))
|
||||
self.assertEqual(events_2[1].stop, datetime(2020, 5, 5, 17, 00))
|
||||
self.assertEqual(events_2[2].start, datetime(2020, 5, 6, 14, 30))
|
||||
self.assertEqual(events_2[2].stop, datetime(2020, 5, 6, 17, 00))
|
||||
|
||||
def test_microsoft_recurrence_delete(self):
|
||||
recurrence_id = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
|
||||
event_ids = self.env['calendar.event'].search([('recurrence_id', '=', recurrence_id.id)], order='start asc').ids
|
||||
values = [{'@odata.type': '#microsoft.graph.event', 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', '@removed': {'reason': 'deleted'}}]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(values))
|
||||
|
||||
recurrence = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
|
||||
events = self.env['calendar.event'].browse(event_ids).exists()
|
||||
self.assertFalse(recurrence, "It should remove recurrence")
|
||||
self.assertFalse(events, "It should remove all events")
|
||||
|
||||
def test_attendees_must_have_email(self):
|
||||
"""
|
||||
Synching with a partner without mail raises a ValidationError because Microsoft don't accept attendees without one.
|
||||
"""
|
||||
MicrosoftCal = MicrosoftCalendarService(self.env['microsoft.service'])
|
||||
partner = self.env['res.partner'].create({
|
||||
'name': 'SuperPartner',
|
||||
})
|
||||
event = self.env['calendar.event'].create({
|
||||
'name': "SuperEvent",
|
||||
'start': datetime(2020, 3, 16, 11, 0),
|
||||
'stop': datetime(2020, 3, 16, 13, 0),
|
||||
'partner_ids': [(4, partner.id)],
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
event._sync_odoo2microsoft(MicrosoftCal)
|
||||
|
||||
def test_cancel_occurence_of_recurrent_event(self):
|
||||
""" The user is invited to a recurrent event. When synced, all events are present, there are three occurrences:
|
||||
- 07/15/2021, 15:00-15:30
|
||||
- 07/16/2021, 15:00-15:30
|
||||
- 07/17/2021, 15:00-15:30
|
||||
Then, the organizer cancels the second occurrence -> The latter should not be displayed anymore
|
||||
"""
|
||||
microsoft_id = 'AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgBGAAADZ59RIxdyh0Kt-MXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAA='
|
||||
# self.env.user.partner_id.email = "odoo_bf_user01@outlook.com"
|
||||
first_sync_values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"pynKRnkCyUmnqILQHcLZEQAABElcNQ=="', 'createdDateTime': '2021-07-15T14:47:40.2996962Z', 'lastModifiedDateTime': '2021-07-15T14:47:40.3783507Z', 'changeKey': 'pynKRnkCyUmnqILQHcLZEQAABElcNQ==', 'categories': [], 'transactionId': None, 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000B35B3B5A8879D70100000000000000001000000008A0949F4EC0A1479E4ED178D87EF679', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'Recurrent Event 1646', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': False, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'tentative', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgBGAAADZ59RIxdyh0Kt%2FMXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAA%3D&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'allowNewTimeProposals': True, 'OccurrenceId': None, 'isDraft': False, 'hideAttendees': False, 'CalendarEventClassifications': [], 'AutoRoomBookingOptions': None, 'onlineMeeting': None, 'id': microsoft_id, 'responseStatus': {'response': 'notResponded', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2021-07-15T15:00:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2021-07-15T15:30:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2021-07-15', 'endDate': '2021-07-17', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [{'type': 'required', 'status': {'response': 'none', 'time': '0001-01-01T00:00:00Z'}, 'emailAddress': {'name': 'Odoo02 Outlook02', 'address': 'odoo_bf_user02@outlook.com'}}, {'type': 'required', 'status': {'response': 'none', 'time': '0001-01-01T00:00:00Z'}, 'emailAddress': {'name': 'Odoo01 Outlook01', 'address': 'odoo_bf_user01@outlook.com'}}], 'organizer': {'emailAddress': {'name': 'Odoo02 Outlook02', 'address': 'odoo_bf_user02@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAACnKcpGeQLJSaeogtAdwtkRAAAESVw1"', 'seriesMasterId': ('%s' % microsoft_id), 'type': 'occurrence', 'id': 'AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgFRAAgIANlHI305wABGAAACZ59RIxdyh0Kt-MXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAAQ', 'start': {'dateTime': '2021-07-15T15:00:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2021-07-15T15:30:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAACnKcpGeQLJSaeogtAdwtkRAAAESVw1"', 'seriesMasterId': microsoft_id, 'type': 'occurrence', 'id': 'AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgFRAAgIANlH7KejgABGAAACZ59RIxdyh0Kt-MXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAAQ', 'start': {'dateTime': '2021-07-16T15:00:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2021-07-16T15:30:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAACnKcpGeQLJSaeogtAdwtkRAAAESVw1"', 'seriesMasterId': microsoft_id, 'type': 'occurrence', 'id': 'AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgFRAAgIANlItdINQABGAAACZ59RIxdyh0Kt-MXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAAQ', 'start': {'dateTime': '2021-07-17T15:00:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2021-07-17T15:30:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
second_sync_values = [
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"pynKRnkCyUmnqILQHcLZEQAABElcUw=="', 'createdDateTime': '2021-07-15T14:47:40.2996962Z', 'lastModifiedDateTime': '2021-07-15T14:51:25.2560888Z', 'changeKey': 'pynKRnkCyUmnqILQHcLZEQAABElcUw==', 'categories': [], 'transactionId': None, 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000B35B3B5A8879D70100000000000000001000000008A0949F4EC0A1479E4ED178D87EF679', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'Recurrent Event 1646', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': False, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'tentative', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgBGAAADZ59RIxdyh0Kt%2FMXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAA%3D&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'allowNewTimeProposals': True, 'OccurrenceId': None, 'isDraft': False, 'hideAttendees': False, 'CalendarEventClassifications': [], 'id': microsoft_id, 'responseStatus': {'response': 'notResponded', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2021-07-15T15:00:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2021-07-15T15:30:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2021-07-15', 'endDate': '2021-07-17', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [{'type': 'required', 'status': {'response': 'none', 'time': '0001-01-01T00:00:00Z'}, 'emailAddress': {'name': 'Odoo02 Outlook02', 'address': 'odoo_bf_user02@outlook.com'}}, {'type': 'required', 'status': {'response': 'none', 'time': '0001-01-01T00:00:00Z'}, 'emailAddress': {'name': 'Odoo01 Outlook01', 'address': 'odoo_bf_user01@outlook.com'}}], 'organizer': {'emailAddress': {'name': 'Odoo02 Outlook02', 'address': 'odoo_bf_user02@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAACnKcpGeQLJSaeogtAdwtkRAAAESVxT"', 'seriesMasterId': microsoft_id, 'type': 'occurrence', 'id': 'AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgFRAAgIANlHI305wABGAAACZ59RIxdyh0Kt-MXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAAQ', 'start': {'dateTime': '2021-07-15T15:00:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2021-07-15T15:30:00.0000000', 'timeZone': 'UTC'}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"pynKRnkCyUmnqILQHcLZEQAABElcUw=="', 'createdDateTime': '2021-07-15T14:51:25.1366139Z', 'lastModifiedDateTime': '2021-07-15T14:51:25.136614Z', 'changeKey': 'pynKRnkCyUmnqILQHcLZEQAABElcUw==', 'categories': [], 'transactionId': None, 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00807E50710B35B3B5A8879D70100000000000000001000000008A0949F4EC0A1479E4ED178D87EF679', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'Canceled: Recurrent Event 1646', 'bodyPreview': '', 'importance': 'high', 'sensitivity': 'normal', 'originalStart': '2021-07-16T15:00:00Z', 'isAllDay': False, 'isCancelled': True, 'isOrganizer': False, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': microsoft_id, 'showAs': 'free', 'type': 'exception', 'webLink': 'https://outlook.live.com/owa/?itemid=AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgFRAAgIANlH7KejgABGAAACZ59RIxdyh0Kt%2FMXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAAQ&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'allowNewTimeProposals': True, 'OccurrenceId': ('OID.%s.2021-07-16' % microsoft_id), 'isDraft': False, 'hideAttendees': False, 'CalendarEventClassifications': [], 'id': 'AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgFRAAgIANlH7KejgABGAAACZ59RIxdyh0Kt-MXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAAQ', 'responseStatus': {'response': 'notResponded', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r\n<meta name="Generator" content="Microsoft Exchange Server">\r\n<!-- converted from text -->\r\n<style><!-- .EmailQuote { margin-left: 1pt; padding-left: 4pt; border-left: #800000 2px solid; } --></style></head>\r\n<body>\r\n<font size="2"><span style="font-size:11pt;"><div class="PlainText"> </div></span></font>\r\n</body>\r\n</html>\r\n'}, 'start': {'dateTime': '2021-07-16T15:00:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2021-07-16T15:30:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'attendees': [{'type': 'required', 'status': {'response': 'none', 'time': '0001-01-01T00:00:00Z'}, 'emailAddress': {'name': 'Odoo02 Outlook02', 'address': 'odoo_bf_user02@outlook.com'}}, {'type': 'required', 'status': {'response': 'none', 'time': '0001-01-01T00:00:00Z'}, 'emailAddress': {'name': 'Odoo01 Outlook01', 'address': 'odoo_bf_user01@outlook.com'}}], 'organizer': {'emailAddress': {'name': 'Odoo02 Outlook02', 'address': 'odoo_bf_user02@outlook.com'}}},
|
||||
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"DwAAABYAAACnKcpGeQLJSaeogtAdwtkRAAAESVxT"', 'seriesMasterId': microsoft_id, 'type': 'occurrence', 'id': 'AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgFRAAgIANlItdINQABGAAACZ59RIxdyh0Kt-MXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAAQ', 'start': {'dateTime': '2021-07-17T15:00:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2021-07-17T15:30:00.0000000', 'timeZone': 'UTC'}}
|
||||
]
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(first_sync_values))
|
||||
recurrent_event = self.env['calendar.recurrence'].search([('microsoft_id', '=', 'AQMkADAwATM3ZmYAZS0zZmMyLWYxYjQtMDACLTAwCgBGAAADZ59RIxdyh0Kt-MXfyCpfwAcApynKRnkCyUmnqILQHcLZEQAAAgENAAAApynKRnkCyUmnqILQHcLZEQAAAARKsSQAAAA=')])
|
||||
self.assertEqual(len(recurrent_event.calendar_event_ids), 3)
|
||||
|
||||
# Need to cheat on the write date, otherwise the second sync won't update the events
|
||||
recurrent_event.write_date = datetime(2021, 7, 15, 14, 00)
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(second_sync_values))
|
||||
self.assertEqual(len(recurrent_event.calendar_event_ids), 2)
|
||||
|
||||
events = recurrent_event.calendar_event_ids.sorted(key=lambda e: e.start)
|
||||
self.assertEqual(events[0].start, datetime(2021, 7, 15, 15, 00))
|
||||
self.assertEqual(events[0].stop, datetime(2021, 7, 15, 15, 30))
|
||||
self.assertEqual(events[1].start, datetime(2021, 7, 17, 15, 00))
|
||||
self.assertEqual(events[1].stop, datetime(2021, 7, 17, 15, 30))
|
||||
|
||||
def test_use_classic_location(self):
|
||||
ms_event = self.single_event
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(ms_event))
|
||||
|
||||
event = self.env['calendar.event'].search([("microsoft_id", "=", ms_event[0]["id"])])
|
||||
self.assertEqual(event.location, ms_event[0]["location"]["displayName"])
|
||||
|
||||
def test_use_url_location(self):
|
||||
ms_event = self.single_event
|
||||
ms_event[0]["location"]["displayName"] = "https://mylocation.com/meeting-room"
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(ms_event))
|
||||
|
||||
event = self.env['calendar.event'].search([("microsoft_id", "=", ms_event[0]["id"])])
|
||||
self.assertEqual(event.location, ms_event[0]["location"]["displayName"])
|
||||
|
||||
def test_use_specific_virtual_location(self):
|
||||
"""
|
||||
If the location of the Outlook event is a specific virtual location (such as a video Teams meeting),
|
||||
use it as videocall location.
|
||||
"""
|
||||
ms_event = self.single_event
|
||||
ms_event[0]["location"]["displayName"] = "https://teams.microsoft.com/l/meeting/1234"
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(ms_event))
|
||||
|
||||
event = self.env['calendar.event'].search([("microsoft_id", "=", ms_event[0]["id"])])
|
||||
self.assertEqual(event.location, False)
|
||||
self.assertEqual(event.videocall_location, ms_event[0]["location"]["displayName"])
|
||||
|
||||
def test_outlook_event_has_online_meeting_url(self):
|
||||
ms_event = self.single_event
|
||||
ms_event[0].update({
|
||||
'isOnlineMeeting': True,
|
||||
'onlineMeeting': {'joinUrl': 'https://video-meeting.com/1234'}
|
||||
})
|
||||
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(ms_event))
|
||||
|
||||
event = self.env['calendar.event'].search([("microsoft_id", "=", ms_event[0]["id"])])
|
||||
self.assertEqual(event.videocall_location, ms_event[0]["onlineMeeting"]["joinUrl"])
|
||||
|
||||
def test_event_reminder_emails_with_microsoft_id(self):
|
||||
"""
|
||||
Odoo shouldn't send email reminders for synced events.
|
||||
Test that events synced to Microsoft (with a `microsoft_id`)
|
||||
are excluded from email alarm notifications.
|
||||
"""
|
||||
now = datetime.now()
|
||||
start = now - relativedelta(minutes=30)
|
||||
end = now + relativedelta(hours=2)
|
||||
alarm = self.env['calendar.alarm'].create({
|
||||
'name': 'Alarm',
|
||||
'alarm_type': 'email',
|
||||
'interval': 'minutes',
|
||||
'duration': 30,
|
||||
})
|
||||
ms_event = self.single_event
|
||||
ms_event[0].update({
|
||||
'isOnlineMeeting': True,
|
||||
'alarm_id': alarm.id,
|
||||
'start': {
|
||||
'dateTime': pytz.utc.localize(start).isoformat(),
|
||||
'timeZone': 'Europe/Brussels'
|
||||
},
|
||||
'reminders': {'overrides': [{"method": "email", "minutes": 30}], 'useDefault': False},
|
||||
'end': {
|
||||
'dateTime': pytz.utc.localize(end).isoformat(),
|
||||
'timeZone': 'Europe/Brussels'
|
||||
},
|
||||
})
|
||||
self.env['calendar.event']._sync_microsoft2odoo(MicrosoftEvent(ms_event))
|
||||
events_by_alarm = self.env['calendar.alarm_manager']._get_events_by_alarm_to_notify('email')
|
||||
self.assertFalse(events_by_alarm, "Events with microsoft_id should not trigger reminders")
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import MicrosoftCalendarService
|
||||
from odoo.addons.microsoft_calendar.models.res_users import User
|
||||
from odoo.addons.microsoft_calendar.models.microsoft_sync import MicrosoftSync
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.addons.microsoft_account.models.microsoft_service import TIMEOUT
|
||||
|
||||
|
||||
def patch_api(func):
|
||||
@patch.object(MicrosoftSync, '_microsoft_insert', MagicMock())
|
||||
@patch.object(MicrosoftSync, '_microsoft_delete', MagicMock())
|
||||
@patch.object(MicrosoftSync, '_microsoft_patch', MagicMock())
|
||||
def patched(self, *args, **kwargs):
|
||||
return func(self, *args, **kwargs)
|
||||
return patched
|
||||
|
||||
@patch.object(User, '_get_microsoft_calendar_token', lambda user: 'dummy-token')
|
||||
class TestSyncOdoo2Microsoft(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.microsoft_service = MicrosoftCalendarService(self.env['microsoft.service'])
|
||||
|
||||
def assertMicrosoftEventInserted(self, values):
|
||||
MicrosoftSync._microsoft_insert.assert_called_once_with(self.microsoft_service, values)
|
||||
|
||||
def assertMicrosoftEventNotInserted(self):
|
||||
MicrosoftSync._microsoft_insert.assert_not_called()
|
||||
|
||||
def assertMicrosoftEventPatched(self, microsoft_id, values, timeout=None):
|
||||
expected_args = (microsoft_id, values)
|
||||
expected_kwargs = {'timeout': timeout} if timeout else {}
|
||||
MicrosoftSync._microsoft_patch.assert_called_once()
|
||||
args, kwargs = MicrosoftSync._microsoft_patch.call_args
|
||||
self.assertEqual(args[1:], expected_args) # skip Google service arg
|
||||
self.assertEqual(kwargs, expected_kwargs)
|
||||
|
||||
@patch_api
|
||||
def test_stop_synchronization(self):
|
||||
self.env.user.stop_microsoft_synchronization()
|
||||
self.assertTrue(self.env.user.microsoft_synchronization_stopped, "The microsoft synchronization flag should be switched on")
|
||||
self.assertFalse(self.env.user._sync_microsoft_calendar(self.microsoft_service), "The microsoft synchronization should be stopped")
|
||||
year = date.today().year - 1
|
||||
|
||||
# If synchronization stopped, creating a new event should not call _google_insert.
|
||||
self.env['calendar.event'].create({
|
||||
'name': "Event",
|
||||
'start': datetime(year, 1, 15, 8, 0),
|
||||
'stop': datetime(year, 1, 15, 18, 0),
|
||||
'privacy': 'private',
|
||||
})
|
||||
self.assertMicrosoftEventNotInserted()
|
||||
|
||||
@patch_api
|
||||
def test_restart_synchronization(self):
|
||||
# Test new event created after stopping synchronization are correctly patched when restarting sync.
|
||||
self.maxDiff = None
|
||||
microsoft_id = 'aaaaaaaaa'
|
||||
year = date.today().year
|
||||
partner = self.env['res.partner'].create({'name': 'Jean-Luc', 'email': 'jean-luc@opoo.com'})
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'Test user Calendar',
|
||||
'login': 'jean-luc@opoo.com',
|
||||
'partner_id': partner.id,
|
||||
})
|
||||
user.stop_microsoft_synchronization()
|
||||
# In case of full sync, limit to a range of 1y in past and 1y in the future by default
|
||||
event = self.env['calendar.event'].with_user(user).create({
|
||||
'microsoft_id': microsoft_id,
|
||||
'name': "Event",
|
||||
'start': datetime(year, 1, 15, 8, 0),
|
||||
'stop': datetime(year, 1, 15, 18, 0),
|
||||
'partner_ids': [(4, partner.id)],
|
||||
})
|
||||
|
||||
user.with_user(user).restart_microsoft_synchronization()
|
||||
event.with_user(user)._sync_odoo2microsoft(self.microsoft_service)
|
||||
microsoft_guid = self.env['ir.config_parameter'].sudo().get_param('microsoft_calendar.microsoft_guid', False)
|
||||
self.assertMicrosoftEventPatched(event.microsoft_id, {
|
||||
'id': event.microsoft_id,
|
||||
'start': {'dateTime': '%s-01-15T08:00:00+00:00' % year, 'timeZone': 'Europe/London'},
|
||||
'end': {'dateTime': '%s-01-15T18:00:00+00:00' % year, 'timeZone': 'Europe/London'},
|
||||
'subject': 'Event',
|
||||
'body': {'content': '', 'contentType': 'html'},
|
||||
'attendees': [],
|
||||
'isAllDay': False,
|
||||
'isOrganizer': True,
|
||||
'isReminderOn': False,
|
||||
'sensitivity': 'normal',
|
||||
'showAs': 'busy',
|
||||
'location': {'displayName': ''},
|
||||
'organizer': {'emailAddress': {'address': 'jean-luc@opoo.com', 'name': 'Test user Calendar'}},
|
||||
'reminderMinutesBeforeStart': 0,
|
||||
'singleValueExtendedProperties': [{
|
||||
'id': 'String {%s} Name odoo_id' % microsoft_guid,
|
||||
'value': str(event.id),
|
||||
}, {
|
||||
'id': 'String {%s} Name owner_odoo_id' % microsoft_guid,
|
||||
'value': str(user.id),
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
from datetime import datetime
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.mail.tests.common import MailCase
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import MicrosoftCalendarService
|
||||
from odoo.addons.microsoft_calendar.models.res_users import ResUsers
|
||||
from odoo.addons.microsoft_calendar.tests.common import TestCommon
|
||||
|
||||
|
||||
class TestSyncOdoo2MicrosoftMail(TestCommon, MailCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.users = []
|
||||
for n in range(1, 4):
|
||||
user = cls.env['res.users'].create({
|
||||
'name': f'user{n}',
|
||||
'login': f'user{n}',
|
||||
'email': f'user{n}@odoo.com',
|
||||
'microsoft_calendar_rtoken': f'abc{n}',
|
||||
'microsoft_calendar_token': f'abc{n}',
|
||||
'microsoft_calendar_token_validity': datetime(9999, 12, 31),
|
||||
})
|
||||
user.res_users_settings_id.write({
|
||||
'microsoft_synchronization_stopped': False,
|
||||
'microsoft_calendar_sync_token': f'{n}_sync_token',
|
||||
})
|
||||
cls.users += [user]
|
||||
|
||||
@freeze_time("2020-01-01")
|
||||
@patch.object(ResUsers, '_get_microsoft_calendar_token', lambda user: user.microsoft_calendar_token)
|
||||
def test_event_creation_for_user(self):
|
||||
"""Check that either emails or synchronization happens correctly when creating an event for another user."""
|
||||
user_root = self.env.ref('base.user_root')
|
||||
self.assertFalse(user_root.microsoft_calendar_token)
|
||||
partner = self.env['res.partner'].create({'name': 'Jean-Luc', 'email': 'jean-luc@opoo.com'})
|
||||
event_values = {
|
||||
'name': 'Event',
|
||||
'need_sync_m': True,
|
||||
'start': datetime(2020, 1, 15, 8, 0),
|
||||
'stop': datetime(2020, 1, 15, 18, 0),
|
||||
}
|
||||
paused_sync_user = self.users[2]
|
||||
paused_sync_user.write({
|
||||
'email': 'ms.sync.paused@test.lan',
|
||||
'microsoft_synchronization_stopped': True,
|
||||
'name': 'Paused Microsoft Sync User',
|
||||
'login': 'ms_sync_paused_user',
|
||||
})
|
||||
self.assertTrue(paused_sync_user.microsoft_synchronization_stopped)
|
||||
for create_user, organizer, mail_notified_partners, attendee in [
|
||||
(user_root, self.users[0], partner + self.users[0].partner_id, partner), # emulates online appointment with user 0
|
||||
(user_root, None, partner, partner), # emulates online resource appointment
|
||||
(self.users[0], None, False, partner),
|
||||
(self.users[0], self.users[0], False, partner),
|
||||
(self.users[0], self.users[1], False, partner),
|
||||
# create user has paused sync and organizer can sync -> will not sync because of bug
|
||||
# only the organizer is notified as we don't notify the author (= create_user.partner_id) on creation
|
||||
(paused_sync_user, self.users[0], self.users[0].partner_id, paused_sync_user.partner_id),
|
||||
]:
|
||||
with self.subTest(create_uid=create_user.name if create_user else None, user_id=organizer.name if organizer else None, attendee=attendee.name):
|
||||
with self.mock_mail_gateway(), patch.object(MicrosoftCalendarService, 'insert') as mock_insert:
|
||||
mock_insert.return_value = ('1', '1')
|
||||
self.env['calendar.event'].with_user(create_user).create({
|
||||
**event_values,
|
||||
'partner_ids': [(4, organizer.partner_id.id), (4, attendee.id)] if organizer else [(4, attendee.id)],
|
||||
'user_id': organizer.id if organizer else False,
|
||||
})
|
||||
self.env.cr.postcommit.run()
|
||||
if not mail_notified_partners:
|
||||
self.assertNotSentEmail()
|
||||
mock_insert.assert_called_once()
|
||||
self.assert_dict_equal(mock_insert.call_args[0][0]['organizer'], {
|
||||
'emailAddress': {'address': organizer.email if organizer else '', 'name': organizer.name if organizer else ''}
|
||||
})
|
||||
else:
|
||||
mock_insert.assert_not_called()
|
||||
for notified_partner in mail_notified_partners:
|
||||
self.assertMailMail(notified_partner, 'sent', author=(organizer or create_user).partner_id)
|
||||
|
||||
def test_change_organizer_pure_odoo_event(self):
|
||||
"""
|
||||
Test that changing organizer on a pure Odoo event (not synced with Microsoft)
|
||||
does not archive the event.
|
||||
"""
|
||||
self.organizer_user.microsoft_synchronization_stopped = True
|
||||
event = self.env["calendar.event"].with_user(self.organizer_user).create({
|
||||
'name': "Pure Odoo Event",
|
||||
'start': datetime(2024, 1, 1, 10, 0),
|
||||
'stop': datetime(2024, 1, 1, 11, 0),
|
||||
'user_id': self.organizer_user.id,
|
||||
'partner_ids': [Command.set([self.organizer_user.partner_id.id, self.attendee_user.partner_id.id])],
|
||||
})
|
||||
|
||||
self.assertFalse(event.microsoft_id)
|
||||
self.assertTrue(event.active)
|
||||
|
||||
event.write({
|
||||
'user_id': self.attendee_user.id,
|
||||
})
|
||||
|
||||
self.assertTrue(event.active, "Pure Odoo event should not be archived when changing organizer")
|
||||
self.assertEqual(event.user_id, self.attendee_user, "Organizer should be updated")
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.parser import parse
|
||||
import logging
|
||||
|
|
@ -8,17 +9,17 @@ from freezegun import freeze_time
|
|||
|
||||
from odoo import Command
|
||||
|
||||
from odoo.addons.microsoft_calendar.models.microsoft_sync import MicrosoftSync
|
||||
from odoo.addons.microsoft_calendar.models.microsoft_sync import MicrosoftCalendarSync
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import MicrosoftCalendarService
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_event import MicrosoftEvent
|
||||
from odoo.addons.microsoft_calendar.models.res_users import User
|
||||
from odoo.addons.microsoft_calendar.utils.event_id_storage import combine_ids
|
||||
from odoo.addons.microsoft_calendar.models.res_users import ResUsers
|
||||
from odoo.addons.microsoft_calendar.tests.common import TestCommon, mock_get_token, _modified_date_in_the_future, patch_api
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@patch.object(User, '_get_microsoft_calendar_token', mock_get_token)
|
||||
|
||||
@patch.object(ResUsers, '_get_microsoft_calendar_token', mock_get_token)
|
||||
class TestUpdateEvents(TestCommon):
|
||||
|
||||
@patch_api
|
||||
|
|
@ -68,8 +69,8 @@ class TestUpdateEvents(TestCommon):
|
|||
# assert
|
||||
self.assertTrue(res)
|
||||
mock_patch.assert_called_once_with(
|
||||
self.simple_event.ms_organizer_event_id,
|
||||
{"subject": "my new simple event"},
|
||||
self.simple_event.microsoft_id,
|
||||
{"subject": "my new simple event", "isOnlineMeeting": False},
|
||||
token=mock_get_token(self.organizer_user),
|
||||
timeout=ANY,
|
||||
)
|
||||
|
|
@ -92,8 +93,8 @@ class TestUpdateEvents(TestCommon):
|
|||
# assert
|
||||
self.assertTrue(res)
|
||||
mock_patch.assert_called_once_with(
|
||||
self.simple_event.ms_organizer_event_id,
|
||||
{"subject": "my new simple event"},
|
||||
self.simple_event.microsoft_id,
|
||||
{"subject": "my new simple event", "isOnlineMeeting": False},
|
||||
token=mock_get_token(self.organizer_user),
|
||||
timeout=ANY,
|
||||
)
|
||||
|
|
@ -123,7 +124,7 @@ class TestUpdateEvents(TestCommon):
|
|||
# assert
|
||||
self.assertTrue(res)
|
||||
mock_patch.assert_called_once_with(
|
||||
self.recurrent_events[modified_event_id].ms_organizer_event_id,
|
||||
self.recurrent_events[modified_event_id].microsoft_id,
|
||||
{'seriesMasterId': 'REC123', 'type': 'exception', "subject": new_name},
|
||||
token=mock_get_token(self.organizer_user),
|
||||
timeout=ANY,
|
||||
|
|
@ -157,7 +158,7 @@ class TestUpdateEvents(TestCommon):
|
|||
# assert
|
||||
self.assertTrue(res)
|
||||
mock_patch.assert_called_once_with(
|
||||
self.recurrent_events[modified_event_id].ms_organizer_event_id,
|
||||
self.recurrent_events[modified_event_id].microsoft_id,
|
||||
{
|
||||
'seriesMasterId': 'REC123',
|
||||
'type': 'exception',
|
||||
|
|
@ -228,7 +229,7 @@ class TestUpdateEvents(TestCommon):
|
|||
# assert
|
||||
self.assertTrue(res)
|
||||
mock_patch.assert_called_once_with(
|
||||
self.recurrent_events[modified_event_id].ms_organizer_event_id,
|
||||
self.recurrent_events[modified_event_id].microsoft_id,
|
||||
{'seriesMasterId': 'REC123', 'type': 'exception', "subject": new_name},
|
||||
token=mock_get_token(self.organizer_user),
|
||||
timeout=ANY,
|
||||
|
|
@ -266,7 +267,7 @@ class TestUpdateEvents(TestCommon):
|
|||
self.assertEqual(mock_patch.call_count, self.recurrent_events_count - modified_event_id)
|
||||
for i in range(modified_event_id, self.recurrent_events_count):
|
||||
mock_patch.assert_any_call(
|
||||
self.recurrent_events[i].ms_organizer_event_id,
|
||||
self.recurrent_events[i].microsoft_id,
|
||||
{'seriesMasterId': 'REC123', 'type': 'exception', "subject": new_name},
|
||||
token=mock_get_token(self.organizer_user),
|
||||
timeout=ANY,
|
||||
|
|
@ -304,7 +305,7 @@ class TestUpdateEvents(TestCommon):
|
|||
existing_recurrences = self.env["calendar.recurrence"].search([])
|
||||
|
||||
expected_deleted_event_ids = [
|
||||
r.ms_organizer_event_id
|
||||
r.microsoft_id
|
||||
for i, r in enumerate(self.recurrent_events)
|
||||
if i in range(modified_event_id + 1, self.recurrent_events_count)
|
||||
]
|
||||
|
|
@ -337,7 +338,7 @@ class TestUpdateEvents(TestCommon):
|
|||
|
||||
# the base event should have been modified
|
||||
mock_patch.assert_called_once_with(
|
||||
self.recurrent_events[modified_event_id].ms_organizer_event_id,
|
||||
self.recurrent_events[modified_event_id].microsoft_id,
|
||||
{
|
||||
'seriesMasterId': 'REC123',
|
||||
'type': 'exception',
|
||||
|
|
@ -373,14 +374,14 @@ class TestUpdateEvents(TestCommon):
|
|||
existing_recurrences = self.env["calendar.recurrence"].search([])
|
||||
|
||||
expected_deleted_event_ids = [
|
||||
r.ms_organizer_event_id
|
||||
r.microsoft_id
|
||||
for i, r in enumerate(self.recurrent_events)
|
||||
if i in range(modified_event_id + 1, self.recurrent_events_count)
|
||||
]
|
||||
|
||||
# as the test overlap the previous event of the updated event, this previous event
|
||||
# should be removed too
|
||||
expected_deleted_event_ids += [self.recurrent_events[modified_event_id - 1].ms_organizer_event_id]
|
||||
expected_deleted_event_ids += [self.recurrent_events[modified_event_id - 1].microsoft_id]
|
||||
|
||||
# act
|
||||
res = self.recurrent_events[modified_event_id].with_user(self.organizer_user).write({
|
||||
|
|
@ -410,7 +411,7 @@ class TestUpdateEvents(TestCommon):
|
|||
|
||||
# the base event should have been modified
|
||||
mock_patch.assert_called_once_with(
|
||||
self.recurrent_events[modified_event_id].ms_organizer_event_id,
|
||||
self.recurrent_events[modified_event_id].microsoft_id,
|
||||
{
|
||||
'seriesMasterId': 'REC123',
|
||||
'type': 'exception',
|
||||
|
|
@ -445,7 +446,7 @@ class TestUpdateEvents(TestCommon):
|
|||
existing_recurrences = self.env["calendar.recurrence"].search([])
|
||||
|
||||
expected_deleted_event_ids = [
|
||||
r.ms_organizer_event_id
|
||||
r.microsoft_id
|
||||
for i, r in enumerate(self.recurrent_events)
|
||||
if i in range(modified_event_id + 1, self.recurrent_events_count)
|
||||
]
|
||||
|
|
@ -478,7 +479,7 @@ class TestUpdateEvents(TestCommon):
|
|||
|
||||
# the base event should have been modified
|
||||
mock_patch.assert_called_once_with(
|
||||
self.recurrent_events[modified_event_id].ms_organizer_event_id,
|
||||
self.recurrent_events[modified_event_id].microsoft_id,
|
||||
{
|
||||
'seriesMasterId': 'REC123',
|
||||
'type': 'exception',
|
||||
|
|
@ -525,7 +526,7 @@ class TestUpdateEvents(TestCommon):
|
|||
self.assertEqual(mock_patch.call_count, self.recurrent_events_count)
|
||||
for i in range(self.recurrent_events_count):
|
||||
mock_patch.assert_any_call(
|
||||
self.recurrent_events[i].ms_organizer_event_id,
|
||||
self.recurrent_events[i].microsoft_id,
|
||||
{'seriesMasterId': 'REC123', 'type': 'exception', "subject": new_name},
|
||||
token=mock_get_token(self.organizer_user),
|
||||
timeout=ANY,
|
||||
|
|
@ -548,7 +549,7 @@ class TestUpdateEvents(TestCommon):
|
|||
new_date = datetime(2021, 9, 25, 10, 0, 0)
|
||||
existing_recurrences = self.env["calendar.recurrence"].search([])
|
||||
expected_deleted_event_ids = [
|
||||
r.ms_organizer_event_id
|
||||
r.microsoft_id
|
||||
for i, r in enumerate(self.recurrent_events)
|
||||
if i in range(1, self.recurrent_events_count)
|
||||
]
|
||||
|
|
@ -571,7 +572,7 @@ class TestUpdateEvents(TestCommon):
|
|||
self.assertEqual(len(new_recurrences.calendar_event_ids), self.recurrent_events_count)
|
||||
|
||||
mock_patch.assert_called_once_with(
|
||||
self.recurrent_events[0].ms_organizer_event_id,
|
||||
self.recurrent_events[0].microsoft_id,
|
||||
{
|
||||
'seriesMasterId': 'REC123',
|
||||
'type': 'exception',
|
||||
|
|
@ -612,7 +613,7 @@ class TestUpdateEvents(TestCommon):
|
|||
new_date = datetime(2021, 9, 25, 10, 0, 0)
|
||||
existing_recurrences = self.env["calendar.recurrence"].search([])
|
||||
expected_deleted_event_ids = [
|
||||
r.ms_organizer_event_id
|
||||
r.microsoft_id
|
||||
for i, r in enumerate(self.recurrent_events)
|
||||
if i in range(1, self.recurrent_events_count)
|
||||
]
|
||||
|
|
@ -635,7 +636,7 @@ class TestUpdateEvents(TestCommon):
|
|||
self.assertEqual(len(new_recurrences.calendar_event_ids), self.recurrent_events_count)
|
||||
|
||||
mock_patch.assert_called_once_with(
|
||||
self.recurrent_events[0].ms_organizer_event_id,
|
||||
self.recurrent_events[0].microsoft_id,
|
||||
{
|
||||
'seriesMasterId': 'REC123',
|
||||
'type': 'exception',
|
||||
|
|
@ -739,7 +740,7 @@ class TestUpdateEvents(TestCommon):
|
|||
self.organizer_user.with_user(self.organizer_user).sudo()._sync_microsoft_calendar()
|
||||
|
||||
# assert
|
||||
updated_event = self.env["calendar.event"].search([('ms_organizer_event_id', '=', ms_event_id)])
|
||||
updated_event = self.env["calendar.event"].search([('microsoft_id', '=', ms_event_id)])
|
||||
self.assertEqual(updated_event.name, new_name)
|
||||
self.assertEqual(updated_event.follow_recurrence, False)
|
||||
|
||||
|
|
@ -767,7 +768,7 @@ class TestUpdateEvents(TestCommon):
|
|||
self.organizer_user.with_user(self.organizer_user).sudo()._sync_microsoft_calendar()
|
||||
|
||||
# assert
|
||||
updated_event = self.env["calendar.event"].search([('ms_organizer_event_id', '=', ms_event_id)])
|
||||
updated_event = self.env["calendar.event"].search([('microsoft_id', '=', ms_event_id)])
|
||||
self.assertEqual(updated_event.start, new_date)
|
||||
self.assertEqual(updated_event.follow_recurrence, False)
|
||||
|
||||
|
|
@ -797,7 +798,7 @@ class TestUpdateEvents(TestCommon):
|
|||
self.organizer_user.with_user(self.organizer_user).sudo()._sync_microsoft_calendar()
|
||||
|
||||
# assert
|
||||
updated_event = self.env["calendar.event"].search([('ms_organizer_event_id', '=', ms_event_id)])
|
||||
updated_event = self.env["calendar.event"].search([('microsoft_id', '=', ms_event_id)])
|
||||
self.assertEqual(updated_event.start, new_date)
|
||||
self.assertEqual(updated_event.follow_recurrence, False)
|
||||
|
||||
|
|
@ -829,10 +830,10 @@ class TestUpdateEvents(TestCommon):
|
|||
|
||||
# assert
|
||||
updated_events = self.env["calendar.event"].search([
|
||||
('ms_organizer_event_id', 'in', tuple(ms_event_ids.keys()))
|
||||
('microsoft_id', 'in', tuple(ms_event_ids.keys()))
|
||||
])
|
||||
for e in updated_events:
|
||||
self.assertEqual(e.name, ms_event_ids[e.ms_organizer_event_id])
|
||||
self.assertEqual(e.name, ms_event_ids[e.microsoft_id])
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
def test_update_start_of_one_event_and_future_of_recurrence_from_outlook_organizer_calendar(self, mock_get_events):
|
||||
|
|
@ -941,14 +942,15 @@ class TestUpdateEvents(TestCommon):
|
|||
# new recurrence
|
||||
self.assertEqual(len(new_recurrences), 1)
|
||||
self.assertEqual(len(new_events), new_recurrence_event_count)
|
||||
self.assertEqual(new_recurrences.ms_organizer_event_id, "REC123_new")
|
||||
self.assertEqual(new_recurrences.microsoft_id, "REC123_new")
|
||||
self.assertEqual(new_recurrences.ms_universal_event_id, "REC456_new")
|
||||
|
||||
for i, e in enumerate(sorted(new_events, key=lambda e: e.id)):
|
||||
self.assert_odoo_event(e, {
|
||||
"start": new_rec_first_event_start_date + timedelta(days=i * self.recurrent_event_interval),
|
||||
"stop": new_rec_first_event_end_date + timedelta(days=i * self.recurrent_event_interval),
|
||||
"microsoft_id": combine_ids(f'REC123_new_{i+1}', f'REC456_new_{i+1}'),
|
||||
"microsoft_id": f'REC123_new_{i+1}',
|
||||
"ms_universal_event_id": f'REC456_new_{i+1}',
|
||||
"recurrence_id": new_recurrences,
|
||||
"follow_recurrence": True,
|
||||
})
|
||||
|
|
@ -1060,14 +1062,15 @@ class TestUpdateEvents(TestCommon):
|
|||
# new recurrence
|
||||
self.assertEqual(len(new_recurrences), 1)
|
||||
self.assertEqual(len(new_events), new_recurrence_event_count)
|
||||
self.assertEqual(new_recurrences.ms_organizer_event_id, "REC123_new")
|
||||
self.assertEqual(new_recurrences.microsoft_id, "REC123_new")
|
||||
self.assertEqual(new_recurrences.ms_universal_event_id, "REC456_new")
|
||||
|
||||
for i, e in enumerate(sorted(new_events, key=lambda e: e.id)):
|
||||
self.assert_odoo_event(e, {
|
||||
"start": new_rec_first_event_start_date + timedelta(days=i * self.recurrent_event_interval),
|
||||
"stop": new_rec_first_event_end_date + timedelta(days=i * self.recurrent_event_interval),
|
||||
"microsoft_id": combine_ids(f'REC123_new_{i+1}', f'REC456_new_{i+1}'),
|
||||
"microsoft_id": f"REC123_new_{i+1}",
|
||||
"ms_universal_event_id": f"REC456_new_{i+1}",
|
||||
"recurrence_id": new_recurrences,
|
||||
"follow_recurrence": True,
|
||||
})
|
||||
|
|
@ -1098,10 +1101,10 @@ class TestUpdateEvents(TestCommon):
|
|||
|
||||
# assert
|
||||
updated_events = self.env["calendar.event"].search([
|
||||
('ms_organizer_event_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
('microsoft_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
])
|
||||
for e in updated_events:
|
||||
self.assertEqual(e.name, ms_events_to_update[e.ms_organizer_event_id])
|
||||
self.assertEqual(e.name, ms_events_to_update[e.microsoft_id])
|
||||
self.assertEqual(e.follow_recurrence, True)
|
||||
|
||||
def _prepare_outlook_events_for_all_events_start_date_update(self, nb_of_events):
|
||||
|
|
@ -1182,12 +1185,12 @@ class TestUpdateEvents(TestCommon):
|
|||
# ----------- ASSERT -----------
|
||||
|
||||
updated_events = self.env["calendar.event"].search([
|
||||
('ms_organizer_event_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
('microsoft_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
])
|
||||
for e in updated_events:
|
||||
self.assertEqual(
|
||||
e.start.strftime("%Y-%m-%dT%H:%M:%S.0000000"),
|
||||
ms_events_to_update[e.ms_organizer_event_id]["dateTime"]
|
||||
ms_events_to_update[e.microsoft_id]["dateTime"]
|
||||
)
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
|
|
@ -1210,14 +1213,13 @@ class TestUpdateEvents(TestCommon):
|
|||
self.organizer_user.with_user(self.organizer_user).sudo()._sync_microsoft_calendar()
|
||||
|
||||
# ----------- ASSERT -----------
|
||||
|
||||
updated_events = self.env["calendar.event"].search([
|
||||
('ms_organizer_event_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
('microsoft_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
])
|
||||
for e in updated_events:
|
||||
self.assertEqual(
|
||||
e.start.strftime("%Y-%m-%dT%H:%M:%S.0000000"),
|
||||
ms_events_to_update[e.ms_organizer_event_id]["dateTime"]
|
||||
ms_events_to_update[e.microsoft_id]["dateTime"]
|
||||
)
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
|
|
@ -1242,12 +1244,12 @@ class TestUpdateEvents(TestCommon):
|
|||
# ----------- ASSERT -----------
|
||||
|
||||
updated_events = self.env["calendar.event"].search([
|
||||
('ms_organizer_event_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
('microsoft_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
])
|
||||
for e in updated_events:
|
||||
self.assertEqual(
|
||||
e.start.strftime("%Y-%m-%dT%H:%M:%S.0000000"),
|
||||
ms_events_to_update[e.ms_organizer_event_id]["dateTime"]
|
||||
ms_events_to_update[e.microsoft_id]["dateTime"]
|
||||
)
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
|
|
@ -1287,12 +1289,12 @@ class TestUpdateEvents(TestCommon):
|
|||
# ----------- ASSERT -----------
|
||||
|
||||
updated_events = self.env["calendar.event"].search([
|
||||
('ms_organizer_event_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
('microsoft_id', 'in', tuple(ms_events_to_update.keys()))
|
||||
])
|
||||
for e in updated_events:
|
||||
self.assertEqual(
|
||||
e.start.strftime("%Y-%m-%dT%H:%M:%S.0000000"),
|
||||
ms_events_to_update[e.ms_organizer_event_id]["dateTime"]
|
||||
ms_events_to_update[e.microsoft_id]["dateTime"]
|
||||
)
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'patch')
|
||||
|
|
@ -1326,6 +1328,27 @@ class TestUpdateEvents(TestCommon):
|
|||
# Assert that no patch call was made due to the recurrence update forbiddance.
|
||||
mock_patch.assert_not_called()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'patch')
|
||||
def test_update_synced_event_with_sync_config_paused(self, mock_patch):
|
||||
"""
|
||||
Updates an event with the synchronization paused, the event must have its field 'need_sync_m' as True
|
||||
for later synchronizing it with Outlook Calendar.
|
||||
"""
|
||||
# Set user synchronization configuration as active and pause it.
|
||||
self.organizer_user.microsoft_synchronization_stopped = False
|
||||
self.organizer_user.pause_microsoft_synchronization()
|
||||
|
||||
# Try to update a simple event in Odoo Calendar.
|
||||
self.simple_event.with_user(self.organizer_user).write({"name": "updated simple event"})
|
||||
self.call_post_commit_hooks()
|
||||
self.simple_event.invalidate_recordset()
|
||||
|
||||
# Ensure that synchronization is paused, delete wasn't called and record is waiting to be synced again.
|
||||
self.assertFalse(self.organizer_user.microsoft_synchronization_stopped)
|
||||
self.assertEqual(self.organizer_user._get_microsoft_sync_status(), "sync_paused")
|
||||
self.assertTrue(self.simple_event.need_sync_m, "Sync variable must be true for updating event when sync re-activates")
|
||||
mock_patch.assert_not_called()
|
||||
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
@patch.object(MicrosoftCalendarService, 'delete')
|
||||
@patch.object(MicrosoftCalendarService, 'insert')
|
||||
|
|
@ -1340,6 +1363,9 @@ class TestUpdateEvents(TestCommon):
|
|||
self.simple_event_values['user_id'] = self.organizer_user.id
|
||||
self.simple_event_values['partner_ids'] = [Command.set([self.organizer_user.partner_id.id])]
|
||||
event = self.env['calendar.event'].with_user(self.organizer_user).create(self.simple_event_values)
|
||||
# Simulate sync where the api update the microsoft_id field
|
||||
event.ms_universal_event_id = "test_id_for_event"
|
||||
event.microsoft_id = "test_id_for_organizer"
|
||||
|
||||
# Deactivate user B's calendar synchronization. Try changing the event organizer to user B.
|
||||
# A ValidationError must be thrown because user B's calendar is not synced.
|
||||
|
|
@ -1360,8 +1386,6 @@ class TestUpdateEvents(TestCommon):
|
|||
mock_get_events.return_value = ([], None)
|
||||
|
||||
# Change the event organizer: user B (the organizer) is synced and now listed as an attendee.
|
||||
event.ms_universal_event_id = "test_id_for_event"
|
||||
event.ms_organizer_event_id = "test_id_for_organizer"
|
||||
event.with_user(self.organizer_user).write({
|
||||
'user_id': self.attendee_user.id,
|
||||
'partner_ids': [Command.set([self.organizer_user.partner_id.id, self.attendee_user.partner_id.id])]
|
||||
|
|
@ -1372,7 +1396,7 @@ class TestUpdateEvents(TestCommon):
|
|||
|
||||
# Ensure that the event was deleted and recreated with the new organizer and the organizer listed as attendee.
|
||||
mock_delete.assert_any_call(
|
||||
event.ms_organizer_event_id,
|
||||
event.microsoft_id,
|
||||
token=mock_get_token(self.attendee_user),
|
||||
timeout=ANY,
|
||||
)
|
||||
|
|
@ -1388,12 +1412,16 @@ class TestUpdateEvents(TestCommon):
|
|||
""" Ensure that sync restart is not blocked when there are recurrence outliers in Odoo database. """
|
||||
# Stop synchronization, set recurrent events as outliers and restart sync with Outlook.
|
||||
self.organizer_user.stop_microsoft_synchronization()
|
||||
self.recurrent_events.with_user(self.organizer_user).write({'microsoft_id': False, 'follow_recurrence': False})
|
||||
self.recurrent_events.with_user(self.organizer_user).write({
|
||||
'microsoft_id': False,
|
||||
'ms_universal_event_id': False,
|
||||
'follow_recurrence': False
|
||||
})
|
||||
self.attendee_user.with_user(self.attendee_user).restart_microsoft_synchronization()
|
||||
self.organizer_user.with_user(self.organizer_user).restart_microsoft_synchronization()
|
||||
self.assertTrue(all(ev.need_sync_m for ev in self.recurrent_events))
|
||||
|
||||
@patch.object(MicrosoftSync, '_write_from_microsoft')
|
||||
@patch.object(MicrosoftCalendarSync, '_write_from_microsoft')
|
||||
@patch.object(MicrosoftCalendarService, 'get_events')
|
||||
def test_update_old_event_synced_with_outlook(self, mock_get_events, mock_write_from_microsoft):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue