Odoo18-Base/odoo/addons/base/tests/test_http_case.py

130 lines
6.2 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import threading
from unittest.mock import patch
from odoo.http import Controller, request, route
from odoo.tests.common import ChromeBrowser, HttpCase, tagged
from odoo.tools import config, logging
_logger = logging.getLogger(__name__)
@tagged('-at_install', 'post_install')
class TestHttpCase(HttpCase):
def test_console_error_string(self):
with self.assertLogs(level='ERROR') as log_catcher:
with self.assertRaises(AssertionError) as error_catcher:
code = "console.error('test error','message')"
with patch('odoo.tests.common.ChromeBrowser.take_screenshot', return_value=None):
self.browser_js(url_path='about:blank', code=code)
# second line must contains error message
self.assertEqual(error_catcher.exception.args[0].splitlines()[-1], "test error message")
self.assertEqual(len(log_catcher.output), 1)
self.assertIn('test error message', log_catcher.output[0])
def test_console_error_object(self):
with self.assertLogs(level='ERROR') as log_catcher:
with self.assertRaises(AssertionError) as error_catcher:
code = "console.error(TypeError('test error message'))"
with patch('odoo.tests.common.ChromeBrowser.take_screenshot', return_value=None):
self.browser_js(url_path='about:blank', code=code)
# second line must contains error message
self.assertEqual(error_catcher.exception.args[0].splitlines()[-2:],
['TypeError: test error message', ' at <anonymous>:1:15'])
self.assertEqual(len(log_catcher.output), 1)
self.assertIn('TypeError: test error message\n at <anonymous>:1:15', log_catcher.output[0])
def test_console_log_object(self):
logger = logging.getLogger('odoo')
level = logger.level
logger.setLevel(logging.INFO)
self.addCleanup(logger.setLevel, level)
with self.assertLogs() as log_catcher:
code = "console.log({custom:{1:'test', 2:'a'}, value:1, description:'dummy'});console.log('test successful');"
self.browser_js(url_path='about:blank', code=code)
console_log_count = 0
for log in log_catcher.output:
if '.browser:' in log:
text = log.split('.browser:', 1)[1]
if text == 'test successful':
continue
self.assertEqual(text, "Object(custom=Object, value=1, description='dummy')")
console_log_count += 1
self.assertEqual(console_log_count, 1)
@tagged('-at_install', 'post_install')
class TestChromeBrowser(HttpCase):
def setUp(self):
super().setUp()
screencasts_dir = config['screencasts'] or config['screenshots']
with patch.dict(config.options, {'screencasts': screencasts_dir, 'screenshots': config['screenshots']}):
self.browser = ChromeBrowser(self)
self.addCleanup(self.browser.stop)
def test_screencasts(self):
self.browser.start_screencast()
self.browser.navigate_to('about:blank')
self.browser._wait_ready()
code = "setTimeout(() => console.log('test successful'), 2000); setInterval(() => document.body.innerText = (new Date()).getTime(), 100);"
self.browser._wait_code_ok(code, 10)
self.browser._save_screencast()
class TestRequestRemaining(HttpCase):
# This test case tries to reproduce the case where a request is lost between two test and is execute during the secone one.
#
# - Test A browser js finishes with a pending request
# - _wait_remaining_requests misses the request since the thread may not be totally spawned (or correctly named)
# - Test B starts and a SELECT is executed
# - The request is executed and makes a concurrent fetchall
# - The test B tries to fetchall and fails since the cursor is already used by the request
#
# Note that similar cases can also consume savepoint, make the main cursor readonly, ...
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.thread_a = None
# this lock is used to ensure the request is executed after test b starts
cls.main_lock = threading.Lock()
cls.main_lock.acquire()
def test_requests_a(self):
class Dummycontroller(Controller):
@route('/web/concurrent', type='http', auth='public', sitemap=False)
def wait(c, **params):
self.assertEqual(request.env.cr.__class__.__name__, 'TestCursor')
request.env.cr.execute('SELECT 1')
request.env.cr.fetchall()
# not that the previous queries are not really needed since the http stack will check the registry
# but this makes the test more clear and robust
_logger.info('B finish')
self.env.registry.clear_cache('routing')
self.addCleanup(self.env.registry.clear_cache, 'routing')
def late_request_thread():
# In some rare case the request may arrive after _wait_remaining_requests.
# this thread is trying to reproduce this case.
_logger.info('Waiting for B to start')
if self.main_lock.acquire(timeout=10):
self.url_open("/web/concurrent", timeout=10)
else:
_logger.error('Something went wrong and thread was not able to aquire lock')
TestRequestRemaining.thread_a = threading.Thread(target=late_request_thread)
self.thread_a.start()
def test_requests_b(self):
self.env.cr.execute('SELECT 1')
with self.assertLogs('odoo.tests.common') as lc:
self.main_lock.release()
_logger.info('B started, waiting for A to finish')
self.thread_a.join()
self.assertEqual(lc.output[0].split(':', 1)[1], 'odoo.tests.common:Request with path /web/concurrent has been ignored during test as it it does not contain the test_cursor cookie or it is expired. (required "/base/tests/test_http_case.py:TestRequestRemaining.test_requests_b", got "/base/tests/test_http_case.py:TestRequestRemaining.test_requests_a")')
self.env.cr.fetchall()