diff --git a/addons/bus/tests/test_websocket_controller.py b/addons/bus/tests/test_websocket_controller.py index 2c477cca6fe..3e4e4706082 100644 --- a/addons/bus/tests/test_websocket_controller.py +++ b/addons/bus/tests/test_websocket_controller.py @@ -2,32 +2,19 @@ import json -from odoo.tests import common +from odoo.tests import common, JsonRpcException 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() - ) + result = self.make_jsonrpc_request('/websocket/peek_notifications', { + 'channels': [], + 'last': 0, + 'is_first_poll': True, + }) + # 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) @@ -36,20 +23,19 @@ class TestWebsocketController(common.HttpCase): 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() - ) + result = self.make_jsonrpc_request('/websocket/peek_notifications', { + 'channels': [], + 'last': 0, + 'is_first_poll': False, + }) + # Reponse is received as long as the session is valid. - self.assertIn('result', response) + self.assertIsNotNone(result) def test_websocket_peek_session_expired_login(self): session = self.authenticate(None, None) # first rpc should be fine - self._make_rpc('/websocket/peek_notifications', { + self.make_jsonrpc_request('/websocket/peek_notifications', { 'channels': [], 'last': 0, 'is_first_poll': True, @@ -58,21 +44,17 @@ class TestWebsocketController(common.HttpCase): 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', { + with self.assertRaises(JsonRpcException, msg='odoo.http.SessionExpiredException'): + self.make_jsonrpc_request('/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']) + }, headers=headers) def test_websocket_peek_session_expired_logout(self): session = self.authenticate('demo', 'demo') # first rpc should be fine - self._make_rpc('/websocket/peek_notifications', { + self.make_jsonrpc_request('/websocket/peek_notifications', { 'channels': [], 'last': 0, 'is_first_poll': True, @@ -80,13 +62,9 @@ class TestWebsocketController(common.HttpCase): 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', { + with self.assertRaises(JsonRpcException, msg='odoo.http.SessionExpiredException'): + self.make_jsonrpc_request('/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']) + }, headers=headers) diff --git a/addons/crm_livechat/tests/test_chatbot_lead.py b/addons/crm_livechat/tests/test_chatbot_lead.py index 31dadf0f308..132de503679 100644 --- a/addons/crm_livechat/tests/test_chatbot_lead.py +++ b/addons/crm_livechat/tests/test_chatbot_lead.py @@ -33,8 +33,12 @@ class CrmChatbotCase(chatbot_common.CrmChatbotCase): self.assertEqual(created_lead.type, 'lead') def _chatbot_create_lead(self, user): - channel_info = self.livechat_channel._open_livechat_discuss_channel( - anonymous_name='Test Visitor', chatbot_script=self.chatbot_script, user_id=user.id) + channel_info = self.make_jsonrpc_request("/im_livechat/get_session", { + 'anonymous_name': 'Test Visitor', + 'channel_id': self.livechat_channel.id, + 'chatbot_script_id': self.chatbot_script.id, + 'user_id': user.id, + }) discuss_channel = self.env['discuss.channel'].sudo().browse(channel_info['id']) self._post_answer_and_trigger_next_step( diff --git a/addons/crm_mail_plugin/tests/test_crm_mail_plugin.py b/addons/crm_mail_plugin/tests/test_crm_mail_plugin.py index 2770ec3e61c..48d699e79fe 100644 --- a/addons/crm_mail_plugin/tests/test_crm_mail_plugin.py +++ b/addons/crm_mail_plugin/tests/test_crm_mail_plugin.py @@ -15,7 +15,7 @@ class TestCrmMailPlugin(TestMailPluginControllerCommon): {"name": "Partner 2"}, ]) - result = self._make_rpc_call("/mail_plugin/partner/get", {"partner_id": partner.id}) + result = self.make_jsonrpc_request("/mail_plugin/partner/get", {"partner_id": partner.id}) self.assertNotIn("leads", result, msg="The user has no access to crm.lead, the leads section should not be visible") @@ -27,7 +27,7 @@ class TestCrmMailPlugin(TestMailPluginControllerCommon): {"name": "Lead Partner 2", "partner_id": partner_2.id}, ]) - result = self._make_rpc_call("/mail_plugin/partner/get", {"partner_id": partner.id}) + result = self.make_jsonrpc_request("/mail_plugin/partner/get", {"partner_id": partner.id}) self.assertIn( "leads", @@ -71,7 +71,7 @@ class TestCrmMailPlugin(TestMailPluginControllerCommon): 'email_subject': 'test subject', } - result = self._make_rpc_call('/mail_plugin/lead/create', params) + result = self.make_jsonrpc_request('/mail_plugin/lead/create', params) # Check that the created lead record has the correct company and return the lead_id self.assertIn( @@ -87,27 +87,3 @@ class TestCrmMailPlugin(TestMailPluginControllerCommon): company_b, msg='The created record should belong to company_B', ) - - def _make_rpc_call(self, url, params): - """ - Makes an RPC call to the specified URL with the given parameters, and returns the 'result' key from the response. - - :param params (dict): A dictionary containing the parameters to send in the RPC call. - :param url (str): The URL to send the RPC call to. - :return (dict): The 'result' key from the response. - """ - - data = { - 'id': 0, - 'jsonrpc': '2.0', - 'method': 'call', - 'params': params, - } - - response = self.url_open( - url, - data=json.dumps(data).encode(), - headers={'Content-Type': 'application/json'} - ).json() - - return response['result'] diff --git a/addons/im_livechat/tests/chatbot_common.py b/addons/im_livechat/tests/chatbot_common.py index 55fddc21cde..414f1372301 100644 --- a/addons/im_livechat/tests/chatbot_common.py +++ b/addons/im_livechat/tests/chatbot_common.py @@ -4,7 +4,7 @@ from odoo.tests import common -class ChatbotCase(common.TransactionCase): +class ChatbotCase(common.HttpCase): @classmethod def setUpClass(cls): diff --git a/addons/im_livechat/tests/common.py b/addons/im_livechat/tests/common.py index 3258256ff49..03910f89774 100644 --- a/addons/im_livechat/tests/common.py +++ b/addons/im_livechat/tests/common.py @@ -1,16 +1,18 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.tests.common import TransactionCase +from odoo.tests.common import HttpCase -class TestImLivechatCommon(TransactionCase): +class TestImLivechatCommon(HttpCase): @classmethod def setUpClass(cls): super().setUpClass() + cls.password = 'Pl1bhD@2!kXZ' cls.operators = cls.env['res.users'].create([{ 'name': 'Michel', 'login': 'michel', + 'password': cls.password, 'livechat_username': "Michel Operator", 'email': 'michel@example.com', }, { diff --git a/addons/im_livechat/tests/test_chatbot_internals.py b/addons/im_livechat/tests/test_chatbot_internals.py index 7c3b230ee92..ad4ec57f3f4 100644 --- a/addons/im_livechat/tests/test_chatbot_internals.py +++ b/addons/im_livechat/tests/test_chatbot_internals.py @@ -44,8 +44,11 @@ class ChatbotCase(chatbot_common.ChatbotCase): "Only step 'step_no_one_available' should be flagged as forward operator child.") def test_chatbot_steps(self): - channel_info = self.livechat_channel._open_livechat_discuss_channel( - anonymous_name='Test Visitor', chatbot_script=self.chatbot_script) + channel_info = self.make_jsonrpc_request("/im_livechat/get_session", { + 'anonymous_name': 'Test Visitor', + 'chatbot_script_id': self.chatbot_script.id, + 'channel_id': self.livechat_channel.id, + }) discuss_channel = self.env['discuss.channel'].browse(channel_info['id']) self.assertEqual(discuss_channel.chatbot_current_step_id, self.step_dispatch) diff --git a/addons/im_livechat/tests/test_get_discuss_channel.py b/addons/im_livechat/tests/test_get_discuss_channel.py index f046ccf0353..305dc312ffc 100644 --- a/addons/im_livechat/tests/test_get_discuss_channel.py +++ b/addons/im_livechat/tests/test_get_discuss_channel.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import timedelta from freezegun import freeze_time +from unittest.mock import patch, PropertyMock from odoo import fields from odoo.addons.im_livechat.tests.common import TestImLivechatCommon @@ -22,11 +23,20 @@ class TestGetDiscussChannel(TestImLivechatCommon): def test_channel_get_livechat_visitor_info(self): belgium = self.env.ref('base.be') public_user = self.env.ref('base.public_user') - test_user = self.env['res.users'].create({'name': 'Roger', 'login': 'roger', 'country_id': belgium.id}) + test_user = self.env['res.users'].create({'name': 'Roger', 'login': 'roger', 'password': self.password, 'country_id': belgium.id}) # ensure visitor info are correct with anonymous operator = self.operators[0] - channel_info = self.livechat_channel.with_user(public_user).sudo()._open_livechat_discuss_channel(anonymous_name='Visitor 22', previous_operator_id=operator.partner_id.id, country_id=belgium.id) + with patch('odoo.http.GeoIP.country_code', new_callable=PropertyMock(return_value=belgium.code)): + channel_info = self.make_jsonrpc_request( + '/im_livechat/get_session', + { + 'anonymous_name': 'Visitor 22', + 'previous_operator_id': operator.partner_id.id, + 'channel_id': self.livechat_channel.id, + 'country_id': belgium.id, + }, + ) self.assertEqual(channel_info['channel']['anonymous_name'], "Visitor 22") self.assertEqual(channel_info['channel']['anonymous_country'], {'code': 'BE', 'id': belgium.id, 'name': 'Belgium'}) @@ -34,7 +44,7 @@ class TestGetDiscussChannel(TestImLivechatCommon): # shape of channelMembers is [('insert', data...)], [0][1] accesses the data self.assertEqual(sorted(map(lambda m: m['persona']['partner'], channel_info['channel']['channelMembers'][0][1]), key=lambda m: m['id']), sorted([{ 'active': True, - 'country': [('clear',)], + 'country': [['clear']], 'id': operator.partner_id.id, 'is_bot': False, 'is_public': False, @@ -48,17 +58,23 @@ class TestGetDiscussChannel(TestImLivechatCommon): }], key=lambda m: m['id'])) # ensure visitor info are correct with real user - channel_info = self.livechat_channel.with_user(test_user).sudo()._open_livechat_discuss_channel(anonymous_name='whatever', previous_operator_id=operator.partner_id.id, user_id=test_user.id) + self.authenticate(test_user.login, self.password) + channel_info = self.make_jsonrpc_request('/im_livechat/get_session', { + 'anonymous_name': 'whatever', + 'previous_operator_id': operator.partner_id.id, + 'user_id': test_user.id, + 'channel_id': self.livechat_channel.id, + }) self.assertFalse(channel_info['channel']['anonymous_name']) - self.assertEqual(channel_info['channel']['anonymous_country'], [('clear',)]) - self.assertEqual(channel_info['channel']['channelMembers'], [('insert', [ + self.assertEqual(channel_info['channel']['anonymous_country'], {'code': 'BE', 'id': belgium.id, 'name': 'Belgium'}) + self.assertEqual(channel_info['channel']['channelMembers'], [['insert', [ { 'channel': {'id': channel_info['id']}, 'id': self.env['discuss.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', '=', operator.partner_id.id)]).id, 'persona': { 'partner': { 'active': True, - 'country': [('clear',)], + 'country': [['clear']], 'id': operator.partner_id.id, 'is_bot': False, 'is_public': False, @@ -84,22 +100,28 @@ class TestGetDiscussChannel(TestImLivechatCommon): }, }, }, - ])]) + ]]]) # ensure visitor info are correct when operator is testing themselves operator = self.operators[0] - channel_info = self.livechat_channel.with_user(operator)._open_livechat_discuss_channel(anonymous_name='whatever', previous_operator_id=operator.partner_id.id, user_id=operator.id) - self.assertEqual(channel_info['operator_pid'], (operator.partner_id.id, "Michel Operator")) + self.authenticate(operator.login, self.password) + channel_info = self.make_jsonrpc_request('/im_livechat/get_session', { + 'anonymous_name': 'whatever', + 'previous_operator_id': operator.partner_id.id, + 'user_id': operator.id, + 'channel_id': self.livechat_channel.id, + }) + self.assertEqual(channel_info['operator_pid'], [operator.partner_id.id, "Michel Operator"]) self.assertFalse(channel_info['channel']['anonymous_name']) - self.assertEqual(channel_info['channel']['anonymous_country'], [('clear',)]) - self.assertEqual(channel_info['channel']['channelMembers'], [('insert', [ + self.assertEqual(channel_info['channel']['anonymous_country'], [['clear']]) + self.assertEqual(channel_info['channel']['channelMembers'], [['insert', [ { 'channel': {'id': channel_info['id']}, 'id': self.env['discuss.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', '=', operator.partner_id.id)]).id, 'persona': { 'partner': { 'active': True, - 'country': [('clear',)], + 'country': [['clear']], 'id': operator.partner_id.id, 'is_bot': False, 'is_public': False, @@ -107,13 +129,13 @@ class TestGetDiscussChannel(TestImLivechatCommon): }, }, }, - ])]) + ]]]) def _open_livechat_discuss_channel(self): discuss_channels = [] for i in range(5): - discuss_channel = self.livechat_channel._open_livechat_discuss_channel('Anonymous') + discuss_channel = self.make_jsonrpc_request('/im_livechat/get_session', {'anonymous_name': 'Anonymous', 'channel_id': self.livechat_channel.id}) discuss_channels.append(discuss_channel) # send a message to mark this channel as 'active' self.env['discuss.channel'].browse(discuss_channel['id']).message_post(body='cc') @@ -122,7 +144,7 @@ class TestGetDiscussChannel(TestImLivechatCommon): def test_channel_not_pinned_for_operator_before_first_message(self): public_user = self.env.ref('base.public_user') - channel_info = self.livechat_channel.with_user(public_user).sudo()._open_livechat_discuss_channel(anonymous_name='whatever') + channel_info = self.make_jsonrpc_request('/im_livechat/get_session', {'anonymous_name': 'whatever', 'channel_id': self.livechat_channel.id}) operator_channel_member = self.env['discuss.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', 'in', self.operators.partner_id.ids)]) self.assertEqual(len(operator_channel_member), 1, "operator should be member of channel") self.assertFalse(operator_channel_member.is_pinned, "channel should not be pinned for operator initially") @@ -139,7 +161,7 @@ class TestGetDiscussChannel(TestImLivechatCommon): 'email': 'michel@example.com', 'livechat_username': 'Michel at your service', }) - channel_info = self.livechat_channel.with_user(public_user).sudo()._open_livechat_discuss_channel(anonymous_name='whatever') + channel_info = self.make_jsonrpc_request('/im_livechat/get_session', {'anonymous_name': 'whatever', 'channel_id': self.livechat_channel.id}) channel = self.env['discuss.channel'].browse(channel_info['id']) channel.with_user(operator).message_post(body='Hello', message_type='comment', subtype_xmlid='mail.mt_comment') message_formats = channel.with_user(public_user).sudo()._channel_fetch_message() @@ -150,7 +172,7 @@ class TestGetDiscussChannel(TestImLivechatCommon): def test_read_channel_unpined_for_operator_after_one_day(self): public_user = self.env.ref('base.public_user') - channel_info = self.livechat_channel.with_user(public_user).sudo()._open_livechat_discuss_channel(anonymous_name='visitor') + channel_info = self.make_jsonrpc_request('/im_livechat/get_session', {'anonymous_name': 'visitor', 'channel_id': self.livechat_channel.id}) member_of_operator = self.env['discuss.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', 'in', self.operators.partner_id.ids)]) message = self.env['discuss.channel'].browse(channel_info['id']).message_post(body='cc') member_of_operator.channel_id.with_user(self.operators.filtered( @@ -162,7 +184,7 @@ class TestGetDiscussChannel(TestImLivechatCommon): def test_unread_channel_not_unpined_for_operator_after_autovacuum(self): public_user = self.env.ref('base.public_user') - channel_info = self.livechat_channel.with_user(public_user).sudo()._open_livechat_discuss_channel(anonymous_name='visitor') + channel_info = self.make_jsonrpc_request('/im_livechat/get_session', {'anonymous_name': 'visitor', 'channel_id': self.livechat_channel.id}) member_of_operator = self.env['discuss.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', 'in', self.operators.partner_id.ids)]) self.env['discuss.channel'].browse(channel_info['id']).message_post(body='cc') with freeze_time(fields.Datetime.to_string(fields.datetime.now() + timedelta(days=1))): diff --git a/addons/im_livechat/tests/test_im_livechat_report.py b/addons/im_livechat/tests/test_im_livechat_report.py index a0f8b14461d..c4cbc3c8692 100644 --- a/addons/im_livechat/tests/test_im_livechat_report.py +++ b/addons/im_livechat/tests/test_im_livechat_report.py @@ -7,31 +7,30 @@ from odoo.addons.im_livechat.tests.common import TestImLivechatCommon class TestImLivechatReport(TestImLivechatCommon): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.env['discuss.channel'].search([('livechat_channel_id', '!=', False)]).unlink() + def setUp(self): + super().setUp() + self.env['discuss.channel'].search([('livechat_channel_id', '!=', False)]).unlink() def _compute_available_operator_ids(channel_self): for record in channel_self: - record.available_operator_ids = cls.operators + record.available_operator_ids = self.operators - with patch.object(type(cls.env['im_livechat.channel']), '_compute_available_operator_ids', _compute_available_operator_ids): - channel_id = cls.livechat_channel._open_livechat_discuss_channel('Anonymous')['id'] + with patch.object(type(self.env['im_livechat.channel']), '_compute_available_operator_ids', _compute_available_operator_ids): + channel_id = self.make_jsonrpc_request("/im_livechat/get_session", {'anonymous_name': 'Anonymous', 'channel_id': self.livechat_channel.id})['id'] - channel = cls.env['discuss.channel'].browse(channel_id) - cls.operator = channel.livechat_operator_id + channel = self.env['discuss.channel'].browse(channel_id) + self.operator = channel.livechat_operator_id - cls._create_message(channel, cls.visitor_user.partner_id, '2023-03-17 06:05:54') - cls._create_message(channel, cls.operator, '2023-03-17 08:15:54') - cls._create_message(channel, cls.operator, '2023-03-17 08:45:54') + self._create_message(channel, self.visitor_user.partner_id, '2023-03-17 06:05:54') + self._create_message(channel, self.operator, '2023-03-17 08:15:54') + self._create_message(channel, self.operator, '2023-03-17 08:45:54') # message with the same record id, but with a different model # should not be taken into account for statistics - partner_message = cls._create_message(channel, cls.operator, '2023-03-17 05:05:54') - partner_message |= cls._create_message(channel, cls.operator, '2023-03-17 09:15:54') + partner_message = self._create_message(channel, self.operator, '2023-03-17 05:05:54') + partner_message |= self._create_message(channel, self.operator, '2023-03-17 09:15:54') partner_message.model = 'res.partner' - cls.env['mail.message'].flush_model() + self.env['mail.message'].flush_model() def test_im_livechat_report_channel(self): report = self.env['im_livechat.report.channel'].search([('livechat_channel_id', '=', self.livechat_channel.id)]) diff --git a/addons/im_livechat/tests/test_message.py b/addons/im_livechat/tests/test_message.py index bb06e2bff5d..8e9e8d204be 100644 --- a/addons/im_livechat/tests/test_message.py +++ b/addons/im_livechat/tests/test_message.py @@ -4,13 +4,14 @@ from markupsafe import Markup from odoo import Command -from odoo.tests.common import users, tagged, TransactionCase +from odoo.tests.common import users, tagged, HttpCase @tagged('post_install', '-at_install') -class TestImLivechatMessage(TransactionCase): +class TestImLivechatMessage(HttpCase): def setUp(self): super().setUp() + self.password = 'Pl1bhD@2!kXZ' self.users = self.env['res.users'].create([ { 'email': 'e.e@example.com', @@ -21,14 +22,20 @@ class TestImLivechatMessage(TransactionCase): 'odoobot_state': 'disabled', 'signature': '--\nErnest', }, - {'name': 'test1', 'login': 'test1', 'email': 'test1@example.com'}, + {'name': 'test1', 'login': 'test1', 'password': self.password, 'email': 'test1@example.com'}, ]) @users('emp') def test_message_format(self): im_livechat_channel = self.env['im_livechat.channel'].sudo().create({'name': 'support', 'user_ids': [Command.link(self.users[0].id)]}) self.env['bus.presence'].create({'user_id': self.users[0].id, 'status': 'online'}) # make available for livechat (ignore leave) - channel_livechat_1 = self.env['discuss.channel'].browse(im_livechat_channel._open_livechat_discuss_channel(anonymous_name='anon 1', previous_operator_id=self.users[0].partner_id.id, user_id=self.users[1].id, country_id=self.env.ref('base.in').id)['id']) + self.authenticate(self.users[1].login, self.password) + channel_livechat_1 = self.env['discuss.channel'].browse(self.make_jsonrpc_request("/im_livechat/get_session", { + 'anonymous_name': 'anon 1', + 'previous_operator_id': self.users[0].partner_id.id, + 'country_id': self.env.ref('base.in').id, + 'channel_id': im_livechat_channel.id, + })['id']) record_rating = self.env['rating.rating'].create({ 'res_model_id': self.env['ir.model']._get('discuss.channel').id, 'res_id': channel_livechat_1.id, diff --git a/addons/payment/tests/http_common.py b/addons/payment/tests/http_common.py index 8c5daa76e04..51d44a9057b 100644 --- a/addons/payment/tests/http_common.py +++ b/addons/payment/tests/http_common.py @@ -1,12 +1,13 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import json +from contextlib import contextmanager from uuid import uuid4 from lxml import etree, objectify from werkzeug import urls -from odoo.tests import HttpCase +from odoo.tests import HttpCase, JsonRpcException from odoo.addons.payment.tests.common import PaymentCommon @@ -69,20 +70,11 @@ class PaymentHttpCommon(PaymentCommon, HttpCase): """ return self.opener.post(url, json=data) - def _make_json_rpc_request(self, url, data=None): - """ Make a JSON-RPC request to the provided URL. - - :param str url: The URL to make the request to - :param dict data: The data to be send in the request body in JSON-RPC 2.0 format - :return: The response of the request - :rtype: :class:`requests.models.Response` - """ - return self.opener.post(url, json={ - 'jsonrpc': '2.0', - 'method': 'call', - 'id': str(uuid4()), - 'params': data, - }) + @contextmanager + def _assertNotFound(self): + with self.assertRaises(JsonRpcException) as cm: + yield + self.assertEqual(cm.exception.code, 404) def _get_tx_context(self, response, form_name): """Extracts txContext & other form info (provider & token ids) @@ -226,15 +218,7 @@ class PaymentHttpCommon(PaymentCommon, HttpCase): """ uri = '/payment/transaction' url = self._build_url(uri) - response = self._make_json_rpc_request(url, route_kwargs) - self.assertEqual(response.status_code, 200) # Check the request went through. - - return response + return self.make_jsonrpc_request(url, route_kwargs) def _get_processing_values(self, **route_kwargs): - response = self._portal_transaction(**route_kwargs) - - self.assertEqual(response.status_code, 200) - - resp_content = json.loads(response.content) - return resp_content['result'] + return self._portal_transaction(**route_kwargs) diff --git a/addons/payment/tests/test_flows.py b/addons/payment/tests/test_flows.py index 2d8abc9799e..e5355360d2b 100644 --- a/addons/payment/tests/test_flows.py +++ b/addons/payment/tests/test_flows.py @@ -5,7 +5,7 @@ from unittest.mock import patch from freezegun import freeze_time -from odoo.tests import tagged +from odoo.tests import tagged, JsonRpcException from odoo.tools import mute_logger from odoo.addons.payment.controllers.portal import PaymentPortal @@ -287,22 +287,19 @@ class TestFlows(PaymentHttpCommon): 'landing_route': 'whatever', }) # Transaction step with a wrong flow --> UserError - with mute_logger('odoo.http'): - response = self._portal_transaction(**transaction_values) - self.assertIn( - "odoo.exceptions.UserError: The payment should either be direct, with redirection, or made by a token.", - response.text) + with mute_logger("odoo.http"), self.assertRaises( + JsonRpcException, + msg='odoo.exceptions.UserError: The payment should either be direct, with redirection, or made by a token.', + ): + self._portal_transaction(**transaction_values) def test_transaction_wrong_token(self): route_values = self._prepare_pay_values() route_values['access_token'] = "abcde" # Transaction step with a wrong access token --> ValidationError - with mute_logger('odoo.http'): - response = self._portal_transaction(**route_values) - self.assertIn( - "odoo.exceptions.ValidationError: The access token is invalid.", - response.text) + with mute_logger('odoo.http'), self.assertRaises(JsonRpcException, msg='odoo.exceptions.ValidationError: The access token is invalid.'): + self._portal_transaction(**route_values) def test_access_disabled_providers_tokens(self): self.partner = self.portal_partner diff --git a/addons/payment/tests/test_multicompany_flows.py b/addons/payment/tests/test_multicompany_flows.py index 416bebd4a8f..8c23fda264e 100644 --- a/addons/payment/tests/test_multicompany_flows.py +++ b/addons/payment/tests/test_multicompany_flows.py @@ -128,6 +128,6 @@ class TestMultiCompanyFlows(PaymentHttpCommon): # Archive token in company A url = self._build_url('/payment/archive_token') - self._make_json_rpc_request(url, {'token_id': token.id}) + self.make_jsonrpc_request(url, {'token_id': token.id}) self.assertFalse(token.active) diff --git a/addons/payment_demo/tests/test_processing_flows.py b/addons/payment_demo/tests/test_processing_flows.py index ee6ff45fe44..cb6c6837e51 100644 --- a/addons/payment_demo/tests/test_processing_flows.py +++ b/addons/payment_demo/tests/test_processing_flows.py @@ -20,5 +20,5 @@ class TestProcessingFlows(PaymentDemoCommon, PaymentHttpCommon): 'odoo.addons.payment.models.payment_transaction.PaymentTransaction' '._handle_notification_data' ) as handle_notification_data_mock: - self._make_json_rpc_request(url, data=self.notification_data) + self.make_jsonrpc_request(url, params=self.notification_data) self.assertEqual(handle_notification_data_mock.call_count, 1) diff --git a/addons/pos_online_payment/tests/online_payment_common.py b/addons/pos_online_payment/tests/online_payment_common.py index 15d9beb154c..2ab190f17e4 100644 --- a/addons/pos_online_payment/tests/online_payment_common.py +++ b/addons/pos_online_payment/tests/online_payment_common.py @@ -23,10 +23,7 @@ class OnlinePaymentCommon(PaymentHttpCommon): def _fake_request_pos_order_pay_transaction_page(self, pos_order_id, route_values): uri = f'/pos/pay/transaction/{pos_order_id}' url = self._build_url(uri) - response = self._make_json_rpc_request(url, route_values) - self.assertEqual(response.status_code, 200) - resp_content = json.loads(response.content) - return resp_content['result'] + return self.make_jsonrpc_request(url, route_values) def _fake_open_pos_order_pay_confirmation_page(self, pos_order_id, access_token, tx_id): self._fake_http_get_request(PaymentPortal._get_landing_route(pos_order_id, access_token, tx_id=tx_id)) diff --git a/addons/test_discuss_full/tests/test_performance.py b/addons/test_discuss_full/tests/test_performance.py index 15695878a0c..13e8424a72e 100644 --- a/addons/test_discuss_full/tests/test_performance.py +++ b/addons/test_discuss_full/tests/test_performance.py @@ -3,17 +3,19 @@ from datetime import date from dateutil.relativedelta import relativedelta +from unittest.mock import patch, PropertyMock from odoo import Command -from odoo.tests.common import users, tagged, TransactionCase, warmup +from odoo.tests.common import users, tagged, HttpCase, warmup from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT @tagged('post_install', '-at_install') -class TestDiscussFullPerformance(TransactionCase): +class TestDiscussFullPerformance(HttpCase): def setUp(self): super().setUp() self.group_user = self.env.ref('base.group_user') + self.password = 'Pl1bhD@2!kXZ' self.users = self.env['res.users'].create([ { 'email': 'e.e@example.com', @@ -24,7 +26,7 @@ class TestDiscussFullPerformance(TransactionCase): 'odoobot_state': 'disabled', 'signature': '--\nErnest', }, - {'name': 'test1', 'login': 'test1', 'email': 'test1@example.com'}, + {'name': 'test1', 'login': 'test1', 'password': self.password, 'email': 'test1@example.com', 'country_id': self.env.ref('base.in').id}, {'name': 'test2', 'login': 'test2', 'email': 'test2@example.com'}, {'name': 'test3', 'login': 'test3'}, {'name': 'test4', 'login': 'test4'}, @@ -82,9 +84,20 @@ class TestDiscussFullPerformance(TransactionCase): # create livechats im_livechat_channel = self.env['im_livechat.channel'].sudo().create({'name': 'support', 'user_ids': [Command.link(self.users[0].id)]}) self.env['bus.presence'].create({'user_id': self.users[0].id, 'status': 'online'}) # make available for livechat (ignore leave) - self.channel_livechat_1 = self.env['discuss.channel'].browse(im_livechat_channel._open_livechat_discuss_channel(anonymous_name='anon 1', previous_operator_id=self.users[0].partner_id.id, user_id=self.users[1].id, country_id=self.env.ref('base.in').id)['id']) + self.authenticate('test1', self.password) + self.channel_livechat_1 = self.env['discuss.channel'].browse(self.make_jsonrpc_request("/im_livechat/get_session", { + 'anonymous_name': 'anon 1', + 'channel_id': im_livechat_channel.id, + 'previous_operator_id': self.users[0].partner_id.id, + })['id']) self.channel_livechat_1.with_user(self.users[1]).message_post(body="test") - self.channel_livechat_2 = self.env['discuss.channel'].browse(im_livechat_channel.with_user(self.env.ref('base.public_user'))._open_livechat_discuss_channel(anonymous_name='anon 2', previous_operator_id=self.users[0].partner_id.id, country_id=self.env.ref('base.be').id)['id']) + self.authenticate(None, None) + with patch("odoo.http.GeoIP.country_code", new_callable=PropertyMock(return_value=self.env.ref('base.be').code)): + self.channel_livechat_2 = self.env['discuss.channel'].browse(self.make_jsonrpc_request("/im_livechat/get_session", { + 'anonymous_name': 'anon 2', + 'channel_id': im_livechat_channel.id, + 'previous_operator_id': self.users[0].partner_id.id, + })['id']) self.channel_livechat_2.with_user(self.env.ref('base.public_user')).sudo().message_post(body="test") # add needaction self.users[0].notification_type = 'inbox' @@ -830,7 +843,11 @@ class TestDiscussFullPerformance(TransactionCase): 'persona': { 'partner': { 'active': True, - 'country': [('clear',)], + 'country': { + 'code': 'IN', + 'id': self.env.ref('base.in').id, + 'name': 'India', + }, 'id': self.users[1].partner_id.id, 'is_bot': False, 'is_public': False, @@ -844,7 +861,7 @@ class TestDiscussFullPerformance(TransactionCase): 'memberCount': 2, 'message_unread_counter': 0, }, - 'create_uid': self.env.user.id, + 'create_uid': self.users[1].id, 'defaultDisplayMode': False, 'description': False, 'group_based_subscription': False, @@ -1016,4 +1033,4 @@ class TestDiscussFullPerformance(TransactionCase): Returns the expected query count. The point of having a separate getter is to allow it to be overriden. """ - return 68 + return 71 diff --git a/addons/test_mail_full/tests/test_portal.py b/addons/test_mail_full/tests/test_portal.py index 09c7492359e..3bf475ebd93 100644 --- a/addons/test_mail_full/tests/test_portal.py +++ b/addons/test_mail_full/tests/test_portal.py @@ -81,24 +81,13 @@ class TestPortalControllers(TestPortal): """Test retrieving chatter messages through the portal controller""" self.authenticate(None, None) message_fetch_url = '/mail/chatter_fetch' - payload = json.dumps({ - 'jsonrpc': '2.0', - 'method': 'call', - 'id': 0, - 'params': { + + def get_chatter_message_count(): + return self.make_jsonrpc_request(message_fetch_url, { 'res_model': 'mail.test.portal', 'res_id': self.record_portal.id, 'token': self.record_portal.access_token, - }, - }) - - def get_chatter_message_count(): - res = self.url_open( - url=message_fetch_url, - data=payload, - headers={'Content-Type': 'application/json'} - ) - return res.json().get('result', {}).get('message_count', 0) + }).get('message_count', 0) self.assertEqual(get_chatter_message_count(), 0) diff --git a/addons/website_livechat/tests/test_chatbot_ui.py b/addons/website_livechat/tests/test_chatbot_ui.py index b87ffce0a17..3bc29a409e8 100644 --- a/addons/website_livechat/tests/test_chatbot_ui.py +++ b/addons/website_livechat/tests/test_chatbot_ui.py @@ -7,7 +7,7 @@ from odoo.addons.website_livechat.tests.common import TestLivechatCommon @tests.tagged('post_install', '-at_install') -class TestLivechatChatbotUI(tests.HttpCase, TestLivechatCommon, ChatbotCase): +class TestLivechatChatbotUI(TestLivechatCommon, ChatbotCase): def setUp(self): super().setUp() self.env['im_livechat.channel'].search([ diff --git a/addons/website_sale/tests/test_delivery_express_checkout_flows.py b/addons/website_sale/tests/test_delivery_express_checkout_flows.py index 890b581601f..a0ec1e5101b 100644 --- a/addons/website_sale/tests/test_delivery_express_checkout_flows.py +++ b/addons/website_sale/tests/test_delivery_express_checkout_flows.py @@ -11,6 +11,7 @@ from odoo.tests import tagged from odoo.addons.base.tests.common import HttpCaseWithUserDemo from odoo.addons.website_sale.controllers.delivery import WebsiteSaleDelivery as WebsiteSaleDeliveryController +from odoo.addons.website_sale.controllers.main import WebsiteSale @tagged('post_install', '-at_install') @@ -107,31 +108,6 @@ class TestWebsiteSaleDeliveryExpressCheckoutFlows(HttpCaseWithUserDemo): 'state': self.user_demo.partner_id.state_id.code, } - def _make_json_rpc_request(self, url, data=None): - """ Make a JSON-RPC request to the provided URL. - - :param str url: The URL to make the request to - :param dict data: The data to be send in the request body in JSON-RPC 2.0 format - :return dict: The result of the JSON-RPC request - """ - rpc_request = { - "jsonrpc": "2.0", - "method": "call", - "id": str(uuid4()), - "params": data, - } - - result = self.url_open( - url, - data=json.dumps(rpc_request).encode(), - headers={"Content-Type": "application/json"}, - ) - - if not result.ok: - return {} - - return result.json().get("result", {}) - def test_express_checkout_public_user_shipping_address_change(self): """ Test that when using express checkout as a public user and selecting a shipping address, a new partner is created if the partner of the SO is the public partner. @@ -143,10 +119,10 @@ class TestWebsiteSaleDeliveryExpressCheckoutFlows(HttpCaseWithUserDemo): 'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment', return_value=self.rate_shipment_result ): - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_shipping_route - ), data={ + ), params={ 'partial_shipping_address': dict(self.express_checkout_anonymized_shipping_values) } ) @@ -175,18 +151,18 @@ class TestWebsiteSaleDeliveryExpressCheckoutFlows(HttpCaseWithUserDemo): 'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment', return_value=self.rate_shipment_result ): - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_shipping_route - ), data={ + ), params={ 'partial_shipping_address': dict(self.express_checkout_anonymized_shipping_values) } ) new_partner = self.sale_order.partner_shipping_id - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_shipping_route - ), data={ + ), params={ 'partial_shipping_address': dict(self.express_checkout_anonymized_shipping_values_2) } ) @@ -213,10 +189,10 @@ class TestWebsiteSaleDeliveryExpressCheckoutFlows(HttpCaseWithUserDemo): 'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment', return_value=self.rate_shipment_result ): - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_shipping_route - ), data={ + ), params={ 'partial_shipping_address': dict(self.express_checkout_anonymized_shipping_values) } ) @@ -235,10 +211,10 @@ class TestWebsiteSaleDeliveryExpressCheckoutFlows(HttpCaseWithUserDemo): 'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment', return_value=self.rate_shipment_result ): - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_shipping_route - ), data={ + ), params={ 'partial_shipping_address': dict(self.express_checkout_anonymized_shipping_values) } ) @@ -269,18 +245,18 @@ class TestWebsiteSaleDeliveryExpressCheckoutFlows(HttpCaseWithUserDemo): 'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment', return_value=self.rate_shipment_result ): - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_shipping_route - ), data={ + ), params={ 'partial_shipping_address': dict(self.express_checkout_anonymized_shipping_values) } ) new_partner = self.sale_order.partner_shipping_id - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_shipping_route - ), data={ + ), params={ 'partial_shipping_address': dict(self.express_checkout_anonymized_shipping_values_2) } ) @@ -325,16 +301,16 @@ class TestWebsiteSaleDeliveryExpressCheckoutFlows(HttpCaseWithUserDemo): 'odoo.addons.delivery.models.delivery_carrier.DeliveryCarrier.rate_shipment', return_value=self.rate_shipment_result ): - shipping_options = self._make_json_rpc_request( + shipping_options = self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_shipping_route - ), data={ + ), params={ 'partial_shipping_address': dict(self.express_checkout_anonymized_demo_shipping_values) } ) self.assertEqual(self.sale_order.partner_id.id, self.user_demo.partner_id.id) - self._make_json_rpc_request(urls.url_join(self.base_url(), '/shop/express'), data={ + self.make_jsonrpc_request(urls.url_join(self.base_url(), WebsiteSale._express_checkout_route), params={ 'billing_address': dict(self.express_checkout_billing_values), 'shipping_address': dict(self.express_checkout_demo_shipping_values), 'shipping_option': shipping_options[0], @@ -355,10 +331,10 @@ class TestWebsiteSaleDeliveryExpressCheckoutFlows(HttpCaseWithUserDemo): ): # Won't create a new partner because the partial information are the same the an # exisiting partner linked to the SO - shipping_options = self._make_json_rpc_request( + shipping_options = self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_shipping_route - ), data={ + ), params={ 'partial_shipping_address': dict(self.express_checkout_anonymized_demo_shipping_values) } ) @@ -366,11 +342,11 @@ class TestWebsiteSaleDeliveryExpressCheckoutFlows(HttpCaseWithUserDemo): # Will create a new partner because the complete shipping information are different than # the partner actually selected. - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleDeliveryController._express_checkout_route - ), data={ + ), params={ 'billing_address': dict(self.express_checkout_billing_values), 'shipping_address': dict(self.express_checkout_demo_shipping_values_2), 'shipping_option': shipping_options[0], diff --git a/addons/website_sale/tests/test_express_checkout_flows.py b/addons/website_sale/tests/test_express_checkout_flows.py index b1a6119b4cc..dd798a159a5 100644 --- a/addons/website_sale/tests/test_express_checkout_flows.py +++ b/addons/website_sale/tests/test_express_checkout_flows.py @@ -47,41 +47,16 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo): 'state': 'AL', } - def _make_json_rpc_request(self, url, data=None): - """ Make a JSON-RPC request to the provided URL. - - :param str url: The URL to make the request to - :param dict data: The data to be send in the request body in JSON-RPC 2.0 format - :return dict: The result of the JSON-RPC request - """ - rpc_request = { - "jsonrpc": "2.0", - "method": "call", - "id": str(uuid4()), - "params": data, - } - - result = self.url_open( - url, - data=json.dumps(rpc_request).encode(), - headers={"Content-Type": "application/json"}, - ) - - if not result.ok: - return {} - - return result.json().get("result", {}) - def test_express_checkout_public_user(self): """ Test that when using express checkout as a public user, a new partner is created. """ session = self.authenticate(None, None) session['sale_order_id'] = self.sale_order.id root.session_store.save(session) - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleController._express_checkout_route - ), data={ + ), params={ 'billing_address': dict(self.express_checkout_billing_values) } ) @@ -105,10 +80,10 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo): session['sale_order_id'] = self.sale_order.id root.session_store.save(session) - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleController._express_checkout_route - ), data={ + ), params={ 'billing_address': { 'name': self.user_demo.partner_id.name, 'email': self.user_demo.partner_id.email, @@ -152,10 +127,10 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo): session['sale_order_id'] = self.sale_order.id root.session_store.save(session) - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleController._express_checkout_route - ), data={ + ), params={ 'billing_address': dict(self.express_checkout_billing_values) } ) @@ -173,10 +148,10 @@ class TestWebsiteSaleExpressCheckoutFlows(HttpCaseWithUserDemo): session['sale_order_id'] = self.sale_order.id root.session_store.save(session) - self._make_json_rpc_request( + self.make_jsonrpc_request( urls.url_join( self.base_url(), WebsiteSaleController._express_checkout_route - ), data={ + ), params={ 'billing_address': dict(self.express_checkout_billing_values) } ) diff --git a/odoo/tests/common.py b/odoo/tests/common.py index a52c017f13f..e562c4abbd6 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -1610,6 +1610,12 @@ class Transport(xmlrpclib.Transport): return super().request(*args, **kwargs) +class JsonRpcException(Exception): + def __init__(self, code, message): + super().__init__(message) + self.code = code + + class HttpCase(TransactionCase): """ Transactional HTTP TestCase with url_open and Chrome headless helpers. """ registry_test_mode = True @@ -1812,6 +1818,34 @@ class HttpCase(TransactionCase): return sup.profile(description=request.httprequest.full_path) return profiler.Nested(_profiler, patch('odoo.http.Request._get_profiler_context_manager', route_profiler)) + def make_jsonrpc_request(self, route, params=None, headers=None): + """Make a JSON-RPC request to the server. + + :param str route: the route to request + :param dict params: the parameters to send + :raises requests.HTTPError: if one occurred + :raises JsonRpcException: if the response contains an error + :return: The 'result' key from the response if any. + """ + data = json.dumps({ + 'id': 0, + 'jsonrpc': '2.0', + 'method': 'call', + 'params': params, + }).encode() + headers = headers or {} + headers['Content-Type'] = 'application/json' + response = self.url_open(route, data, headers=headers) + response.raise_for_status() + decoded_response = response.json() + if 'result' in decoded_response: + return decoded_response['result'] + if 'error' in decoded_response: + raise JsonRpcException( + code=decoded_response['error']['code'], + message=decoded_response['error']['data']['name'] + ) + def no_retry(arg): """Disable auto retry on decorated test method or test class"""