From now on mail.mail is considered as a technical model. Indeed people should not really manually craft mails by hand. Instead various functional flows should either send mails, either craft mails based on some user input. We therefore make mail restricted to admin users. Flows creating mail.mail are updated to use sudo, and ensure it was done in a context that makes sense to delegate this power to the user. Task ID 1853147 PR #32243
103 lines
4.3 KiB
Python
103 lines
4.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import psycopg2
|
|
|
|
from odoo import api
|
|
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
|
|
from odoo.addons.test_mail.tests.common import TestMailCommon
|
|
from odoo.tests import common
|
|
from odoo.tools import mute_logger
|
|
|
|
|
|
class TestMailMail(TestMailCommon):
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_mail')
|
|
def test_mail_message_notify_from_mail_mail(self):
|
|
# Due ot post-commit hooks, store send emails in every step
|
|
mail = self.env['mail.mail'].sudo().create({
|
|
'body_html': '<p>Test</p>',
|
|
'email_to': 'test@example.com',
|
|
'partner_ids': [(4, self.user_employee.partner_id.id)]
|
|
})
|
|
with self.mock_mail_gateway():
|
|
mail.send()
|
|
self.assertSentEmail(mail.env.user.partner_id, ['test@example.com'])
|
|
self.assertEqual(len(self._mails), 1)
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_mail')
|
|
def test_mail_message_values_unicode(self):
|
|
mail = self.env['mail.mail'].sudo().create({
|
|
'body_html': '<p>Test</p>',
|
|
'email_to': 'test.😊@example.com',
|
|
'partner_ids': [(4, self.user_employee.partner_id.id)]
|
|
})
|
|
|
|
self.assertRaises(MailDeliveryException, lambda: mail.send(raise_exception=True))
|
|
|
|
|
|
class TestMailMailRace(common.TransactionCase):
|
|
|
|
@mute_logger('odoo.addons.mail.models.mail_mail')
|
|
def test_mail_bounce_during_send(self):
|
|
self.partner = self.env['res.partner'].create({
|
|
'name': 'Ernest Partner',
|
|
})
|
|
# we need to simulate a mail sent by the cron task, first create mail, message and notification by hand
|
|
mail = self.env['mail.mail'].sudo().create({
|
|
'body_html': '<p>Test</p>',
|
|
'notification': True,
|
|
'state': 'outgoing',
|
|
'recipient_ids': [(4, self.partner.id)]
|
|
})
|
|
message = self.env['mail.message'].create({
|
|
'subject': 'S',
|
|
'body': 'B',
|
|
'subtype_id': self.ref('mail.mt_comment'),
|
|
'notification_ids': [(0, 0, {
|
|
'res_partner_id': self.partner.id,
|
|
'mail_id': mail.id,
|
|
'notification_type': 'email',
|
|
'is_read': True,
|
|
'notification_status': 'ready',
|
|
})],
|
|
})
|
|
notif = self.env['mail.notification'].search([('res_partner_id', '=', self.partner.id)])
|
|
# we need to commit transaction or cr will keep the lock on notif
|
|
self.cr.commit()
|
|
|
|
# patch send_email in order to create a concurent update and check the notif is already locked by _send()
|
|
this = self # coding in javascript ruinned my life
|
|
bounce_deferred = []
|
|
@api.model
|
|
def send_email(self, message, *args, **kwargs):
|
|
with this.registry.cursor() as cr, mute_logger('odoo.sql_db'):
|
|
try:
|
|
# try ro aquire lock (no wait) on notification (should fail)
|
|
cr.execute("SELECT notification_status FROM mail_message_res_partner_needaction_rel WHERE id = %s FOR UPDATE NOWAIT", [notif.id])
|
|
except psycopg2.OperationalError:
|
|
# record already locked by send, all good
|
|
bounce_deferred.append(True)
|
|
else:
|
|
# this should trigger psycopg2.extensions.TransactionRollbackError in send().
|
|
# Only here to simulate the initial use case
|
|
# If the record is lock, this line would create a deadlock since we are in the same thread
|
|
# In practice, the update will wait the end of the send() transaction and set the notif as bounce, as expeced
|
|
cr.execute("UPDATE mail_message_res_partner_needaction_rel SET notification_status='bounce' WHERE id = %s", [notif.id])
|
|
return message['Message-Id']
|
|
self.env['ir.mail_server']._patch_method('send_email', send_email)
|
|
|
|
mail.send()
|
|
|
|
self.assertTrue(bounce_deferred, "The bounce should have been deferred")
|
|
self.assertEqual(notif.notification_status, 'sent')
|
|
|
|
# some cleaning since we commited the cr
|
|
self.env['ir.mail_server']._revert_method('send_email')
|
|
|
|
notif.unlink()
|
|
message.unlink()
|
|
mail.unlink()
|
|
self.partner.unlink()
|
|
self.env.cr.commit()
|