From f11f9f221ee470704cdd1f30bf0bc5e6ad605cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 11 May 2023 10:39:51 +0000 Subject: [PATCH] [IMP] test_mail_full: add performance tests for portal_message_format Purpose of this commit is to add performance tests for 'portal_message_format' that is used when displaying chatter in frontend (e.g. customer portal). It allows to keep an eye and to optimize part of that code allowing cross-apps optimization. Task-3322905 Part-of: odoo/odoo#121104 --- .../models/test_mail_models_mail.py | 1 - .../tests/test_mail_performance.py | 264 ++++++++++++++++-- 2 files changed, 243 insertions(+), 22 deletions(-) diff --git a/addons/test_mail_full/models/test_mail_models_mail.py b/addons/test_mail_full/models/test_mail_models_mail.py index 12db1ac29c8..7deea6c1d87 100644 --- a/addons/test_mail_full/models/test_mail_models_mail.py +++ b/addons/test_mail_full/models/test_mail_models_mail.py @@ -104,7 +104,6 @@ class MailTestRatingThread(models.Model): - partner_id: value returned by the base _rating_get_partner method - user_id: value returned by the base _rating_get_operator method """ - _description = 'Model for testing rating without the rating mixin' _name = 'mail.test.rating.thread' _inherit = 'mail.thread' diff --git a/addons/test_mail_full/tests/test_mail_performance.py b/addons/test_mail_full/tests/test_mail_performance.py index eb36ab99bd5..c9b44bec40d 100644 --- a/addons/test_mail_full/tests/test_mail_performance.py +++ b/addons/test_mail_full/tests/test_mail_performance.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from datetime import datetime, timedelta from markupsafe import Markup + from odoo.addons.mail.tests.common import mail_new_test_user from odoo.addons.test_mail.tests.test_performance import BaseMailPerformance from odoo.tests.common import users, warmup @@ -10,11 +12,11 @@ from odoo.tools import mute_logger @tagged('mail_performance', 'post_install', '-at_install') -class TestMailPerformance(BaseMailPerformance): +class FullBaseMailPerformance(BaseMailPerformance): @classmethod def setUpClass(cls): - super(TestMailPerformance, cls).setUpClass() + super().setUpClass() # users / followers cls.user_emp_email = mail_new_test_user( @@ -38,36 +40,43 @@ class TestMailPerformance(BaseMailPerformance): name='Paul Portal', ) cls.customers = cls.env['res.partner'].create([ - {'country_id': cls.env.ref('base.be').id, - 'email': 'customer.full.test.1@example.com', - 'name': 'Test Full Customer 1', - 'mobile': '0456112233', - 'phone': '0456112233', + { + 'country_id': cls.env.ref('base.be').id, + 'email': f'customer.full.test.{idx}@example.com', + 'name': f'Test Full Customer {idx}', + 'mobile': f'045600000{idx}', + 'phone': f'045611111{idx}', + } for idx in range(5) + ]) + cls.test_users = cls.user_employee + cls.user_test + cls.user_test_email + cls.user_emp_email + cls.user_portal + + # records + cls.record_containers = cls.env['mail.test.container.mc'].create([ + { + 'alias_name': 'test-alias-0', + 'customer_id': cls.customers[0].id, + 'name': 'Test Container 1', }, - {'country_id': cls.env.ref('base.be').id, - 'email': 'customer.full.test.2@example.com', - 'name': 'Test Full Customer 2', - 'mobile': '0456223344', - 'phone': '0456112233', + { + 'alias_name': 'test-alias-1', + 'customer_id': cls.customers[1].id, + 'name': 'Test Container 2', }, ]) - - # record - cls.record_container = cls.env['mail.test.container.mc'].create({ - 'alias_name': 'test-alias', - 'customer_id': cls.customer.id, - 'name': 'Test Container', - }) cls.record_ticket = cls.env['mail.test.ticket.mc'].create({ 'email_from': 'email.from@test.example.com', - 'container_id': cls.record_container.id, + 'container_id': cls.record_containers[0].id, 'customer_id': False, 'name': 'Test Ticket', 'user_id': cls.user_emp_email.id, }) cls.record_ticket.message_subscribe(cls.customers.ids + cls.user_admin.partner_id.ids + cls.user_portal.partner_id.ids) - def test_initial_values(self): + +@tagged('mail_performance', 'post_install', '-at_install') +class TestMailPerformance(FullBaseMailPerformance): + + def test_assert_initial_values(self): """ Simply ensure some values through all tests """ record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket.ids) self.assertEqual(record_ticket.message_partner_ids, @@ -95,3 +104,216 @@ class TestMailPerformance(BaseMailPerformance): new_message.notified_partner_ids, self.user_emp_email.partner_id + self.user_admin.partner_id + self.customers + self.user_portal.partner_id ) + + +@tagged('mail_performance', 'post_install', '-at_install') +class TestPortalFormatPerformance(FullBaseMailPerformance): + """Test performance of `portal_message_format` with multiple messages + with multiple attachments, with ratings. + + Those messages might not make sense functionally but they are crafted to + cover as much of the code as possible in regard to number of queries. + + Setup : + * 5 records (self.containers -> 5 mail.test.rating records, with + a different customer_id each) + * 2 messages / record + * 2 attachments / message + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # rating-enabled test records + cls.record_ratings = cls.env['mail.test.rating'].create([ + { + 'customer_id': cls.customers[idx].id, + 'name': f'TestRating_{idx}', + 'user_id': cls.test_users[idx].id, + + } + for idx in range(5) + ]) + + # messages and ratings + user_id_field = cls.env['ir.model.fields']._get(cls.record_ratings._name, 'user_id') + comment_subtype_id = cls.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment') + cls.messages_all = cls.env['mail.message'].sudo().create([ + { + 'attachment_ids': [ + (0, 0, { + 'datas': 'data', + 'name': f'Test file {att_idx}', + 'res_id': record.id, + 'res_model': record._name, + }) + for att_idx in range(2) + ], + 'author_id': record.customer_id.id, + 'body': f'

Test {msg_idx}

', + 'date': datetime(2023, 5, 15, 10, 30, 5), + 'email_from': record.customer_id.email_formatted, + 'link_preview_ids': [ + (0, 0, { + 'source_url': 'https://www.odoo.com', + }), (0, 0, { + 'source_url': 'https://www.example.com', + }), + ], + 'notification_ids': [ + (0, 0, { + 'is_read': False, + 'notification_type': 'inbox', + 'res_partner_id': cls.customers[(msg_idx * 2)].id, + }), + (0, 0, { + 'is_read': True, + 'notification_type': 'email', + 'notification_status': 'sent', + 'res_partner_id': cls.customers[(msg_idx * 2) + 1].id, + }), + ], + 'message_type': 'comment', + 'model': record._name, + 'partner_ids': [ + (4, cls.customers[(msg_idx * 2)].id), + (4, cls.customers[record_idx].id), + ], + 'reaction_ids': [ + (0, 0, { + 'content': 'https://www.odoo.com', + 'partner_id': cls.customers[(msg_idx * 2) + 1].id + }), (0, 0, { + 'content': 'https://www.example.com', + 'partner_id': cls.customers[record_idx].id + }), + ], + 'res_id': record.id, + 'subject': f'Test Rating {msg_idx}', + 'subtype_id': comment_subtype_id, + 'starred_partner_ids': [ + (4, cls.customers[(msg_idx * 2)].id), + (4, cls.customers[(msg_idx * 2) + 1].id), + ], + 'tracking_value_ids': [ + (0, 0, { + 'field': user_id_field.id, + 'field_desc': 'Responsible', + 'new_value_char': 'new 1', + 'new_value_integer': record.user_id.id, + 'old_value_char': 'old 1', + 'old_value_integer': cls.user_admin.id, + }), + ] + } + for msg_idx in range(2) + for record_idx, record in enumerate(cls.record_ratings) + ]) + + cls.messages_records = [cls.env[message.model].browse(message.res_id) for message in cls.messages_all] + # ratings values related to rating-enabled records + cls.ratings_all = cls.env['rating.rating'].sudo().create([ + { + 'consumed': True, + 'message_id': message.id, + 'partner_id': record.customer_id.id, + 'publisher_comment': 'Comment', + 'publisher_id': cls.user_admin.partner_id.id, + 'publisher_datetime': datetime(2023, 5, 15, 10, 30, 5) - timedelta(days=2), + 'rated_partner_id': record.user_id.partner_id.id, + 'rating': 4, + 'res_id': message.res_id, + 'res_model_id': cls.env['ir.model']._get_id(message.model), + } + for rating_idx in range(2) + for message, record in zip(cls.messages_all, cls.messages_records) + ]) + + def test_assert_initial_values(self): + self.assertEqual(len(self.messages_all), 5 * 2) + self.assertEqual(len(self.ratings_all), len(self.messages_all) * 2) + + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') + @users('employee') + @warmup + def test_portal_message_format_norating(self): + messages_all = self.messages_all.with_user(self.env.user) + + with self.assertQueryCount(employee=31): + # res = messages_all.portal_message_format(options=None) + res = messages_all.portal_message_format(options={'rating_include': False}) + + comment_subtype = self.env.ref('mail.mt_comment') + self.assertEqual(len(res), len(messages_all)) + for format_res, message, record in zip(res, messages_all, self.messages_records): + self.assertEqual(len(format_res['attachment_ids']), 2) + self.assertEqual( + format_res['attachment_ids'], + [ + { + 'access_token': message.attachment_ids[0].access_token, + 'checksum': message.attachment_ids[0].checksum, + 'filename': 'Test file 1', + 'id': message.attachment_ids[0].id, + 'mimetype': 'application/octet-stream', + 'name': 'Test file 1', + 'res_id': record.id, + 'res_model': record._name, + }, { + 'access_token': message.attachment_ids[1].access_token, + 'checksum': message.attachment_ids[1].checksum, + 'filename': 'Test file 0', + 'id': message.attachment_ids[1].id, + 'mimetype': 'application/octet-stream', + 'name': 'Test file 0', + 'res_id': record.id, + 'res_model': record._name, + } + ] + ) + self.assertEqual(format_res['author_id'], (record.customer_id.id, record.customer_id.display_name)) + self.assertEqual(format_res['author_avatar_url'], f'/web/image/mail.message/{message.id}/author_avatar/50x50') + self.assertEqual(format_res['date'], datetime(2023, 5, 15, 10, 30, 5)) + self.assertEqual(format_res['published_date_str'], 'May 15, 2023, 10:30:05 AM') + self.assertEqual(format_res['id'], message.id) + self.assertFalse(format_res['is_internal']) + self.assertFalse(format_res['is_message_subtype_note']) + self.assertEqual(format_res['subtype_id'], (comment_subtype.id, comment_subtype.name)) + # should not be in, not asked + self.assertNotIn('rating', format_res) + self.assertNotIn('rating_stats', format_res) + self.assertNotIn('rating_value', format_res) + + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') + @users('employee') + @warmup + def test_portal_message_format_rating(self): + messages_all = self.messages_all.with_user(self.env.user) + + with self.assertQueryCount(employee=45): + res = messages_all.portal_message_format(options={'rating_include': True}) + + self.assertEqual(len(res), len(messages_all)) + for format_res, _message, _record in zip(res, messages_all, self.messages_records): + self.assertEqual(format_res['rating']['publisher_avatar'], f'/web/image/res.partner/{self.partner_admin.id}/avatar_128/50x50') + self.assertEqual(format_res['rating']['publisher_comment'], 'Comment') + self.assertEqual(format_res['rating']['publisher_id'], self.partner_admin.id) + self.assertEqual(format_res['rating']['publisher_datetime'], 'May 13, 2023, 10:30:05 AM') + self.assertEqual(format_res['rating']['publisher_name'], self.partner_admin.display_name) + self.assertDictEqual( + format_res['rating_stats'], + {'avg': 4.0, 'total': 4, 'percent': {1: 0.0, 2: 0.0, 3: 0.0, 4: 100.0, 5: 0.0}} + ) + self.assertEqual(format_res['rating_value'], 4) + + @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') + @users('employee') + @warmup + def test_portal_message_format_monorecord(self): + message = self.messages_all[0].with_user(self.env.user) + + with self.assertQueryCount(employee=18): + res = message.portal_message_format(options={'rating_include': True}) + + self.assertEqual(len(res), 1)