From 6e347b196d4bf40929d88ab2c835ba3fbc4dac7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 21 Apr 2020 14:07:55 +0000 Subject: [PATCH] [MOV] (test_)mail(_full): redispatch some tests and split test mail models file PURPOSE Have a cleaner test_mail addons SPECIFICATIONS Keep only mail-related tests, move odoobot in test mail full, send "update notification" tests in mail (specific to mail). Merge some test files to lessen number of files, perform light file renaming. Split test mail models file to prepare some cleaning in those models and tests. LINKS Prepares Task ID 2238597 (clean notification models) Prepares Task ID 2083854 (improve mass mailing technical flows) PR #49891 --- addons/mail/tests/__init__.py | 1 + .../tests/test_update_notification.py | 0 addons/test_mail/models/__init__.py | 4 +- .../models/test_mail_corner_case_models.py | 53 ++++++++ addons/test_mail/models/test_mail_models.py | 57 --------- .../models/test_mail_thread_models.py | 12 ++ addons/test_mail/tests/__init__.py | 5 +- addons/test_mail/tests/test_mail_redirect.py | 67 ---------- ...scuss.py => test_mail_thread_internals.py} | 65 ++++++++++ addons/test_mail/tests/test_message_track.py | 115 +++++++++--------- addons/test_mail/tests/test_performance.py | 53 ++++---- addons/test_mail_full/tests/__init__.py | 1 + .../tests/test_odoobot.py | 0 13 files changed, 222 insertions(+), 211 deletions(-) rename addons/{test_mail => mail}/tests/test_update_notification.py (100%) create mode 100644 addons/test_mail/models/test_mail_corner_case_models.py create mode 100644 addons/test_mail/models/test_mail_thread_models.py delete mode 100644 addons/test_mail/tests/test_mail_redirect.py rename addons/test_mail/tests/{test_discuss.py => test_mail_thread_internals.py} (78%) rename addons/{test_mail => test_mail_full}/tests/test_odoobot.py (100%) diff --git a/addons/mail/tests/__init__.py b/addons/mail/tests/__init__.py index 4706daea1c6..15ce69a9bd7 100644 --- a/addons/mail/tests/__init__.py +++ b/addons/mail/tests/__init__.py @@ -2,3 +2,4 @@ from . import common from . import test_res_partner +from . import test_update_notification diff --git a/addons/test_mail/tests/test_update_notification.py b/addons/mail/tests/test_update_notification.py similarity index 100% rename from addons/test_mail/tests/test_update_notification.py rename to addons/mail/tests/test_update_notification.py diff --git a/addons/test_mail/models/__init__.py b/addons/test_mail/models/__init__.py index 420d064776c..0f5cea0a294 100644 --- a/addons/test_mail/models/__init__.py +++ b/addons/test_mail/models/__init__.py @@ -1,3 +1,5 @@ # -*- coding: utf-8 -*- -from . import test_mail_models \ No newline at end of file +from . import test_mail_models +from . import test_mail_corner_case_models +from . import test_mail_thread_models diff --git a/addons/test_mail/models/test_mail_corner_case_models.py b/addons/test_mail/models/test_mail_corner_case_models.py new file mode 100644 index 00000000000..7f65194cd8e --- /dev/null +++ b/addons/test_mail/models/test_mail_corner_case_models.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class MailModel(models.Model): + _name = 'test_performance.mail' + _description = 'Test Performance Mail' + _inherit = 'mail.thread' + + name = fields.Char() + value = fields.Integer() + value_pc = fields.Float(compute="_value_pc", store=True) + track = fields.Char(default='test', tracking=True) + partner_id = fields.Many2one('res.partner', string='Customer') + + @api.depends('value') + def _value_pc(self): + for record in self: + record.value_pc = float(record.value) / 100 + + +class MailTrackingModel(models.Model): + _description = 'Test Tracking Model' + _name = 'mail.test.tracking' + _inherit = ['mail.thread'] + + name = fields.Char(required=True, tracking=True) + field_0 = fields.Char(tracking=True) + field_1 = fields.Char(tracking=True) + field_2 = fields.Char(tracking=True) + + +class MailCompute(models.Model): + _name = 'mail.test.compute' + _description = "Test model with several tracked computed fields" + _inherit = ['mail.thread'] + + partner_id = fields.Many2one('res.partner', tracking=True) + partner_name = fields.Char(related='partner_id.name', store=True, tracking=True) + partner_email = fields.Char(related='partner_id.email', store=True, tracking=True) + partner_phone = fields.Char(related='partner_id.phone', tracking=True) + + +class MailMultiCompany(models.Model): + """ This model can be used in multi company tests""" + _name = 'mail.test.multi.company' + _description = "Test Multi Company Mail" + _inherit = 'mail.thread' + + name = fields.Char() + company_id = fields.Many2one('res.company') diff --git a/addons/test_mail/models/test_mail_models.py b/addons/test_mail/models/test_mail_models.py index 0e420b83eba..55549133a3d 100644 --- a/addons/test_mail/models/test_mail_models.py +++ b/addons/test_mail/models/test_mail_models.py @@ -122,60 +122,3 @@ class MailTestAlias(models.Model): values['alias_force_thread_id'] = self.id values['alias_parent_thread_id'] = self.id return values - - -class MailModel(models.Model): - _name = 'test_performance.mail' - _description = 'Test Performance Mail' - _inherit = 'mail.thread' - - name = fields.Char() - value = fields.Integer() - value_pc = fields.Float(compute="_value_pc", store=True) - track = fields.Char(default='test', tracking=True) - partner_id = fields.Many2one('res.partner', string='Customer') - - @api.depends('value') - def _value_pc(self): - for record in self: - record.value_pc = float(record.value) / 100 - - -class MailCC(models.Model): - _name = 'mail.test.cc' - _description = "Test Email CC Thread" - _inherit = ['mail.thread.cc'] - - name = fields.Char() - - -class MailMultiCompany(models.Model): - """ This model can be used in multi company tests""" - _name = 'mail.test.multi.company' - _description = "Test Multi Company Mail" - _inherit = 'mail.thread' - - name = fields.Char() - company_id = fields.Many2one('res.company') - - -class MailTrackingModel(models.Model): - _description = 'Test Tracking Model' - _name = 'mail.test.tracking' - _inherit = ['mail.thread'] - - name = fields.Char(required=True, tracking=True) - field_0 = fields.Char(tracking=True) - field_1 = fields.Char(tracking=True) - field_2 = fields.Char(tracking=True) - - -class MailCompute(models.Model): - _name = 'mail.test.compute' - _description = "Test model with several tracked computed fields" - _inherit = ['mail.thread'] - - partner_id = fields.Many2one('res.partner', tracking=True) - partner_name = fields.Char(related='partner_id.name', store=True, tracking=True) - partner_email = fields.Char(related='partner_id.email', store=True, tracking=True) - partner_phone = fields.Char(related='partner_id.phone', tracking=True) diff --git a/addons/test_mail/models/test_mail_thread_models.py b/addons/test_mail/models/test_mail_thread_models.py new file mode 100644 index 00000000000..685b5c2afd1 --- /dev/null +++ b/addons/test_mail/models/test_mail_thread_models.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class MailCC(models.Model): + _name = 'mail.test.cc' + _description = "Test Email CC Thread" + _inherit = ['mail.thread.cc'] + + name = fields.Char() diff --git a/addons/test_mail/tests/__init__.py b/addons/test_mail/tests/__init__.py index 78d3b1516b6..3aac76d3df9 100644 --- a/addons/test_mail/tests/__init__.py +++ b/addons/test_mail/tests/__init__.py @@ -2,19 +2,16 @@ from . import test_invite from . import test_ir_actions -from . import test_discuss from . import test_mail_activity from . import test_mail_followers from . import test_mail_message from . import test_mail_mail from . import test_mail_channel from . import test_mail_gateway -from . import test_mail_redirect from . import test_mail_template +from . import test_mail_thread_internals from . import test_message_composer from . import test_message_management from . import test_message_post from . import test_message_track -from . import test_odoobot from . import test_performance -from . import test_update_notification diff --git a/addons/test_mail/tests/test_mail_redirect.py b/addons/test_mail/tests/test_mail_redirect.py deleted file mode 100644 index 706a9704319..00000000000 --- a/addons/test_mail/tests/test_mail_redirect.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from werkzeug.urls import url_decode, url_parse -from odoo.tests.common import HttpCase, tagged - - -@tagged('-at_install', 'post_install') -class TestMultiCompany(HttpCase): - - def test_redirect_to_records(self): - - self.company_A = self.env['res.company'].create({ - 'name': 'Company A', - 'user_ids': [(4, self.ref('base.user_admin'))], - }) - - self.company_B = self.env['res.company'].create({ - 'name': 'Company B', - }) - - self.multi_company_record = self.env['mail.test.multi.company'].create({ - 'name': 'Multi Company Record', - 'company_id': self.company_A.id, - }) - - # Test Case 0 - # Not logged, redirect to web/login - response = self.url_open('/mail/view?model=%s&res_id=%s' % ( - self.multi_company_record._name, - self.multi_company_record.id), timeout=15) - - path = url_parse(response.url).path - self.assertEqual(path, '/web/login') - - self.authenticate('admin', 'admin') - - # Test Case 1 - # Logged into company 1, try accessing record in company A - # _redirect_to_record should add company A in allowed_company_ids - response = self.url_open('/mail/view?model=%s&res_id=%s' % ( - self.multi_company_record._name, - self.multi_company_record.id), timeout=15) - - self.assertEqual(response.status_code, 200) - - fragment = url_parse(response.url).fragment - cids = url_decode(fragment)['cids'] - - self.assertEqual(cids, '1,%s' % (self.company_A.id)) - - # Test Case 2 - # Logged into company 1, try accessing record in company B - # _redirect_to_record should redirect to messaging as the user - # doesn't have any access for this company - self.multi_company_record.company_id = self.company_B - - response = self.url_open('/mail/view?model=%s&res_id=%s' % ( - self.multi_company_record._name, - self.multi_company_record.id), timeout=15) - - self.assertEqual(response.status_code, 200) - - fragment = url_parse(response.url).fragment - action = url_decode(fragment)['action'] - - self.assertEqual(action, 'mail.action_discuss') diff --git a/addons/test_mail/tests/test_discuss.py b/addons/test_mail/tests/test_mail_thread_internals.py similarity index 78% rename from addons/test_mail/tests/test_discuss.py rename to addons/test_mail/tests/test_mail_thread_internals.py index 86b9a7de0cb..1674d9ca312 100644 --- a/addons/test_mail/tests/test_discuss.py +++ b/addons/test_mail/tests/test_mail_thread_internals.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from werkzeug.urls import url_parse, url_decode + from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients +from odoo.tests.common import tagged, HttpCase from odoo.tools import mute_logger @@ -141,3 +144,65 @@ class TestDiscuss(TestMailCommon, TestRecipients): (False, 'cc1@example.com', 'CC Email'), (False, 'cc2@example.com', 'CC Email'), ], 'cc should be in suggestions') + + +@tagged('-at_install', 'post_install') +class TestMultiCompany(HttpCase): + + def test_redirect_to_records(self): + + self.company_A = self.env['res.company'].create({ + 'name': 'Company A', + 'user_ids': [(4, self.ref('base.user_admin'))], + }) + + self.company_B = self.env['res.company'].create({ + 'name': 'Company B', + }) + + self.multi_company_record = self.env['mail.test.multi.company'].create({ + 'name': 'Multi Company Record', + 'company_id': self.company_A.id, + }) + + # Test Case 0 + # Not logged, redirect to web/login + response = self.url_open('/mail/view?model=%s&res_id=%s' % ( + self.multi_company_record._name, + self.multi_company_record.id), timeout=15) + + path = url_parse(response.url).path + self.assertEqual(path, '/web/login') + + self.authenticate('admin', 'admin') + + # Test Case 1 + # Logged into company 1, try accessing record in company A + # _redirect_to_record should add company A in allowed_company_ids + response = self.url_open('/mail/view?model=%s&res_id=%s' % ( + self.multi_company_record._name, + self.multi_company_record.id), timeout=15) + + self.assertEqual(response.status_code, 200) + + fragment = url_parse(response.url).fragment + cids = url_decode(fragment)['cids'] + + self.assertEqual(cids, '1,%s' % (self.company_A.id)) + + # Test Case 2 + # Logged into company 1, try accessing record in company B + # _redirect_to_record should redirect to messaging as the user + # doesn't have any access for this company + self.multi_company_record.company_id = self.company_B + + response = self.url_open('/mail/view?model=%s&res_id=%s' % ( + self.multi_company_record._name, + self.multi_company_record.id), timeout=15) + + self.assertEqual(response.status_code, 200) + + fragment = url_parse(response.url).fragment + action = url_decode(fragment)['action'] + + self.assertEqual(action, 'mail.action_discuss') diff --git a/addons/test_mail/tests/test_message_track.py b/addons/test_mail/tests/test_message_track.py index 20ee3d74c4b..45ae711d32b 100644 --- a/addons/test_mail/tests/test_message_track.py +++ b/addons/test_mail/tests/test_message_track.py @@ -4,8 +4,10 @@ from unittest.mock import patch from odoo.addons.test_mail.tests.common import TestMailCommon +from odoo.tests.common import tagged +@tagged('mail_track') class TestTracking(TestMailCommon): def setUp(self): @@ -127,61 +129,6 @@ class TestTracking(TestMailCommon): # one email send due to template self.assertSentEmail(self.record.env.user.partner_id, [self.partner_admin], body='

Hello Test

') - def test_message_tracking_sequence(self): - """ Update some tracked fields and check that the mail.tracking.value are ordered according to their tracking_sequence""" - self.record.write({ - 'name': 'Zboub', - 'customer_id': self.user_admin.partner_id.id, - 'user_id': self.user_admin.id, - 'umbrella_id': self.env['mail.test'].with_context(mail_create_nosubscribe=True).create({'name': 'Umbrella'}).id - }) - self.flush_tracking() - self.assertEqual(len(self.record.message_ids), 1, 'should have 1 tracking message') - - tracking_values = self.env['mail.tracking.value'].search([('mail_message_id', '=', self.record.message_ids.id)]) - self.assertEqual(tracking_values[0].tracking_sequence, 1) - self.assertEqual(tracking_values[1].tracking_sequence, 2) - self.assertEqual(tracking_values[2].tracking_sequence, 100) - - def test_track_groups(self): - # patch the group attribute - field = self.record._fields['email_from'] - self.addCleanup(setattr, field, 'groups', field.groups) - field.groups = 'base.group_erp_manager' - - self.record.sudo().write({'email_from': 'X'}) - self.flush_tracking() - - msg_emp = self.record.message_ids.message_format() - msg_sudo = self.record.sudo().message_ids.message_format() - self.assertFalse(msg_emp[0].get('tracking_value_ids'), "should not have protected tracking values") - self.assertTrue(msg_sudo[0].get('tracking_value_ids'), "should have protected tracking values") - - def test_notify_track_groups(self): - # patch the group attribute - field = self.record._fields['email_from'] - self.addCleanup(setattr, field, 'groups', field.groups) - field.groups = 'base.group_erp_manager' - - self.record.sudo().write({'email_from': 'X'}) - self.flush_tracking() - - msg_emp = self.record._notify_prepare_template_context(self.record.message_ids, {}) - msg_sudo = self.record.sudo()._notify_prepare_template_context(self.record.message_ids, {}) - self.assertFalse(msg_emp.get('tracking_values'), "should not have protected tracking values") - self.assertTrue(msg_sudo.get('tracking_values'), "should have protected tracking values") - - def test_unlinked_field(self): - record_sudo = self.record.sudo() - record_sudo.write({'email_from': 'X'}) # create a tracking value - self.flush_tracking() - self.assertEqual(len(record_sudo.message_ids.tracking_value_ids), 1) - ir_model_field = self.env['ir.model.fields'].search([ - ('model', '=', 'mail.test.full'), - ('name', '=', 'email_from')]) - ir_model_field.with_context(_force_unlink=True).unlink() - self.assertEqual(len(record_sudo.message_ids.tracking_value_ids), 0) - def test_create_partner_from_tracking_multicompany(self): company1 = self.env['res.company'].create({'name': 'company1'}) self.env.user.write({'company_ids': [(4, company1.id, False)]}) @@ -334,3 +281,61 @@ class TestTracking(TestMailCommon): ('partner_name', 'char', 'Fool', 'Bar'), ('partner_email', 'char', 'foo@example.com', 'bar@example.com'), ]) + + +@tagged('mail_track') +class TestTrackingInternals(TestMailCommon): + + def setUp(self): + super(TestTrackingInternals, self).setUp() + + record = self.env['mail.test.full'].with_user(self.user_employee).with_context(self._test_context).create({ + 'name': 'Test', + }) + self.flush_tracking() + self.record = record.with_context(mail_notrack=False) + + def test_track_groups(self): + field = self.record._fields['email_from'] + self.addCleanup(setattr, field, 'groups', field.groups) + field.groups = 'base.group_erp_manager' + + self.record.sudo().write({'email_from': 'X'}) + self.flush_tracking() + + msg_emp = self.record.message_ids.message_format() + msg_sudo = self.record.sudo().message_ids.message_format() + self.assertFalse(msg_emp[0].get('tracking_value_ids'), "should not have protected tracking values") + self.assertTrue(msg_sudo[0].get('tracking_value_ids'), "should have protected tracking values") + + msg_emp = self.record._notify_prepare_template_context(self.record.message_ids, {}) + msg_sudo = self.record.sudo()._notify_prepare_template_context(self.record.message_ids, {}) + self.assertFalse(msg_emp.get('tracking_values'), "should not have protected tracking values") + self.assertTrue(msg_sudo.get('tracking_values'), "should have protected tracking values") + + def test_track_sequence(self): + """ Update some tracked fields and check that the mail.tracking.value are ordered according to their tracking_sequence""" + self.record.write({ + 'name': 'Zboub', + 'customer_id': self.user_admin.partner_id.id, + 'user_id': self.user_admin.id, + 'umbrella_id': self.env['mail.test'].with_context(mail_create_nosubscribe=True).create({'name': 'Umbrella'}).id + }) + self.flush_tracking() + self.assertEqual(len(self.record.message_ids), 1, 'should have 1 tracking message') + + tracking_values = self.env['mail.tracking.value'].search([('mail_message_id', '=', self.record.message_ids.id)]) + self.assertEqual(tracking_values[0].tracking_sequence, 1) + self.assertEqual(tracking_values[1].tracking_sequence, 2) + self.assertEqual(tracking_values[2].tracking_sequence, 100) + + def test_unlinked_field(self): + record_sudo = self.record.sudo() + record_sudo.write({'email_from': 'new_value'}) # create a tracking value + self.flush_tracking() + self.assertEqual(len(record_sudo.message_ids.tracking_value_ids), 1) + ir_model_field = self.env['ir.model.fields'].search([ + ('model', '=', 'mail.test.full'), + ('name', '=', 'email_from')]) + ir_model_field.with_context(_force_unlink=True).unlink() + self.assertEqual(len(record_sudo.message_ids.tracking_value_ids), 0) diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index de82171251b..1d8ab7729c9 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -183,6 +183,11 @@ class TestMailAPIPerformance(BaseMailPerformance): 'notification_type': 'inbox', 'groups_id': [(6, 0, [self.env.ref('base.group_user').id])], }) + + # automatically follow activities, for backward compatibility concerning query count + self.env.ref('mail.mt_activities').write({'default': True}) + + def _create_test_records(self): self.test_record_full = self.env['mail.test.full'].with_context(self._quick_create_ctx).create({ 'name': 'TestRecord', 'customer_id': self.customer.id, @@ -199,9 +204,6 @@ class TestMailAPIPerformance(BaseMailPerformance): 'email_to': '${("%s Customer <%s>" % (object.name, object.email_from)) | safe}', }) - # automatically follow activities, for backward compatibility concerning query count - self.env.ref('mail.mt_activities').write({'default': True}) - @users('__system__', 'emp') @warmup def test_adv_activity(self): @@ -255,6 +257,7 @@ class TestMailAPIPerformance(BaseMailPerformance): @warmup @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail') def test_mail_composer(self): + self._create_test_records() test_record = self.env['mail.test.full'].browse(self.test_record_full.id) customer_id = self.customer.id with self.assertQueryCount(__system__=4, emp=4): @@ -274,6 +277,7 @@ class TestMailAPIPerformance(BaseMailPerformance): @warmup @mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') def test_mail_composer_w_template(self): + self._create_test_records() test_record = self.env['mail.test.full'].browse(self.test_record_full.id) test_template = self.env['mail.template'].browse(self.test_template_full.id) # TODO FIXME non deterministic, last check 25 Mar 2020. Runbot at most +7 compared to local. @@ -395,6 +399,25 @@ class TestMailAPIPerformance(BaseMailPerformance): with self.assertQueryCount(__system__=2, emp=2): record.message_subscribe(partner_ids=self.user_test.partner_id.ids, subtype_ids=subtype_ids) + @mute_logger('odoo.models.unlink') + @users('__system__', 'emp') + @warmup + def test_message_track(self): + record = self.env['mail.test.tracking'].create({'name': 'Zizizatestname'}) + with self.assertQueryCount(__system__=3, emp=3): + record.write({'name': 'Zizizanewtestname'}) + record.flush() + + with self.assertQueryCount(__system__=5, emp=5): + record.write({'field_%s' % (i): 'Tracked Char Fields %s' % (i) for i in range(3)}) + record.flush() + + with self.assertQueryCount(__system__=6, emp=6): + record.write({'field_%s' % (i): 'Field Without Cache %s' % (i) for i in range(3)}) + record.flush() + record.write({'field_%s' % (i): 'Field With Cache %s' % (i) for i in range(3)}) + record.flush() + @tagged('mail_performance') class TestMailComplexPerformance(BaseMailPerformance): @@ -949,27 +972,3 @@ class TestMailHeavyPerformancePost(BaseMailPerformance): self.assertEqual(self.attachements.mapped('res_model'), [record._name for i in range(3)]) self.assertEqual(self.attachements.mapped('res_id'), [record.id for i in range(3)]) # self.assertEqual(record.message_ids[0].notified_partner_ids, []) - - -@tagged('mail_performance') -class TestTrackingPerformance(BaseMailPerformance): - - @users('__system__', 'demo') - @warmup - def test_tracking_multiple_fields(self): - """ Check that tracking multiple fields is scalable """ - - record = self.env['mail.test.tracking'].create({'name': 'Zizizatestname'}) - with self.assertQueryCount(__system__=3, demo=3): - record.write({'name': 'Zizizanewtestname'}) - record.flush() - - with self.assertQueryCount(__system__=5, demo=5): - record.write({'field_%s' % (i): 'Tracked Char Fields %s' % (i) for i in range(3)}) - record.flush() - - with self.assertQueryCount(__system__=6, demo=6): - record.write({'field_%s' % (i): 'Field Without Cache %s' % (i) for i in range(3)}) - record.flush() - record.write({'field_%s' % (i): 'Field With Cache %s' % (i) for i in range(3)}) - record.flush() diff --git a/addons/test_mail_full/tests/__init__.py b/addons/test_mail_full/tests/__init__.py index ce173406381..149facf9fa7 100644 --- a/addons/test_mail_full/tests/__init__.py +++ b/addons/test_mail_full/tests/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from . import common +from . import test_odoobot from . import test_phone_blacklist from . import test_mass_sms from . import test_sms_composer diff --git a/addons/test_mail/tests/test_odoobot.py b/addons/test_mail_full/tests/test_odoobot.py similarity index 100% rename from addons/test_mail/tests/test_odoobot.py rename to addons/test_mail_full/tests/test_odoobot.py