Purpose ======= The bounce emails aren't stored in Odoo, which can complicate the debugging of the email sending. Now, we store this bounce email, and we allow the user to read it from the interface, so he can easily find the issue when an email sending fail. Specifications ============== The bounce email is stored on the mail notification for standard emails sending, and on the mailing traces when using mass mailing. For some email providers (e.g. Yahoo), the "Final-Recipient" header is not present. Normally, it allows us to retrieve the original recipient of the email which bounced and then the partner. So if this header is not there in a bounce email, we take the first recipient of the parent <mail.message>. Change the way that we parse the email body, for the bounce email. For most email providers, the first mail body is the one that contains the error and the next one contains the parent email body. So, the current logic might ignore this body for Outlook and Yahoo. Task-2116296 closes odoo/odoo#105923 Related: odoo/enterprise#34051 Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
1726 lines
92 KiB
Python
1726 lines
92 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import socket
|
|
|
|
from datetime import datetime
|
|
|
|
from unittest.mock import DEFAULT
|
|
from unittest.mock import patch
|
|
|
|
from odoo import exceptions
|
|
from odoo.addons.mail.tests.common import mail_new_test_user
|
|
from odoo.addons.test_mail.data import test_mail_data
|
|
from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE
|
|
from odoo.addons.test_mail.models.test_mail_models import MailTestGateway
|
|
from odoo.addons.test_mail.tests.common import TestMailCommon
|
|
from odoo.sql_db import Cursor
|
|
from odoo.tests import tagged
|
|
from odoo.tests.common import users
|
|
from odoo.tools import email_split_and_format, formataddr, mute_logger
|
|
|
|
|
|
@tagged('mail_gateway')
|
|
class TestEmailParsing(TestMailCommon):
|
|
|
|
def test_message_parse_and_replace_binary_octetstream(self):
|
|
""" Incoming email containing a wrong Content-Type as described in RFC2046/section-3 """
|
|
received_mail = self.from_string(test_mail_data.MAIL_MULTIPART_BINARY_OCTET_STREAM)
|
|
with self.assertLogs('odoo.addons.mail.models.mail_thread', level="WARNING") as capture:
|
|
extracted_mail = self.env['mail.thread']._message_parse_extract_payload(received_mail, {})
|
|
|
|
self.assertEqual(len(extracted_mail['attachments']), 1)
|
|
attachment = extracted_mail['attachments'][0]
|
|
self.assertEqual(attachment.fname, 'hello_world.dat')
|
|
self.assertEqual(attachment.content, b'Hello world\n')
|
|
self.assertEqual(capture.output, [
|
|
("WARNING:odoo.addons.mail.models.mail_thread:Message containing an unexpected "
|
|
"Content-Type 'binary/octet-stream', assuming 'application/octet-stream'"),
|
|
])
|
|
|
|
def test_message_parse_body(self):
|
|
# test pure plaintext
|
|
plaintext = self.format(test_mail_data.MAIL_TEMPLATE_PLAINTEXT, email_from='"Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>')
|
|
res = self.env['mail.thread'].message_parse(self.from_string(plaintext))
|
|
self.assertIn('Please call me as soon as possible this afternoon!', res['body'])
|
|
|
|
# test multipart / text and html -> html has priority
|
|
multipart = self.format(MAIL_TEMPLATE, email_from='"Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>')
|
|
res = self.env['mail.thread'].message_parse(self.from_string(multipart))
|
|
self.assertIn('<p>Please call me as soon as possible this afternoon!</p>', res['body'])
|
|
|
|
# test multipart / mixed
|
|
res = self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_MULTIPART_MIXED))
|
|
self.assertNotIn(
|
|
'Should create a multipart/mixed: from gmail, *bold*, with attachment', res['body'],
|
|
'message_parse: text version should not be in body after parsing multipart/mixed')
|
|
self.assertIn(
|
|
'<div dir="ltr">Should create a multipart/mixed: from gmail, <b>bold</b>, with attachment.<br clear="all"><div><br></div>', res['body'],
|
|
'message_parse: html version should be in body after parsing multipart/mixed')
|
|
|
|
res = self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_MULTIPART_MIXED_TWO))
|
|
self.assertNotIn('First and second part', res['body'],
|
|
'message_parse: text version should not be in body after parsing multipart/mixed')
|
|
self.assertIn('First part', res['body'],
|
|
'message_parse: first part of the html version should be in body after parsing multipart/mixed')
|
|
self.assertIn('Second part', res['body'],
|
|
'message_parse: second part of the html version should be in body after parsing multipart/mixed')
|
|
|
|
res = self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_SINGLE_BINARY))
|
|
self.assertEqual(res['body'], '')
|
|
self.assertEqual(res['attachments'][0][0], 'thetruth.pdf')
|
|
|
|
res = self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_FORWARDED))
|
|
self.assertIn(res['recipients'], ['lucie@petitebedaine.fr,raoul@grosbedon.fr', 'raoul@grosbedon.fr,lucie@petitebedaine.fr'])
|
|
|
|
res = self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_MULTIPART_WEIRD_FILENAME))
|
|
self.assertEqual(res['attachments'][0][0], '62_@;,][)=.(ÇÀÉ.txt')
|
|
|
|
def test_message_parse_bugs(self):
|
|
""" Various corner cases or message parsing """
|
|
# message without Final-Recipient
|
|
self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_NO_FINAL_RECIPIENT))
|
|
|
|
# message with empty body (including only void characters)
|
|
res = self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_NO_BODY))
|
|
self.assertEqual(res['body'], '\n \n', 'Gateway should not crash with void content')
|
|
|
|
def test_message_parse_eml(self):
|
|
# Test that the parsing of mail with embedded emails as eml(msg) which generates empty attachments, can be processed.
|
|
mail = self.format(test_mail_data.MAIL_EML_ATTACHMENT, email_from='"Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>', to='generic@test.com')
|
|
self.env['mail.thread'].message_parse(self.from_string(mail))
|
|
|
|
def test_message_parse_eml_bounce_headers(self):
|
|
# Test Text/RFC822-Headers MIME content-type
|
|
msg_id = '<861878175823148.1577183525.736005783081055-openerp-19177-account.invoice@mycompany.example.com>'
|
|
mail = self.format(
|
|
test_mail_data.MAIL_EML_ATTACHMENT_BOUNCE_HEADERS,
|
|
email_from='MAILER-DAEMON@example.com (Mail Delivery System)',
|
|
to='test_bounce+82240-account.invoice-19177@mycompany.example.com',
|
|
# msg_id goes to the attachment's Message-Id header
|
|
msg_id=msg_id,
|
|
)
|
|
res = self.env['mail.thread'].message_parse(self.from_string(mail))
|
|
|
|
self.assertEqual(res['bounced_msg_ids'], [msg_id], "Message-Id is not extracted from Text/RFC822-Headers attachment")
|
|
|
|
def test_message_parse_extract_bounce_rfc822_headers_qp(self):
|
|
# Incoming bounce for unexisting Outlook address
|
|
# bounce back sometimes with a Content-Type `text/rfc822-headers`
|
|
# and Content-Type-Encoding `quoted-printable`
|
|
partner = self.env['res.partner'].create({
|
|
'name':'Mitchelle Admine',
|
|
'email':'rdesfrdgtfdrfesd@outlook.com'
|
|
})
|
|
message = self.env['mail.message'].create({
|
|
'message_id' : '<368396033905967.1673346177.695352554321289-openerp-11-sale.order@eupp00>'
|
|
})
|
|
incoming_bounce = self.format(
|
|
test_mail_data.MAIL_BOUNCE_QP_RFC822_HEADERS,
|
|
email_from='MAILER-DAEMON@mailserver.odoo.com (Mail Delivery System)',
|
|
email_to='bounce@xxx.odoo.com',
|
|
delivered_to='bounce@xxx.odoo.com'
|
|
)
|
|
|
|
msg_dict = {}
|
|
msg = self.env['mail.thread']._message_parse_extract_bounce(self.from_string(incoming_bounce), msg_dict)
|
|
self.assertEqual(msg['bounced_email'], partner.email, "The sender email should be correctly parsed")
|
|
self.assertEqual(msg['bounced_partner'], partner, "A partner with this email should exist")
|
|
self.assertEqual(msg['bounced_msg_ids'][0], message.message_id, "The sender message-id should correctly parsed")
|
|
self.assertEqual(msg['bounced_message'], message, "An existing message with this message_id should exist")
|
|
|
|
def test_message_parse_plaintext(self):
|
|
""" Incoming email in plaintext should be stored as html """
|
|
mail = self.format(test_mail_data.MAIL_TEMPLATE_PLAINTEXT, email_from='"Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>', to='generic@test.com')
|
|
res = self.env['mail.thread'].message_parse(self.from_string(mail))
|
|
self.assertIn('<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>', res['body'])
|
|
|
|
def test_message_parse_xhtml(self):
|
|
# Test that the parsing of XHTML mails does not fail
|
|
self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_XHTML))
|
|
|
|
@tagged('mail_gateway')
|
|
class TestMailAlias(TestMailCommon):
|
|
|
|
@users('employee')
|
|
@mute_logger('odoo.addons.base.models.ir_model')
|
|
def test_alias_creation(self):
|
|
record = self.env['mail.test.container'].create({
|
|
'name': 'Test Record',
|
|
'alias_name': 'alias.test',
|
|
'alias_contact': 'followers',
|
|
})
|
|
self.assertEqual(record.alias_id.alias_model_id, self.env['ir.model']._get('mail.test.container'))
|
|
self.assertEqual(record.alias_id.alias_force_thread_id, record.id)
|
|
self.assertEqual(record.alias_id.alias_parent_model_id, self.env['ir.model']._get('mail.test.container'))
|
|
self.assertEqual(record.alias_id.alias_parent_thread_id, record.id)
|
|
self.assertEqual(record.alias_id.alias_name, 'alias.test')
|
|
self.assertEqual(record.alias_id.alias_contact, 'followers')
|
|
|
|
record.write({
|
|
'alias_name': 'better.alias.test',
|
|
'alias_defaults': "{'default_name': 'defaults'}"
|
|
})
|
|
self.assertEqual(record.alias_id.alias_name, 'better.alias.test')
|
|
self.assertEqual(record.alias_id.alias_defaults, "{'default_name': 'defaults'}")
|
|
|
|
with self.assertRaises(exceptions.AccessError):
|
|
record.write({
|
|
'alias_force_thread_id': 0,
|
|
})
|
|
|
|
with self.assertRaises(exceptions.AccessError):
|
|
record.write({
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.gateway').id,
|
|
})
|
|
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
record.write({'alias_defaults': "{'custom_field': brokendict"})
|
|
|
|
def test_alias_sanitize(self):
|
|
alias = self.env['mail.alias'].create({
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.container').id,
|
|
'alias_name': 'bidule...inc.',
|
|
})
|
|
self.assertEqual(alias.alias_name, 'bidule.inc', 'Emails cannot start or end with a dot, there cannot be a sequence of dots.')
|
|
|
|
def test_alias_setup(self):
|
|
alias = self.env['mail.alias'].create({
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.container').id,
|
|
'alias_name': 'b4r+_#_R3wl$$',
|
|
})
|
|
self.assertEqual(alias.alias_name, 'b4r+_-_r3wl-', 'Disallowed chars should be replaced by hyphens')
|
|
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
alias.write({'alias_defaults': "{'custom_field': brokendict"})
|
|
|
|
def test_alias_name_unique(self):
|
|
alias_model_id = self.env['ir.model']._get('mail.test.gateway').id
|
|
catchall_alias = self.env['ir.config_parameter'].sudo().get_param('mail.catchall.alias')
|
|
bounce_alias = self.env['ir.config_parameter'].sudo().get_param('mail.bounce.alias')
|
|
|
|
# test you cannot create aliases matching bounce / catchall
|
|
with self.assertRaises(exceptions.UserError), self.cr.savepoint():
|
|
self.env['mail.alias'].create({'alias_model_id': alias_model_id, 'alias_name': catchall_alias})
|
|
with self.assertRaises(exceptions.UserError), self.cr.savepoint():
|
|
self.env['mail.alias'].create({'alias_model_id': alias_model_id, 'alias_name': bounce_alias})
|
|
|
|
new_mail_alias = self.env['mail.alias'].create({
|
|
'alias_model_id': alias_model_id,
|
|
'alias_name': 'unused.test.alias'
|
|
})
|
|
|
|
# test that re-using catchall and bounce alias raises UserError
|
|
with self.assertRaises(exceptions.UserError), self.cr.savepoint():
|
|
new_mail_alias.write({
|
|
'alias_name': catchall_alias
|
|
})
|
|
with self.assertRaises(exceptions.UserError), self.cr.savepoint():
|
|
new_mail_alias.write({
|
|
'alias_name': bounce_alias
|
|
})
|
|
|
|
new_mail_alias.write({'alias_name': 'another.unused.test.alias'})
|
|
|
|
# test that duplicating an alias should have blank name
|
|
copy_new_mail_alias = new_mail_alias.copy()
|
|
self.assertFalse(copy_new_mail_alias.alias_name)
|
|
|
|
# cannot set catchall / bounce to used alias
|
|
with self.assertRaises(exceptions.UserError), self.cr.savepoint():
|
|
self.env['ir.config_parameter'].sudo().set_param('mail.catchall.alias', new_mail_alias.alias_name)
|
|
with self.assertRaises(exceptions.UserError), self.cr.savepoint():
|
|
self.env['ir.config_parameter'].sudo().set_param('mail.bounce.alias', new_mail_alias.alias_name)
|
|
|
|
|
|
@tagged('mail_gateway')
|
|
class TestMailAliasMixin(TestMailCommon):
|
|
|
|
@users('employee')
|
|
def test_alias_mixin_copy_content(self):
|
|
self.assertFalse(self.env.user.has_group('base.group_system'), 'Test user should not have Administrator access')
|
|
|
|
record = self.env['mail.test.container'].create({
|
|
'name': 'Test Record',
|
|
'alias_name': 'test.record',
|
|
'alias_contact': 'followers',
|
|
'alias_bounced_content': False,
|
|
})
|
|
self.assertFalse(record.alias_bounced_content)
|
|
record_copy = record.copy()
|
|
self.assertFalse(record_copy.alias_bounced_content)
|
|
|
|
new_content = '<p>Bounced Content</p>'
|
|
record_copy.write({'alias_bounced_content': new_content})
|
|
self.assertEqual(record_copy.alias_bounced_content, new_content)
|
|
record_copy2 = record_copy.copy()
|
|
self.assertEqual(record_copy2.alias_bounced_content, new_content)
|
|
|
|
|
|
@tagged('mail_gateway')
|
|
class TestMailgateway(TestMailCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(TestMailgateway, cls).setUpClass()
|
|
cls.test_model = cls.env['ir.model']._get('mail.test.gateway')
|
|
cls.email_from = '"Sylvie Lelitre" <test.sylvie.lelitre@agrolait.com>'
|
|
|
|
cls.test_record = cls.env['mail.test.gateway'].with_context(cls._test_context).create({
|
|
'name': 'Test',
|
|
'email_from': 'ignasse@example.com',
|
|
}).with_context({})
|
|
|
|
cls.partner_1 = cls.env['res.partner'].with_context(cls._test_context).create({
|
|
'name': 'Valid Lelitre',
|
|
'email': 'valid.lelitre@agrolait.com',
|
|
})
|
|
# groups@.. will cause the creation of new mail.test.gateway
|
|
cls.alias = cls.env['mail.alias'].create({
|
|
'alias_name': 'groups',
|
|
'alias_user_id': False,
|
|
'alias_model_id': cls.test_model.id,
|
|
'alias_contact': 'everyone'})
|
|
|
|
# Set a first message on public group to test update and hierarchy
|
|
cls.fake_email = cls._create_gateway_message(cls.test_record, '123456')
|
|
|
|
@classmethod
|
|
def _create_gateway_message(cls, record, msg_id_prefix, **values):
|
|
msg_values = {
|
|
'author_id': cls.partner_1.id,
|
|
'email_from': cls.partner_1.email_formatted,
|
|
'body': '<p>Generic body</p>',
|
|
'message_id': f'<{msg_id_prefix}-openerp-{record.id}-{record._name}@{socket.gethostname()}>',
|
|
'message_type': 'email',
|
|
'model': record._name,
|
|
'res_id': record.id,
|
|
'subject': 'Generic Message',
|
|
'subtype_id': cls.env.ref('mail.mt_comment').id,
|
|
}
|
|
msg_values.update(**values)
|
|
return cls.env['mail.message'].create(msg_values)
|
|
|
|
# --------------------------------------------------
|
|
# Base low-level tests
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_alias_basic(self):
|
|
""" Test details of created message going through mailgateway """
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Specific')
|
|
|
|
# Test: one group created by mailgateway administrator as user_id is not set
|
|
self.assertEqual(len(record), 1, 'message_process: a new mail.test should have been created')
|
|
res = record.get_metadata()[0].get('create_uid') or [None]
|
|
self.assertEqual(res[0], self.env.uid)
|
|
|
|
# Test: one message that is the incoming email
|
|
self.assertEqual(len(record.message_ids), 1)
|
|
msg = record.message_ids[0]
|
|
self.assertEqual(msg.subject, 'Specific')
|
|
self.assertIn('Please call me as soon as possible this afternoon!', msg.body)
|
|
self.assertEqual(msg.message_type, 'email')
|
|
self.assertEqual(msg.subtype_id, self.env.ref('mail.mt_comment'))
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_cid(self):
|
|
record = self.format_and_process(test_mail_data.MAIL_MULTIPART_IMAGE, self.email_from, 'groups@test.com')
|
|
message = record.message_ids[0]
|
|
for attachment in message.attachment_ids:
|
|
self.assertIn('/web/image/%s' % attachment.id, message.body)
|
|
self.assertEqual(
|
|
set(message.attachment_ids.mapped('name')),
|
|
set(['rosaçée.gif', 'verte!µ.gif', 'orangée.gif']))
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_followers(self):
|
|
""" Incoming email: recognized author not archived and not odoobot:
|
|
added as follower. Also test corner cases: archived, private. """
|
|
partner_archived, partner_private = self.env['res.partner'].create([
|
|
{
|
|
'active': False,
|
|
'email': 'archived.customer@text.example.com',
|
|
'phone': '0032455112233',
|
|
'name': 'Archived Customer',
|
|
'type': 'contact',
|
|
},
|
|
{
|
|
'email': 'private.customer@text.example.com',
|
|
'phone': '0032455112233',
|
|
'name': 'Private Customer',
|
|
'type': 'private',
|
|
},
|
|
])
|
|
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com')
|
|
|
|
self.assertEqual(record.message_ids[0].author_id, self.partner_1,
|
|
'message_process: recognized email -> author_id')
|
|
self.assertEqual(record.message_ids[0].email_from, self.partner_1.email_formatted)
|
|
self.assertEqual(record.message_follower_ids.partner_id, self.partner_1,
|
|
'message_process: recognized email -> added as follower')
|
|
self.assertEqual(record.message_partner_ids, self.partner_1,
|
|
'message_process: recognized email -> added as follower')
|
|
|
|
# just an email -> no follower
|
|
with self.mock_mail_gateway():
|
|
record2 = self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'groups@test.com',
|
|
subject='Another Email')
|
|
|
|
self.assertEqual(record2.message_ids[0].author_id, self.env['res.partner'])
|
|
self.assertEqual(record2.message_ids[0].email_from, self.email_from)
|
|
self.assertEqual(record2.message_follower_ids.partner_id, self.env['res.partner'],
|
|
'message_process: unrecognized email -> no follower')
|
|
self.assertEqual(record2.message_partner_ids, self.env['res.partner'],
|
|
'message_process: unrecognized email -> no follower')
|
|
|
|
# archived partner -> no follower
|
|
with self.mock_mail_gateway():
|
|
record3 = self.format_and_process(
|
|
MAIL_TEMPLATE, partner_archived.email_formatted, 'groups@test.com',
|
|
subject='Archived Partner')
|
|
|
|
self.assertEqual(record3.message_ids[0].author_id, self.env['res.partner'])
|
|
self.assertEqual(record3.message_ids[0].email_from, partner_archived.email_formatted)
|
|
self.assertEqual(record3.message_follower_ids.partner_id, self.env['res.partner'],
|
|
'message_process: archived partner -> no follower')
|
|
self.assertEqual(record3.message_partner_ids, self.env['res.partner'],
|
|
'message_process: archived partner -> no follower')
|
|
|
|
# partner_root -> never again
|
|
odoobot = self.env.ref('base.partner_root')
|
|
odoobot.active = True
|
|
odoobot.email = 'odoobot@example.com'
|
|
with self.mock_mail_gateway():
|
|
record4 = self.format_and_process(
|
|
MAIL_TEMPLATE, odoobot.email_formatted, 'groups@test.com',
|
|
subject='Odoobot Automatic Answer')
|
|
|
|
self.assertEqual(record4.message_ids[0].author_id, odoobot)
|
|
self.assertEqual(record4.message_ids[0].email_from, odoobot.email_formatted)
|
|
self.assertEqual(record4.message_follower_ids.partner_id, self.env['res.partner'],
|
|
'message_process: odoobot -> no follower')
|
|
self.assertEqual(record4.message_partner_ids, self.env['res.partner'],
|
|
'message_process: odoobot -> no follower')
|
|
|
|
# private partner
|
|
with self.mock_mail_gateway():
|
|
record5 = self.format_and_process(
|
|
MAIL_TEMPLATE, partner_private.email_formatted, 'groups@test.com',
|
|
subject='Private Partner')
|
|
|
|
self.assertEqual(record5.message_ids[0].author_id, partner_private)
|
|
self.assertEqual(record5.message_ids[0].email_from, partner_private.email_formatted)
|
|
self.assertEqual(record5.message_follower_ids.partner_id, partner_private,
|
|
'message_process: private partner is recognized')
|
|
self.assertEqual(record5.message_partner_ids, partner_private,
|
|
'message_process: private partner is recognized')
|
|
|
|
# --------------------------------------------------
|
|
# Author recognition
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_email_email_from(self):
|
|
""" Incoming email: not recognized author: email_from, no author_id, no followers """
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com')
|
|
self.assertFalse(record.message_ids[0].author_id, 'message_process: unrecognized email -> no author_id')
|
|
self.assertEqual(record.message_ids[0].email_from, self.email_from)
|
|
self.assertEqual(len(record.message_partner_ids), 0,
|
|
'message_process: newly create group should not have any follower')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_email_author(self):
|
|
""" Incoming email: recognized author: email_from, author_id, added as follower """
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com', subject='Test1')
|
|
|
|
self.assertEqual(record.message_ids[0].author_id, self.partner_1,
|
|
'message_process: recognized email -> author_id')
|
|
self.assertEqual(record.message_ids[0].email_from, self.partner_1.email_formatted)
|
|
self.assertNotSentEmail() # No notification / bounce should be sent
|
|
|
|
# Email recognized if partner has a formatted email
|
|
self.partner_1.write({'email': '"Valid Lelitre" <%s>' % self.partner_1.email})
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email, 'groups@test.com', subject='Test2')
|
|
|
|
self.assertEqual(record.message_ids[0].author_id, self.partner_1,
|
|
'message_process: recognized email -> author_id')
|
|
self.assertEqual(record.message_ids[0].email_from, self.partner_1.email)
|
|
self.assertNotSentEmail() # No notification / bounce should be sent
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink')
|
|
def test_message_process_email_partner_find(self):
|
|
""" Finding the partner based on email, based on partner / user / follower """
|
|
self.alias.write({'alias_force_thread_id': self.test_record.id})
|
|
from_1 = self.env['res.partner'].create({'name': 'Brice Denisse', 'email': 'from.test@example.com'})
|
|
|
|
self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, 'groups@test.com')
|
|
self.assertEqual(self.test_record.message_ids[0].author_id, from_1)
|
|
self.test_record.message_unsubscribe([from_1.id])
|
|
|
|
from_2 = mail_new_test_user(self.env, login='B', groups='base.group_user', name='User Denisse', email='from.test@example.com')
|
|
|
|
self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, 'groups@test.com')
|
|
self.assertEqual(self.test_record.message_ids[0].author_id, from_2.partner_id)
|
|
self.test_record.message_unsubscribe([from_2.partner_id.id])
|
|
|
|
from_3 = self.env['res.partner'].create({'name': 'FOllower Denisse', 'email': 'from.test@example.com'})
|
|
self.test_record.message_subscribe([from_3.id])
|
|
|
|
self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, 'groups@test.com')
|
|
self.assertEqual(self.test_record.message_ids[0].author_id, from_3)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_email_author_exclude_alias(self):
|
|
""" Do not set alias as author to avoid including aliases in discussions """
|
|
from_1 = self.env['res.partner'].create({'name': 'Brice Denisse', 'email': 'from.test@test.com'})
|
|
self.env['mail.alias'].create({
|
|
'alias_name': 'from.test',
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.gateway').id
|
|
})
|
|
|
|
record = self.format_and_process(MAIL_TEMPLATE, from_1.email_formatted, 'groups@test.com')
|
|
self.assertFalse(record.message_ids[0].author_id)
|
|
self.assertEqual(record.message_ids[0].email_from, from_1.email_formatted)
|
|
|
|
# --------------------------------------------------
|
|
# Alias configuration
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
|
def test_message_process_alias_config_bounced_content(self):
|
|
""" Custom bounced message for the alias => Received this custom message """
|
|
self.alias.write({
|
|
'alias_contact': 'partners',
|
|
'alias_bounced_content': '<p>What Is Dead May Never Die</p>'
|
|
})
|
|
|
|
# Test: custom bounced content
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Should Bounce')
|
|
self.assertFalse(record, 'message_process: should have bounced')
|
|
self.assertSentEmail('"MAILER-DAEMON" <bounce.test@test.com>', ['whatever-2a840@postmaster.twitter.com'], body_content='<p>What Is Dead May Never Die</p>')
|
|
|
|
for empty_content in [
|
|
'<p><br></p>', '<p><br> </p>', '<p><br /></p >',
|
|
'<p style="margin: 4px"></p>',
|
|
'<div style="margin: 4px"></div>',
|
|
'<p class="oe_testing"><br></p>',
|
|
'<p><span style="font-weight: bolder;"><font style="color: rgb(255, 0, 0);" class=" "></font></span><br></p>',
|
|
]:
|
|
self.alias.write({
|
|
'alias_contact': 'partners',
|
|
'alias_bounced_content': empty_content,
|
|
})
|
|
|
|
# Test: with "empty" bounced content (simulate view, putting always '<p></br></p>' in html field)
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Should Bounce')
|
|
self.assertFalse(record, 'message_process: should have bounced')
|
|
# Check if default (hardcoded) value is in the mail content
|
|
self.assertSentEmail(
|
|
'"MAILER-DAEMON" <bounce.test@test.com>', ['whatever-2a840@postmaster.twitter.com'],
|
|
body_content='<p>Dear Sender,<br /><br />The message below could not be accepted by the address %s' % self.alias.display_name.lower()
|
|
)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
|
def test_message_process_alias_config_bounced_to(self):
|
|
""" Check bounce message contains the bouncing alias, not a generic "to" """
|
|
self.alias.write({'alias_contact': 'partners'})
|
|
bounce_message_with_alias = '<p>Dear Sender,<br /><br />The message below could not be accepted by the address %s' % self.alias.display_name.lower()
|
|
|
|
# Bounce is To
|
|
with self.mock_mail_gateway():
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'groups@example.com',
|
|
cc='other@gmail.com', subject='Should Bounce')
|
|
self.assertIn(bounce_message_with_alias, self._mails[0].get('body'))
|
|
|
|
# Bounce is CC
|
|
with self.mock_mail_gateway():
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'other@gmail.com',
|
|
cc='groups@example.com', subject='Should Bounce')
|
|
self.assertIn(bounce_message_with_alias, self._mails[0].get('body'))
|
|
|
|
# Bounce is part of To
|
|
with self.mock_mail_gateway():
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'other@gmail.com, groups@example.com',
|
|
subject='Should Bounce')
|
|
self.assertIn(bounce_message_with_alias, self._mails[0].get('body'))
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models', 'odoo.sql_db')
|
|
def test_message_process_alias_config_invalid_defaults(self):
|
|
"""Sending a mail to a misconfigured alias must change its status to invalid + notify sender and responsible."""
|
|
test_model_track = self.env['ir.model']._get('mail.test.track')
|
|
container_custom = self.env['mail.test.container'].create({})
|
|
alias_valid = self.env['mail.alias'].create({
|
|
'alias_name': 'valid',
|
|
'alias_user_id': self.user_employee.id,
|
|
'alias_model_id': test_model_track.id,
|
|
'alias_contact': 'everyone',
|
|
'alias_defaults': f"{{'container_id': {container_custom.id}}}",
|
|
})
|
|
|
|
# Test that it works when the reference to container_id in alias default is not dangling.
|
|
self.assertEqual(alias_valid.alias_status, 'not_tested')
|
|
with self.mock_mail_gateway(), patch('odoo.addons.mail.models.mail_alias.Alias._set_alias_invalid',
|
|
autospec=True) as _set_alias_invalid_mock:
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'valid@test.com', subject='Valid',
|
|
target_model=test_model_track.model)
|
|
_set_alias_invalid_mock.assert_not_called()
|
|
self.assertNotSentEmail()
|
|
self.assertEqual(record.container_id, container_custom)
|
|
self.assertEqual(alias_valid.alias_status, 'valid')
|
|
|
|
# Test with a dangling reference that must trigger bounce emails and set the alias status to invalid.
|
|
container_custom.unlink()
|
|
with self.assertRaises(Exception), patch('odoo.addons.mail.models.mail_alias.Alias._set_alias_invalid',
|
|
autospec=True) as _set_alias_invalid_mock:
|
|
self.format_and_process(MAIL_TEMPLATE, self.email_from, 'valid@test.com', subject='Invalid',
|
|
target_model=test_model_track.model)
|
|
|
|
# method executed in another transaction, so we cannot test its result directly but just below
|
|
_set_alias_invalid_mock.assert_called_once()
|
|
|
|
# call notify_alias_invalid on the test transaction to validate its effect
|
|
alias, message, message_dict = _set_alias_invalid_mock.call_args.args
|
|
with self.mock_mail_gateway():
|
|
alias = self.env['mail.alias'].browse(alias.id) # load alias in test transaction
|
|
alias._set_alias_invalid(message, message_dict)
|
|
|
|
self.assertEqual(alias_valid.alias_status, 'invalid')
|
|
self.assertSentEmail('"MAILER-DAEMON" <bounce.test@test.com>',
|
|
[alias_valid.alias_user_id.partner_id.email_formatted],
|
|
subject='Re: Invalid')
|
|
# Not sent to self.email_from because a return path is present in MAIL_TEMPLATE
|
|
self.assertSentEmail('"MAILER-DAEMON" <bounce.test@test.com>',
|
|
['whatever-2a840@postmaster.twitter.com'],
|
|
subject='Re: Invalid',
|
|
body=alias_valid._get_alias_invalid_body(message_dict))
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
@patch('odoo.addons.mail.models.mail_alias.Alias._set_alias_invalid')
|
|
def test_message_process_alias_defaults(self, _set_alias_invalid):
|
|
""" Test alias defaults and inner values """
|
|
self.alias.write({
|
|
'alias_user_id': self.user_employee.id,
|
|
'alias_defaults': "{'custom_field': 'defaults_custom'}"
|
|
})
|
|
|
|
self.assertEqual(self.alias.alias_status, 'not_tested')
|
|
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Specific')
|
|
|
|
_set_alias_invalid.assert_not_called()
|
|
self.assertEqual(self.alias.alias_status, 'valid')
|
|
self.assertEqual(len(record), 1)
|
|
res = record.get_metadata()[0].get('create_uid') or [None]
|
|
self.assertEqual(res[0], self.user_employee.id)
|
|
self.assertEqual(record.name, 'Specific')
|
|
self.assertEqual(record.custom_field, 'defaults_custom')
|
|
|
|
self.alias.write({'alias_defaults': '""'})
|
|
self.assertEqual(self.alias.alias_status, 'not_tested', 'Updating alias_defaults must reset status')
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Specific2')
|
|
|
|
_set_alias_invalid.assert_not_called()
|
|
self.assertEqual(len(record), 1)
|
|
res = record.get_metadata()[0].get('create_uid') or [None]
|
|
self.assertEqual(res[0], self.user_employee.id)
|
|
self.assertEqual(record.name, 'Specific2')
|
|
self.assertFalse(record.custom_field)
|
|
|
|
self.assertEqual(self.alias.alias_status, 'valid')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_alias_user_id(self):
|
|
""" Test alias ownership """
|
|
self.alias.write({'alias_user_id': self.user_employee.id})
|
|
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com')
|
|
self.assertEqual(len(record), 1)
|
|
res = record.get_metadata()[0].get('create_uid') or [None]
|
|
self.assertEqual(res[0], self.user_employee.id)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_alias_everyone(self):
|
|
""" Incoming email: everyone: new record + message_new """
|
|
self.alias.write({'alias_contact': 'everyone'})
|
|
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Specific')
|
|
self.assertEqual(len(record), 1)
|
|
self.assertEqual(len(record.message_ids), 1)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
|
def test_message_process_alias_partners_bounce(self):
|
|
""" Incoming email from an unknown partner on a Partners only alias -> bounce + test bounce email """
|
|
self.alias.write({'alias_contact': 'partners'})
|
|
|
|
# Test: no group created, email bounced
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Should Bounce')
|
|
self.assertFalse(record)
|
|
self.assertSentEmail('"MAILER-DAEMON" <bounce.test@test.com>', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Should Bounce')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
|
def test_message_process_alias_followers_bounce(self):
|
|
""" Incoming email from unknown partner / not follower partner on a Followers only alias -> bounce """
|
|
self.alias.write({
|
|
'alias_contact': 'followers',
|
|
'alias_parent_model_id': self.env['ir.model']._get('mail.test.gateway').id,
|
|
'alias_parent_thread_id': self.test_record.id,
|
|
})
|
|
|
|
# Test: unknown on followers alias -> bounce
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Should Bounce')
|
|
self.assertFalse(record, 'message_process: should have bounced')
|
|
self.assertSentEmail('"MAILER-DAEMON" <bounce.test@test.com>', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Should Bounce')
|
|
|
|
# Test: partner on followers alias -> bounce
|
|
self._init_mail_mock()
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com', subject='Should Bounce')
|
|
self.assertFalse(record, 'message_process: should have bounced')
|
|
self.assertSentEmail('"MAILER-DAEMON" <bounce.test@test.com>', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Should Bounce')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_alias_partner(self):
|
|
""" Incoming email from a known partner on a Partners alias -> ok (+ test on alias.user_id) """
|
|
self.alias.write({'alias_contact': 'partners'})
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com')
|
|
|
|
# Test: one group created by alias user
|
|
self.assertEqual(len(record), 1)
|
|
self.assertEqual(len(record.message_ids), 1)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_alias_followers(self):
|
|
""" Incoming email from a parent document follower on a Followers only alias -> ok """
|
|
self.alias.write({
|
|
'alias_contact': 'followers',
|
|
'alias_parent_model_id': self.env['ir.model']._get('mail.test.gateway').id,
|
|
'alias_parent_thread_id': self.test_record.id,
|
|
})
|
|
self.test_record.message_subscribe(partner_ids=[self.partner_1.id])
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@test.com')
|
|
|
|
# Test: one group created by Raoul (or Sylvie maybe, if we implement it)
|
|
self.assertEqual(len(record), 1)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
|
def test_message_process_alias_update(self):
|
|
""" Incoming email update discussion + notification email """
|
|
self.alias.write({'alias_force_thread_id': self.test_record.id})
|
|
|
|
self.test_record.message_subscribe(partner_ids=[self.partner_1.id])
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'groups@test.com>',
|
|
msg_id='<1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>', subject='Re: cats')
|
|
|
|
# Test: no new group + new message
|
|
self.assertFalse(record, 'message_process: alias update should not create new records')
|
|
self.assertEqual(len(self.test_record.message_ids), 2)
|
|
# Test: sent emails: 1 (Sylvie copy of the incoming email)
|
|
self.assertSentEmail(self.email_from, [self.partner_1], subject='Re: cats')
|
|
|
|
# --------------------------------------------------
|
|
# Creator recognition
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_create_uid_crash(self):
|
|
def _employee_crash(*args, **kwargs):
|
|
""" If employee is test employee, consider they have no access on document """
|
|
recordset = args[0]
|
|
if recordset.env.uid == self.user_employee.id and not recordset.env.su:
|
|
if kwargs.get('raise_exception', True):
|
|
raise exceptions.AccessError('Hop hop hop Ernest, please step back.')
|
|
return False
|
|
return DEFAULT
|
|
|
|
with patch.object(MailTestGateway, 'check_access_rights', autospec=True, side_effect=_employee_crash):
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, 'groups@test.com', subject='NoEmployeeAllowed')
|
|
self.assertEqual(record.create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].subject, 'NoEmployeeAllowed')
|
|
self.assertEqual(record.message_ids[0].create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].author_id, self.user_employee.partner_id)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_create_uid_email(self):
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, 'groups@test.com', subject='Email Found')
|
|
self.assertEqual(record.create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].subject, 'Email Found')
|
|
self.assertEqual(record.message_ids[0].create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].author_id, self.user_employee.partner_id)
|
|
|
|
record = self.format_and_process(MAIL_TEMPLATE, 'Another name <%s>' % self.user_employee.email, 'groups@test.com', subject='Email OtherName')
|
|
self.assertEqual(record.create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].subject, 'Email OtherName')
|
|
self.assertEqual(record.message_ids[0].create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].author_id, self.user_employee.partner_id)
|
|
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_normalized, 'groups@test.com', subject='Email SimpleEmail')
|
|
self.assertEqual(record.create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].subject, 'Email SimpleEmail')
|
|
self.assertEqual(record.message_ids[0].create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].author_id, self.user_employee.partner_id)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink')
|
|
def test_message_process_create_uid_email_follower(self):
|
|
self.alias.write({
|
|
'alias_parent_model_id': self.test_model.id,
|
|
'alias_parent_thread_id': self.test_record.id,
|
|
})
|
|
follower_user = mail_new_test_user(self.env, login='better', groups='base.group_user', name='Ernest Follower', email=self.user_employee.email)
|
|
self.test_record.message_subscribe(follower_user.partner_id.ids)
|
|
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, 'groups@test.com', subject='FollowerWinner')
|
|
self.assertEqual(record.create_uid, follower_user)
|
|
self.assertEqual(record.message_ids[0].subject, 'FollowerWinner')
|
|
self.assertEqual(record.message_ids[0].create_uid, follower_user)
|
|
self.assertEqual(record.message_ids[0].author_id, follower_user.partner_id)
|
|
|
|
# name order win
|
|
self.test_record.message_unsubscribe(follower_user.partner_id.ids)
|
|
self.test_record.flush_recordset()
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.user_employee.email_formatted, 'groups@test.com', subject='FirstFoundWinner')
|
|
self.assertEqual(record.create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].subject, 'FirstFoundWinner')
|
|
self.assertEqual(record.message_ids[0].create_uid, self.user_employee)
|
|
self.assertEqual(record.message_ids[0].author_id, self.user_employee.partner_id)
|
|
|
|
# --------------------------------------------------
|
|
# Alias routing management
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_route_alias_no_domain(self):
|
|
""" Incoming email: write to alias even if no domain set: considered as valid alias """
|
|
self.env['ir.config_parameter'].set_param('mail.catchall.domain', '')
|
|
|
|
new_record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, 'groups@another.domain.com', subject='Test Subject')
|
|
# Test: one group created
|
|
self.assertEqual(len(new_record), 1, 'message_process: a new mail.test.simple should have been created')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_route_alias_forward_bypass_reply_first(self):
|
|
""" Incoming email: write to two "new thread" alias, one as a reply, one being another model -> consider as a forward """
|
|
self.assertEqual(len(self.test_record.message_ids), 1)
|
|
|
|
# test@.. will cause the creation of new mail.test
|
|
new_alias_2 = self.env['mail.alias'].create({
|
|
'alias_name': 'test',
|
|
'alias_user_id': False,
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.container').id,
|
|
'alias_contact': 'everyone',
|
|
})
|
|
new_rec = self.format_and_process(
|
|
MAIL_TEMPLATE, self.partner_1.email_formatted,
|
|
'%s@%s, %s@%s' % (new_alias_2.alias_name, self.alias_domain, self.alias.alias_name, self.alias_domain),
|
|
subject='Test Subject',
|
|
extra='In-Reply-To:\r\n\t%s\n' % self.fake_email.message_id,
|
|
target_model=new_alias_2.alias_model_id.model
|
|
)
|
|
# Forward created a new record in mail.test
|
|
self.assertEqual(len(new_rec), 1, 'message_process: a new mail.test should have been created')
|
|
self.assertEqual(new_rec._name, new_alias_2.alias_model_id.model)
|
|
# No new post on test_record, no new record in mail.test.simple either
|
|
self.assertEqual(len(self.test_record.message_ids), 1, 'message_process: should not post on replied record as forward should bypass it')
|
|
new_simple = self.env['mail.test.simple'].search([('name', '=', 'Test Subject')])
|
|
self.assertEqual(len(new_simple), 0, 'message_process: a new mail.test should not have been created')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_route_alias_forward_bypass_reply_second(self):
|
|
""" Incoming email: write to two "new thread" alias, one as a reply, one being another model -> consider as a forward """
|
|
self.assertEqual(len(self.test_record.message_ids), 1)
|
|
|
|
# test@.. will cause the creation of new mail.test
|
|
new_alias_2 = self.env['mail.alias'].create({
|
|
'alias_name': 'test',
|
|
'alias_user_id': False,
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.container').id,
|
|
'alias_contact': 'everyone',
|
|
})
|
|
new_rec = self.format_and_process(
|
|
MAIL_TEMPLATE, self.partner_1.email_formatted,
|
|
'%s@%s, %s@%s' % (self.alias.alias_name, self.alias_domain, new_alias_2.alias_name, self.alias_domain),
|
|
subject='Test Subject',
|
|
extra='In-Reply-To:\r\n\t%s\n' % self.fake_email.message_id,
|
|
target_model=new_alias_2.alias_model_id.model
|
|
)
|
|
# Forward created a new record in mail.test
|
|
self.assertEqual(len(new_rec), 1, 'message_process: a new mail.test should have been created')
|
|
self.assertEqual(new_rec._name, new_alias_2.alias_model_id.model)
|
|
# No new post on test_record, no new record in mail.test.simple either
|
|
self.assertEqual(len(self.test_record.message_ids), 1, 'message_process: should not post on replied record as forward should bypass it')
|
|
new_simple = self.env['mail.test.simple'].search([('name', '=', 'Test Subject')])
|
|
self.assertEqual(len(new_simple), 0, 'message_process: a new mail.test should not have been created')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_route_alias_forward_bypass_update_alias(self):
|
|
""" Incoming email: write to one "update", one "new thread" alias, one as a reply, one being another model -> consider as a forward """
|
|
self.assertEqual(len(self.test_record.message_ids), 1)
|
|
self.alias.write({
|
|
'alias_force_thread_id': self.test_record.id,
|
|
})
|
|
|
|
# test@.. will cause the creation of new mail.test
|
|
new_alias_2 = self.env['mail.alias'].create({
|
|
'alias_name': 'test',
|
|
'alias_user_id': False,
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.container').id,
|
|
'alias_contact': 'everyone',
|
|
})
|
|
new_rec = self.format_and_process(
|
|
MAIL_TEMPLATE, self.partner_1.email_formatted,
|
|
'%s@%s, %s@%s' % (new_alias_2.alias_name, self.alias_domain, self.alias.alias_name, self.alias_domain),
|
|
subject='Test Subject',
|
|
extra='In-Reply-To:\r\n\t%s\n' % self.fake_email.message_id,
|
|
target_model=new_alias_2.alias_model_id.model
|
|
)
|
|
# Forward created a new record in mail.test
|
|
self.assertEqual(len(new_rec), 1, 'message_process: a new mail.test should have been created')
|
|
self.assertEqual(new_rec._name, new_alias_2.alias_model_id.model)
|
|
# No new post on test_record, no new record in mail.test.simple either
|
|
self.assertEqual(len(self.test_record.message_ids), 1, 'message_process: should not post on replied record as forward should bypass it')
|
|
# No new record on first alias model
|
|
new_simple = self.env['mail.test.gateway'].search([('name', '=', 'Test Subject')])
|
|
self.assertEqual(len(new_simple), 0, 'message_process: a new mail.test should not have been created')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_route_alias_multiple_new(self):
|
|
""" Incoming email: write to two aliases creating records: both should be activated """
|
|
# test@.. will cause the creation of new mail.test
|
|
new_alias_2 = self.env['mail.alias'].create({
|
|
'alias_name': 'test',
|
|
'alias_user_id': False,
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.container').id,
|
|
'alias_contact': 'everyone',
|
|
})
|
|
new_rec = self.format_and_process(
|
|
MAIL_TEMPLATE, self.partner_1.email_formatted,
|
|
'%s@%s, %s@%s' % (self.alias.alias_name, self.alias_domain, new_alias_2.alias_name, self.alias_domain),
|
|
subject='Test Subject',
|
|
target_model=new_alias_2.alias_model_id.model
|
|
)
|
|
# New record in both mail.test (new_alias_2) and mail.test.simple (self.alias)
|
|
self.assertEqual(len(new_rec), 1, 'message_process: a new mail.test should have been created')
|
|
self.assertEqual(new_rec._name, new_alias_2.alias_model_id.model)
|
|
new_simple = self.env['mail.test.gateway'].search([('name', '=', 'Test Subject')])
|
|
self.assertEqual(len(new_simple), 1, 'message_process: a new mail.test should have been created')
|
|
|
|
# --------------------------------------------------
|
|
# Email Management
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_route_bounce(self):
|
|
"""Incoming email: bounce using bounce alias: no record creation """
|
|
with self.mock_mail_gateway():
|
|
new_recs = self.format_and_process(
|
|
MAIL_TEMPLATE, self.partner_1.email_formatted,
|
|
'%s@%s' % (self.alias_bounce, self.alias_domain),
|
|
subject='Should bounce',
|
|
)
|
|
self.assertFalse(new_recs)
|
|
self.assertNotSentEmail()
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_route_bounce_other_recipients(self):
|
|
"""Incoming email: bounce processing: bounce should be computed even if not first recipient """
|
|
with self.mock_mail_gateway():
|
|
new_recs = self.format_and_process(
|
|
MAIL_TEMPLATE, self.partner_1.email_formatted,
|
|
'%s@%s, %s@%s' % (
|
|
self.alias.alias_name, self.alias_domain,
|
|
self.alias_bounce, self.alias_domain
|
|
),
|
|
subject='Should bounce',
|
|
)
|
|
self.assertFalse(new_recs)
|
|
self.assertNotSentEmail()
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
|
def test_message_route_write_to_catchall(self):
|
|
""" Writing directly to catchall should bounce """
|
|
# Test: no group created, email bounced
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(
|
|
MAIL_TEMPLATE, self.partner_1.email_formatted,
|
|
'"My Super Catchall" <%s@%s>' % (self.alias_catchall, self.alias_domain),
|
|
subject='Should Bounce')
|
|
self.assertFalse(record)
|
|
self.assertSentEmail('"MAILER-DAEMON" <bounce.test@test.com>', ['whatever-2a840@postmaster.twitter.com'], subject='Re: Should Bounce')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_route_write_to_catchall_other_recipients_first(self):
|
|
""" Writing directly to catchall and a valid alias should take alias """
|
|
# Test: no group created, email bounced
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(
|
|
MAIL_TEMPLATE, self.partner_1.email_formatted,
|
|
'%s@%s, %s@%s' % (self.alias_catchall, self.alias_domain, self.alias.alias_name, self.alias_domain),
|
|
subject='Catchall Not Blocking'
|
|
)
|
|
# Test: one group created
|
|
self.assertEqual(len(record), 1, 'message_process: a new mail.test should have been created')
|
|
# No bounce email
|
|
self.assertNotSentEmail()
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_route_write_to_catchall_other_recipients_second(self):
|
|
""" Writing directly to catchall and a valid alias should take alias """
|
|
# Test: no group created, email bounced
|
|
with self.mock_mail_gateway():
|
|
record = self.format_and_process(
|
|
MAIL_TEMPLATE, self.partner_1.email_formatted,
|
|
'%s@%s, %s@%s' % (self.alias.alias_name, self.alias_domain, self.alias_catchall, self.alias_domain),
|
|
subject='Catchall Not Blocking'
|
|
)
|
|
# Test: one group created
|
|
self.assertEqual(len(record), 1, 'message_process: a new mail.test should have been created')
|
|
# No bounce email
|
|
self.assertNotSentEmail()
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_bounce_alias(self):
|
|
""" Writing to bounce alias is considered as a bounce even if not multipart/report bounce structure """
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
bounced_mail_id = 4442
|
|
bounce_email_to = '%s@%s' % ('bounce.test', 'test.com')
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.partner_1.email_formatted, bounce_email_to, subject='Undelivered Mail Returned to Sender')
|
|
self.assertFalse(record)
|
|
# No information found in bounce email -> not possible to do anything except avoiding email
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_bounce_from_mailer_demon(self):
|
|
""" MAILER_DAEMON emails are considered as bounce """
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
record = self.format_and_process(MAIL_TEMPLATE, 'MAILER-DAEMON@example.com', 'groups@test.com', subject='Undelivered Mail Returned to Sender')
|
|
self.assertFalse(record)
|
|
# No information found in bounce email -> not possible to do anything except avoiding email
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_bounce_missing_final_recipient(self):
|
|
"""The Final-Recipient header is missing, the partner must be found thanks to the original mail message."""
|
|
email = test_mail_data.MAIL_BOUNCE.replace('Final-Recipient', 'XX')
|
|
email = email.replace('Original-Recipient', 'XX')
|
|
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
# no notification to find, won't be able to find the correct recipient
|
|
extra = self.fake_email.message_id
|
|
record = self.format_and_process(email, self.partner_1.email_formatted, 'bounce.test@%test.com', subject='Undelivered Mail Returned to Sender', extra=extra)
|
|
self.assertFalse(record)
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
# the partner will be found in the <mail.notification> res_partner_id
|
|
extra = self.fake_email.message_id
|
|
self.env['mail.notification'].create({
|
|
"res_partner_id": self.partner_1.id,
|
|
"mail_message_id": self.fake_email.id,
|
|
})
|
|
record = self.format_and_process(email, self.partner_1.email_formatted, 'bounce.test@%test.com', subject='Undelivered Mail Returned to Sender', extra=extra)
|
|
self.assertFalse(record)
|
|
self.assertEqual(self.partner_1.message_bounce, 1)
|
|
self.assertEqual(self.test_record.message_bounce, 1)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_bounce_multipart_alias(self):
|
|
""" Multipart/report bounce correctly make related partner bounce """
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
bounced_mail_id = 4442
|
|
bounce_email_to = '%s@%s' % ('bounce.test', 'test.com')
|
|
record = self.format_and_process(test_mail_data.MAIL_BOUNCE, self.partner_1.email_formatted, bounce_email_to, subject='Undelivered Mail Returned to Sender')
|
|
self.assertFalse(record)
|
|
# Missing in reply to message_id -> cannot find original record
|
|
self.assertEqual(self.partner_1.message_bounce, 1)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_bounce_multipart_alias_reply(self):
|
|
""" Multipart/report bounce correctly make related partner and record found in bounce email bounce """
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
notification = self.env['mail.notification'].create({
|
|
'mail_message_id': self.fake_email.id,
|
|
'res_partner_id': self.partner_1.id,
|
|
})
|
|
|
|
bounced_mail_id = 4442
|
|
bounce_email_to = '%s@%s' % ('bounce.test', 'test.com')
|
|
extra = self.fake_email.message_id
|
|
record = self.format_and_process(test_mail_data.MAIL_BOUNCE, self.partner_1.email_formatted, bounce_email_to, subject='Undelivered Mail Returned to Sender', extra=extra)
|
|
self.assertFalse(record)
|
|
self.assertEqual(self.partner_1.message_bounce, 1)
|
|
self.assertEqual(self.test_record.message_bounce, 1)
|
|
self.assertIn(
|
|
'This is the mail system at host mail2.test.ironsky.',
|
|
notification.failure_reason,
|
|
msg='Should store the bounce email body on the notification')
|
|
self.assertEqual(notification.failure_type, 'mail_bounce')
|
|
self.assertEqual(notification.notification_status, 'bounce')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_bounce_multipart_alias_whatever_from(self):
|
|
""" Multipart/report bounce correctly make related record found in bounce email bounce """
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
bounced_mail_id = 4442
|
|
bounce_email_to = '%s@%s' % ('bounce.test', 'test.com')
|
|
extra = self.fake_email.message_id
|
|
record = self.format_and_process(test_mail_data.MAIL_BOUNCE, 'Whatever <what@ever.com>', bounce_email_to, subject='Undelivered Mail Returned to Sender', extra=extra)
|
|
self.assertFalse(record)
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 1)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_bounce_multipart_whatever_to_and_from(self):
|
|
""" Multipart/report bounce correctly make related record found in bounce email bounce """
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
extra = self.fake_email.message_id
|
|
record = self.format_and_process(test_mail_data.MAIL_BOUNCE, 'Whatever <what@ever.com>', 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra)
|
|
self.assertFalse(record)
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 1)
|
|
|
|
# The local part of the FROM is not "MAILER-DAEMON", and the Content type is slightly
|
|
# different. Thanks to the report type, it still should be detected as a bounce email.
|
|
email = test_mail_data.MAIL_BOUNCE.replace('multipart/report;', 'multipart/report:')
|
|
email = email.replace('MAILER-DAEMON@mail2.test.ironsky', 'email@mail2.test.ironsky')
|
|
self.assertIn('report-type=delivery-status', email)
|
|
extra = self.fake_email.message_id
|
|
record = self.format_and_process(email, 'Whatever <what@ever.com>', 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra)
|
|
self.assertFalse(record)
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(self.test_record.message_bounce, 2)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink')
|
|
def test_message_process_bounce_records_channel(self):
|
|
""" Test blacklist allow to multi-bounce and auto update of mail.channel """
|
|
other_record = self.env['mail.test.gateway'].create({
|
|
'email_from': 'Another name <%s>' % self.partner_1.email
|
|
})
|
|
yet_other_record = self.env['mail.test.gateway'].create({
|
|
'email_from': 'Yet Another name <%s>' % self.partner_1.email.upper()
|
|
})
|
|
test_channel = self.env['mail.channel'].create({
|
|
'name': 'Test',
|
|
'channel_partner_ids': [(4, self.partner_1.id)],
|
|
})
|
|
self.fake_email.write({
|
|
'model': 'mail.channel',
|
|
'res_id': test_channel.id,
|
|
})
|
|
self.assertIn(self.partner_1, test_channel.channel_partner_ids)
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.assertEqual(other_record.message_bounce, 0)
|
|
self.assertEqual(yet_other_record.message_bounce, 0)
|
|
|
|
extra = self.fake_email.message_id
|
|
for i in range(10):
|
|
record = self.format_and_process(test_mail_data.MAIL_BOUNCE, 'A third name <%s>' % self.partner_1.email, 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra)
|
|
self.assertFalse(record)
|
|
self.assertEqual(self.partner_1.message_bounce, 10)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
self.assertEqual(other_record.message_bounce, 10)
|
|
self.assertEqual(yet_other_record.message_bounce, 10)
|
|
self.assertNotIn(self.partner_1, test_channel.channel_partner_ids)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_bounce_records_partner(self):
|
|
""" Test blacklist + bounce on ``res.partner`` model """
|
|
self.assertEqual(self.partner_1.message_bounce, 0)
|
|
self.fake_email.write({
|
|
'model': 'res.partner',
|
|
'res_id': self.partner_1.id,
|
|
})
|
|
|
|
extra = self.fake_email.message_id
|
|
record = self.format_and_process(test_mail_data.MAIL_BOUNCE, self.partner_1.email_formatted, 'groups@test.com', subject='Undelivered Mail Returned to Sender', extra=extra)
|
|
self.assertFalse(record)
|
|
self.assertEqual(self.partner_1.message_bounce, 1)
|
|
self.assertEqual(self.test_record.message_bounce, 0)
|
|
|
|
# --------------------------------------------------
|
|
# Thread formation
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_in_reply_to(self):
|
|
""" Incoming email using in-rely-to should go into the right destination even with a wrong destination """
|
|
init_msg_count = len(self.test_record.message_ids)
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, 'valid.other@gmail.com', 'erroneous@test.com>',
|
|
subject='Re: news', extra='In-Reply-To:\r\n\t%s\n' % self.fake_email.message_id)
|
|
|
|
self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1)
|
|
self.assertEqual(self.fake_email.child_ids, self.test_record.message_ids[0])
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_references(self):
|
|
""" Incoming email using references should go into the right destination even with a wrong destination """
|
|
init_msg_count = len(self.test_record.message_ids)
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'erroneous@test.com',
|
|
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id)
|
|
|
|
self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1)
|
|
self.assertEqual(self.fake_email.child_ids, self.test_record.message_ids[0])
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.addons.mail.models.mail_mail', 'odoo.tests')
|
|
def test_message_process_references_multi_parent(self):
|
|
""" Incoming email with multiple references """
|
|
reply1 = self._create_gateway_message(
|
|
self.test_record, 'reply1', parent_id=self.fake_email.id,
|
|
)
|
|
reply2 = self._create_gateway_message(
|
|
self.test_record, 'reply2', parent_id=self.fake_email.id,
|
|
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
|
|
)
|
|
reply1_1 = self._create_gateway_message(
|
|
self.test_record, 'reply1_1', parent_id=reply1.id,
|
|
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
|
|
)
|
|
reply2_1 = self._create_gateway_message(
|
|
self.test_record, 'reply2_1', parent_id=reply2.id,
|
|
)
|
|
|
|
# reply to reply1 using multiple references
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'groups@test.com',
|
|
subject='Reply to reply1',
|
|
extra=f'References: {reply1.message_id} {self.fake_email.message_id}'
|
|
)
|
|
new_msg = self.test_record.message_ids[0]
|
|
self.assertEqual(new_msg.parent_id, self.fake_email, 'Mail: flattening attach to original message')
|
|
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_comment'), 'Mail: reply to a comment should be a comment')
|
|
|
|
# ordering should not impact
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'groups@test.com',
|
|
subject='Reply to reply1 (order issue)',
|
|
extra=f'References: {self.fake_email.message_id} {reply1.message_id}'
|
|
)
|
|
new_msg = self.test_record.message_ids[0]
|
|
self.assertEqual(new_msg.parent_id, self.fake_email, 'Mail: flattening attach to original message')
|
|
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_comment'), 'Mail: reply to a comment should be a comment')
|
|
|
|
# history with last one being a note
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'groups@test.com',
|
|
subject='Reply to reply1_1',
|
|
extra=f'References: {reply1_1.message_id} {self.fake_email.message_id}'
|
|
)
|
|
new_msg = self.test_record.message_ids[0]
|
|
self.assertEqual(new_msg.parent_id, self.fake_email, 'Mail: flattening attach to original message')
|
|
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_note'), 'Mail: reply to a note should be a note')
|
|
|
|
# messed up history (two child branches): gateway initial parent is newest one
|
|
# (then may change with flattening when posting on record)
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'groups@test.com',
|
|
subject='Reply to reply2_1 (with noise)',
|
|
extra=f'References: {reply1_1.message_id} {reply2_1.message_id}'
|
|
)
|
|
new_msg = self.test_record.message_ids[0]
|
|
self.assertEqual(new_msg.parent_id, self.fake_email, 'Mail: flattening attach to original message')
|
|
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_comment'), 'Mail: parent should be a comment (before flattening)')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.addons.mail.models.mail_mail', 'odoo.tests')
|
|
def test_message_process_references_multi_parent_notflat(self):
|
|
""" Incoming email with multiple references with ``_mail_flat_thread``
|
|
being False (mail.group/mail.channel behavior like). """
|
|
test_record = self.env['mail.test.gateway.groups'].create({
|
|
'alias_name': 'test.gateway',
|
|
'name': 'Test',
|
|
'email_from': 'ignasse@example.com',
|
|
})
|
|
|
|
# Set a first message on public group to test update and hierarchy
|
|
first_msg = self._create_gateway_message(test_record, 'first_msg')
|
|
reply1 = self._create_gateway_message(
|
|
test_record, 'reply1', parent_id=first_msg.id,
|
|
)
|
|
reply2 = self._create_gateway_message(
|
|
test_record, 'reply2', parent_id=first_msg.id,
|
|
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
|
|
)
|
|
reply1_1 = self._create_gateway_message(
|
|
test_record, 'reply1_1', parent_id=reply1.id,
|
|
subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
|
|
)
|
|
reply2_1 = self._create_gateway_message(
|
|
test_record, 'reply2_1', parent_id=reply2.id,
|
|
)
|
|
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com',
|
|
subject='Reply to reply1',
|
|
extra=f'References: {reply1.message_id}'
|
|
)
|
|
new_msg = test_record.message_ids[0]
|
|
self.assertEqual(new_msg.parent_id, first_msg, 'Mail: pseudo no flattening: getting up one level (reply1 parent)')
|
|
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_comment'), 'Mail: parent should be a comment')
|
|
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com',
|
|
subject='Reply to reply1_1 (with noise)',
|
|
extra=f'References: {reply1_1.message_id} {reply1.message_id} {reply1.message_id}'
|
|
)
|
|
new_msg = test_record.message_ids[0]
|
|
self.assertEqual(new_msg.parent_id, reply1, 'Mail: pseudo no flattening: getting up one level (reply1_1 parent)')
|
|
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_note'), 'Mail: reply to a note should be a note')
|
|
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com',
|
|
subject='Reply to reply2_1 (with noise)',
|
|
extra=f'References: {reply2_1.message_id} {reply1_1.message_id}'
|
|
)
|
|
new_msg = test_record.message_ids[0]
|
|
self.assertEqual(new_msg.parent_id, reply2, 'Mail: pseudo no flattening: getting up one level (reply2_1 parent')
|
|
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_comment'), 'Mail: parent should be a comment')
|
|
|
|
# no references: new discussion thread started
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com',
|
|
subject='New thread',
|
|
extra='References:'
|
|
)
|
|
new_thread = test_record.message_ids[0]
|
|
self.assertFalse(new_thread.parent_id, 'Mail: pseudo no flattening: no parent means new thread')
|
|
self.assertEqual(new_thread.subject, 'New thread')
|
|
self.assertEqual(new_thread.subtype_id, self.env.ref('mail.mt_comment'), 'Mail: parent should be a comment')
|
|
|
|
# mixed up references: newer message wins
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'test.gateway@test.com',
|
|
subject='New thread',
|
|
extra=f'References: {new_thread.message_id} {reply1_1.message_id}'
|
|
)
|
|
new_msg = test_record.message_ids[0]
|
|
self.assertEqual(new_msg.parent_id, new_thread)
|
|
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_comment'), 'Mail: parent should be a comment')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_references_external(self):
|
|
""" Incoming email being a reply to an external email processed by odoo should update thread accordingly """
|
|
new_message_id = '<ThisIsTooMuchFake.MonsterEmail.789@agrolait.com>'
|
|
self.fake_email.write({
|
|
'message_id': new_message_id
|
|
})
|
|
init_msg_count = len(self.test_record.message_ids)
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'erroneous@test.com',
|
|
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id)
|
|
|
|
self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1)
|
|
self.assertEqual(self.fake_email.child_ids, self.test_record.message_ids[0])
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_references_external_buggy_message_id(self):
|
|
"""
|
|
Incoming email being a reply to an external email processed by
|
|
odoo should update thread accordingly. Special case when the
|
|
external mail service wrongly folds the message_id on several
|
|
lines.
|
|
"""
|
|
new_message_id = '<ThisIsTooMuchFake.MonsterEmail.789@agrolait.com>'
|
|
buggy_message_id = new_message_id.replace('MonsterEmail', 'Monster\r\n Email')
|
|
self.fake_email.write({
|
|
'message_id': new_message_id
|
|
})
|
|
init_msg_count = len(self.test_record.message_ids)
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'erroneous@test.com',
|
|
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % buggy_message_id)
|
|
|
|
self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1)
|
|
self.assertEqual(self.fake_email.child_ids, self.test_record.message_ids[0])
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_process_references_forward(self):
|
|
""" Incoming email using references but with alias forward should not go into references destination """
|
|
self.env['mail.alias'].create({
|
|
'alias_name': 'test.alias',
|
|
'alias_user_id': False,
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.container').id,
|
|
'alias_contact': 'everyone',
|
|
})
|
|
init_msg_count = len(self.test_record.message_ids)
|
|
res_test = self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'test.alias@test.com',
|
|
subject='My Dear Forward', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id,
|
|
target_model='mail.test.container')
|
|
|
|
self.assertEqual(len(self.test_record.message_ids), init_msg_count)
|
|
self.assertEqual(len(self.fake_email.child_ids), 0)
|
|
self.assertEqual(res_test.name, 'My Dear Forward')
|
|
self.assertEqual(len(res_test.message_ids), 1)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_references_forward_same_model(self):
|
|
""" Incoming email using references but with alias forward on same model should be considered as a reply """
|
|
self.env['mail.alias'].create({
|
|
'alias_name': 'test.alias',
|
|
'alias_user_id': False,
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.gateway').id,
|
|
'alias_contact': 'everyone',
|
|
})
|
|
init_msg_count = len(self.test_record.message_ids)
|
|
res_test = self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'test.alias@test.com',
|
|
subject='My Dear Forward', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id,
|
|
target_model='mail.test.container')
|
|
|
|
self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1)
|
|
self.assertEqual(len(self.fake_email.child_ids), 1)
|
|
self.assertFalse(res_test)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_references_forward_cc(self):
|
|
""" Incoming email using references but with alias forward in CC should be considered as a repy (To > Cc) """
|
|
self.env['mail.alias'].create({
|
|
'alias_name': 'test.alias',
|
|
'alias_user_id': False,
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.container').id,
|
|
'alias_contact': 'everyone',
|
|
})
|
|
init_msg_count = len(self.test_record.message_ids)
|
|
res_test = self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'catchall.test@test.com', cc='test.alias@test.com',
|
|
subject='My Dear Forward', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % self.fake_email.message_id,
|
|
target_model='mail.test.container')
|
|
|
|
self.assertEqual(len(self.test_record.message_ids), init_msg_count + 1)
|
|
self.assertEqual(len(self.fake_email.child_ids), 1)
|
|
self.assertFalse(res_test)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
|
def test_message_process_reply_to_new_thread(self):
|
|
""" Test replies not being considered as replies but use destination information instead (aka, mass post + specific reply to using aliases) """
|
|
first_record = self.env['mail.test.simple'].with_user(self.user_employee).create({'name': 'Replies to Record'})
|
|
record_msg = first_record.message_post(
|
|
subject='Discussion',
|
|
reply_to_force_new=False,
|
|
subtype_xmlid='mail.mt_comment',
|
|
)
|
|
self.assertEqual(record_msg.reply_to, formataddr(('%s %s' % (self.user_employee.company_id.name, first_record.name), '%s@%s' % ('catchall.test', 'test.com'))))
|
|
mail_msg = first_record.message_post(
|
|
subject='Replies to Record',
|
|
reply_to='groups@test.com',
|
|
reply_to_force_new=True,
|
|
subtype_xmlid='mail.mt_comment',
|
|
)
|
|
self.assertEqual(mail_msg.reply_to, 'groups@test.com')
|
|
|
|
# reply to mail but should be considered as a new mail for alias
|
|
msgID = '<this.is.duplicate.test@iron.sky>'
|
|
res_test = self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, record_msg.reply_to, cc='',
|
|
subject='Re: Replies to Record', extra='In-Reply-To: %s' % record_msg.message_id,
|
|
msg_id=msgID, target_model='mail.test.simple')
|
|
incoming_msg = self.env['mail.message'].search([('message_id', '=', msgID)])
|
|
self.assertFalse(res_test)
|
|
self.assertEqual(incoming_msg.model, 'mail.test.simple')
|
|
self.assertEqual(incoming_msg.parent_id, first_record.message_ids[-1])
|
|
self.assertTrue(incoming_msg.res_id == first_record.id)
|
|
|
|
# reply to mail but should be considered as a new mail for alias
|
|
msgID = '<this.is.for.testing@iron.sky>'
|
|
res_test = self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, mail_msg.reply_to, cc='',
|
|
subject='Re: Replies to Record', extra='In-Reply-To: %s' % mail_msg.message_id,
|
|
msg_id=msgID, target_model='mail.test.gateway')
|
|
incoming_msg = self.env['mail.message'].search([('message_id', '=', msgID)])
|
|
self.assertEqual(len(res_test), 1)
|
|
self.assertEqual(res_test.name, 'Re: Replies to Record')
|
|
self.assertEqual(incoming_msg.model, 'mail.test.gateway')
|
|
self.assertFalse(incoming_msg.parent_id)
|
|
self.assertTrue(incoming_msg.res_id == res_test.id)
|
|
|
|
# --------------------------------------------------
|
|
# Gateway / Record synchronization
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_gateway_values_base64_image(self):
|
|
"""New record with mail that contains base64 inline image."""
|
|
target_model = "mail.test.field.type"
|
|
alias = self.env["mail.alias"].create({
|
|
"alias_name": "base64-lover",
|
|
"alias_model_id": self.env["ir.model"]._get(target_model).id,
|
|
"alias_defaults": "{}",
|
|
"alias_contact": "everyone",
|
|
})
|
|
record = self.format_and_process(
|
|
test_mail_data.MAIL_TEMPLATE_EXTRA_HTML, self.email_from,
|
|
'%s@%s' % (alias.alias_name, self.alias_catchall),
|
|
subject='base64 image to alias',
|
|
target_model=target_model,
|
|
extra_html='<img src="data:image/png;base64,iV/+OkI=">',
|
|
)
|
|
self.assertEqual(record.type, "first")
|
|
self.assertEqual(len(record.message_ids[0].attachment_ids), 1)
|
|
self.assertEqual(record.message_ids[0].attachment_ids[0].name, "image0")
|
|
self.assertEqual(record.message_ids[0].attachment_ids[0].type, "binary")
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_gateway_values_base64_image_walias(self):
|
|
"""New record with mail that contains base64 inline image + default values
|
|
coming from alias."""
|
|
target_model = "mail.test.field.type"
|
|
alias = self.env["mail.alias"].create({
|
|
"alias_name": "base64-lover",
|
|
"alias_model_id": self.env["ir.model"]._get(target_model).id,
|
|
"alias_defaults": "{'type': 'second'}",
|
|
"alias_contact": "everyone",
|
|
})
|
|
record = self.format_and_process(
|
|
test_mail_data.MAIL_TEMPLATE_EXTRA_HTML, self.email_from,
|
|
'%s@%s' % (alias.alias_name, self.alias_catchall),
|
|
subject='base64 image to alias',
|
|
target_model=target_model,
|
|
extra_html='<img src="data:image/png;base64,iV/+OkI=">',
|
|
)
|
|
self.assertEqual(record.type, "second")
|
|
self.assertEqual(len(record.message_ids[0].attachment_ids), 1)
|
|
self.assertEqual(record.message_ids[0].attachment_ids[0].name, "image0")
|
|
self.assertEqual(record.message_ids[0].attachment_ids[0].type, "binary")
|
|
|
|
# --------------------------------------------------
|
|
# Thread formation: mail gateway corner cases
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_extra_model_res_id(self):
|
|
""" Incoming email with ref holding model / res_id but that does not match any message in the thread: must raise since OpenERP saas-3 """
|
|
self.assertRaises(ValueError,
|
|
self.format_and_process, MAIL_TEMPLATE,
|
|
self.partner_1.email_formatted, 'noone@test.com', subject='spam',
|
|
extra='In-Reply-To: <12321321-openerp-%d-mail.test.gateway@%s>' % (self.test_record.id, socket.gethostname()))
|
|
|
|
# when 6.1 messages are present, compat mode is available
|
|
# Odoo 10 update: compat mode has been removed and should not work anymore
|
|
self.fake_email.write({'message_id': False})
|
|
# Do: compat mode accepts partial-matching emails
|
|
self.assertRaises(
|
|
ValueError,
|
|
self.format_and_process, MAIL_TEMPLATE,
|
|
self.partner_1.email_formatted, 'noone@test.com>', subject='spam',
|
|
extra='In-Reply-To: <12321321-openerp-%d-mail.test.gateway@%s>' % (self.test_record.id, socket.gethostname()))
|
|
|
|
# Test created messages
|
|
self.assertEqual(len(self.test_record.message_ids), 1)
|
|
self.assertEqual(len(self.test_record.message_ids[0].child_ids), 0)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_duplicate(self):
|
|
""" Duplicate emails (same message_id) are not processed """
|
|
self.alias.write({'alias_force_thread_id': self.test_record.id,})
|
|
|
|
# Post a base message
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Re: super cats', msg_id='<123?456.diff1@agrolait.com>')
|
|
self.assertFalse(record)
|
|
self.assertEqual(len(self.test_record.message_ids), 2)
|
|
|
|
# Do: due to some issue, same email goes back into the mailgateway
|
|
record = self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'groups@test.com', subject='Re: news',
|
|
msg_id='<123?456.diff1@agrolait.com>', extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>\n')
|
|
self.assertFalse(record)
|
|
self.assertEqual(len(self.test_record.message_ids), 2)
|
|
|
|
# Test: message_id is still unique
|
|
no_of_msg = self.env['mail.message'].search_count([('message_id', 'ilike', '<123?456.diff1@agrolait.com>')])
|
|
self.assertEqual(no_of_msg, 1,
|
|
'message_process: message with already existing message_id should not have been duplicated')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_crash_wrong_model(self):
|
|
""" Incoming email with model that does not accepts incoming emails must raise """
|
|
self.assertRaises(ValueError,
|
|
self.format_and_process,
|
|
MAIL_TEMPLATE, self.email_from, 'noone@test.com',
|
|
subject='spam', extra='', model='res.country')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_crash_no_data(self):
|
|
""" Incoming email without model and without alias must raise """
|
|
self.assertRaises(ValueError,
|
|
self.format_and_process,
|
|
MAIL_TEMPLATE, self.email_from, 'noone@test.com',
|
|
subject='spam', extra='')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
|
def test_message_process_fallback(self):
|
|
""" Incoming email with model that accepting incoming emails as fallback """
|
|
record = self.format_and_process(
|
|
MAIL_TEMPLATE, self.email_from, 'noone@test.com',
|
|
subject='Spammy', extra='', model='mail.test.gateway')
|
|
self.assertEqual(len(record), 1)
|
|
self.assertEqual(record.name, 'Spammy')
|
|
self.assertEqual(record._name, 'mail.test.gateway')
|
|
|
|
# --------------------------------------------------
|
|
# Emails loop detection
|
|
# --------------------------------------------------
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.addons.mail.models.mail_mail')
|
|
@patch.object(Cursor, 'now', lambda *args, **kwargs: datetime(2022, 1, 1, 10, 0, 0))
|
|
def test_routing_loop_alias(self):
|
|
"""Test the limit on the number of record we can create by alias."""
|
|
self.env['ir.config_parameter'].sudo().set_param('mail.gateway.loop.minutes', 30)
|
|
self.env['ir.config_parameter'].sudo().set_param('mail.gateway.loop.threshold', 5)
|
|
|
|
self.env['mail.gateway.allowed'].create([
|
|
{'email': 'Bob@EXAMPLE.com'},
|
|
{'email': '"Alice From Example" <alice@EXAMPLE.com>'},
|
|
{'email': '"Eve From Example" <eve@EXAMPLE.com>'},
|
|
])
|
|
|
|
alias = self.env['mail.alias'].create({
|
|
'alias_name': 'test',
|
|
'alias_user_id': False,
|
|
'alias_model_id': self.env['ir.model']._get('mail.test.container').id,
|
|
'alias_contact': 'everyone',
|
|
})
|
|
|
|
# Send an email 2 hours ago, should not have an impact on more recent emails
|
|
with patch.object(Cursor, 'now', lambda *args, **kwargs: datetime(2022, 1, 1, 8, 0, 0)):
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE,
|
|
self.email_from,
|
|
f'{self.alias.alias_name}@{self.alias_domain}',
|
|
subject='Test alias loop old',
|
|
target_model=alias.alias_model_id.model,
|
|
)
|
|
|
|
for i in range(5):
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE,
|
|
self.email_from,
|
|
f'{self.alias.alias_name}@{self.alias_domain}',
|
|
subject=f'Test alias loop {i}',
|
|
target_model=alias.alias_model_id.model,
|
|
)
|
|
|
|
records = self.env['mail.test.gateway'].search([('name', 'ilike', 'Test alias loop %')])
|
|
self.assertEqual(len(records), 6, 'Should have created 6 <mail.test.gateway>')
|
|
self.assertEqual(set(records.mapped('email_from')), set([self.email_from]),
|
|
msg='Should have automatically filled the email field')
|
|
|
|
self.assertEqual(set(records.mapped('email_from')), {self.email_from})
|
|
|
|
for email_from in (self.email_from, self.email_from.upper()):
|
|
with self.mock_mail_gateway():
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE,
|
|
email_from,
|
|
f'{self.alias.alias_name}@{self.alias_domain}',
|
|
subject='Test alias loop X',
|
|
target_model=alias.alias_model_id.model,
|
|
return_path=email_from,
|
|
)
|
|
|
|
new_record = self.env['mail.test.gateway'].search([('name', '=', 'Test alias loop X')])
|
|
self.assertFalse(
|
|
new_record,
|
|
msg='The loop should have been detected and the record should not have been created')
|
|
|
|
self.assertSentEmail('"MAILER-DAEMON" <bounce.test@test.com>', [email_from])
|
|
self.assertIn('-loop-detection-bounce-email@', self._mails[0]['references'],
|
|
msg='The "bounce email" tag must be in the reference')
|
|
|
|
# The reply to the bounce email must be ignored
|
|
with self.mock_mail_gateway():
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE,
|
|
self.email_from,
|
|
f'{self.alias.alias_name}@{self.alias_domain}',
|
|
subject='Test alias loop X',
|
|
target_model=alias.alias_model_id.model,
|
|
return_path=self.email_from,
|
|
extra='References: <test-1337-loop-detection-bounce-email@odoo.com>',
|
|
)
|
|
|
|
self.assertNotSentEmail()
|
|
|
|
# Email address in the whitelist should not have the restriction
|
|
for i in range(10):
|
|
self.format_and_process(
|
|
MAIL_TEMPLATE,
|
|
'alice@example.com',
|
|
f'{self.alias.alias_name}@{self.alias_domain}',
|
|
subject=f'Whitelist test alias loop {i}',
|
|
target_model=alias.alias_model_id.model,
|
|
)
|
|
|
|
records = self.env['mail.test.gateway'].search([('name', 'ilike', 'Whitelist test alias loop %')])
|
|
self.assertEqual(len(records), 10, msg='Email whitelisted should not have the restriction')
|
|
|
|
|
|
class TestMailThreadCC(TestMailCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(TestMailThreadCC, cls).setUpClass()
|
|
|
|
cls.email_from = 'Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>'
|
|
cls.alias = cls.env['mail.alias'].create({
|
|
'alias_name': 'cc_record',
|
|
'alias_user_id': False,
|
|
'alias_model_id': cls.env['ir.model']._get('mail.test.cc').id,
|
|
'alias_contact': 'everyone'})
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_cc_new(self):
|
|
record = self.format_and_process(MAIL_TEMPLATE, self.email_from, 'cc_record@test.com',
|
|
cc='cc1@example.com, cc2@example.com', target_model='mail.test.cc')
|
|
cc = email_split_and_format(record.email_cc)
|
|
self.assertEqual(sorted(cc), ['cc1@example.com', 'cc2@example.com'])
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_cc_update_with_old(self):
|
|
record = self.env['mail.test.cc'].create({'email_cc': 'cc1 <cc1@example.com>, cc2@example.com'})
|
|
self.alias.write({'alias_force_thread_id': record.id})
|
|
|
|
self.format_and_process(MAIL_TEMPLATE, self.email_from, 'cc_record@test.com',
|
|
cc='cc2 <cc2@example.com>, cc3@example.com', target_model='mail.test.cc')
|
|
cc = email_split_and_format(record.email_cc)
|
|
self.assertEqual(sorted(cc), ['"cc1" <cc1@example.com>', 'cc2@example.com', 'cc3@example.com'], 'new cc should have been added on record (unique)')
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_thread')
|
|
def test_message_cc_update_no_old(self):
|
|
record = self.env['mail.test.cc'].create({})
|
|
self.alias.write({'alias_force_thread_id': record.id})
|
|
|
|
self.format_and_process(MAIL_TEMPLATE, self.email_from, 'cc_record@test.com',
|
|
cc='cc2 <cc2@example.com>, cc3@example.com', target_model='mail.test.cc')
|
|
cc = email_split_and_format(record.email_cc)
|
|
self.assertEqual(sorted(cc), ['"cc2" <cc2@example.com>', 'cc3@example.com'], 'new cc should have been added on record (unique)')
|