test_mailing.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from odoo.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE
  4. from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
  5. from odoo.tests import tagged
  6. from odoo.tests.common import users
  7. from odoo.tools import mute_logger, email_normalize
  8. @tagged('mass_mailing')
  9. class TestMassMailing(TestMassMailCommon):
  10. @classmethod
  11. def setUpClass(cls):
  12. super(TestMassMailing, cls).setUpClass()
  13. @users('user_marketing')
  14. @mute_logger('odoo.addons.mail.models.mail_thread')
  15. def test_mailing_gateway_reply(self):
  16. customers = self.env['res.partner']
  17. for x in range(0, 3):
  18. customers |= self.env['res.partner'].create({
  19. 'name': 'Customer_%02d' % x,
  20. 'email': '"Customer_%02d" <customer_%02d@test.example.com' % (x, x),
  21. })
  22. mailing = self.env['mailing.mailing'].create({
  23. 'name': 'TestName',
  24. 'subject': 'TestSubject',
  25. 'body_html': 'Hello <t t-out="object.name" />',
  26. 'reply_to_mode': 'new',
  27. 'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
  28. 'keep_archives': True,
  29. 'mailing_model_id': self.env['ir.model']._get('res.partner').id,
  30. 'mailing_domain': '%s' % [('id', 'in', customers.ids)],
  31. })
  32. mailing.action_put_in_queue()
  33. with self.mock_mail_gateway(mail_unlink_sent=False):
  34. mailing.action_send_mail()
  35. self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[0], use_in_reply_to=True)
  36. self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[1], use_in_reply_to=False)
  37. # customer2 looses headers
  38. mail_mail = self._find_mail_mail_wrecord(customers[2])
  39. self.format_and_process(
  40. MAIL_TEMPLATE,
  41. mail_mail.email_to,
  42. mail_mail.reply_to,
  43. subject='Re: %s' % mail_mail.subject,
  44. extra='',
  45. msg_id='<123456.%s.%d@test.example.com>' % (customers[2]._name, customers[2].id),
  46. target_model=customers[2]._name, target_field=customers[2]._rec_name,
  47. )
  48. mailing.flush_recordset()
  49. # check traces status
  50. traces = self.env['mailing.trace'].search([('model', '=', customers._name), ('res_id', 'in', customers.ids)])
  51. self.assertEqual(len(traces), 3)
  52. customer0_trace = traces.filtered(lambda t: t.res_id == customers[0].id)
  53. self.assertEqual(customer0_trace.trace_status, 'reply')
  54. customer1_trace = traces.filtered(lambda t: t.res_id == customers[1].id)
  55. self.assertEqual(customer1_trace.trace_status, 'reply')
  56. customer2_trace = traces.filtered(lambda t: t.res_id == customers[2].id)
  57. self.assertEqual(customer2_trace.trace_status, 'sent')
  58. # check mailing statistics
  59. self.assertEqual(mailing.sent, 3)
  60. self.assertEqual(mailing.delivered, 3)
  61. self.assertEqual(mailing.opened, 2)
  62. self.assertEqual(mailing.replied, 2)
  63. @users('user_marketing')
  64. @mute_logger('odoo.addons.mail.models.mail_mail')
  65. def test_mailing_gateway_update(self):
  66. mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
  67. recipients = self._create_mailing_test_records(model='mailing.test.optout', count=5)
  68. self.assertEqual(len(recipients), 5)
  69. mailing.write({
  70. 'mailing_model_id': self.env['ir.model']._get('mailing.test.optout'),
  71. 'mailing_domain': [('id', 'in', recipients.ids)]
  72. })
  73. with self.mock_mail_gateway(mail_unlink_sent=False):
  74. mailing.action_send_mail()
  75. self.assertMailTraces(
  76. [{'email': record.email_normalized}
  77. for record in recipients],
  78. mailing, recipients,
  79. mail_links_info=[[
  80. ('url0', 'https://www.odoo.tz/my/%s' % record.name, True, {}),
  81. ('url1', 'https://www.odoo.be', True, {}),
  82. ('url2', 'https://www.odoo.com', True, {}),
  83. ('url3', 'https://www.odoo.eu', True, {}),
  84. ('url4', 'https://www.example.com/foo/bar?baz=qux', True, {'baz': 'qux'}),
  85. ('url5', '%s/event/dummy-event-0' % mailing.get_base_url(), True, {}),
  86. # view is not shortened and parsed at sending
  87. ('url6', '%s/view' % mailing.get_base_url(), False, {}),
  88. ('url7', 'mailto:test@odoo.com', False, {}),
  89. # unsubscribe is not shortened and parsed at sending
  90. ('url8', '%s/unsubscribe_from_list' % mailing.get_base_url(), False, {}),
  91. ] for record in recipients],
  92. check_mail=True
  93. )
  94. self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5)
  95. # simulate a click
  96. self.gateway_mail_click(mailing, recipients[0], 'https://www.odoo.be')
  97. mailing.invalidate_recordset()
  98. self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, clicked=1)
  99. # simulate a bounce
  100. self.assertEqual(recipients[1].message_bounce, 0)
  101. self.gateway_mail_bounce(mailing, recipients[1])
  102. mailing.invalidate_recordset()
  103. self.assertMailingStatistics(mailing, expected=5, delivered=4, sent=5, opened=1, clicked=1, bounced=1)
  104. self.assertEqual(recipients[1].message_bounce, 1)
  105. @users('user_marketing')
  106. @mute_logger('odoo.addons.mail.models.mail_mail')
  107. def test_mailing_recipients(self):
  108. """ Test recipient-specific computation, with email, formatting,
  109. multi-emails, ... to test corner cases. Blacklist mixin impact is
  110. tested. """
  111. (customer_mult, customer_fmt, customer_unic,
  112. customer_case, customer_weird, customer_weird_2
  113. ) = self.env['res.partner'].create([
  114. {
  115. 'email': 'customer.multi.1@example.com, "Test Multi 2" <customer.multi.2@example.com>',
  116. 'name': 'MultiEMail',
  117. }, {
  118. 'email': '"Formatted Customer" <test.customer.format@example.com>',
  119. 'name': 'FormattedEmail',
  120. }, {
  121. 'email': '"Unicode Customer" <test.customer.😊@example.com>',
  122. 'name': 'UnicodeEmail',
  123. }, {
  124. 'email': 'TEST.CUSTOMER.CASE@EXAMPLE.COM',
  125. 'name': 'CaseEmail',
  126. }, {
  127. 'email': 'test.customer.weird@example.com Weird Format',
  128. 'name': 'WeirdFormatEmail',
  129. }, {
  130. 'email': 'Weird Format2 test.customer.weird.2@example.com',
  131. 'name': 'WeirdFormatEmail2',
  132. }
  133. ])
  134. # check difference of email management between a classic model and a model
  135. # with an 'email_normalized' field (blacklist mixin)
  136. for dst_model in ['mailing.test.customer', 'mailing.test.blacklist']:
  137. with self.subTest(dst_model=dst_model):
  138. (record_p_mult, record_p_fmt, record_p_unic,
  139. record_p_case, record_p_weird, record_p_weird_2,
  140. record_mult, record_fmt, record_unic,
  141. record_case, recod_weird, record_weird_2
  142. ) = self.env[dst_model].create([
  143. {
  144. 'customer_id': customer_mult.id,
  145. }, {
  146. 'customer_id': customer_fmt.id,
  147. }, {
  148. 'customer_id': customer_unic.id,
  149. }, {
  150. 'customer_id': customer_case.id,
  151. }, {
  152. 'customer_id': customer_weird.id,
  153. }, {
  154. 'customer_id': customer_weird_2.id,
  155. }, {
  156. 'email_from': 'record.multi.1@example.com, "Record Multi 2" <record.multi.2@example.com>',
  157. }, {
  158. 'email_from': '"Formatted Record" <record.format@example.com>',
  159. }, {
  160. 'email_from': '"Unicode Record" <record.😊@example.com>',
  161. }, {
  162. 'email_from': 'TEST.RECORD.CASE@EXAMPLE.COM',
  163. }, {
  164. 'email_from': 'test.record.weird@example.com Weird Format',
  165. }, {
  166. 'email_from': 'Weird Format2 test.record.weird.2@example.com',
  167. }
  168. ])
  169. test_records = (
  170. record_p_mult + record_p_fmt + record_p_unic +
  171. record_p_case + record_p_weird + record_p_weird_2 +
  172. record_mult + record_fmt + record_unic +
  173. record_case + recod_weird + record_weird_2
  174. )
  175. mailing = self.env['mailing.mailing'].create({
  176. 'body_html': """<div><p>Hello ${object.name}</p>""",
  177. 'mailing_domain': [('id', 'in', test_records.ids)],
  178. 'mailing_model_id': self.env['ir.model']._get_id(dst_model),
  179. 'mailing_type': 'mail',
  180. 'name': 'SourceName',
  181. 'preview': 'Hi ${object.name} :)',
  182. 'reply_to_mode': 'update',
  183. 'subject': 'MailingSubject',
  184. })
  185. with self.mock_mail_gateway(mail_unlink_sent=False):
  186. mailing.action_send_mail()
  187. # Difference in email, email_to_recipients and email_to_mail
  188. # -> email: trace email: normalized, to ease its management, mainly technical
  189. # -> email_to_mail: mail.mail email: email_to stored in outgoing mail.mail (can be multi)
  190. # -> email_to_recipients: email_to for outgoing emails, list means several recipients
  191. self.assertMailTraces(
  192. [
  193. {'email': 'customer.multi.1@example.com, "Test Multi 2" <customer.multi.2@example.com>',
  194. 'email_to_recipients': [[f'"{customer_mult.name}" <customer.multi.1@example.com>', f'"{customer_mult.name}" <customer.multi.2@example.com>']],
  195. 'failure_type': False,
  196. 'partner': customer_mult,
  197. 'trace_status': 'sent'},
  198. {'email': '"Formatted Customer" <test.customer.format@example.com>',
  199. # mail to avoids double encapsulation
  200. 'email_to_recipients': [[f'"{customer_fmt.name}" <test.customer.format@example.com>']],
  201. 'failure_type': False,
  202. 'partner': customer_fmt,
  203. 'trace_status': 'sent'},
  204. {'email': '"Unicode Customer" <test.customer.😊@example.com>',
  205. # mail to avoids double encapsulation
  206. 'email_to_recipients': [[f'"{customer_unic.name}" <test.customer.😊@example.com>']],
  207. 'failure_type': False,
  208. 'partner': customer_unic,
  209. 'trace_status': 'sent'},
  210. {'email': 'TEST.CUSTOMER.CASE@EXAMPLE.COM',
  211. 'email_to_recipients': [[f'"{customer_case.name}" <test.customer.case@example.com>']],
  212. 'failure_type': False,
  213. 'partner': customer_case,
  214. 'trace_status': 'sent'}, # lower cased
  215. {'email': 'test.customer.weird@example.com Weird Format',
  216. 'email_to_recipients': [[f'"{customer_weird.name}" <test.customer.weird@example.comweirdformat>']],
  217. 'failure_type': False,
  218. 'partner': customer_weird,
  219. 'trace_status': 'sent'}, # concatenates everything after domain
  220. {'email': 'Weird Format2 test.customer.weird.2@example.com',
  221. 'email_to_recipients': [[f'"{customer_weird_2.name}" <test.customer.weird.2@example.com>']],
  222. 'failure_type': False,
  223. 'partner': customer_weird_2,
  224. 'trace_status': 'sent'},
  225. {'email': 'record.multi.1@example.com',
  226. 'email_to_mail': 'record.multi.1@example.com,record.multi.2@example.com',
  227. 'email_to_recipients': [['record.multi.1@example.com', 'record.multi.2@example.com']],
  228. 'failure_type': False,
  229. 'trace_status': 'sent'},
  230. {'email': 'record.format@example.com',
  231. 'email_to_mail': 'record.format@example.com',
  232. 'email_to_recipients': [['record.format@example.com']],
  233. 'failure_type': False,
  234. 'trace_status': 'sent'},
  235. {'email': 'record.😊@example.com',
  236. 'email_to_mail': 'record.😊@example.com',
  237. 'email_to_recipients': [['record.😊@example.com']],
  238. 'failure_type': False,
  239. 'trace_status': 'sent'},
  240. {'email': 'test.record.case@example.com',
  241. 'email_to_mail': 'test.record.case@example.com',
  242. 'email_to_recipients': [['test.record.case@example.com']],
  243. 'failure_type': False,
  244. 'trace_status': 'sent'},
  245. {'email': 'test.record.weird@example.comweirdformat',
  246. 'email_to_mail': 'test.record.weird@example.comweirdformat',
  247. 'email_to_recipients': [['test.record.weird@example.comweirdformat']],
  248. 'failure_type': False,
  249. 'trace_status': 'sent'},
  250. {'email': 'test.record.weird.2@example.com',
  251. 'email_to_mail': 'test.record.weird.2@example.com',
  252. 'email_to_recipients': [['test.record.weird.2@example.com']],
  253. 'failure_type': False,
  254. 'trace_status': 'sent'},
  255. ],
  256. mailing,
  257. test_records,
  258. check_mail=True,
  259. )
  260. @users('user_marketing')
  261. @mute_logger('odoo.addons.mail.models.mail_mail')
  262. def test_mailing_reply_to_mode_new(self):
  263. mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
  264. recipients = self._create_mailing_test_records(model='mailing.test.blacklist', count=5)
  265. self.assertEqual(len(recipients), 5)
  266. initial_messages = recipients.message_ids
  267. mailing.write({
  268. 'mailing_domain': [('id', 'in', recipients.ids)],
  269. 'keep_archives': False,
  270. 'reply_to_mode': 'new',
  271. 'reply_to': self.test_alias.display_name,
  272. })
  273. with self.mock_mail_gateway(mail_unlink_sent=True):
  274. mailing.action_send_mail()
  275. answer_rec = self.gateway_mail_reply_wemail(MAIL_TEMPLATE, recipients[0].email_normalized, target_model=self.test_alias.alias_model_id.model)
  276. self.assertTrue(bool(answer_rec))
  277. self.assertEqual(answer_rec.name, 'Re: %s' % mailing.subject)
  278. self.assertEqual(
  279. answer_rec.message_ids.subject, 'Re: %s' % mailing.subject,
  280. 'Answer should be logged')
  281. self.assertEqual(recipients.message_ids, initial_messages)
  282. self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, replied=1)
  283. @users('user_marketing')
  284. @mute_logger('odoo.addons.mail.models.mail_mail')
  285. def test_mailing_reply_to_mode_update(self):
  286. mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
  287. recipients = self._create_mailing_test_records(model='mailing.test.blacklist', count=5)
  288. self.assertEqual(len(recipients), 5)
  289. mailing.write({
  290. 'mailing_domain': [('id', 'in', recipients.ids)],
  291. 'keep_archives': False,
  292. 'reply_to_mode': 'update',
  293. 'reply_to': self.test_alias.display_name,
  294. })
  295. with self.mock_mail_gateway(mail_unlink_sent=True):
  296. mailing.action_send_mail()
  297. answer_rec = self.gateway_mail_reply_wemail(MAIL_TEMPLATE, recipients[0].email_normalized, target_model=self.test_alias.alias_model_id.model)
  298. self.assertFalse(bool(answer_rec))
  299. self.assertEqual(
  300. recipients[0].message_ids[1].subject, mailing.subject,
  301. 'Should have keep a log (to enable thread-based answer)')
  302. self.assertEqual(
  303. recipients[0].message_ids[0].subject, 'Re: %s' % mailing.subject,
  304. 'Answer should be logged')
  305. self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, replied=1)
  306. @users('user_marketing')
  307. @mute_logger('odoo.addons.mail.models.mail_thread')
  308. def test_mailing_trace_utm(self):
  309. """ Test mailing UTMs are caught on reply"""
  310. self._create_mailing_list()
  311. self.test_alias.write({
  312. 'alias_model_id': self.env['ir.model']._get('mailing.test.utm').id
  313. })
  314. source = self.env['utm.source'].create({'name': 'Source test'})
  315. medium = self.env['utm.medium'].create({'name': 'Medium test'})
  316. campaign = self.env['utm.campaign'].create({'name': 'Campaign test'})
  317. subject = 'MassMailingTestUTM'
  318. mailing = self.env['mailing.mailing'].create({
  319. 'name': 'UTMTest',
  320. 'subject': subject,
  321. 'body_html': '<p>Hello <t t-out="object.name"/></p>',
  322. 'reply_to_mode': 'new',
  323. 'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
  324. 'keep_archives': True,
  325. 'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
  326. 'contact_list_ids': [(4, self.mailing_list_1.id)],
  327. 'source_id': source.id,
  328. 'medium_id': medium.id,
  329. 'campaign_id': campaign.id
  330. })
  331. with self.mock_mail_gateway(mail_unlink_sent=False):
  332. mailing.action_send_mail()
  333. traces = self.env['mailing.trace'].search([('model', '=', self.mailing_list_1.contact_ids._name), ('res_id', 'in', self.mailing_list_1.contact_ids.ids)])
  334. self.assertEqual(len(traces), 3)
  335. # simulate response to mailing
  336. self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[0], use_in_reply_to=True)
  337. self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[1], use_in_reply_to=False)
  338. mailing_test_utms = self.env['mailing.test.utm'].search([('name', '=', 'Re: %s' % subject)])
  339. self.assertEqual(len(mailing_test_utms), 2)
  340. for test_utm in mailing_test_utms:
  341. self.assertEqual(test_utm.campaign_id, campaign)
  342. self.assertEqual(test_utm.source_id, source)
  343. self.assertEqual(test_utm.medium_id, medium)
  344. @users('user_marketing')
  345. @mute_logger('odoo.addons.mail.models.mail_mail')
  346. def test_mailing_w_blacklist(self):
  347. mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
  348. recipients = self._create_mailing_test_records(count=5)
  349. # blacklist records 2, 3, 4
  350. self.env['mail.blacklist'].create({'email': recipients[2].email_normalized})
  351. self.env['mail.blacklist'].create({'email': recipients[3].email_normalized})
  352. self.env['mail.blacklist'].create({'email': recipients[4].email_normalized})
  353. # unblacklist record 2
  354. self.env['mail.blacklist'].action_remove_with_reason(
  355. recipients[2].email_normalized, "human error"
  356. )
  357. self.env['mail.blacklist'].flush_model(['active'])
  358. mailing.write({'mailing_domain': [('id', 'in', recipients.ids)]})
  359. with self.mock_mail_gateway(mail_unlink_sent=False):
  360. mailing.action_send_mail()
  361. self.assertMailTraces(
  362. [{'email': 'test.record.00@test.example.com'},
  363. {'email': 'test.record.01@test.example.com'},
  364. {'email': 'test.record.02@test.example.com'},
  365. {'email': 'test.record.03@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'},
  366. {'email': 'test.record.04@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'}],
  367. mailing, recipients, check_mail=True
  368. )
  369. self.assertEqual(mailing.canceled, 2)
  370. @users('user_marketing')
  371. @mute_logger('odoo.addons.mail.models.mail_mail')
  372. def test_mailing_w_blacklist_nomixin(self):
  373. """Test that blacklist is applied even if the target model doesn't inherit
  374. from mail.thread.blacklist."""
  375. test_records = self._create_mailing_test_records(model='mailing.test.simple', count=2)
  376. self.mailing_bl.write({
  377. 'mailing_domain': [('id', 'in', test_records.ids)],
  378. 'mailing_model_id': self.env['ir.model']._get('mailing.test.simple').id,
  379. })
  380. self.env['mail.blacklist'].create([{
  381. 'email': test_records[0].email_from,
  382. 'active': True,
  383. }])
  384. with self.mock_mail_gateway(mail_unlink_sent=False):
  385. self.mailing_bl.action_send_mail()
  386. self.assertMailTraces([
  387. {'email': email_normalize(test_records[0].email_from), 'trace_status': 'cancel', 'failure_type': 'mail_bl'},
  388. {'email': email_normalize(test_records[1].email_from), 'trace_status': 'sent'},
  389. ], self.mailing_bl, test_records, check_mail=False)
  390. @users('user_marketing')
  391. @mute_logger('odoo.addons.mail.models.mail_mail')
  392. def test_mailing_w_opt_out(self):
  393. mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
  394. recipients = self._create_mailing_test_records(model='mailing.test.optout', count=5)
  395. # optout records 0 and 1
  396. (recipients[0] | recipients[1]).write({'opt_out': True})
  397. # blacklist records 4
  398. self.env['mail.blacklist'].create({'email': recipients[4].email_normalized})
  399. mailing.write({
  400. 'mailing_model_id': self.env['ir.model']._get('mailing.test.optout'),
  401. 'mailing_domain': [('id', 'in', recipients.ids)]
  402. })
  403. with self.mock_mail_gateway(mail_unlink_sent=False):
  404. mailing.action_send_mail()
  405. self.assertMailTraces(
  406. [{'email': 'test.record.00@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_optout'},
  407. {'email': 'test.record.01@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_optout'},
  408. {'email': 'test.record.02@test.example.com'},
  409. {'email': 'test.record.03@test.example.com'},
  410. {'email': 'test.record.04@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'}],
  411. mailing, recipients, check_mail=True
  412. )
  413. self.assertEqual(mailing.canceled, 3)
  414. @users('user_marketing')
  415. def test_mailing_w_seenlist(self):
  416. """
  417. Tests whether function `_get_seen_list` is correctly able to identify duplicate emails,
  418. even through different batches.
  419. Mails use different names to make sure they are recognized as duplicates even without being
  420. normalized (e.g.: '"jc" <0@example.com>' and '"vd" <0@example.com>' are duplicates)
  421. """
  422. BATCH_SIZE = 5
  423. names = ['jc', 'vd']
  424. emails = [f'test.{i}@example.com' for i in range(BATCH_SIZE)]
  425. records = self.env['mailing.test.partner'].create([{
  426. 'name': f'test_duplicates {i}', 'email_from': f'"{names[i % 2]}" <{emails[i % BATCH_SIZE]}>'
  427. } for i in range(20)])
  428. mailing = self.env['mailing.mailing'].create({
  429. 'mailing_domain': [('name', 'ilike', 'test_duplicates %')],
  430. 'mailing_model_id': self.env.ref('test_mass_mailing.model_mailing_test_partner').id,
  431. 'name': 'test duplicates',
  432. 'subject': 'test duplicates',
  433. })
  434. with self.mock_mail_gateway():
  435. for i in range(0, 20, BATCH_SIZE):
  436. mailing.action_send_mail(records[i:i + BATCH_SIZE].mapped('id'))
  437. self.assertEqual(len(self._mails), BATCH_SIZE)
  438. self.assertEqual(mailing.canceled, 15)
  439. mails_sent = [email_normalize(mail['email_to'][0]) for mail in self._mails]
  440. for email in emails:
  441. self.assertEqual(mails_sent.count(email), 1)
  442. @users('user_marketing')
  443. def test_mailing_w_seenlist_unstored_partner(self):
  444. """ Test seen list when partners are not stored. """
  445. test_customers = self.env['res.partner'].sudo().create([
  446. {'email': f'"Mailing Partner {idx}" <email.from.{idx}@test.example.com',
  447. 'name': f'Mailing Partner {idx}',
  448. } for idx in range(8)
  449. ])
  450. test_records = self.env['mailing.test.partner.unstored'].create([
  451. {'email_from': f'email.from.{idx}@test.example.com',
  452. 'name': f'Mailing Record {idx}',
  453. } for idx in range(10)
  454. ])
  455. self.assertEqual(test_records[:8].partner_id, test_customers)
  456. self.assertFalse(test_records[9:].partner_id)
  457. mailing = self.env['mailing.mailing'].create({
  458. 'body_html': '<p>Marketing stuff for ${object.name}</p>',
  459. 'mailing_domain': [('id', 'in', test_records.ids)],
  460. 'mailing_model_id': self.env['ir.model']._get_id('mailing.test.partner.unstored'),
  461. 'name': 'test',
  462. 'subject': 'Blacklisted',
  463. })
  464. # create existing traces to check the seen list
  465. traces = self._create_sent_traces(
  466. mailing,
  467. test_records[:3]
  468. )
  469. traces.flush_model()
  470. # check remaining recipients effectively check seen list
  471. mailing.action_put_in_queue()
  472. res_ids = mailing._get_remaining_recipients()
  473. self.assertEqual(sorted(res_ids), sorted(test_records[3:].ids))
  474. with self.mock_mail_gateway(mail_unlink_sent=False):
  475. mailing.action_send_mail()
  476. self.assertEqual(len(self._mails), 7, 'Mailing: seen list should contain 3 existing traces')
  477. @users('user_marketing')
  478. @mute_logger('odoo.addons.mail.models.mail_mail')
  479. def test_mailing_mailing_list_optout(self):
  480. """ Test mailing list model specific optout behavior """
  481. mailing_contact_1 = self.env['mailing.contact'].create({'name': 'test 1A', 'email': 'test@test.example.com'})
  482. mailing_contact_2 = self.env['mailing.contact'].create({'name': 'test 1B', 'email': 'test@test.example.com'})
  483. mailing_contact_3 = self.env['mailing.contact'].create({'name': 'test 3', 'email': 'test3@test.example.com'})
  484. mailing_contact_4 = self.env['mailing.contact'].create({'name': 'test 4', 'email': 'test4@test.example.com'})
  485. mailing_contact_5 = self.env['mailing.contact'].create({'name': 'test 5', 'email': 'test5@test.example.com'})
  486. # create mailing list record
  487. mailing_list_1 = self.env['mailing.list'].create({
  488. 'name': 'A',
  489. 'contact_ids': [
  490. (4, mailing_contact_1.id),
  491. (4, mailing_contact_2.id),
  492. (4, mailing_contact_3.id),
  493. (4, mailing_contact_5.id),
  494. ]
  495. })
  496. mailing_list_2 = self.env['mailing.list'].create({
  497. 'name': 'B',
  498. 'contact_ids': [
  499. (4, mailing_contact_3.id),
  500. (4, mailing_contact_4.id),
  501. ]
  502. })
  503. # contact_1 is optout but same email is not optout from the same list
  504. # contact 3 is optout in list 1 but not in list 2
  505. # contact 5 is optout
  506. subs = self.env['mailing.contact.subscription'].search([
  507. '|', '|',
  508. '&', ('contact_id', '=', mailing_contact_1.id), ('list_id', '=', mailing_list_1.id),
  509. '&', ('contact_id', '=', mailing_contact_3.id), ('list_id', '=', mailing_list_1.id),
  510. '&', ('contact_id', '=', mailing_contact_5.id), ('list_id', '=', mailing_list_1.id)
  511. ])
  512. subs.write({'opt_out': True})
  513. # create mass mailing record
  514. mailing = self.env['mailing.mailing'].create({
  515. 'name': 'SourceName',
  516. 'subject': 'MailingSubject',
  517. 'body_html': '<p>Hello <t t-out="object.name"/></p>',
  518. 'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
  519. 'contact_list_ids': [(4, ml.id) for ml in mailing_list_1 | mailing_list_2],
  520. })
  521. with self.mock_mail_gateway(mail_unlink_sent=False):
  522. mailing.action_send_mail()
  523. self.assertMailTraces(
  524. [{'email': 'test@test.example.com', 'trace_status': 'sent'},
  525. {'email': 'test@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_dup'},
  526. {'email': 'test3@test.example.com'},
  527. {'email': 'test4@test.example.com'},
  528. {'email': 'test5@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_optout'}],
  529. mailing,
  530. mailing_contact_1 + mailing_contact_2 + mailing_contact_3 + mailing_contact_4 + mailing_contact_5,
  531. check_mail=True
  532. )
  533. self.assertEqual(mailing.canceled, 2)