[FIX] microsoft_calendar: outdated sync token error fetching events
Before this commit, when Odoo faced an internal error and lost the current synchronization token with Microsoft, after fetching events from Graph API an `410 Gone` was being thrown carrying the `SyncStateNotFound` code.
Since we didn't handle this error code, we were experiencing a traceback after the request:
```
2023-11-15 09:16:15,046 4 ERROR report-one-16-0-staging-1-10315840 odoo.addons.microsoft_account.models.microsoft_service: Bad microsoft request : b'{"error":{"code":"SyncStateNotFound","message":"The sync state generation is not found; generation=25;[highest=28][28][26][27]."}}' !
Traceback (most recent call last):
File "/home/odoo/src/odoo/addons/microsoft_account/models/microsoft_service.py", line 154, in _do_request
res.raise_for_status()
File "/usr/lib/python3/dist-packages/requests/models.py", line 943, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 410 Client Error: Gone for url: https://graph.microsoft.com/v1.0/me/calendarView/delta?%24deltatoken=MOCK_TOKEN_HERE
2023-11-15 09:16:15,051 4 ERROR report-one-16-0-staging-1-10315840 odoo.http: Exception during request handling.
Traceback (most recent call last):
File "/home/odoo/src/odoo/odoo/http.py", line 2003, in __call__
response = request._serve_db()
File "/home/odoo/src/odoo/odoo/http.py", line 1589, in _serve_db
return service_model.retrying(self._serve_ir_http, self.env)
File "/home/odoo/src/odoo/odoo/service/model.py", line 133, in retrying
result = func()
File "/home/odoo/src/odoo/odoo/http.py", line 1616, in _serve_ir_http
response = self.dispatcher.dispatch(rule.endpoint, args)
File "/home/odoo/src/odoo/odoo/http.py", line 1820, in dispatch
result = self.request.registry['ir.http']._dispatch(endpoint)
File "/home/odoo/src/odoo/addons/website/models/ir_http.py", line 237, in _dispatch
response = super()._dispatch(endpoint)
File "/home/odoo/src/odoo/odoo/addons/base/models/ir_http.py", line 154, in _dispatch
result = endpoint(**request.params)
File "/home/odoo/src/odoo/odoo/http.py", line 697, in route_wrapper
result = endpoint(self, *args, **params_ok)
File "/home/odoo/src/odoo/addons/microsoft_calendar/controllers/main.py", line 55, in sync_data
need_refresh = request.env.user.sudo().with_context(sync_context)._sync_microsoft_calendar()
File "/home/odoo/src/odoo/addons/microsoft_calendar/models/res_users.py", line 99, in _sync_microsoft_calendar
events, next_sync_token = calendar_service.get_events(self.microsoft_calendar_sync_token, token=token)
File "/home/odoo/src/odoo/addons/microsoft_calendar/utils/microsoft_calendar.py", line 20, in wrapped
return func(self, *args, **kwargs)
File "/home/odoo/src/odoo/addons/microsoft_calendar/utils/microsoft_calendar.py", line 152, in get_events
events, next_sync_token = self._get_events_delta(sync_token=sync_token, token=token, timeout=timeout)
File "/home/odoo/src/odoo/addons/microsoft_calendar/utils/microsoft_calendar.py", line 20, in wrapped
return func(self, *args, **kwargs)
File "/home/odoo/src/odoo/addons/microsoft_calendar/utils/microsoft_calendar.py", line 111, in _get_events_delta
raise e
File "/home/odoo/src/odoo/addons/microsoft_calendar/utils/microsoft_calendar.py", line 105, in _get_events_delta
events, next_sync_token = self._get_events_from_paginated_url(
File "/home/odoo/src/odoo/addons/microsoft_calendar/utils/microsoft_calendar.py", line 20, in wrapped
return func(self, *args, **kwargs)
File "/home/odoo/src/odoo/addons/microsoft_calendar/utils/microsoft_calendar.py", line 76, in _get_events_from_paginated_url
_, data, _ = self.microsoft_service._do_request(
File "/home/odoo/src/odoo/addons/microsoft_account/models/microsoft_service.py", line 173, in _do_request
raise error
File "/home/odoo/src/odoo/addons/microsoft_account/models/microsoft_service.py", line 154, in _do_request
res.raise_for_status()
File "/usr/lib/python3/dist-packages/requests/models.py", line 943, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 410 Client Error: Gone for url: https://graph.microsoft.com/v1.0/me/calendarView/delta?%24deltatoken=MOCK_TOKEN_HERE
```
After this commit, everytime we receive the `SyncStateNotFound` code from Microsoft, we will trigger the full synchronization with Graph API. This way, the outdated token will be replaced with a brand new token. A function which checks if the full sync is required was added in order to be mocked by the new unit test, since it is necessary mocking its return value for testing if a given `HTTPError` with the 'SyncStateNotFound' error code will call the all events fetching.
closes odoo/odoo#142982
Task-id: 3597211
X-original-commit: 54ae2f2b469e3bd5294386b882e57d7519dc044a
Signed-off-by: Arnaud Joset (arj) <arj@odoo.com>
This commit is contained in:
@@ -62,8 +62,9 @@ class TestMicrosoftService(TransactionCase):
|
||||
with self.assertRaises(Exception):
|
||||
self.service._get_events_delta(token=self.fake_token, timeout=DEFAULT_TIMEOUT)
|
||||
|
||||
@patch.object(MicrosoftCalendarService, "_check_full_sync_required")
|
||||
@patch.object(MicrosoftService, "_do_request")
|
||||
def test_get_events_delta_token_error(self, mock_do_request):
|
||||
def test_get_events_delta_token_error(self, mock_do_request, mock_check_full_sync_required):
|
||||
"""
|
||||
When the provided sync token is invalid, an exception should be raised and then
|
||||
a full sync should be done.
|
||||
@@ -72,6 +73,7 @@ class TestMicrosoftService(TransactionCase):
|
||||
requests.HTTPError(response=MagicMock(status_code=410, content="fullSyncRequired")),
|
||||
self._do_request_result({"value": []}),
|
||||
]
|
||||
mock_check_full_sync_required.return_value = (True)
|
||||
|
||||
events, next_token = self.service._get_events_delta(
|
||||
token=self.fake_token, sync_token=self.fake_sync_token, timeout=DEFAULT_TIMEOUT
|
||||
@@ -437,3 +439,25 @@ class TestMicrosoftService(TransactionCase):
|
||||
json.dumps(values),
|
||||
self.header, method="POST", timeout=DEFAULT_TIMEOUT
|
||||
)
|
||||
|
||||
@patch.object(MicrosoftCalendarService, "_check_full_sync_required")
|
||||
@patch.object(MicrosoftService, "_do_request")
|
||||
def test_get_events_delta_with_outdated_sync_token(self, mock_do_request, mock_check_full_sync_required):
|
||||
""" When an outdated sync token is provided, we must fetch all events again for updating the old token. """
|
||||
# Throw a 'HTTPError' when the token is outdated, thus triggering the fetching of all events.
|
||||
# Simulate a scenario which the full sync is required, such as when getting the 'SyncStateNotFound' error code.
|
||||
mock_do_request.side_effect = [
|
||||
requests.HTTPError(response=MagicMock(status_code=410, error={'code': "SyncStateNotFound"})),
|
||||
self._do_request_result({"value": []}),
|
||||
]
|
||||
mock_check_full_sync_required.return_value = (True)
|
||||
|
||||
# Call the regular 'delta' get events with an outdated token for triggering the all events fetching.
|
||||
self.env.user.microsoft_calendar_sync_token = self.fake_sync_token
|
||||
self.service._get_events_delta(token=self.fake_token, sync_token=self.fake_sync_token, timeout=DEFAULT_TIMEOUT)
|
||||
|
||||
# Two calls must have been made: one call with the outdated sync token and another one with no sync token.
|
||||
mock_do_request.assert_has_calls([
|
||||
self.call_with_sync_token,
|
||||
self.call_without_sync_token
|
||||
])
|
||||
|
||||
@@ -92,6 +92,12 @@ class MicrosoftCalendarService():
|
||||
|
||||
return events, next_sync_token
|
||||
|
||||
def _check_full_sync_required(self, response):
|
||||
""" Checks if full sync is required according to the error code received. """
|
||||
response_json = response.json()
|
||||
response_code = response_json.get('error', {}).get('code', '')
|
||||
return any(error_code in response_code for error_code in ['fullSyncRequired', 'SyncStateNotFound'])
|
||||
|
||||
@requires_auth_token
|
||||
def _get_events_delta(self, sync_token=None, token=None, timeout=TIMEOUT):
|
||||
"""
|
||||
@@ -105,7 +111,8 @@ class MicrosoftCalendarService():
|
||||
events, next_sync_token = self._get_events_from_paginated_url(
|
||||
url, params=params, token=token, timeout=timeout)
|
||||
except requests.HTTPError as e:
|
||||
if e.response.status_code == 410 and 'fullSyncRequired' in str(e.response.content) and sync_token:
|
||||
full_sync_needed = self._check_full_sync_required(e.response)
|
||||
if e.response.status_code == 410 and full_sync_needed and sync_token:
|
||||
# retry with a full sync
|
||||
return self._get_events_delta(token=token, timeout=timeout)
|
||||
raise e
|
||||
|
||||
Reference in New Issue
Block a user