409 lines
15 KiB
Python
409 lines
15 KiB
Python
|
from datetime import datetime
|
||
|
from dateutil.relativedelta import relativedelta
|
||
|
from pytz import UTC
|
||
|
|
||
|
from odoo.addons.microsoft_calendar.utils.microsoft_event import MicrosoftEvent
|
||
|
from odoo.addons.microsoft_calendar.tests.common import TestCommon, patch_api
|
||
|
|
||
|
class TestMicrosoftEvent(TestCommon):
|
||
|
|
||
|
@patch_api
|
||
|
def setUp(self):
|
||
|
super().setUp()
|
||
|
self.create_events_for_tests()
|
||
|
|
||
|
def test_already_mapped_events(self):
|
||
|
|
||
|
# arrange
|
||
|
event_id = self.simple_event.ms_organizer_event_id
|
||
|
event_uid = self.simple_event.ms_universal_event_id
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "singleInstance",
|
||
|
"_odoo_id": self.simple_event.id,
|
||
|
"iCalUId": event_uid,
|
||
|
"id": event_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
mapped = events._load_odoo_ids_from_db(self.env)
|
||
|
|
||
|
# assert
|
||
|
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):
|
||
|
|
||
|
# arrange
|
||
|
event_id = self.simple_event.ms_organizer_event_id
|
||
|
event_uid = self.simple_event.ms_universal_event_id
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "singleInstance",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": event_uid,
|
||
|
"id": event_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
mapped = events._load_odoo_ids_from_db(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(mapped._events), 1)
|
||
|
self.assertEqual(mapped._events[event_id]["_odoo_id"], self.simple_event.id)
|
||
|
|
||
|
def test_map_an_event_using_instance_id(self):
|
||
|
"""
|
||
|
Here, the Odoo event has an uid but the Outlook event has not.
|
||
|
"""
|
||
|
# arrange
|
||
|
event_id = self.simple_event.ms_organizer_event_id
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "singleInstance",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": False,
|
||
|
"id": event_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
mapped = events._load_odoo_ids_from_db(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(mapped._events), 1)
|
||
|
self.assertEqual(mapped._events[event_id]["_odoo_id"], self.simple_event.id)
|
||
|
|
||
|
def test_map_an_event_without_uid_using_instance_id(self):
|
||
|
"""
|
||
|
Here, the Odoo event has no uid but the Outlook event has one.
|
||
|
"""
|
||
|
|
||
|
# arrange
|
||
|
event_id = self.simple_event.ms_organizer_event_id
|
||
|
event_uid = self.simple_event.ms_universal_event_id
|
||
|
self.simple_event.ms_universal_event_id = False
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "singleInstance",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": event_uid,
|
||
|
"id": event_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
mapped = events._load_odoo_ids_from_db(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(mapped._events), 1)
|
||
|
self.assertEqual(mapped._events[event_id]["_odoo_id"], self.simple_event.id)
|
||
|
self.assertEqual(self.simple_event.ms_universal_event_id, event_uid)
|
||
|
|
||
|
def test_map_an_event_without_uid_using_instance_id_2(self):
|
||
|
"""
|
||
|
Here, both Odoo event and Outlook event have no uid.
|
||
|
"""
|
||
|
|
||
|
# arrange
|
||
|
event_id = self.simple_event.ms_organizer_event_id
|
||
|
self.simple_event.ms_universal_event_id = False
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "singleInstance",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": False,
|
||
|
"id": event_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
mapped = events._load_odoo_ids_from_db(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(mapped._events), 1)
|
||
|
self.assertEqual(mapped._events[event_id]["_odoo_id"], self.simple_event.id)
|
||
|
self.assertEqual(self.simple_event.ms_universal_event_id, False)
|
||
|
|
||
|
def test_map_a_recurrence_using_global_id(self):
|
||
|
|
||
|
# arrange
|
||
|
rec_id = self.recurrence.ms_organizer_event_id
|
||
|
rec_uid = self.recurrence.ms_universal_event_id
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "seriesMaster",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": rec_uid,
|
||
|
"id": rec_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
mapped = events._load_odoo_ids_from_db(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(mapped._events), 1)
|
||
|
self.assertEqual(mapped._events[rec_id]["_odoo_id"], self.recurrence.id)
|
||
|
|
||
|
def test_map_a_recurrence_using_instance_id(self):
|
||
|
|
||
|
# arrange
|
||
|
rec_id = self.recurrence.ms_organizer_event_id
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "seriesMaster",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": False,
|
||
|
"id": rec_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
mapped = events._load_odoo_ids_from_db(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(mapped._events), 1)
|
||
|
self.assertEqual(mapped._events[rec_id]["_odoo_id"], self.recurrence.id)
|
||
|
|
||
|
def test_try_to_map_mixed_of_single_events_and_recurrences(self):
|
||
|
|
||
|
# arrange
|
||
|
event_id = self.simple_event.ms_organizer_event_id
|
||
|
event_uid = self.simple_event.ms_universal_event_id
|
||
|
rec_id = self.recurrence.ms_organizer_event_id
|
||
|
rec_uid = self.recurrence.ms_universal_event_id
|
||
|
|
||
|
events = MicrosoftEvent([
|
||
|
{
|
||
|
"type": "seriesMaster",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": rec_uid,
|
||
|
"id": rec_id,
|
||
|
},
|
||
|
{
|
||
|
"type": "singleInstance",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": event_uid,
|
||
|
"id": event_id,
|
||
|
},
|
||
|
])
|
||
|
|
||
|
# act & assert
|
||
|
with self.assertRaises(TypeError):
|
||
|
events._load_odoo_ids_from_db(self.env)
|
||
|
|
||
|
def test_match_event_only(self):
|
||
|
|
||
|
# arrange
|
||
|
event_id = self.simple_event.ms_organizer_event_id
|
||
|
event_uid = self.simple_event.ms_universal_event_id
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "singleInstance",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": event_uid,
|
||
|
"id": event_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
matched = events.match_with_odoo_events(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(matched._events), 1)
|
||
|
self.assertEqual(matched._events[event_id]["_odoo_id"], self.simple_event.id)
|
||
|
|
||
|
def test_match_recurrence_only(self):
|
||
|
|
||
|
# arrange
|
||
|
rec_id = self.recurrence.ms_organizer_event_id
|
||
|
rec_uid = self.recurrence.ms_universal_event_id
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "seriesMaster",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": rec_uid,
|
||
|
"id": rec_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
matched = events.match_with_odoo_events(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(matched._events), 1)
|
||
|
self.assertEqual(matched._events[rec_id]["_odoo_id"], self.recurrence.id)
|
||
|
|
||
|
def test_match_not_typed_recurrence(self):
|
||
|
"""
|
||
|
When a recurrence is deleted, Outlook returns the id of the deleted recurrence
|
||
|
without the type of event, so it's not directly possible to know that it's a
|
||
|
recurrence.
|
||
|
"""
|
||
|
# arrange
|
||
|
rec_id = self.recurrence.ms_organizer_event_id
|
||
|
rec_uid = self.recurrence.ms_universal_event_id
|
||
|
events = MicrosoftEvent([{
|
||
|
"@removed": {
|
||
|
"reason": "deleted",
|
||
|
},
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": rec_uid,
|
||
|
"id": rec_id,
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
matched = events.match_with_odoo_events(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(matched._events), 1)
|
||
|
self.assertEqual(matched._events[rec_id]["_odoo_id"], self.recurrence.id)
|
||
|
|
||
|
def test_match_mix_of_events_and_recurrences(self):
|
||
|
|
||
|
# arrange
|
||
|
event_id = self.simple_event.ms_organizer_event_id
|
||
|
event_uid = self.simple_event.ms_universal_event_id
|
||
|
rec_id = self.recurrence.ms_organizer_event_id
|
||
|
rec_uid = self.recurrence.ms_universal_event_id
|
||
|
|
||
|
events = MicrosoftEvent([
|
||
|
{
|
||
|
"type": "singleInstance",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": event_uid,
|
||
|
"id": event_id,
|
||
|
},
|
||
|
{
|
||
|
"@removed": {
|
||
|
"reason": "deleted",
|
||
|
},
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": rec_uid,
|
||
|
"id": rec_id,
|
||
|
}
|
||
|
])
|
||
|
|
||
|
# act
|
||
|
matched = events.match_with_odoo_events(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(matched._events), 2)
|
||
|
self.assertEqual(matched._events[event_id]["_odoo_id"], self.simple_event.id)
|
||
|
self.assertEqual(matched._events[rec_id]["_odoo_id"], self.recurrence.id)
|
||
|
|
||
|
def test_ignore_not_found_items(self):
|
||
|
|
||
|
# arrange
|
||
|
events = MicrosoftEvent([{
|
||
|
"type": "singleInstance",
|
||
|
"_odoo_id": False,
|
||
|
"iCalUId": "UNKNOWN_EVENT",
|
||
|
"id": "UNKNOWN_EVENT",
|
||
|
}])
|
||
|
|
||
|
# act
|
||
|
matched = events.match_with_odoo_events(self.env)
|
||
|
|
||
|
# assert
|
||
|
self.assertEqual(len(matched._events), 0)
|
||
|
|
||
|
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 = ''
|
||
|
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.assertNotIn(self.simple_event, synced_events)
|
||
|
self.assertIn(self.simple_event, not_synced_events)
|
||
|
|
||
|
def test_microsoft_event_readonly(self):
|
||
|
event = MicrosoftEvent()
|
||
|
with self.assertRaises(TypeError):
|
||
|
event._events['foo'] = 'bar'
|
||
|
with self.assertRaises(AttributeError):
|
||
|
event._events.update({'foo': 'bar'})
|
||
|
with self.assertRaises(TypeError):
|
||
|
dict.update(event._events, {'foo': 'bar'})
|
||
|
|
||
|
def test_performance_check(self):
|
||
|
# Test what happens when microsoft returns a lot of data
|
||
|
# This test does not aim to check what we do with the data but it ensure that we are able to process it.
|
||
|
# Other tests take care of how we update odoo records with the api result.
|
||
|
|
||
|
start_date = datetime(2023, 9, 25, 17, 25)
|
||
|
record_count = 10000
|
||
|
single_event_data = [{
|
||
|
'@odata.type': '#microsoft.graph.event',
|
||
|
'@odata.etag': f'W/"AAAAAA{x}"',
|
||
|
'type': 'singleInstance',
|
||
|
'createdDateTime': (start_date + relativedelta(minutes=x)).isoformat(),
|
||
|
'lastModifiedDateTime': (datetime.now().astimezone(UTC) + relativedelta(days=3)).isoformat(),
|
||
|
'changeKey': f'ZS2uEVAVyU6BMZ3m6cH{x}mtgAADI/Dig==',
|
||
|
'categories': [],
|
||
|
'originalStartTimeZone': 'Romance Standard Time',
|
||
|
'originalEndTimeZone': 'Romance Standard Time',
|
||
|
'id': f'AA{x}',
|
||
|
'subject': f"Subject of {x}",
|
||
|
'bodyPreview': f"Body of {x}",
|
||
|
'start': {'dateTime': (start_date + relativedelta(minutes=x)).isoformat(), 'timeZone': 'UTC'},
|
||
|
'end': {'dateTime': (start_date + relativedelta(minutes=x)).isoformat(), 'timeZone': 'UTC'},
|
||
|
'isOrganizer': True,
|
||
|
'organizer': {'emailAddress': {'name': f'outlook_{x}@outlook.com', 'address': f'outlook_{x}@outlook.com'}},
|
||
|
} for x in range(record_count)]
|
||
|
|
||
|
events = MicrosoftEvent(single_event_data)
|
||
|
mapped = events._load_odoo_ids_from_db(self.env)
|
||
|
self.assertFalse(mapped, "No odoo record should correspond to the microsoft values")
|
||
|
|
||
|
recurring_event_data = [{
|
||
|
'@odata.type': '#microsoft.graph.event',
|
||
|
'@odata.etag': f'W/"{x}IaZKQ=="',
|
||
|
'createdDateTime': (start_date + relativedelta(minutes=(2*x))).isoformat(),
|
||
|
'lastModifiedDateTime': (datetime.now().astimezone(UTC) + relativedelta(days=3)).isoformat(),
|
||
|
'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ==',
|
||
|
'categories': [],
|
||
|
'originalStartTimeZone': 'Romance Standard Time',
|
||
|
'originalEndTimeZone': 'Romance Standard Time',
|
||
|
'iCalUId': f'XX{x}',
|
||
|
'id': f'AAA{x}',
|
||
|
'reminderMinutesBeforeStart': 15,
|
||
|
'isReminderOn': True,
|
||
|
'hasAttachments': False,
|
||
|
'subject': f'My recurrent event {x}',
|
||
|
'bodyPreview': '', 'importance':
|
||
|
'normal', 'sensitivity': 'normal',
|
||
|
'isAllDay': False, 'isCancelled': False,
|
||
|
'isOrganizer': True, 'IsRoomRequested': False,
|
||
|
'AutoRoomBookingStatus': 'None',
|
||
|
'responseRequested': True,
|
||
|
'seriesMasterId': None,
|
||
|
'showAs': 'busy',
|
||
|
'type': 'seriesMaster',
|
||
|
'webLink': f'https://outlook.live.com/owa/?itemid={x}&exvsurl=1&path=/calendar/item',
|
||
|
'onlineMeetingUrl': None,
|
||
|
'isOnlineMeeting': False,
|
||
|
'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True,
|
||
|
'IsDraft': False,
|
||
|
'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': f'outlook_{x}@outlook.com',
|
||
|
'address': f'outlook_{x}@outlook.com'}}
|
||
|
} for x in range(record_count)]
|
||
|
|
||
|
recurrences = MicrosoftEvent(recurring_event_data)
|
||
|
mapped = recurrences._load_odoo_ids_from_db(self.env)
|
||
|
self.assertFalse(mapped, "No odoo record should correspond to the microsoft values")
|