diff --git a/odoo/addons/test_http/controllers.py b/odoo/addons/test_http/controllers.py index e304548da04..a7dc5b9a5f8 100644 --- a/odoo/addons/test_http/controllers.py +++ b/odoo/addons/test_http/controllers.py @@ -141,3 +141,10 @@ class TestHttp(http.Controller): def touch(self): request.session.touch() return '' + + # ===================================================== + # Errors + # ===================================================== + @http.route('/test_http/json_value_error', type='json', auth='none') + def json_value_error(self): + raise ValueError('Unknown destination') diff --git a/odoo/addons/test_http/tests/test_http.py b/odoo/addons/test_http/tests/test_http.py index 5edc787b1b5..d916a6bc3ee 100644 --- a/odoo/addons/test_http/tests/test_http.py +++ b/odoo/addons/test_http/tests/test_http.py @@ -572,3 +572,61 @@ class TestHttpSession(TestHttpBase): res.raise_for_status() self.assertEqual(res.text, str(GEOIP_ODOO_FARM_2)) mock_resolve.assert_called_once() + +class TestHttpJsonError(TestHttpBase): + + jsonrpc_error_structure = { + 'error': { + 'code': ..., + 'data': { + 'arguments': ..., + 'context': ..., + 'debug': ..., + 'message': ..., + 'name': ..., + }, + 'message': ..., + }, + 'id': ..., + 'jsonrpc': ..., + } + + def assertIsErrorPayload(self, payload): + self.assertEqual( + set(payload), + set(self.jsonrpc_error_structure), + ) + self.assertEqual( + set(payload['error']), + set(self.jsonrpc_error_structure['error']), + ) + self.assertEqual( + set(payload['error']['data']), + set(self.jsonrpc_error_structure['error']['data']), + ) + + + @mute_logger('odoo.http') + def test_errorjson0_value_error(self): + res = self.db_url_open('/test_http/json_value_error', + data=json.dumps({'jsonrpc': '2.0', 'id': 1234, 'params': {}}), + headers=CT_JSON + ) + res.raise_for_status() + + self.assertEqual(res.status_code, 200) + self.assertEqual(res.headers.get('Content-Type', ''), 'application/json; charset=utf-8') + + payload = res.json() + self.assertIsErrorPayload(payload) + + error_data = payload['error']['data'] + self.assertEqual(error_data['name'], 'builtins.ValueError') + self.assertEqual(error_data['message'], 'Unknown destination') + self.assertEqual(error_data['arguments'], ['Unknown destination']) + self.assertEqual(error_data['context'], {}) + + @mute_logger('odoo.http') + def test_errorjson1_dev_mode_werkzeug(self): + with patch.object(config, 'options', {**config.options, 'dev_mode': 'werkzeug'}): + self.test_errorjson0_value_error() diff --git a/odoo/http.py b/odoo/http.py index 300651d2afa..12e13cb8873 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -1382,7 +1382,7 @@ class Request: except Exception as exc: if isinstance(exc, HTTPException) and exc.code is None: raise # bubble up to odoo.http.Application.__call__ - if 'werkzeug' in config['dev_mode']: + if 'werkzeug' in config['dev_mode'] and self.dispatcher.routing_type != 'json': raise # bubble up to werkzeug.debug.DebuggedApplication exc.error_response = self.registry['ir.http']._handle_error(exc) raise @@ -1793,7 +1793,7 @@ class Application: # Server is running with --dev=werkzeug, bubble the error up # to werkzeug so he can fire up a debugger. - if 'werkzeug' in config['dev_mode']: + if 'werkzeug' in config['dev_mode'] and request.dispatcher.routing_type != 'json': raise # Ensure there is always a Response attached to the exception.