diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/calendar/tests | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/calendar/tests')
| -rw-r--r-- | addons/calendar/tests/__init__.py | 10 | ||||
| -rw-r--r-- | addons/calendar/tests/test_access_rights.py | 90 | ||||
| -rw-r--r-- | addons/calendar/tests/test_attendees.py | 70 | ||||
| -rw-r--r-- | addons/calendar/tests/test_calendar.py | 348 | ||||
| -rw-r--r-- | addons/calendar/tests/test_calendar_controller.py | 47 | ||||
| -rw-r--r-- | addons/calendar/tests/test_calendar_recurrent_event_case2.py | 55 | ||||
| -rw-r--r-- | addons/calendar/tests/test_event_notifications.py | 163 | ||||
| -rw-r--r-- | addons/calendar/tests/test_event_recurrence.py | 662 |
8 files changed, 1445 insertions, 0 deletions
diff --git a/addons/calendar/tests/__init__.py b/addons/calendar/tests/__init__.py new file mode 100644 index 00000000..e1650094 --- /dev/null +++ b/addons/calendar/tests/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_access_rights +from . import test_attendees +from . import test_calendar +from . import test_calendar_controller +from . import test_calendar_recurrent_event_case2 +from . import test_event_recurrence +from . import test_event_notifications diff --git a/addons/calendar/tests/test_access_rights.py b/addons/calendar/tests/test_access_rights.py new file mode 100644 index 00000000..2aaff1ea --- /dev/null +++ b/addons/calendar/tests/test_access_rights.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from datetime import datetime + +from odoo.tests.common import SavepointCase, new_test_user +from odoo.exceptions import AccessError + + +class TestAccessRights(SavepointCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.john = new_test_user(cls.env, login='john', groups='base.group_user') + cls.raoul = new_test_user(cls.env, login='raoul', groups='base.group_user') + cls.portal = new_test_user(cls.env, login='pot', groups='base.group_portal') + + def create_event(self, user, **values): + return self.env['calendar.event'].with_user(user).create(dict({ + 'name': 'Event', + 'start': datetime(2020, 2, 2, 8, 0), + 'stop': datetime(2020, 2, 2, 18, 0), + 'user_id': user.id, + }, **values)) + + def read_event(self, user, events, field): + data = events.with_user(user).read([field]) + if len(events) == 1: + return data[0][field] + mapped_data = {record['id']: record for record in data} + # Keep the same order + return [mapped_data[eid][field] for eid in events.ids] + + def test_private_read_name(self): + event = self.create_event( + self.john, + privacy='private', + name='my private event', + ) + self.assertEqual(self.read_event(self.john, event, 'name'), 'my private event', "Owner should be able to read the event") + self.assertEqual(self.read_event(self.raoul, event, 'name'), 'Busy', "Private value should be obfuscated") + with self.assertRaises(AccessError): + self.read_event(self.portal, event, 'name') + + def test_private_other_field(self): + event = self.create_event( + self.john, + privacy='private', + location='in the Sky', + ) + self.assertEqual(self.read_event(self.john, event, 'location'), 'in the Sky', "Owner should be able to read the event") + self.assertEqual(self.read_event(self.raoul, event, 'location'), False, "Private value should be obfuscated") + with self.assertRaises(AccessError): + self.read_event(self.portal, event, 'location') + + def test_private_and_public(self): + private = self.create_event( + self.john, + privacy='private', + location='in the Sky', + ) + public = self.create_event( + self.john, + privacy='public', + location='In Hell', + ) + [private_location, public_location] = self.read_event(self.raoul, private + public, 'location') + self.assertEqual(private_location, False, "Private value should be obfuscated") + self.assertEqual(public_location, 'In Hell', "Public value should not be obfuscated") + + def test_read_group_public(self): + event = self.create_event(self.john) + data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['start'], groupby='start') + self.assertTrue(data, "It should be able to read group") + + def test_read_group_private(self): + event = self.create_event(self.john) + with self.assertRaises(AccessError): + self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['name'], groupby='name') + + def test_read_group_agg(self): + event = self.create_event(self.john) + data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['start'], groupby='start:week') + self.assertTrue(data, "It should be able to read group") + + def test_read_group_list(self): + event = self.create_event(self.john) + data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['start'], groupby=['start']) + self.assertTrue(data, "It should be able to read group") diff --git a/addons/calendar/tests/test_attendees.py b/addons/calendar/tests/test_attendees.py new file mode 100644 index 00000000..7b3389b7 --- /dev/null +++ b/addons/calendar/tests/test_attendees.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from datetime import datetime + +from odoo.tests.common import SavepointCase, new_test_user + + +class TestEventNotifications(SavepointCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.event = cls.env['calendar.event'].create({ + 'name': "Doom's day", + 'start': datetime(2019, 10, 25, 8, 0), + 'stop': datetime(2019, 10, 27, 18, 0), + }).with_context(mail_notrack=True) + cls.user = new_test_user(cls.env, 'xav', email='em@il.com', notification_type='inbox') + cls.partner = cls.user.partner_id + + def test_attendee_added(self): + self.event.partner_ids = self.partner + self.assertTrue(self.event.attendee_ids, "It should have created an attendee") + self.assertEqual(self.event.attendee_ids.partner_id, self.partner, "It should be linked to the partner") + self.assertIn(self.partner, self.event.message_follower_ids.partner_id, "He should be follower of the event") + + def test_attendee_added_create(self): + event = self.env['calendar.event'].create({ + 'name': "Doom's day", + 'start': datetime(2019, 10, 25, 8, 0), + 'stop': datetime(2019, 10, 27, 18, 0), + 'partner_ids': [(4, self.partner.id)], + }) + self.assertTrue(event.attendee_ids, "It should have created an attendee") + self.assertEqual(event.attendee_ids.partner_id, self.partner, "It should be linked to the partner") + self.assertIn(self.partner, event.message_follower_ids.partner_id, "He should be follower of the event") + + def test_attendee_added_multi(self): + event = self.env['calendar.event'].create({ + 'name': "Doom's day", + 'start': datetime(2019, 10, 25, 8, 0), + 'stop': datetime(2019, 10, 27, 18, 0), + }) + events = self.event | event + events.partner_ids = self.partner + self.assertEqual(len(events.attendee_ids), 2, "It should have created one attendee per event") + + def test_existing_attendee_added(self): + self.event.partner_ids = self.partner + attendee = self.event.attendee_ids + self.event.write({'partner_ids': [(4, self.partner.id)]}) # Add existing partner + self.assertEqual(self.event.attendee_ids, attendee, "It should not have created an new attendee record") + + def test_attendee_add_self(self): + self.event.with_user(self.user).partner_ids = self.partner + self.assertTrue(self.event.attendee_ids, "It should have created an attendee") + self.assertEqual(self.event.attendee_ids.partner_id, self.partner, "It should be linked to the partner") + self.assertEqual(self.event.attendee_ids.state, 'accepted', "It should be accepted for the current user") + + def test_attendee_removed(self): + partner_bis = self.env['res.partner'].create({'name': "Xavier"}) + self.event.partner_ids = partner_bis + attendee = self.event.attendee_ids + self.event.partner_ids |= self.partner + self.event.partner_ids -= self.partner + self.assertEqual(attendee, self.event.attendee_ids, "It should not have re-created an attendee record") + self.assertNotIn(self.partner, self.event.attendee_ids.partner_id, "It should have removed the attendee") + self.assertNotIn(self.partner, self.event.message_follower_ids.partner_id, "It should have unsubscribed the partner") + self.assertIn(partner_bis, self.event.attendee_ids.partner_id, "It should have left the attendee") diff --git a/addons/calendar/tests/test_calendar.py b/addons/calendar/tests/test_calendar.py new file mode 100644 index 00000000..ebece3f1 --- /dev/null +++ b/addons/calendar/tests/test_calendar.py @@ -0,0 +1,348 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import datetime + +from datetime import datetime, timedelta, time + +from odoo import fields +from odoo.addons.base.tests.common import SavepointCaseWithUserDemo +import pytz +import re + + +class TestCalendar(SavepointCaseWithUserDemo): + + def setUp(self): + super(TestCalendar, self).setUp() + + self.CalendarEvent = self.env['calendar.event'] + # In Order to test calendar, I will first create One Simple Event with real data + self.event_tech_presentation = self.CalendarEvent.create({ + 'privacy': 'private', + 'start': '2011-04-30 16:00:00', + 'stop': '2011-04-30 18:30:00', + 'description': 'The Technical Presentation will cover following topics:\n* Creating Odoo class\n* Views\n* Wizards\n* Workflows', + 'duration': 2.5, + 'location': 'Odoo S.A.', + 'name': 'Technical Presentation' + }) + + def test_event_order(self): + """ check the ordering of events when searching """ + def create_event(name, date): + return self.CalendarEvent.create({ + 'name': name, + 'start': date + ' 12:00:00', + 'stop': date + ' 14:00:00', + }) + foo1 = create_event('foo', '2011-04-01') + foo2 = create_event('foo', '2011-06-01') + bar1 = create_event('bar', '2011-05-01') + bar2 = create_event('bar', '2011-06-01') + domain = [('id', 'in', (foo1 + foo2 + bar1 + bar2).ids)] + + # sort them by name only + events = self.CalendarEvent.search(domain, order='name') + self.assertEqual(events.mapped('name'), ['bar', 'bar', 'foo', 'foo']) + events = self.CalendarEvent.search(domain, order='name desc') + self.assertEqual(events.mapped('name'), ['foo', 'foo', 'bar', 'bar']) + + # sort them by start date only + events = self.CalendarEvent.search(domain, order='start') + self.assertEqual(events.mapped('start'), (foo1 + bar1 + foo2 + bar2).mapped('start')) + events = self.CalendarEvent.search(domain, order='start desc') + self.assertEqual(events.mapped('start'), (foo2 + bar2 + bar1 + foo1).mapped('start')) + + # sort them by name then start date + events = self.CalendarEvent.search(domain, order='name asc, start asc') + self.assertEqual(list(events), [bar1, bar2, foo1, foo2]) + events = self.CalendarEvent.search(domain, order='name asc, start desc') + self.assertEqual(list(events), [bar2, bar1, foo2, foo1]) + events = self.CalendarEvent.search(domain, order='name desc, start asc') + self.assertEqual(list(events), [foo1, foo2, bar1, bar2]) + events = self.CalendarEvent.search(domain, order='name desc, start desc') + self.assertEqual(list(events), [foo2, foo1, bar2, bar1]) + + # sort them by start date then name + events = self.CalendarEvent.search(domain, order='start asc, name asc') + self.assertEqual(list(events), [foo1, bar1, bar2, foo2]) + events = self.CalendarEvent.search(domain, order='start asc, name desc') + self.assertEqual(list(events), [foo1, bar1, foo2, bar2]) + events = self.CalendarEvent.search(domain, order='start desc, name asc') + self.assertEqual(list(events), [bar2, foo2, bar1, foo1]) + events = self.CalendarEvent.search(domain, order='start desc, name desc') + self.assertEqual(list(events), [foo2, bar2, bar1, foo1]) + + def test_event_activity(self): + # ensure meeting activity type exists + meeting_act_type = self.env['mail.activity.type'].search([('category', '=', 'meeting')], limit=1) + if not meeting_act_type: + meeting_act_type = self.env['mail.activity.type'].create({ + 'name': 'Meeting Test', + 'category': 'meeting', + }) + + # have a test model inheriting from activities + test_record = self.env['res.partner'].create({ + 'name': 'Test', + }) + now = datetime.now() + test_user = self.user_demo + test_name, test_description, test_description2 = 'Test-Meeting', 'Test-Description', 'NotTest' + test_note, test_note2 = '<p>Test-Description</p>', '<p>NotTest</p>' + + # create using default_* keys + test_event = self.env['calendar.event'].with_user(test_user).with_context( + default_res_model=test_record._name, + default_res_id=test_record.id, + ).create({ + 'name': test_name, + 'description': test_description, + 'start': fields.Datetime.to_string(now + timedelta(days=-1)), + 'stop': fields.Datetime.to_string(now + timedelta(hours=2)), + 'user_id': self.env.user.id, + }) + self.assertEqual(test_event.res_model, test_record._name) + self.assertEqual(test_event.res_id, test_record.id) + self.assertEqual(len(test_record.activity_ids), 1) + self.assertEqual(test_record.activity_ids.summary, test_name) + self.assertEqual(test_record.activity_ids.note, test_note) + self.assertEqual(test_record.activity_ids.user_id, self.env.user) + self.assertEqual(test_record.activity_ids.date_deadline, (now + timedelta(days=-1)).date()) + + # updating event should update activity + test_event.write({ + 'name': '%s2' % test_name, + 'description': test_description2, + 'start': fields.Datetime.to_string(now + timedelta(days=-2)), + 'user_id': test_user.id, + }) + self.assertEqual(test_record.activity_ids.summary, '%s2' % test_name) + self.assertEqual(test_record.activity_ids.note, test_note2) + self.assertEqual(test_record.activity_ids.user_id, test_user) + self.assertEqual(test_record.activity_ids.date_deadline, (now + timedelta(days=-2)).date()) + + # update event with a description that have a special character and a new line + test_description3 = 'Test & \n Description' + test_note3 = '<p>Test & <br> Description</p>' + test_event.write({ + 'description': test_description3, + }) + + self.assertEqual(test_record.activity_ids.note, test_note3) + + # deleting meeting should delete its activity + test_record.activity_ids.unlink() + self.assertEqual(self.env['calendar.event'], self.env['calendar.event'].search([('name', '=', test_name)])) + + # create using active_model keys + test_event = self.env['calendar.event'].with_user(self.user_demo).with_context( + active_model=test_record._name, + active_id=test_record.id, + ).create({ + 'name': test_name, + 'description': test_description, + 'start': now + timedelta(days=-1), + 'stop': now + timedelta(hours=2), + 'user_id': self.env.user.id, + }) + self.assertEqual(test_event.res_model, test_record._name) + self.assertEqual(test_event.res_id, test_record.id) + self.assertEqual(len(test_record.activity_ids), 1) + + def test_event_allday(self): + self.env.user.tz = 'Pacific/Honolulu' + + event = self.CalendarEvent.create({ + 'name': 'All Day', + 'start': "2018-10-16 00:00:00", + 'start_date': "2018-10-16", + 'stop': "2018-10-18 00:00:00", + 'stop_date': "2018-10-18", + 'allday': True, + }) + event.invalidate_cache() + self.assertEqual(str(event.start), '2018-10-16 08:00:00') + self.assertEqual(str(event.stop), '2018-10-18 18:00:00') + + def test_recurring_around_dst(self): + m = self.CalendarEvent.create({ + 'name': "wheee", + 'start': '2018-10-27 14:30:00', + 'allday': False, + 'rrule': u'FREQ=DAILY;INTERVAL=1;COUNT=4', + 'recurrency': True, + 'stop': '2018-10-27 16:30:00', + 'event_tz': 'Europe/Brussels', + }) + + start_recurring_dates = m.recurrence_id.calendar_event_ids.sorted('start').mapped('start') + self.assertEqual(len(start_recurring_dates), 4) + + for d in start_recurring_dates: + if d.day < 28: # DST switch happens between 2018-10-27 and 2018-10-28 + self.assertEqual(d.hour, 14) + else: + self.assertEqual(d.hour, 15) + self.assertEqual(d.minute, 30) + + def test_event_activity_timezone(self): + activty_type = self.env['mail.activity.type'].create({ + 'name': 'Meeting', + 'category': 'meeting' + }) + + activity_id = self.env['mail.activity'].create({ + 'summary': 'Meeting with partner', + 'activity_type_id': activty_type.id, + 'res_model_id': self.env['ir.model'].search([('model', '=', 'res.partner')], limit=1).id, + 'res_id': self.env['res.partner'].create({'name': 'A Partner'}).id, + }) + + calendar_event = self.env['calendar.event'].create({ + 'name': 'Meeting with partner', + 'activity_ids': [(6, False, activity_id.ids)], + 'start': '2018-11-12 21:00:00', + 'stop': '2018-11-13 00:00:00', + }) + + # Check output in UTC + self.assertEqual(str(activity_id.date_deadline), '2018-11-12') + + # Check output in the user's tz + # write on the event to trigger sync of activities + calendar_event.with_context({'tz': 'Australia/Brisbane'}).write({ + 'start': '2018-11-12 21:00:00', + }) + + self.assertEqual(str(activity_id.date_deadline), '2018-11-13') + + def test_event_allday_activity_timezone(self): + # Covers use case of commit eef4c3b48bcb4feac028bf640b545006dd0c9b91 + # Also, read the comment in the code at calendar.event._inverse_dates + activty_type = self.env['mail.activity.type'].create({ + 'name': 'Meeting', + 'category': 'meeting' + }) + + activity_id = self.env['mail.activity'].create({ + 'summary': 'Meeting with partner', + 'activity_type_id': activty_type.id, + 'res_model_id': self.env['ir.model'].search([('model', '=', 'res.partner')], limit=1).id, + 'res_id': self.env['res.partner'].create({'name': 'A Partner'}).id, + }) + + calendar_event = self.env['calendar.event'].create({ + 'name': 'All Day', + 'start': "2018-10-16 00:00:00", + 'start_date': "2018-10-16", + 'stop': "2018-10-18 00:00:00", + 'stop_date': "2018-10-18", + 'allday': True, + 'activity_ids': [(6, False, activity_id.ids)], + }) + + # Check output in UTC + self.assertEqual(str(activity_id.date_deadline), '2018-10-16') + + # Check output in the user's tz + # write on the event to trigger sync of activities + calendar_event.with_context({'tz': 'Pacific/Honolulu'}).write({ + 'start': '2018-10-16 00:00:00', + 'start_date': '2018-10-16', + }) + + self.assertEqual(str(activity_id.date_deadline), '2018-10-16') + + def test_event_creation_mail(self): + """ + Check that mail are sent to the attendees on event creation + Check that mail are sent to the added attendees on event edit + Check that mail are NOT sent to the attendees when the event date is past + """ + + def _test_one_mail_per_attendee(self, partners): + # check that every attendee receive a (single) mail for the event + for partner in partners: + mail = self.env['mail.message'].sudo().search([ + ('notified_partner_ids', 'in', partner.id), + ]) + self.assertEqual(len(mail), 1) + + partners = [ + self.env['res.partner'].create({'name': 'testuser0', 'email': u'bob@example.com'}), + self.env['res.partner'].create({'name': 'testuser1', 'email': u'alice@example.com'}), + ] + partner_ids = [(6, False, [p.id for p in partners]),] + now = fields.Datetime.context_timestamp(partners[0], fields.Datetime.now()) + m = self.CalendarEvent.create({ + 'name': "mailTest1", + 'allday': False, + 'rrule': u'FREQ=DAILY;INTERVAL=1;COUNT=5', + 'recurrency': True, + 'partner_ids': partner_ids, + 'start': fields.Datetime.to_string(now + timedelta(days=10)), + 'stop': fields.Datetime.to_string(now + timedelta(days=15)), + }) + + # every partner should have 1 mail sent + _test_one_mail_per_attendee(self, partners) + + # adding more partners to the event + partners.extend([ + self.env['res.partner'].create({'name': 'testuser2', 'email': u'marc@example.com'}), + self.env['res.partner'].create({'name': 'testuser3', 'email': u'carl@example.com'}), + self.env['res.partner'].create({'name': 'testuser4', 'email': u'alain@example.com'}), + ]) + partner_ids = [(6, False, [p.id for p in partners]),] + m.write({ + 'partner_ids': partner_ids, + 'recurrence_update': 'all_events', + }) + + # more email should be sent + _test_one_mail_per_attendee(self, partners) + + # create a new event in the past + self.CalendarEvent.create({ + 'name': "NOmailTest", + 'allday': False, + 'recurrency': False, + 'partner_ids': partner_ids, + 'start': fields.Datetime.to_string(now - timedelta(days=10)), + 'stop': fields.Datetime.to_string(now - timedelta(days=9)), + }) + + # no more email should be sent + _test_one_mail_per_attendee(self, partners) + + def test_event_creation_sudo_other_company(self): + """ Check Access right issue when create event with sudo + + Create a company, a user in that company + Create an event for someone else in another company as sudo + Should not failed for acces right check + """ + now = fields.Datetime.context_timestamp(self.partner_demo, fields.Datetime.now()) + + web_company = self.env['res.company'].sudo().create({'name': "Website Company"}) + web_user = self.env['res.users'].with_company(web_company).sudo().create({ + 'name': 'web user', + 'login': 'web', + 'company_id': web_company.id + }) + self.CalendarEvent.with_user(web_user).with_company(web_company).sudo().create({ + 'name': "Test", + 'allday': False, + 'recurrency': False, + 'partner_ids': [(6, 0, self.partner_demo.ids)], + 'alarm_ids': [(0, 0, { + 'name': 'Alarm', + 'alarm_type': 'notification', + 'interval': 'minutes', + 'duration': 30, + })], + 'user_id': self.user_demo.id, + 'start': fields.Datetime.to_string(now + timedelta(hours=5)), + 'stop': fields.Datetime.to_string(now + timedelta(hours=6)), + }) diff --git a/addons/calendar/tests/test_calendar_controller.py b/addons/calendar/tests/test_calendar_controller.py new file mode 100644 index 00000000..32dc7abc --- /dev/null +++ b/addons/calendar/tests/test_calendar_controller.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from datetime import datetime + +from odoo.tests.common import HttpCase, new_test_user, tagged + + +@tagged("post_install", "-at_install") +class TestCalendarController(HttpCase): + def setUp(self): + super().setUp() + self.user = new_test_user(self.env, "test_user_1", email="test_user_1@nowhere.com", tz="UTC") + self.other_user = new_test_user(self.env, "test_user_2", email="test_user_2@nowhere.com", password="P@ssw0rd!", tz="UTC") + self.partner = self.user.partner_id + self.event = ( + self.env["calendar.event"] + .create( + { + "name": "Doom's day", + "start": datetime(2019, 10, 25, 8, 0), + "stop": datetime(2019, 10, 27, 18, 0), + "partner_ids": [(4, self.partner.id)], + } + ) + .with_context(mail_notrack=True) + ) + + def test_accept_meeting_unauthenticated(self): + self.event.write({"partner_ids": [(4, self.other_user.partner_id.id)]}) + token = self.event.attendee_ids[1].access_token + url = "/calendar/meeting/accept?token=%s&id=%d" % (token, self.event.id) + res = self.url_open(url) + + self.assertEqual(res.status_code, 200, "Response should = OK") + self.event.attendee_ids[1].invalidate_cache() + self.assertEqual(self.event.attendee_ids[1].state, "accepted", "Attendee should have accepted") + + def test_accept_meeting_authenticated(self): + self.event.write({"partner_ids": [(4, self.other_user.partner_id.id)]}) + token = self.event.attendee_ids[1].access_token + url = "/calendar/meeting/accept?token=%s&id=%d" % (token, self.event.id) + self.authenticate("test_user_2", "P@ssw0rd!") + res = self.url_open(url) + + self.assertEqual(res.status_code, 200, "Response should = OK") + self.event.attendee_ids[1].invalidate_cache() + self.assertEqual(self.event.attendee_ids[1].state, "accepted", "Attendee should have accepted") diff --git a/addons/calendar/tests/test_calendar_recurrent_event_case2.py b/addons/calendar/tests/test_calendar_recurrent_event_case2.py new file mode 100644 index 00000000..036bbc0a --- /dev/null +++ b/addons/calendar/tests/test_calendar_recurrent_event_case2.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import common + + +class TestRecurrentEvent(common.TransactionCase): + + def setUp(self): + super(TestRecurrentEvent, self).setUp() + + self.CalendarEvent = self.env['calendar.event'] + + def test_recurrent_meeting1(self): + # In order to test recurrent meetings in Odoo, I create meetings with different recurrence using different test cases. + # I create a recurrent meeting with daily recurrence and fixed amount of time. + self.CalendarEvent.create({ + 'count': 5, + 'start': '2011-04-13 11:04:00', + 'stop': '2011-04-13 12:04:00', + 'duration': 1.0, + 'name': 'Test Meeting', + 'recurrency': True, + 'rrule_type': 'daily' + }) + # I search for all the recurrent meetings + meetings_count = self.CalendarEvent.with_context({'virtual_id': True}).search_count([ + ('start', '>=', '2011-03-13'), ('stop', '<=', '2011-05-13') + ]) + self.assertEqual(meetings_count, 5, 'Recurrent daily meetings are not created !') + + def test_recurrent_meeting2(self): + # I create a weekly meeting till a particular end date. + self.CalendarEvent.create({ + 'start': '2011-04-18 11:47:00', + 'stop': '2011-04-18 12:47:00', + 'day': 1, + 'duration': 1.0, + 'until': '2011-04-30', + 'end_type': 'end_date', + 'fr': True, + 'mo': True, + 'th': True, + 'tu': True, + 'we': True, + 'name': 'Review code with programmer', + 'recurrency': True, + 'rrule_type': 'weekly' + }) + + # I search for all the recurrent weekly meetings. + meetings_count = self.CalendarEvent.search_count([ + ('start', '>=', '2011-03-13'), ('stop', '<=', '2011-05-13') + ]) + self.assertEqual(meetings_count, 10, 'Recurrent weekly meetings are not created !') diff --git a/addons/calendar/tests/test_event_notifications.py b/addons/calendar/tests/test_event_notifications.py new file mode 100644 index 00000000..ff618e7f --- /dev/null +++ b/addons/calendar/tests/test_event_notifications.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from unittest.mock import patch +from datetime import datetime, date +from dateutil.relativedelta import relativedelta + +from odoo import fields +from odoo.tests.common import SavepointCase, new_test_user +from odoo.addons.mail.tests.common import MailCase + + +class TestEventNotifications(SavepointCase, MailCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.event = cls.env['calendar.event'].create({ + 'name': "Doom's day", + 'start': datetime(2019, 10, 25, 8, 0), + 'stop': datetime(2019, 10, 27, 18, 0), + }).with_context(mail_notrack=True) + cls.user = new_test_user(cls.env, 'xav', email='em@il.com', notification_type='inbox') + cls.partner = cls.user.partner_id + + def test_message_invite(self): + with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], { + 'message_type': 'user_notification', + 'subtype': 'mail.mt_note', + }): + self.event.partner_ids = self.partner + + def test_message_invite_self(self): + with self.assertNoNotifications(): + self.event.with_user(self.user).partner_ids = self.partner + + def test_message_inactive_invite(self): + self.event.active = False + with self.assertNoNotifications(): + self.event.partner_ids = self.partner + + def test_message_set_inactive_invite(self): + self.event.active = False + with self.assertNoNotifications(): + self.event.write({ + 'partner_ids': [(4, self.partner.id)], + 'active': False, + }) + + def test_message_datetime_changed(self): + self.event.partner_ids = self.partner + "Invitation to Presentation of the new Calendar" + with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], { + 'message_type': 'user_notification', + 'subtype': 'mail.mt_note', + }): + self.event.start = fields.Datetime.now() + relativedelta(days=1) + + def test_message_date_changed(self): + self.event.write({ + 'allday': True, + 'start_date': fields.Date.today() + relativedelta(days=7), + 'stop_date': fields.Date.today() + relativedelta(days=8), + }) + self.event.partner_ids = self.partner + with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], { + 'message_type': 'user_notification', + 'subtype': 'mail.mt_note', + }): + self.event.start_date += relativedelta(days=-1) + + def test_message_date_changed_past(self): + self.event.write({ + 'allday': True, + 'start_date': fields.Date.today(), + 'stop_date': fields.Date.today() + relativedelta(days=1), + }) + self.event.partner_ids = self.partner + with self.assertNoNotifications(): + self.event.write({'start': date(2019, 1, 1)}) + + def test_message_set_inactive_date_changed(self): + self.event.write({ + 'allday': True, + 'start_date': date(2019, 10, 15), + 'stop_date': date(2019, 10, 15), + }) + self.event.partner_ids = self.partner + with self.assertNoNotifications(): + self.event.write({ + 'start_date': self.event.start_date - relativedelta(days=1), + 'active': False, + }) + + def test_message_inactive_date_changed(self): + self.event.write({ + 'allday': True, + 'start_date': date(2019, 10, 15), + 'stop_date': date(2019, 10, 15), + 'active': False, + }) + self.event.partner_ids = self.partner + with self.assertNoNotifications(): + self.event.start_date += relativedelta(days=-1) + + def test_message_add_and_date_changed(self): + self.event.partner_ids -= self.partner + with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], { + 'message_type': 'user_notification', + 'subtype': 'mail.mt_note', + }): + self.event.write({ + 'start': self.event.start - relativedelta(days=1), + 'partner_ids': [(4, self.partner.id)], + }) + + def test_bus_notif(self): + alarm = self.env['calendar.alarm'].create({ + 'name': 'Alarm', + 'alarm_type': 'notification', + 'interval': 'minutes', + 'duration': 30, + }) + now = fields.Datetime.now() + with patch.object(fields.Datetime, 'now', lambda: now): + with self.assertBus([(self.env.cr.dbname, 'calendar.alarm', self.partner.id)]): + self.event.with_context(no_mail_to_attendees=True).write({ + 'start': now + relativedelta(minutes=50), + 'stop': now + relativedelta(minutes=55), + 'partner_ids': [(4, self.partner.id)], + 'alarm_ids': [(4, alarm.id)] + }) + bus_message = [{ + "alarm_id": alarm.id, + "event_id": self.event.id, + "title": "Doom's day", + "message": self.event.display_time, + "timer": 20*60, + "notify_at": fields.Datetime.to_string(now + relativedelta(minutes=20)), + }] + notif = self.env['calendar.alarm_manager'].with_user(self.user).get_next_notif() + self.assertEqual(notif, bus_message) + + def test_email_alarm(self): + alarm = self.env['calendar.alarm'].create({ + 'name': 'Alarm', + 'alarm_type': 'email', + 'interval': 'minutes', + 'duration': 20, + }) + now = fields.Datetime.now() + self.event.write({ + 'start': now + relativedelta(minutes=15), + 'stop': now + relativedelta(minutes=18), + 'partner_ids': [(4, self.partner.id)], + 'alarm_ids': [(4, alarm.id)], + }) + with patch.object(fields.Datetime, 'now', lambda: now): + with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], { + 'message_type': 'user_notification', + 'subtype': 'mail.mt_note', + }): + self.env['calendar.alarm_manager'].with_context(lastcall=now - relativedelta(minutes=15))._get_partner_next_mail(self.partner) diff --git a/addons/calendar/tests/test_event_recurrence.py b/addons/calendar/tests/test_event_recurrence.py new file mode 100644 index 00000000..73ef7c67 --- /dev/null +++ b/addons/calendar/tests/test_event_recurrence.py @@ -0,0 +1,662 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.exceptions import UserError +import pytz +from datetime import datetime, date +from dateutil.relativedelta import relativedelta + +from odoo.tests.common import SavepointCase + + +class TestRecurrentEvents(SavepointCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + lang = cls.env['res.lang']._lang_get(cls.env.user.lang) + lang.week_start = '1' # Monday + + def assertEventDates(self, events, dates): + events = events.sorted('start') + self.assertEqual(len(events), len(dates), "Wrong number of events in the recurrence") + self.assertTrue(all(events.mapped('active')), "All events should be active") + for event, dates in zip(events, dates): + start, stop = dates + self.assertEqual(event.start, start) + self.assertEqual(event.stop, stop) + + +class TestCreateRecurrentEvents(TestRecurrentEvents): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.event = cls.env['calendar.event'].create({ + 'name': 'Recurrent Event', + 'start': datetime(2019, 10, 25, 8, 0), + 'stop': datetime(2019, 10, 27, 18, 0), + 'recurrency': True, + }) + + def test_weekly_count(self): + """ Every week, on Tuesdays, for 3 occurences """ + detached_events = self.event._apply_recurrence_values({ + 'rrule_type': 'weekly', + 'tu': True, + 'interval': 1, + 'count': 3, + 'event_tz': 'UTC', + }) + self.assertEqual(detached_events, self.event, "It should be detached from the recurrence") + self.assertFalse(self.event.recurrence_id, "It should be detached from the recurrence") + recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)]) + events = recurrence.calendar_event_ids + self.assertEqual(len(events), 3, "It should have 3 events in the recurrence") + self.assertEventDates(events, [ + (datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)), + (datetime(2019, 10, 29, 8, 0), datetime(2019, 10, 31, 18, 0)), + (datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)), + ]) + + def test_weekly_interval_2(self): + self.event._apply_recurrence_values({ + 'interval': 2, + 'rrule_type': 'weekly', + 'tu': True, + 'count': 2, + 'event_tz': 'UTC', + }) + recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)]) + events = recurrence.calendar_event_ids + self.assertEventDates(events, [ + (datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)), + (datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)), + ]) + + def test_weekly_interval_2_week_start_sunday(self): + lang = self.env['res.lang']._lang_get(self.env.user.lang) + lang.week_start = '7' # Sunday + + self.event._apply_recurrence_values({ + 'interval': 2, + 'rrule_type': 'weekly', + 'tu': True, + 'count': 2, + 'event_tz': 'UTC', + }) + recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)]) + events = recurrence.calendar_event_ids + self.assertEventDates(events, [ + (datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)), + (datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)), + ]) + lang.week_start = '1' # Monday + + def test_weekly_until(self): + self.event._apply_recurrence_values({ + 'rrule_type': 'weekly', + 'tu': True, + 'interval': 2, + 'end_type': 'end_date', + 'until': datetime(2019, 11, 15), + 'event_tz': 'UTC', + }) + recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)]) + events = recurrence.calendar_event_ids + self.assertEqual(len(events), 2, "It should have 2 events in the recurrence") + self.assertEventDates(events, [ + (datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)), + (datetime(2019, 11, 5, 8, 0), datetime(2019, 11, 7, 18, 0)), + ]) + + def test_monthly_count_by_date(self): + self.event._apply_recurrence_values({ + 'rrule_type': 'monthly', + 'interval': 2, + 'month_by': 'date', + 'day': 15, + 'end_type': 'count', + 'count': 3, + 'event_tz': 'UTC', + }) + recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)]) + events = recurrence.calendar_event_ids + self.assertEqual(len(events), 3, "It should have 3 events in the recurrence") + self.assertEventDates(events, [ + (datetime(2019, 10, 15, 8, 0), datetime(2019, 10, 17, 18, 0)), + (datetime(2019, 12, 15, 8, 0), datetime(2019, 12, 17, 18, 0)), + (datetime(2020, 2, 15, 8, 0), datetime(2020, 2, 17, 18, 0)), + ]) + + def test_monthly_count_by_date_31(self): + self.event._apply_recurrence_values({ + 'rrule_type': 'monthly', + 'interval': 1, + 'month_by': 'date', + 'day': 31, + 'end_type': 'count', + 'count': 3, + 'event_tz': 'UTC', + }) + recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)]) + events = recurrence.calendar_event_ids + self.assertEqual(len(events), 3, "It should have 3 events in the recurrence") + self.assertEventDates(events, [ + (datetime(2019, 10, 31, 8, 0), datetime(2019, 11, 2, 18, 0)), + # Missing 31th in November + (datetime(2019, 12, 31, 8, 0), datetime(2020, 1, 2, 18, 0)), + (datetime(2020, 1, 31, 8, 0), datetime(2020, 2, 2, 18, 0)), + ]) + + def test_monthly_until_by_day(self): + """ Every 2 months, on the third Tuesday, until 15th March 2020 """ + self.event._apply_recurrence_values({ + 'rrule_type': 'monthly', + 'interval': 2, + 'month_by': 'day', + 'byday': '3', + 'weekday': 'TU', + 'end_type': 'end_date', + 'until': date(2020, 3, 15), + 'event_tz': 'UTC', + }) + recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)]) + events = recurrence.calendar_event_ids + self.assertEqual(len(events), 3, "It should have 3 events in the recurrence") + self.assertEventDates(events, [ + (datetime(2019, 10, 15, 8, 0), datetime(2019, 10, 17, 18, 0)), + (datetime(2019, 12, 17, 8, 0), datetime(2019, 12, 19, 18, 0)), + (datetime(2020, 2, 18, 8, 0), datetime(2020, 2, 20, 18, 0)), + ]) + + def test_monthly_until_by_day_last(self): + """ Every 2 months, on the last Wednesday, until 15th January 2020 """ + self.event._apply_recurrence_values({ + 'interval': 2, + 'rrule_type': 'monthly', + 'month_by': 'day', + 'weekday': 'WE', + 'byday': '-1', + 'end_type': 'end_date', + 'until': date(2020, 1, 15), + 'event_tz': 'UTC', + }) + recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)]) + events = recurrence.calendar_event_ids + self.assertEqual(len(events), 2, "It should have 3 events in the recurrence") + self.assertEventDates(events, [ + (datetime(2019, 10, 30, 8, 0), datetime(2019, 11, 1, 18, 0)), + (datetime(2019, 12, 25, 8, 0), datetime(2019, 12, 27, 18, 0)), + ]) + + def test_yearly_count(self): + self.event._apply_recurrence_values({ + 'interval': 2, + 'rrule_type': 'yearly', + 'count': 2, + 'event_tz': 'UTC', + }) + events = self.event.recurrence_id.calendar_event_ids + self.assertEqual(len(events), 2, "It should have 3 events in the recurrence") + self.assertEventDates(events, [ + (self.event.start, self.event.stop), + (self.event.start + relativedelta(years=2), self.event.stop + relativedelta(years=2)), + ]) + + def test_dst_timezone(self): + """ Test hours stays the same, regardless of DST changes """ + self.event.start = datetime(2002, 10, 26, 10, 0) + self.event.stop = datetime(2002, 10, 26, 12, 0) + self.event._apply_recurrence_values({ + 'interval': 2, + 'rrule_type': 'weekly', + 'mo': True, + 'count': '2', + 'event_tz': 'US/Eastern', # DST change on 2002/10/27 + }) + recurrence = self.env['calendar.recurrence'].search([('base_event_id', '=', self.event.id)]) + self.assertEventDates(recurrence.calendar_event_ids, [ + (datetime(2002, 10, 21, 10, 0), datetime(2002, 10, 21, 12, 0)), + (datetime(2002, 11, 4, 11, 0), datetime(2002, 11, 4, 13, 0)), + ]) + + def test_ambiguous_dst_time_winter(self): + """ Test hours stays the same, regardless of DST changes """ + eastern = pytz.timezone('US/Eastern') + dt = eastern.localize(datetime(2002, 10, 20, 1, 30, 00)).astimezone(pytz.utc).replace(tzinfo=None) + # Next occurence happens at 1:30am on 27th Oct 2002 which happened twice in the US/Eastern + # timezone when the clocks where put back at the end of Daylight Saving Time + self.event.start = dt + self.event.stop = dt + relativedelta(hours=1) + self.event._apply_recurrence_values({ + 'interval': 1, + 'rrule_type': 'weekly', + 'su': True, + 'count': '2', + 'event_tz': 'US/Eastern' # DST change on 2002/4/7 + }) + events = self.event.recurrence_id.calendar_event_ids + self.assertEqual(events.mapped('duration'), [1, 1]) + self.assertEventDates(events, [ + (datetime(2002, 10, 20, 5, 30), datetime(2002, 10, 20, 6, 30)), + (datetime(2002, 10, 27, 6, 30), datetime(2002, 10, 27, 7, 30)), + ]) + + def test_ambiguous_dst_time_spring(self): + """ Test hours stays the same, regardless of DST changes """ + eastern = pytz.timezone('US/Eastern') + dt = eastern.localize(datetime(2002, 3, 31, 2, 30, 00)).astimezone(pytz.utc).replace(tzinfo=None) + # Next occurence happens 2:30am on 7th April 2002 which never happened at all in the + # US/Eastern timezone, as the clocks where put forward at 2:00am skipping the entire hour + self.event.start = dt + self.event.stop = dt + relativedelta(hours=1) + self.event._apply_recurrence_values({ + 'interval': 1, + 'rrule_type': 'weekly', + 'su': True, + 'count': '2', + 'event_tz': 'US/Eastern' # DST change on 2002/4/7 + }) + events = self.event.recurrence_id.calendar_event_ids + self.assertEqual(events.mapped('duration'), [1, 1]) + # The event begins at "the same time" (i.e. 2h30 after midnight), but that day, 2h30 after midnight happens to be at 3:30 am + self.assertEventDates(events, [ + (datetime(2002, 3, 31, 7, 30), datetime(2002, 3, 31, 8, 30)), + (datetime(2002, 4, 7, 7, 30), datetime(2002, 4, 7, 8, 30)), + ]) + + def test_ambiguous_full_day(self): + """ Test date stays the same, regardless of DST changes """ + self.event.write({ + 'start': datetime(2020, 3, 23, 0, 0), + 'stop': datetime(2020, 3, 23, 23, 59), + }) + self.event.allday = True + self.event._apply_recurrence_values({ + 'interval': 1, + 'rrule_type': 'weekly', + 'mo': True, + 'count': 2, + 'event_tz': 'Europe/Brussels' # DST change on 2020/3/23 + }) + events = self.event.recurrence_id.calendar_event_ids + self.assertEventDates(events, [ + (datetime(2020, 3, 23, 0, 0), datetime(2020, 3, 23, 23, 59)), + (datetime(2020, 3, 30, 0, 0), datetime(2020, 3, 30, 23, 59)), + ]) + + +class TestUpdateRecurrentEvents(TestRecurrentEvents): + + @classmethod + def setUpClass(cls): + super().setUpClass() + event = cls.env['calendar.event'].create({ + 'name': 'Recurrent Event', + 'start': datetime(2019, 10, 22, 1, 0), + 'stop': datetime(2019, 10, 24, 18, 0), + 'recurrency': True, + 'rrule_type': 'weekly', + 'tu': True, + 'interval': 1, + 'count': 3, + 'event_tz': 'Etc/GMT-4', + }) + cls.recurrence = event.recurrence_id + cls.events = event.recurrence_id.calendar_event_ids.sorted('start') + + def test_shift_future(self): + event = self.events[1] + self.events[1].write({ + 'recurrence_update': 'future_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + self.assertEqual(self.recurrence.end_type, 'end_date') + self.assertEqual(self.recurrence.until, date(2019, 10, 27)) + self.assertEventDates(self.recurrence.calendar_event_ids, [ + (datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), + ]) + new_recurrence = event.recurrence_id + self.assertNotEqual(self.recurrence, new_recurrence) + self.assertEqual(new_recurrence.count, 2) + self.assertEqual(new_recurrence.dtstart, datetime(2019, 11, 2, 1, 0)) + self.assertFalse(new_recurrence.tu) + self.assertTrue(new_recurrence.sa) + self.assertEventDates(new_recurrence.calendar_event_ids, [ + (datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 5, 18, 0)), + (datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 12, 18, 0)), + ]) + + def test_shift_future_first(self): + event = self.events[0] + self.events[0].write({ + 'recurrence_update': 'future_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + new_recurrence = event.recurrence_id + self.assertFalse(self.recurrence.exists()) + self.assertEqual(new_recurrence.count, 3) + self.assertEqual(new_recurrence.dtstart, datetime(2019, 10, 26, 1, 0)) + self.assertFalse(new_recurrence.tu) + self.assertTrue(new_recurrence.sa) + self.assertEventDates(new_recurrence.calendar_event_ids, [ + (datetime(2019, 10, 26, 1, 0), datetime(2019, 10, 29, 18, 0)), + (datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 5, 18, 0)), + (datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 12, 18, 0)), + ]) + + def test_shift_reapply(self): + event = self.events[2] + self.events[2].write({ + 'recurrence_update': 'future_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + # re-Applying the first recurrence should be idempotent + self.recurrence._apply_recurrence() + self.assertEventDates(self.recurrence.calendar_event_ids, [ + (datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), + (datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)), + ]) + + def test_shift_all(self): + event = self.events[1] + with self.assertRaises(UserError): + event.write({ + 'recurrence_update': 'all_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + + def test_change_week_day_rrule(self): + recurrence = self.events.recurrence_id + recurrence.rrule = 'FREQ=WEEKLY;COUNT=3;BYDAY=WE' # from TU to WE + self.assertFalse(self.recurrence.tu) + self.assertTrue(self.recurrence.we) + + def test_shift_all_base_inactive(self): + self.recurrence.base_event_id.active = False + event = self.events[1] + with self.assertRaises(UserError): + event.write({ + 'recurrence_update': 'all_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + + def test_shift_all_with_outlier(self): + outlier = self.events[1] + outlier.write({ + 'recurrence_update': 'self_only', + 'start': datetime(2019, 9, 26, 1, 0), # Thursday + 'stop': datetime(2019, 9, 26, 18, 0), + }) + event = self.events[0] + with self.assertRaises(UserError): + event.write({ + 'recurrence_update': 'all_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + + def test_update_recurrence_future(self): + event = self.events[1] + event.write({ + 'recurrence_update': 'future_events', + 'fr': True, # recurrence is now Tuesday AND Friday + 'count': 4, + }) + self.assertEventDates(self.recurrence.calendar_event_ids, [ + (datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), # Tu + ]) + + self.assertEventDates(event.recurrence_id.calendar_event_ids, [ + (datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)), # Tu + (datetime(2019, 11, 1, 1, 0), datetime(2019, 11, 3, 18, 0)), # Fr + (datetime(2019, 11, 5, 1, 0), datetime(2019, 11, 7, 18, 0)), # Tu + (datetime(2019, 11, 8, 1, 0), datetime(2019, 11, 10, 18, 0)), # Fr + ]) + + events = event.recurrence_id.calendar_event_ids.sorted('start') + self.assertEqual(events[0], self.events[1], "Events on Tuesdays should not have changed") + self.assertEqual(events[2], self.events[2], "Events on Tuesdays should not have changed") + self.assertNotEqual(events.recurrence_id, self.recurrence, "Events should no longer be linked to the original recurrence") + self.assertEqual(events.recurrence_id.count, 4, "The new recurrence should have 4") + self.assertTrue(event.recurrence_id.tu) + self.assertTrue(event.recurrence_id.fr) + + def test_update_recurrence_all(self): + with self.assertRaises(UserError): + self.events[1].write({ + 'recurrence_update': 'all_events', + 'mo': True, # recurrence is now Tuesday AND Monday + }) + + def test_shift_single(self): + event = self.events[1] + event.write({ + 'recurrence_update': 'self_only', + 'name': "Updated event", + 'start': event.start - relativedelta(hours=2) + }) + self.events[0].write({ + 'recurrence_update': 'future_events', + 'start': event.start + relativedelta(hours=4), + 'stop': event.stop + relativedelta(hours=5), + }) + + def test_break_recurrence_future(self): + event = self.events[1] + event.write({ + 'recurrence_update': 'future_events', + 'recurrency': False, + }) + self.assertFalse(event.recurrence_id) + self.assertTrue(self.events[0].active) + self.assertTrue(self.events[1].active) + self.assertFalse(self.events[2].exists()) + self.assertEqual(self.recurrence.until, date(2019, 10, 27)) + self.assertEqual(self.recurrence.end_type, 'end_date') + self.assertEventDates(self.recurrence.calendar_event_ids, [ + (datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), + ]) + + def test_break_recurrence_all(self): + event = self.events[1] + event.write({ + 'recurrence_update': 'all_events', + 'recurrency': False, + 'count': 0, # In practice, JS framework sends updated recurrency fields, since they have been recomputed, triggered by the `recurrency` change + }) + self.assertFalse(self.events[0].exists()) + self.assertTrue(event.active) + self.assertFalse(self.events[2].exists()) + self.assertFalse(event.recurrence_id) + self.assertFalse(self.recurrence.exists()) + + def test_all_day_shift(self): + recurrence = self.env['calendar.event'].create({ + 'name': 'Recurrent Event', + 'start_date': datetime(2019, 10, 22), + 'stop_date': datetime(2019, 10, 24), + 'recurrency': True, + 'rrule_type': 'weekly', + 'tu': True, + 'interval': 1, + 'count': 3, + 'event_tz': 'Etc/GMT-4', + 'allday': True, + }).recurrence_id + events = recurrence.calendar_event_ids.sorted('start') + event = events[1] + event.write({ + 'recurrence_update': 'future_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + self.assertEqual(recurrence.end_type, 'end_date') + self.assertEqual(recurrence.until, date(2019, 10, 27)) + self.assertEventDates(recurrence.calendar_event_ids, [ + (datetime(2019, 10, 22, 8, 0), datetime(2019, 10, 24, 18, 0)), + ]) + new_recurrence = event.recurrence_id + self.assertNotEqual(recurrence, new_recurrence) + self.assertEqual(new_recurrence.count, 2) + self.assertEqual(new_recurrence.dtstart, datetime(2019, 11, 2, 8, 0)) + self.assertFalse(new_recurrence.tu) + self.assertTrue(new_recurrence.sa) + self.assertEventDates(new_recurrence.calendar_event_ids, [ + (datetime(2019, 11, 2, 8, 0), datetime(2019, 11, 5, 18, 0)), + (datetime(2019, 11, 9, 8, 0), datetime(2019, 11, 12, 18, 0)), + ]) + + # TODO test followers, and alarms are copied + + +class TestUpdateMultiDayWeeklyRecurrentEvents(TestRecurrentEvents): + + @classmethod + def setUpClass(cls): + super().setUpClass() + event = cls.env['calendar.event'].create({ + 'name': 'Recurrent Event', + 'start': datetime(2019, 10, 22, 1, 0), + 'stop': datetime(2019, 10, 24, 18, 0), + 'recurrency': True, + 'rrule_type': 'weekly', + 'tu': True, + 'fr': True, + 'interval': 1, + 'count': 3, + 'event_tz': 'Etc/GMT-4', + }) + cls.recurrence = event.recurrence_id + cls.events = event.recurrence_id.calendar_event_ids.sorted('start') + # Tuesday datetime(2019, 10, 22, 1, 0) + # Friday datetime(2019, 10, 25, 1, 0) + # Tuesday datetime(2019, 10, 29, 1, 0) + + def test_shift_all_multiple_weekdays(self): + event = self.events[0] # Tuesday + with self.assertRaises(UserError): + event.write({ + 'recurrence_update': 'all_events', + 'start': event.start + relativedelta(days=2), + 'stop': event.stop + relativedelta(days=2), + }) + + def test_shift_all_multiple_weekdays_duration(self): + event = self.events[0] # Tuesday + with self.assertRaises(UserError): + event.write({ + 'recurrence_update': 'all_events', + 'start': event.start + relativedelta(days=2), + 'stop': event.stop + relativedelta(days=3), + }) + + def test_shift_future_multiple_weekdays(self): + event = self.events[1] # Friday + event.write({ + 'recurrence_update': 'future_events', + 'start': event.start + relativedelta(days=3), + 'stop': event.stop + relativedelta(days=3), + }) + self.assertTrue(self.recurrence.fr) + self.assertTrue(self.recurrence.tu) + self.assertTrue(event.recurrence_id.tu) + self.assertTrue(event.recurrence_id.mo) + self.assertFalse(event.recurrence_id.fr) + self.assertEqual(event.recurrence_id.count, 2) + + +class TestUpdateMonthlyByDay(TestRecurrentEvents): + + @classmethod + def setUpClass(cls): + super().setUpClass() + event = cls.env['calendar.event'].create({ + 'name': 'Recurrent Event', + 'start': datetime(2019, 10, 15, 1, 0), + 'stop': datetime(2019, 10, 16, 18, 0), + 'recurrency': True, + 'rrule_type': 'monthly', + 'interval': 1, + 'count': 3, + 'month_by': 'day', + 'weekday': 'TU', + 'byday': '3', + 'event_tz': 'Etc/GMT-4', + }) + cls.recurrence = event.recurrence_id + cls.events = event.recurrence_id.calendar_event_ids.sorted('start') + # datetime(2019, 10, 15, 1, 0) + # datetime(2019, 11, 19, 1, 0) + # datetime(2019, 12, 17, 1, 0) + + def test_shift_all(self): + event = self.events[1] + with self.assertRaises(UserError): + event.write({ + 'recurrence_update': 'all_events', + 'start': event.start - relativedelta(days=5), + 'stop': event.stop - relativedelta(days=4), + }) + + +class TestUpdateMonthlyByDate(TestRecurrentEvents): + + @classmethod + def setUpClass(cls): + super().setUpClass() + event = cls.env['calendar.event'].create({ + 'name': 'Recurrent Event', + 'start': datetime(2019, 10, 22, 1, 0), + 'stop': datetime(2019, 10, 24, 18, 0), + 'recurrency': True, + 'rrule_type': 'monthly', + 'interval': 1, + 'count': 3, + 'month_by': 'date', + 'day': 22, + 'event_tz': 'Etc/GMT-4', + }) + cls.recurrence = event.recurrence_id + cls.events = event.recurrence_id.calendar_event_ids.sorted('start') + # datetime(2019, 10, 22, 1, 0) + # datetime(2019, 11, 22, 1, 0) + # datetime(2019, 12, 22, 1, 0) + + def test_shift_future(self): + event = self.events[1] + event.write({ + 'recurrence_update': 'future_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + self.assertEventDates(self.recurrence.calendar_event_ids, [ + (datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)), + ]) + self.assertEventDates(event.recurrence_id.calendar_event_ids, [ + (datetime(2019, 11, 26, 1, 0), datetime(2019, 11, 29, 18, 0)), + (datetime(2019, 12, 26, 1, 0), datetime(2019, 12, 29, 18, 0)), + ]) + + def test_shift_all(self): + event = self.events[1] + with self.assertRaises(UserError): + event.write({ + 'recurrence_update': 'all_events', + 'start': event.start + relativedelta(days=4), + 'stop': event.stop + relativedelta(days=5), + }) + + def test_update_all(self): + event = self.events[1] + with self.assertRaises(UserError): + event.write({ + 'recurrence_update': 'all_events', + 'day': 25, + }) |
