summaryrefslogtreecommitdiff
path: root/addons/calendar/tests
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/calendar/tests
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/calendar/tests')
-rw-r--r--addons/calendar/tests/__init__.py10
-rw-r--r--addons/calendar/tests/test_access_rights.py90
-rw-r--r--addons/calendar/tests/test_attendees.py70
-rw-r--r--addons/calendar/tests/test_calendar.py348
-rw-r--r--addons/calendar/tests/test_calendar_controller.py47
-rw-r--r--addons/calendar/tests/test_calendar_recurrent_event_case2.py55
-rw-r--r--addons/calendar/tests/test_event_notifications.py163
-rw-r--r--addons/calendar/tests/test_event_recurrence.py662
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 &amp; <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,
+ })