# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import json import socket import urllib.parse import odoo from odoo.addons.mail.tests.common import mail_new_test_user from odoo.addons.sms.tests.common import SMSCommon from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE from odoo.tests import tagged from markupsafe import Markup from unittest.mock import patch @tagged('mail_push') class TestMailMobile(SMSCommon): @classmethod def setUpClass(cls): super(TestMailMobile, cls).setUpClass() cls.original_domain = cls.env['ir.config_parameter'].sudo().get_param('web.base.url') cls.env['ir.config_parameter'].sudo().set_param('web.base.url', 'http://yourcompany.odoo.com') @classmethod def tearDownClass(cls): cls.env['ir.config_parameter'].sudo().set_param('web.base.url', cls.original_domain) super(TestMailMobile, cls).tearDownClass() def test_override_url_in_mail(self): url = self.env['mail.thread']._notify_get_action_link('view', model='mail.activity', res_id=1) original_expected_link = 'http://yourcompany.odoo.com/mail/view?model=mail.activity&res_id=1' expected_url = 'https://redirect-url.email/?link={0}&apn={1}&afl={0}&ibi={1}&ifl={0}'.format( urllib.parse.quote(original_expected_link, safe=''), 'com.odoo.mobile', ) self.assertEqual(url, expected_url) def test_blacklist_not_override_url_in_mail(self): url = self.env['mail.thread']._notify_get_action_link('view', model='mail.activity', res_id=1, access_token='test') self.assertFalse(url.startswith('https://redirect-url.email/')) @tagged('post_install', '-at_install', 'mail_push') class TestPushNotification(SMSCommon): @classmethod def setUpClass(cls): super(TestPushNotification, cls).setUpClass() sudo_icp = cls.env['ir.config_parameter'].sudo() sudo_icp.set_param('odoo_ocn.project_id', 'Test') sudo_icp.set_param('mail_mobile.enable_ocn', True) channel = cls.env['discuss.channel'].with_context(cls._test_context) cls.user_email = cls.user_employee cls.user_email.notification_type = 'email' cls.user_email.partner_id.ocn_token = 'Test OCN Email' cls.user_inbox = mail_new_test_user( cls.env, login='user_inbox', groups='base.group_user', name='User Inbox', notification_type='inbox' ) cls.user_inbox.partner_id.ocn_token = 'Test OCN Inbox' cls.record_simple = cls.env['mail.test.simple'].with_context(cls._test_context).create({ 'name': 'Test', 'email_from': 'ignasse@example.com' }) cls.record_simple.message_subscribe(partner_ids=[ cls.user_email.partner_id.id, cls.user_inbox.partner_id.id, ]) cls.alias_gateway = cls.env['mail.alias'].create({ 'alias_contact': 'everyone', 'alias_domain': cls.mail_alias_domain.id, 'alias_model_id': cls.env['ir.model']._get_id('mail.test.gateway.company'), 'alias_name': 'alias.gateway', }) cls.direct_message_channel = channel.with_user(cls.user_email).create({ 'channel_partner_ids': [ (4, cls.user_email.partner_id.id), (4, cls.user_inbox.partner_id.id), ], 'channel_type': 'chat', 'name': "Direct Message", }) cls.group_channel = cls.env['discuss.channel']._create_channel(name='Channel', group_id=None) cls.group_channel._add_members(users=cls.user_email | cls.user_inbox) @patch('odoo.addons.mail_mobile.models.mail_thread.iap_jsonrpc') def test_push_notifications(self, jsonrpc): # Test No Inbox Condition self.record_simple.with_user(self.user_inbox).message_notify( partner_ids=self.user_email.partner_id.ids, body="Test", subject="Test Activity", ) jsonrpc.assert_not_called() self.record_simple.with_user(self.user_email).message_notify( partner_ids=self.user_inbox.partner_id.ids, body="Test message send via OCN", subject="Test Activity", ) jsonrpc.assert_called_once() self.assertEqual(jsonrpc.call_args[1]['params']['data']['model'], 'mail.test.simple') self.assertEqual(jsonrpc.call_args[1]['params']['data']['res_id'], self.record_simple.id) self.assertEqual(jsonrpc.call_args[1]['params']['data']['author_name'], self.user_email.name) self.assertIn( "Test message send via OCN", jsonrpc.call_args[1]['params']['data']['body'], 'The body must be "Test message send via OCN"' ) # Reset the mock counter jsonrpc.reset_mock() # Test Tracking Message mail_test_ticket = self.env['mail.test.ticket'].with_context(self._test_context) record_full = mail_test_ticket.with_user(self.user_email).create({ 'name': 'Test', }) record_full = record_full.with_context(mail_notrack=False) container = self.env['mail.test.container'].create({'name': 'Container'}) record_full.message_subscribe( partner_ids=[self.user_email.partner_id.id], subtype_ids=[self.env.ref('test_mail.st_mail_test_ticket_container_upd').id], ) record_full.write({ 'name': 'Test2', 'email_from': 'noone@example.com', 'container_id': container.id, }) self.flush_tracking() jsonrpc.assert_not_called() container2 = self.env['mail.test.container'].create({'name': 'Container Two'}) record_full.message_subscribe( partner_ids=[self.user_inbox.partner_id.id], subtype_ids=[self.env.ref('test_mail.st_mail_test_ticket_container_upd').id], ) record_full.write({ 'name': 'Test3', 'email_from': 'noone@example.com', 'container_id': container2.id, }) self.flush_tracking() jsonrpc.assert_called_once() # As the tracking values are converted to text. We check the '→' added by ocn_client. self.assertIn( '→', jsonrpc.call_args[1]['params']['data']['body'], 'No Tracking Message found' ) @patch('odoo.addons.mail_mobile.models.mail_thread.iap_jsonrpc') def test_push_notifications_android_channel(self, jsonrpc): # Test Direct Message self.direct_message_channel.with_user(self.user_email).message_post( body="Test", message_type='comment', subtype_xmlid='mail.mt_comment') self.assertEqual( jsonrpc.call_args[1]['params']['data']['android_channel_id'], 'DirectMessage', 'The type of Android channel must be DirectMessage' ) # FIXME: mobile apps use old "mail.channel" and cannot be changed on iOS self.assertEqual( jsonrpc.call_args[1]['params']['data']['model'], 'mail.channel', 'The model must be mail.channel (for Mobile App)' ) # Reset the mock counter jsonrpc.reset_mock() # Test Following Message self.record_simple.with_user(self.user_email).message_post( body="Test", message_type='comment', subtype_xmlid="mail.mt_comment" ) self.assertEqual( jsonrpc.call_args[1]['params']['data']['android_channel_id'], 'Following', 'The type of Android channel must be Following' ) # Reset the mock counter jsonrpc.reset_mock() # Test Channel Message self.group_channel.with_user(self.user_email).message_post( body="Test", partner_ids=self.user_inbox.partner_id.ids, message_type='comment', subtype_xmlid='mail.mt_comment') self.assertEqual( jsonrpc.call_args[1]['params']['data']['android_channel_id'], 'ChannelMessage', 'The type of Android channel must be ChannelMessage' ) # Reset the mock counter jsonrpc.reset_mock() # Test AtMention Message self.record_simple.with_user(self.user_email).message_post( body=Markup('@user') % self.user_inbox.partner_id.id, message_type='comment', subtype_xmlid="mail.mt_comment" ) self.assertEqual( jsonrpc.call_args[1]['params']['data']['android_channel_id'], 'AtMention', 'The type of Android channel must be AtMention' ) @patch('odoo.addons.mail_mobile.models.mail_thread.iap_jsonrpc') def test_push_notifications_mail_replay(self, jsonrpc): with self.mock_mail_gateway(): test_record = self.format_and_process( MAIL_TEMPLATE, self.user_email.email_formatted, f'{self.alias_gateway.display_name}, {self.user_inbox.email_formatted}', subject='Test Record Creation', target_model='mail.test.gateway.company', ) self.assertEqual(len(test_record.message_ids), 1) self.assertEqual(test_record.message_partner_ids, self.user_email.partner_id) test_record.message_subscribe(partner_ids=[self.user_inbox.partner_id.id]) for include_as_external, has_notif in ((False, True), (True, False)): with self.mock_mail_gateway(): to = f'{self.alias_gateway.display_name}' if include_as_external: to += f', {self.user_inbox.email_formatted}' self.format_and_process( MAIL_TEMPLATE, self.user_email.email_formatted, to, subject='Repy By Email', extra='In-Reply-To:\r\n\t%s\n' % test_record.message_ids[-1].message_id, ) if has_notif: # user_inbox is notified by Odoo, hence receives a push notification jsonrpc.assert_called_once() self.assertEqual(jsonrpc.call_args[1]['params']['data']['author_name'], self.user_email.name) self.assertIn( "Please call me as soon as possible this afternoon!\n\n--\nSylvie", jsonrpc.call_args[1]['params']['data']['body'], 'The body must contain the text send by mail' ) else: jsonrpc.assert_not_called() jsonrpc.reset_mock() @patch('odoo.addons.mail_mobile.models.mail_thread.iap_jsonrpc') @patch.object(odoo.addons.mail.models.mail_thread, 'push_to_end_point') def test_push_notifications_whatsapp(self, push_to_end_point, iap_jsonrpc): """ Verifies that the system correctly triggers web push notifications upon posting a WhatsApp message. Notifications can either be sent immediately or scheduled via cron. At the time this test was written, notifications are sent directly if the number of recipient devices is fewer than MAX_DIRECT_PUSH. """ self.env['mail.push.device'].get_web_push_vapid_public_key() self.vapid_public_key = self.env['mail.push.device'].get_web_push_vapid_public_key() mail_push_device = self.env['mail.push.device'].sudo().create([{ 'endpoint': 'https://test.odoo.com/webpush/user1', 'expiration_time': None, 'keys': json.dumps({ 'p256dh': 'BGbhnoP_91U7oR59BaaSx0JnDv2oEooYnJRV2AbY5TBeKGCRCf0HcIJ9bOKchUCDH4cHYWo9SYDz3U-8vSxPL_A', 'auth': 'DJFdtAgZwrT6yYkUMgUqow' }), 'partner_id': self.user_inbox.partner_id.id, }]) self.direct_message_channel.with_user(self.user_email).message_post( body=('This whatsapp message should generate web push notification'), message_type='whatsapp_message', subtype_xmlid='mail.mt_comment' ) self.assertEqual(self.env['mail.push'].search_count([]), 0, 'Notification scheduled with cron, expected only direct notification') self.assertEqual(push_to_end_point.call_args.kwargs.get('device')['id'], mail_push_device.id, 'Web push notification (from whatsapp_message) was not sent to the right device') push_to_end_point.assert_called_once() push_to_end_point.reset_mock() self.direct_message_channel.with_user(self.user_email).message_post( body=('This comment message should generate any web push notifications'), message_type='comment', subtype_xmlid='mail.mt_comment' ) self.assertEqual(self.env['mail.push'].search_count([]), 0, 'Notification scheduled with cron, expected only direct notification') self.assertEqual(push_to_end_point.call_args.kwargs.get('device')['id'], mail_push_device.id, 'Web push notification (from comment) was not sent to the right device') push_to_end_point.assert_called_once() push_to_end_point.reset_mock() self.direct_message_channel.with_user(self.user_email).message_post( body=('This email message should not generate any web push notifications'), message_type='email', subtype_xmlid='mail.mt_comment' ) self.assertFalse(push_to_end_point.called, 'Email message should not generate any web push notifications')