In order to know whether or not the websocket session is up to date, the session is saved when opening a websocket connection. Then, for each incoming/outgoing message, we check if the session still exists on the file system. When it does not exist anymore, the websocket connection is refreshed. This ensures the session is always up to date. Odoo sh proxies the websocket connection and needs a way to tell when a websocket session is expired. This commit adds the same check that is done for each incoming websocket message in the websocket peek route in order to raise a `SessionExpiredException` when the session is outdated. This will allow odoo sh to catch this error and to refresh their websocket connection accordingly. closes odoo/odoo#100416 Signed-off-by: Julien Castiaux <juc@odoo.com>
93 lines
3.4 KiB
Python
93 lines
3.4 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import json
|
|
|
|
from odoo.tests import common
|
|
|
|
|
|
class TestWebsocketController(common.HttpCase):
|
|
def _make_rpc(self, route, params, headers=None):
|
|
data = json.dumps({
|
|
'id': 0,
|
|
'jsonrpc': '2.0',
|
|
'method': 'call',
|
|
'params': params,
|
|
}).encode()
|
|
headers = headers or {}
|
|
headers['Content-Type'] = 'application/json'
|
|
return self.url_open(route, data, headers=headers)
|
|
|
|
def test_websocket_peek(self):
|
|
response = json.loads(
|
|
self._make_rpc('/websocket/peek_notifications', {
|
|
'channels': [],
|
|
'last': 0,
|
|
'is_first_poll': True,
|
|
}).content.decode()
|
|
)
|
|
# Response containing channels/notifications is retrieved and is
|
|
# conform to excpectations.
|
|
result = response.get('result')
|
|
self.assertIsNotNone(result)
|
|
channels = result.get('channels')
|
|
self.assertIsNotNone(channels)
|
|
self.assertIsInstance(channels, list)
|
|
notifications = result.get('notifications')
|
|
self.assertIsNotNone(notifications)
|
|
self.assertIsInstance(notifications, list)
|
|
|
|
response = json.loads(
|
|
self._make_rpc('/websocket/peek_notifications', {
|
|
'channels': [],
|
|
'last': 0,
|
|
'is_first_poll': False,
|
|
}).content.decode()
|
|
)
|
|
# Reponse is received as long as the session is valid.
|
|
self.assertIn('result', response)
|
|
|
|
def test_websocket_peek_session_expired_login(self):
|
|
session = self.authenticate(None, None)
|
|
# first rpc should be fine
|
|
self._make_rpc('/websocket/peek_notifications', {
|
|
'channels': [],
|
|
'last': 0,
|
|
'is_first_poll': True,
|
|
})
|
|
|
|
self.authenticate('admin', 'admin')
|
|
# rpc with outdated session should lead to error.
|
|
headers = {'Cookie': f'session_id={session.sid};'}
|
|
response = json.loads(
|
|
self._make_rpc('/websocket/peek_notifications', {
|
|
'channels': [],
|
|
'last': 0,
|
|
'is_first_poll': False,
|
|
}, headers=headers).content.decode()
|
|
)
|
|
error = response.get('error')
|
|
self.assertIsNotNone(error, 'Sending a poll with an outdated session should lead to error')
|
|
self.assertEqual('odoo.http.SessionExpiredException', error['data']['name'])
|
|
|
|
def test_websocket_peek_session_expired_logout(self):
|
|
session = self.authenticate('demo', 'demo')
|
|
# first rpc should be fine
|
|
self._make_rpc('/websocket/peek_notifications', {
|
|
'channels': [],
|
|
'last': 0,
|
|
'is_first_poll': True,
|
|
})
|
|
self.url_open('/web/session/logout')
|
|
# rpc with outdated session should lead to error.
|
|
headers = {'Cookie': f'session_id={session.sid};'}
|
|
response = json.loads(
|
|
self._make_rpc('/websocket/peek_notifications', {
|
|
'channels': [],
|
|
'last': 0,
|
|
'is_first_poll': False,
|
|
}, headers=headers).content.decode()
|
|
)
|
|
error = response.get('error')
|
|
self.assertIsNotNone(error, 'Sending a poll with an outdated session should lead to error')
|
|
self.assertEqual('odoo.http.SessionExpiredException', error['data']['name'])
|