# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime from dateutil.relativedelta import relativedelta from unittest.mock import patch from odoo import exceptions from odoo.addons.base.tests.test_ir_cron import CronMixinCase from odoo.addons.event.tests.common import EventCase from odoo.addons.mail.tests.common import MockEmail from odoo.tests import tagged, users, warmup from odoo.tools import formataddr, mute_logger @tagged('event_mail', 'post_install', '-at_install') class TestMailSchedule(EventCase, MockEmail, CronMixinCase): @classmethod def setUpClass(cls): super().setUpClass() cls.env.company.write({ 'email': 'info@yourcompany.example.com', 'name': 'YourCompany', }) cls.event_cron_id = cls.env.ref('event.event_mail_scheduler') # deactivate other schedulers to avoid messing with crons cls.env['event.mail'].search([]).unlink() # consider asynchronous sending as default sending cls.env["ir.config_parameter"].set_param("event.event_mail_async", False) # freeze some datetimes, and ensure more than 1D+1H before event starts # to ease time-based scheduler check # Since `now` is used to set the `create_date` of an event and create_date # has often microseconds, we set it to ensure that the scheduler we still be # launched if scheduled_date == create_date - microseconds cls.reference_now = datetime(2021, 3, 20, 14, 30, 15, 123456) cls.event_date_begin = datetime(2021, 3, 22, 8, 0, 0) cls.event_date_end = datetime(2021, 3, 24, 18, 0, 0) cls._setup_test_reports() with cls.mock_datetime_and_now(cls, cls.reference_now): # create with admin to force create_date cls.test_event = cls.env['event.event'].create({ 'name': 'TestEventMail', 'user_id': cls.user_eventmanager.id, 'date_begin': cls.event_date_begin, 'date_end': cls.event_date_end, 'event_mail_ids': [ (0, 0, { # right at subscription 'interval_unit': 'now', 'interval_type': 'after_sub', 'template_ref': f'mail.template,{cls.template_subscription.id}', }), (0, 0, { # one hour after subscription 'interval_nbr': 1, 'interval_unit': 'hours', 'interval_type': 'after_sub', 'template_ref': f'mail.template,{cls.template_subscription.id}', }), (0, 0, { # 1 days before event 'interval_nbr': 1, 'interval_unit': 'days', 'interval_type': 'before_event', 'template_ref': f'mail.template,{cls.template_reminder.id}', }), (0, 0, { # immediately after event 'interval_nbr': 1, 'interval_unit': 'hours', 'interval_type': 'after_event', 'template_ref': f'mail.template,{cls.template_reminder.id}', }), ] }) def test_assert_initial_values(self): """ Ensure base values for tests """ test_event = self.test_event # event data self.assertEqual(test_event.create_date, self.reference_now) # check subscription scheduler after_sub_scheduler = self.env['event.mail'].search([('event_id', '=', test_event.id), ('interval_type', '=', 'after_sub'), ('interval_unit', '=', 'now')]) self.assertEqual(len(after_sub_scheduler), 1, 'event: wrong scheduler creation') self.assertEqual(after_sub_scheduler.scheduled_date, test_event.create_date.replace(microsecond=0)) self.assertEqual(after_sub_scheduler.mail_state, 'running') self.assertEqual(after_sub_scheduler.mail_count_done, 0) after_sub_scheduler_2 = self.env['event.mail'].search([('event_id', '=', test_event.id), ('interval_type', '=', 'after_sub'), ('interval_unit', '=', 'hours')]) self.assertEqual(len(after_sub_scheduler_2), 1, 'event: wrong scheduler creation') self.assertEqual(after_sub_scheduler_2.scheduled_date, test_event.create_date.replace(microsecond=0) + relativedelta(hours=1)) self.assertEqual(after_sub_scheduler_2.mail_state, 'running') self.assertEqual(after_sub_scheduler_2.mail_count_done, 0) # check before event scheduler event_prev_scheduler = self.env['event.mail'].search([('event_id', '=', test_event.id), ('interval_type', '=', 'before_event')]) self.assertEqual(len(event_prev_scheduler), 1, 'event: wrong scheduler creation') self.assertEqual(event_prev_scheduler.scheduled_date, self.event_date_begin + relativedelta(days=-1)) self.assertFalse(event_prev_scheduler.mail_done) self.assertEqual(event_prev_scheduler.mail_state, 'scheduled') self.assertEqual(event_prev_scheduler.mail_count_done, 0) # check after event scheduler event_next_scheduler = self.env['event.mail'].search([('event_id', '=', test_event.id), ('interval_type', '=', 'after_event')]) self.assertEqual(len(event_next_scheduler), 1, 'event: wrong scheduler creation') self.assertEqual(event_next_scheduler.scheduled_date, self.event_date_end + relativedelta(hours=1)) self.assertFalse(event_next_scheduler.mail_done) self.assertEqual(event_next_scheduler.mail_state, 'scheduled') self.assertEqual(event_next_scheduler.mail_count_done, 0) @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') @users('user_eventmanager') def test_event_mail_schedule(self): """ Test mail scheduling for events """ test_event = self.test_event.with_env(self.env) now = self.reference_now schedulers = self.env['event.mail'].search([('event_id', '=', test_event.id)]) after_sub_scheduler = schedulers.filtered(lambda s: s.interval_type == 'after_sub' and s.interval_unit == 'now') after_sub_scheduler_2 = schedulers.filtered(lambda s: s.interval_type == 'after_sub' and s.interval_unit == 'hours') event_prev_scheduler = schedulers.filtered(lambda s: s.interval_type == 'before_event') event_next_scheduler = schedulers.filtered(lambda s: s.interval_type == 'after_event') # check iterative work, update params to check call count batch_size, render_limit = 2, 10 self.env['ir.config_parameter'].sudo().set_param('mail.batch_size', batch_size) self.env['ir.config_parameter'].sudo().set_param('mail.render.cron.limit', render_limit) # create some registrations EventMailRegistration = type(self.env['event.mail.registration']) exec_origin = EventMailRegistration._execute_on_registrations with patch.object( EventMailRegistration, '_execute_on_registrations', autospec=True, wraps=EventMailRegistration, side_effect=exec_origin, ) as mock_exec, \ self.mock_datetime_and_now(now), self.mock_mail_gateway(), \ self.capture_triggers('event.event_mail_scheduler') as capture: attendees = self.env['event.registration'].with_user(self.user_eventuser).create([ { 'event_id': test_event.id, 'name': f'Reg{idx}', 'email': f'reg1{idx}@example.com', } for idx in range(15)] + [{ 'event_id': test_event.id, 'name': 'RegDraft', 'email': 'reg_draft@example.com', 'state': 'draft', }, { 'event_id': test_event.id, 'name': 'RegCancel', 'email': 'reg_cancel@example.com', 'state': 'cancel', } ]) # iterative check self.assertEqual( mock_exec.call_count, 5, "Should have called 5 times execution (batch of 2 until 10 registrations)" ) # REGISTRATIONS / PRE SCHEDULERS # -------------------------------------------------- # check registration state self.assertTrue(all(reg.state == 'open' for reg in attendees[:15]), 'Registrations: should be auto-confirmed') self.assertListEqual(attendees[15:].mapped('state'), ['draft', 'cancel']) self.assertTrue(all(reg.create_date == now for reg in attendees), 'Registrations: should have open date set to confirm date') # verify that subscription scheduler was auto-executed after each registration self.assertEqual( len(after_sub_scheduler.mail_registration_ids), 15, 'Should have 15 scheduled communication (1 / registration), as we schedule more' 'than cron limit as create should be quick.') for idx, mail_registration in enumerate(after_sub_scheduler.mail_registration_ids): self.assertEqual(mail_registration.scheduled_date, now.replace(microsecond=0)) if idx < 10: self.assertTrue(mail_registration.mail_sent, 'event: registration mail should be sent at registration creation') else: self.assertFalse(mail_registration.mail_sent, 'event: registration mail should be scheduled, too much for limit') self.assertEqual(after_sub_scheduler.mail_state, 'running') self.assertEqual(after_sub_scheduler.mail_count_done, 10, 'event: not all subscription mails should have been sent as too much for limit') # cron should have been triggered for the remaining registrations self.assertSchedulerCronTriggers(capture, [now]) # check emails effectively sent self.assertEqual(len(self._new_mails), 10, 'event: should have 10 scheduled emails (1 / executed registration)') self.assertMailMailWEmails( [formataddr((reg.name, reg.email)) for reg in attendees[:10]], 'outgoing', content=None, fields_values={ 'email_from': self.user_eventmanager.company_id.email_formatted, 'subject': f'Confirmation for {test_event.name}', }) # same for second scheduler: scheduled but not sent self.assertEqual( len(after_sub_scheduler_2.mail_registration_ids), 15, 'Should have 15 scheduled communication (1 / registration)') for mail_registration in after_sub_scheduler_2.mail_registration_ids: self.assertEqual(mail_registration.scheduled_date, now.replace(microsecond=0) + relativedelta(hours=1)) self.assertFalse(mail_registration.mail_sent, 'event: registration mail should be scheduled, not sent') self.assertEqual(after_sub_scheduler_2.mail_count_done, 0, 'event: all subscription mails should be scheduled, not sent') # RE-RUN SCHEDULER TO COMPLETE SENDING # -------------------------------------------------- with patch.object( EventMailRegistration, '_execute_on_registrations', autospec=True, wraps=EventMailRegistration, side_effect=exec_origin, ) as mock_exec, \ self.mock_datetime_and_now(now), self.mock_mail_gateway(), \ self.capture_triggers('event.event_mail_scheduler') as capture: self.event_cron_id.method_direct_trigger() # iterative check self.assertEqual( mock_exec.call_count, 3, "Should have called 3 times execution (batch of 2 with 5 registrations left = 3 iterations)" ) # verify that subscription scheduler was auto-executed after each registration self.assertEqual(len(after_sub_scheduler.mail_registration_ids), 15) for mail_registration in after_sub_scheduler.mail_registration_ids: self.assertEqual(mail_registration.scheduled_date, now.replace(microsecond=0)) self.assertTrue(mail_registration.mail_sent) self.assertEqual(after_sub_scheduler.mail_state, 'running') self.assertEqual( after_sub_scheduler.mail_count_done, 15, 'Should have sent all mails, as cron limit is set to 20' ) # check emails effectively sent self.assertEqual(len(self._new_mails), 5, 'event: should have 5 scheduled emails (1 / executed registration)') self.assertMailMailWEmails( [formataddr((reg.name, reg.email)) for reg in attendees[10:15]], 'outgoing', content=None, fields_values={ 'email_from': self.user_eventmanager.company_id.email_formatted, 'subject': f'Confirmation for {test_event.name}', }) # SECOND ATTENDEE-BASED SCHEDULER (LATER) - UPDATE ITERATIVE # -------------------------------------------------- # check default behavior, batch of 50 to run up to 1000 attendees self.env['ir.config_parameter'].sudo().set_param('mail.batch_size', False) self.env['ir.config_parameter'].sudo().set_param('mail.render.cron.limit', False) # execute event reminder scheduler explicitly, before scheduled date -> should not do anything with self.mock_datetime_and_now(now), self.mock_mail_gateway(): after_sub_scheduler_2.execute() self.assertFalse(any(mail_reg.mail_sent for mail_reg in after_sub_scheduler_2.mail_registration_ids)) self.assertEqual(after_sub_scheduler_2.mail_count_done, 0) self.assertEqual(len(self._new_mails), 0, 'event: should not send mails before scheduled date') # execute event reminder scheduler, right at scheduled date -> should sent mails now_registration = now + relativedelta(hours=1) with patch.object( EventMailRegistration, '_execute_on_registrations', autospec=True, wraps=EventMailRegistration, side_effect=exec_origin, ) as mock_exec, \ self.mock_datetime_and_now(now_registration), self.mock_mail_gateway(), \ self.capture_triggers('event.event_mail_scheduler') as capture: self.event_cron_id.method_direct_trigger() # iterative check self.assertEqual( mock_exec.call_count, 1, "Should have called 1 times execution (batch of 50 with 15 registrations = 1 iteration)" ) # verify that subscription scheduler was auto-executed after each registration self.assertEqual(len(after_sub_scheduler_2.mail_registration_ids), 15, 'event: should have 15 scheduled communication (1 / registration)') self.assertTrue(all(mail_reg.mail_sent for mail_reg in after_sub_scheduler_2.mail_registration_ids)) self.assertEqual(after_sub_scheduler_2.mail_state, 'running') self.assertEqual(after_sub_scheduler_2.mail_count_done, 15, 'All subscriptions emails should have been sent') # check emails effectively sent self.assertEqual(len(self._new_mails), 15, 'event: should have 15 scheduled emails (1 / registration)') self.assertMailMailWEmails( [formataddr((reg.name, reg.email)) for reg in attendees[:15]], 'outgoing', content=None, fields_values={ 'email_from': self.user_eventmanager.company_id.email_formatted, 'subject': f'Confirmation for {test_event.name}', }) # PRE SCHEDULERS (MOVE FORWARD IN TIME) # -------------------------------------------------- self.assertFalse(event_prev_scheduler.mail_done) self.assertEqual(event_prev_scheduler.mail_state, 'scheduled') # simulate cron running before scheduled date -> should not do anything now_start = self.event_date_begin + relativedelta(hours=-25, microsecond=654321) with self.mock_datetime_and_now(now_start), self.mock_mail_gateway(): self.event_cron_id.method_direct_trigger() self.assertFalse(event_prev_scheduler.mail_done) self.assertEqual(event_prev_scheduler.mail_state, 'scheduled') self.assertEqual(event_prev_scheduler.mail_count_done, 0) self.assertEqual(len(self._new_mails), 0) # execute cron to run schedulers after scheduled date now_start = self.event_date_begin + relativedelta(hours=-23, microsecond=654321) with self.mock_datetime_and_now(now_start), self.mock_mail_gateway(): self.event_cron_id.method_direct_trigger() # check that scheduler is finished self.assertTrue(event_prev_scheduler.mail_done, 'event: reminder scheduler should have run') self.assertEqual(event_prev_scheduler.mail_state, 'sent', 'event: reminder scheduler should have run') # check emails effectively sent self.assertEqual(len(self._new_mails), 15, 'event: should have scheduled 15 mails (1 / registration)') self.assertMailMailWEmails( [formataddr((reg.name, reg.email)) for reg in attendees[:15]], 'outgoing', content=None, fields_values={ 'email_from': self.user_eventmanager.company_id.email_formatted, 'subject': f'Reminder for {test_event.name}: tomorrow', }) # NEW REGISTRATION EFFECT ON SCHEDULERS # -------------------------------------------------- with self.mock_datetime_and_now(now_start), self.mock_mail_gateway(): new_attendee = self.env['event.registration'].create({ 'event_id': test_event.id, 'name': 'Reg3', 'email': 'reg3@example.com', 'state': 'draft', }) # no more seats self.assertEqual(new_attendee.state, 'draft') # schedulers state untouched self.assertTrue(event_prev_scheduler.mail_done) self.assertFalse(event_next_scheduler.mail_done) # confirm registration -> should trigger registration schedulers # NOTE: currently all schedulers are based on create_date # meaning several communications may be sent in the time time with self.mock_datetime_and_now(now_start + relativedelta(hours=1)), self.mock_mail_gateway(): new_attendee.action_confirm() # verify that subscription scheduler was auto-executed after new registration confirmed self.assertEqual(len(after_sub_scheduler.mail_registration_ids), 16, 'event: should have 16 scheduled communication (1 / registration)') new_mail_reg = after_sub_scheduler.mail_registration_ids.filtered(lambda mail_reg: mail_reg.registration_id == new_attendee) self.assertEqual(new_mail_reg.scheduled_date, now_start.replace(microsecond=0)) self.assertTrue(new_mail_reg.mail_sent, 'event: registration mail should be sent at registration creation') self.assertEqual(after_sub_scheduler.mail_count_done, 16, 'event: all subscription mails should have been sent') # verify that subscription scheduler was auto-executed after new registration confirmed self.assertEqual(len(after_sub_scheduler_2.mail_registration_ids), 16, 'event: should have 16 scheduled communication (1 / registration)') new_mail_reg = after_sub_scheduler_2.mail_registration_ids.filtered(lambda mail_reg: mail_reg.registration_id == new_attendee) self.assertEqual(new_mail_reg.scheduled_date, now_start.replace(microsecond=0) + relativedelta(hours=1)) self.assertTrue(new_mail_reg.mail_sent, 'event: registration mail should be sent at registration creation') self.assertEqual(after_sub_scheduler_2.mail_count_done, 16, 'event: all subscription mails should have been sent') # check emails effectively sent self.assertEqual(len(self._new_mails), 2, 'event: should have 1 scheduled emails (new registration only)') # manual check because 2 identical mails are sent and mail tools do not support it easily for mail in self._new_mails: self.assertEqual(mail.email_from, self.user_eventmanager.company_id.email_formatted) self.assertEqual(mail.subject, f'Confirmation for {test_event.name}') self.assertEqual(mail.state, 'outgoing') self.assertEqual(mail.email_to, formataddr((new_attendee.name, new_attendee.email))) # POST SCHEDULERS (MOVE FORWARD IN TIME) # -------------------------------------------------- self.assertFalse(event_next_scheduler.mail_done) # execute event reminder scheduler explicitly after its schedule date new_end = self.event_date_end + relativedelta(hours=2) with self.mock_datetime_and_now(new_end), self.mock_mail_gateway(): (attendees + new_attendee).invalidate_recordset(['event_date_range']) self.event_cron_id.method_direct_trigger() # check that scheduler is finished self.assertTrue(event_next_scheduler.mail_done, 'event: reminder scheduler should should have run') self.assertEqual(event_next_scheduler.mail_state, 'sent', 'event: reminder scheduler should have run') self.assertEqual(event_next_scheduler.mail_count_done, 16) # check emails effectively sent self.assertEqual(len(self._new_mails), 16, 'event: should have scheduled 3 mails, one for each registration') self.assertMailMailWEmails( [formataddr((reg.name, reg.email)) for reg in attendees[:15] + new_attendee], 'outgoing', content=None, fields_values={ 'email_from': self.user_eventmanager.company_id.email_formatted, 'subject': f"Reminder for {test_event.name}: today", }) @mute_logger('odoo.addons.event.models.event_mail') @users('user_eventmanager') def test_event_mail_schedule_fail_global_composer(self): """ Simulate a fail during composer usage e.g. invalid field path, template / model change, ... to check defensive behavior """ cron = self.env.ref("event.event_mail_scheduler").sudo() before_scheduler = self.test_event.event_mail_ids.filtered(lambda s: s.interval_type == "before_event") self.assertTrue(before_scheduler) self._create_registrations(self.test_event, 2) def _patched_send_mail(self, *args, **kwargs): raise exceptions.ValidationError('Some error') with patch.object(type(self.env["mail.compose.message"]), "_action_send_mail_mass_mail", _patched_send_mail), \ self.mock_datetime_and_now(self.reference_now + relativedelta(days=3)), \ self.mock_mail_gateway(): cron.method_direct_trigger() self.assertFalse(before_scheduler.mail_done) @users('user_eventmanager') def test_event_mail_schedule_fail_global_no_registrations(self): """ Be sure no registrations = no crash in composer """ cron = self.env.ref("event.event_mail_scheduler").sudo() before_scheduler = self.test_event.event_mail_ids.filtered(lambda s: s.interval_type == "before_event") self.test_event.registration_ids.unlink() with self.mock_datetime_and_now(self.reference_now + relativedelta(days=3)), \ self.mock_mail_gateway(): cron.method_direct_trigger() self.assertTrue(before_scheduler.mail_done) @mute_logger( 'odoo.addons.event.models.event_mail', 'odoo.addons.event.models.event_mail_registration', 'odoo.addons.event.models.event_registration', ) def test_event_mail_schedule_fail_registration_composer(self): """ Simulate a fail during composer usage e.g. invalid field path, template / model change, ... to check defensive behavior """ onsub_scheduler = self.test_event.event_mail_ids.filtered(lambda s: s.interval_type == "after_sub" and s.interval_unit == "now") self.assertTrue(onsub_scheduler) self.assertEqual(onsub_scheduler.mail_count_done, 0) def _patched_send_mail(self, *args, **kwargs): raise exceptions.ValidationError('Some error') with patch.object(type(self.env["mail.compose.message"]), "_action_send_mail_mass_mail", _patched_send_mail), \ self.mock_mail_gateway(): registration = self.env['event.registration'].with_user(self.user_eventmanager).create({ "email": "test@email.com", "event_id": self.test_event.id, "name": "Mitchell Admin", "phone": "(255)-595-8393", }) self.assertTrue(registration.exists(), "Registration record should exist after creation.") self.assertEqual(onsub_scheduler.mail_count_done, 0) @mute_logger('odoo.addons.event.models.event_mail') @users('user_eventmanager') def test_event_mail_schedule_fail_registration_template_removed(self): """ Test flow where scheduler fails due to template being removed. """ after_sub_scheduler = self.test_event.event_mail_ids.filtered(lambda s: s.interval_type == 'after_sub') self.assertTrue(after_sub_scheduler) self.template_subscription.sudo().unlink() self.assertFalse(after_sub_scheduler.exists(), "When removing template, scheduler should be removed") @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') @users('user_eventmanager') @warmup def test_event_mail_schedule_on_subscription(self): """ Test emails sent on subscription, notably to avoid bottlenecks """ test_event = self.test_event.with_env(self.env) reference_now = self.reference_now # remove on subscription, to create hanging registrations schedulers = self.env['event.mail'].search([('event_id', '=', test_event.id)]) _sub_scheduler = schedulers.filtered(lambda s: s.interval_type == 'after_sub' and s.interval_unit == 'now') _sub_scheduler.unlink() # consider having hanging registrations, still not processed (e.g. adding # a new scheduler after) self.env.invalidate_all() # event 19 with self.assertQueryCount(37), self.mock_datetime_and_now(reference_now), \ self.mock_mail_gateway(): _existing = self.env['event.registration'].create([ { 'email': f'existing.attendee.{idx}@test.example.com', 'event_id': test_event.id, 'name': f'Attendee {idx}', } for idx in range(5) ]) self.assertEqual(len(self._new_mails), 0) self.assertEqual(self.mail_mail_create_mocked.call_count, 0) # add on subscription scheduler, then new registrations ! yay ! check what # happens with old ones test_event.write({'event_mail_ids': [ (0, 0, { # right at subscription 'interval_unit': 'now', 'interval_type': 'after_sub', 'template_ref': f'mail.template,{self.template_subscription.id}', }), ]}) self.env.invalidate_all() # event 50 with self.assertQueryCount(67), \ self.mock_datetime_and_now(reference_now + relativedelta(minutes=10)), \ self.mock_mail_gateway(): _new = self.env['event.registration'].create([ { 'email': f'new.attendee.{idx}@test.example.com', 'event_id': test_event.id, 'name': f'New Attendee {idx}', } for idx in range(2) ]) self.assertEqual(len(self._new_mails), 2, 'EventMail: should be limited to new registrations') self.assertEqual(self.mail_mail_create_mocked.call_count, 1, 'EventMail: should create mails in batch for new registrations') @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') @users('user_eventmanager') def test_event_mail_schedule_on_subscription_async(self): """ Async mode for schedulers activated, should not send communication in the same transaction. """ test_event = self.test_event.with_env(self.env) cron = self.env.ref('event.event_mail_scheduler') reference_now = self.reference_now self.env['ir.config_parameter'].sudo().set_param('event.event_mail_async', True) with self.capture_triggers(cron.id) as capt, \ self.mock_datetime_and_now(reference_now + relativedelta(minutes=10)), \ self.mock_mail_gateway(): existing = self.env['event.registration'].create([ { 'email': f'new.async.attendee.{idx}@test.example.com', 'event_id': test_event.id, 'name': f'New Async Attendee {idx}', } for idx in range(5) ]) self.assertEqual(len(self._new_mails), 0) self.assertEqual(self.mail_mail_create_mocked.call_count, 0) capt.records.ensure_one() self.assertEqual(capt.records.call_at, reference_now.replace(microsecond=0) + relativedelta(minutes=10)) # run cron: emails should be send for registrations with self.mock_datetime_and_now(reference_now + relativedelta(minutes=10)), \ self.mock_mail_gateway(): cron.sudo().method_direct_trigger() self.assertMailMailWEmails( [formataddr((reg.name, reg.email)) for reg in existing], "outgoing", content=f"Hello your registration to {test_event.name} is confirmed", fields_values={ 'email_from': self.user_eventmanager.company_id.email_formatted, 'subject': f'Confirmation for {test_event.name}', }) @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') def test_unique_event_mail_ids(self): # create event with default event_mail_ids lines test_event = self.env['event.event'].with_user(self.user_eventmanager).create({ 'name': "TestEvent", 'date_begin': datetime.now(), 'date_end': datetime.now() + relativedelta(days=1), 'seats_max': 2, 'seats_limited': True, }) event_mail_ids_initial = test_event.event_mail_ids self._create_registrations(test_event, 1) aftersub = test_event.event_mail_ids.filtered(lambda mail: mail.interval_type == "after_sub") self.assertTrue(aftersub) self.assertEqual(len(test_event.event_mail_ids), 3, "Should have 3 communication lines") self.assertEqual(aftersub.mail_count_done, 1, "Should have sent first mail immediately") # change the event type that has event_type_mail_ids having one identical and one non-identical configuration event_type = self.env['event.type'].create({ 'name': "Go Sports", 'event_type_mail_ids': [ (0, 0, { 'interval_nbr': 0, 'interval_unit': 'now', 'interval_type': 'after_sub', 'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_subscription') }), (0, 0, { 'interval_nbr': 5, 'interval_unit': 'hours', 'interval_type': 'before_event', 'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_reminder') }), ] }) test_event.event_type_id = event_type self.assertTrue(aftersub in test_event.event_mail_ids, "Sent communication should not have been removed") mail_not_done = event_mail_ids_initial - aftersub self.assertFalse(test_event.event_mail_ids & mail_not_done, "Other default communication lines should have been removed") self.assertEqual(len(test_event.event_mail_ids), 2, "Should now have only two communication lines") mails_to_send = test_event.event_mail_ids - aftersub duplicate_mails = mails_to_send.filtered(lambda mail: mail.notification_type == 'mail' and\ mail.interval_nbr == 0 and\ mail.interval_unit == 'now' and\ mail.interval_type == 'after_sub' and\ mail.template_ref.id == self.env['ir.model.data']._xmlid_to_res_id('event.event_subscription')) self.assertEqual(len(duplicate_mails), 0, "The duplicate configuration (first one from event_type.event_type_mail_ids which has same configuration as the sent one) should not have been added") @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') def test_archived_event_mail_schedule(self): """ Test mail scheduling for archived events """ event_cron_id = self.env.ref('event.event_mail_scheduler') # deactivate other schedulers to avoid messing with crons self.env['event.mail'].search([]).unlink() # freeze some datetimes, and ensure more than 1D+1H before event starts # to ease time-based scheduler check now = datetime(2023, 7, 24, 14, 30, 15) event_date_begin = datetime(2023, 7, 26, 8, 0, 0) event_date_end = datetime(2023, 7, 28, 18, 0, 0) with self.mock_datetime_and_now(now): test_event = self.env['event.event'].with_user(self.user_eventmanager).create({ 'name': 'TestEventMail', 'date_begin': event_date_begin, 'date_end': event_date_end, 'event_mail_ids': [ (0, 0, { # right at subscription 'interval_unit': 'now', 'interval_type': 'after_sub', 'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_subscription')}), (0, 0, { # 3 hours before event 'interval_nbr': 3, 'interval_unit': 'hours', 'interval_type': 'before_event', 'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_reminder')}) ] }) # check event scheduler scheduler = self.env['event.mail'].search([('event_id', '=', test_event.id)]) self.assertEqual(len(scheduler), 2, 'event: wrong scheduler creation') event_prev_scheduler = self.env['event.mail'].search([('event_id', '=', test_event.id), ('interval_type', '=', 'before_event')]) with self.mock_datetime_and_now(now), self.mock_mail_gateway(): self.env['event.registration'].create({ 'event_id': test_event.id, 'name': 'Reg1', 'email': 'reg1@example.com', }) self.env['event.registration'].create({ 'event_id': test_event.id, 'name': 'Reg2', 'email': 'reg2@example.com', }) # check emails effectively sent self.assertEqual(len(self._new_mails), 2, 'event: should have 2 scheduled emails (1 / registration)') # Archive the Event test_event.action_archive() # execute cron to run schedulers now_start = event_date_begin + relativedelta(hours=-3) with self.mock_datetime_and_now(now_start), self.mock_mail_gateway(): event_cron_id.method_direct_trigger() # check that scheduler is not executed self.assertFalse(event_prev_scheduler.mail_done, 'event: reminder scheduler should should have run')