From 0feaa4f6508b435a2359e477a0fdf183466a9b53 Mon Sep 17 00:00:00 2001 From: tsm-odoo Date: Wed, 29 Nov 2023 11:07:20 +0100 Subject: [PATCH] [FIX] bus, website_livechat: prevent psycopg2 errors during tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `psycopg2` errors sometimes happen during tests. This is caused by race conditions between several `cr.execute()`/`cr.fetch*`. This is even worse with websockets that are long lived connections that sometimes open a cursor in the middle of the test. Before websockets, longpolling was just disabled during tests. This commits disable websockets as well in order to prevent those errors as much as possible. Some live chat tours were updated in [1] after the websockets intro- duction due to some race conditions. This was reverted as the fix relied on a hack on websockets that are now unavailable. fixes runbot-25357,24809 [1]: https://github.com/odoo/odoo/pull/103521 closes odoo/odoo#143647 X-original-commit: e3b07a301036fcce76ebbbd856b507326b88160a Signed-off-by: Alexandre Kühn (aku) Signed-off-by: Matthieu Stockbauer (tsm) --- addons/bus/controllers/websocket.py | 5 ----- addons/bus/static/tests/bus_tests.js | 1 + addons/bus/tests/common.py | 6 ++++-- addons/bus/tests/test_websocket_caryall.py | 11 ----------- addons/bus/websocket.py | 8 +++++++- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/addons/bus/controllers/websocket.py b/addons/bus/controllers/websocket.py index d870b3da29c..2056b37d74e 100644 --- a/addons/bus/controllers/websocket.py +++ b/addons/bus/controllers/websocket.py @@ -1,7 +1,6 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import json -from werkzeug.exceptions import ServiceUnavailable from odoo.http import Controller, request, route, SessionExpiredException from odoo.addons.base.models.assetsbundle import AssetsBundle @@ -16,10 +15,6 @@ class WebsocketController(Controller): Handle the websocket handshake, upgrade the connection if successfull. """ - is_headful_browser = request.httprequest.user_agent and 'Headless' not in request.httprequest.user_agent.string - if request.registry.in_test_mode() and is_headful_browser: - # Prevent browsers from interfering with the unittests - raise ServiceUnavailable() return WebsocketConnectionHandler.open_connection(request) @route('/websocket/health', type='http', auth='none', save_session=False) diff --git a/addons/bus/static/tests/bus_tests.js b/addons/bus/static/tests/bus_tests.js index e0f9ddfd5c9..f393996b4cb 100644 --- a/addons/bus/static/tests/bus_tests.js +++ b/addons/bus/static/tests/bus_tests.js @@ -358,6 +358,7 @@ QUnit.test("Can reconnect after late close event", async (assert) => { QUnit.test("Fallback on simple worker when shared worker failed to initialize", async (assert) => { addBusServicesToRegistry(); + patchWebsocketWorkerWithCleanup(); const originalSharedWorker = browser.SharedWorker; const originalWorker = browser.Worker; patchWithCleanup(browser, { diff --git a/addons/bus/tests/common.py b/addons/bus/tests/common.py index e332c2ec348..7d260983e2d 100644 --- a/addons/bus/tests/common.py +++ b/addons/bus/tests/common.py @@ -12,12 +12,12 @@ except ImportError: websocket = None import odoo.tools -from odoo.tests import HOST, common +from odoo.tests import HOST, HttpCase from ..websocket import CloseCode, Websocket, WebsocketConnectionHandler from ..models.bus import dispatch, hashable, channel_with_db -class WebsocketCase(common.HttpCase): +class WebsocketCase(HttpCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -25,6 +25,8 @@ class WebsocketCase(common.HttpCase): cls._logger.warning("websocket-client module is not installed") raise unittest.SkipTest("websocket-client module is not installed") cls._WEBSOCKET_URL = f"ws://{HOST}:{odoo.tools.config['http_port']}/websocket" + websocket_allowed_patch = patch.object(WebsocketConnectionHandler, "websocket_allowed", return_value=True) + cls.startClassPatcher(websocket_allowed_patch) def setUp(self): super().setUp() diff --git a/addons/bus/tests/test_websocket_caryall.py b/addons/bus/tests/test_websocket_caryall.py index 3e26e661c9d..5c8e70dc7d3 100644 --- a/addons/bus/tests/test_websocket_caryall.py +++ b/addons/bus/tests/test_websocket_caryall.py @@ -183,17 +183,6 @@ class TestWebsocketCaryall(WebsocketCase): self.assertEqual(notifications[0]['message']['type'], 'notif_type') self.assertEqual(notifications[0]['message']['payload'], 'another_message') - def test_opening_websocket_connection_during_tests(self): - # During tests, browsers can't open websocket connections. - headers = ['User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36'] - with self.assertRaises(WebSocketBadStatusException) as error_catcher: - self.websocket_connect(header=headers) - self.assertEqual(error_catcher.exception.status_code, 503) - - # But ChromeHeadless still can. - headers = ['User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/102.0.0.0 Safari/537.36'] - self.websocket_connect() - def test_subscribe_higher_last_notification_id(self): server_last_notification_id = self.env['bus.bus'].sudo().search([], limit=1, order='id desc').id or 0 client_last_notification_id = server_last_notification_id + 1 diff --git a/addons/bus/websocket.py b/addons/bus/websocket.py index 3d4bd77b46c..d44938c1d93 100644 --- a/addons/bus/websocket.py +++ b/addons/bus/websocket.py @@ -19,7 +19,7 @@ from urllib.parse import urlparse from weakref import WeakSet from werkzeug.local import LocalStack -from werkzeug.exceptions import BadRequest, HTTPException +from werkzeug.exceptions import BadRequest, HTTPException, ServiceUnavailable import odoo from odoo import api @@ -821,6 +821,10 @@ class WebsocketConnectionHandler: 'sec-websocket-version', 'upgrade', 'origin', } + @classmethod + def websocket_allowed(cls, request): + return not request.registry.in_test_mode() + @classmethod def open_connection(cls, request): """ @@ -831,6 +835,8 @@ class WebsocketConnectionHandler: versions the client supports and those we support. :raise: BadRequest if the handshake data is incorrect. """ + if not cls.websocket_allowed(request): + raise ServiceUnavailable("Websocket is disabled in test mode") cls._handle_public_configuration(request) try: response = cls._get_handshake_response(request.httprequest.headers)