Initial commit: Technical packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:51 +02:00
commit 3473fa71a0
873 changed files with 297766 additions and 0 deletions

View file

@ -0,0 +1,13 @@
# -*- 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
from . import test_mail_activity_mixin
from . import test_res_partner
from . import test_recurrence_rule

View file

@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo.tests.common import TransactionCase, new_test_user
from odoo.exceptions import AccessError
from odoo.tools import mute_logger
class TestAccessRights(TransactionCase):
@classmethod
@mute_logger('odoo.tests', 'odoo.addons.auth_signup.models.res_users')
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.george = new_test_user(cls.env, login='george', groups='base.group_user')
cls.portal = new_test_user(cls.env, login='pot', groups='base.group_portal')
cls.admin_user = new_test_user(cls.env, login='admin_user', groups='base.group_partner_manager,base.group_user')
def create_event(self, user, **values):
return self.env['calendar.event'].with_user(user).create({
'name': 'Event',
'start': datetime(2020, 2, 2, 8, 0),
'stop': datetime(2020, 2, 2, 18, 0),
'user_id': user.id,
'partner_ids': [(4, self.george.partner_id.id, 0)],
**values
})
def read_event(self, user, events, field):
data = events.with_user(user).read([field])
if len(events) == 1:
return data[0][field]
return [r[field] for r in data]
# don't spam logs with ACL failures from portal
@mute_logger('odoo.addons.base.models.ir_rule')
def test_privacy(self):
event = self.create_event(
self.john,
privacy='private',
name='my private event',
location='in the Sky'
)
for user, field, expect, error in [
# public field, any employee can read
(self.john, 'privacy', 'private', None),
(self.george, 'privacy', 'private', None),
(self.raoul, 'privacy', 'private', None),
(self.portal, 'privacy', None, AccessError),
# substituted private field, only owner and invitees can read, other
# employees get substitution
(self.john, 'name', 'my private event', None),
(self.george, 'name', 'my private event', None),
(self.raoul, 'name', 'Busy', None),
(self.portal, 'name', None, AccessError),
# computed from private field
(self.john, 'display_name', 'my private event', None),
(self.george, 'display_name', 'my private event', None),
(self.raoul, 'display_name', 'Busy', None),
(self.portal, 'display_name', None, AccessError),
# non-substituted private field, only owner and invitees can read,
# other employees get an empty field
(self.john, 'location', 'in the Sky', None),
(self.george, 'location', 'in the Sky', None),
(self.raoul, 'location', False, None),
(self.portal, 'location', None, AccessError),
# non-substituted sequence field
(self.john, 'partner_ids', self.george.partner_id, None),
(self.george, 'partner_ids', self.george.partner_id, None),
(self.raoul, 'partner_ids', self.env['res.partner'], None),
(self.portal, 'partner_ids', None, AccessError),
]:
self.env.invalidate_all()
with self.subTest("private read", user=user.display_name, field=field, error=error):
e = event.with_user(user)
if error:
with self.assertRaises(error):
_ = e[field]
else:
self.assertEqual(e[field], expect)
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.assertFalse(private_location, "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")
data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['name'],
groupby='name')
self.assertTrue(data, "It should be able to read group")
def test_read_group_private(self):
event = self.create_event(self.john, privacy='private')
result = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['name'], groupby='name')
self.assertFalse(result, "Private events should not be fetched")
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")
def test_private_attendee(self):
event = self.create_event(
self.john,
privacy='private',
location='in the Sky',
)
partners = (self.john|self.raoul).mapped('partner_id')
event.write({'partner_ids': [(6, 0, partners.ids)]})
self.assertEqual(self.read_event(self.raoul, event, 'location'), 'in the Sky',
"Owner should be able to read the event")
with self.assertRaises(AccessError):
self.read_event(self.portal, event, 'location')
def test_meeting_edit_access_notification_handle_in_odoo(self):
# set notifications to "handle in Odoo" in Preferences for john, raoul, and george
(self.john | self.raoul | self.george).write({'notification_type': 'inbox'})
# raoul creates a meeting for john, excluding themselves
meeting = self.env['calendar.event'].with_user(self.raoul).create({
'name': 'Test Meeting',
'start': datetime.now(),
'stop': datetime.now() + timedelta(hours=2),
'user_id': self.john.id,
'partner_ids': [(4, self.raoul.partner_id.id)],
})
# george tries to modify the start date of the meeting to a future date
# this verifies that users with "handle in Odoo" notification setting can
# successfully edit meetings created by other users. If this write fails,
# it indicates that there might be an issue with access rights for meeting attendees.
meeting = meeting.with_user(self.george)
meeting.write({
'start': datetime.now() + timedelta(days=2),
'stop': datetime.now() + timedelta(days=2, hours=2),
})
def test_hide_sensitive_fields_private_events_from_uninvited_admins(self):
"""
Ensure that it is not possible fetching sensitive fields for uninvited administrators,
i.e. admins who are not attendees of private events. Sensitive fields are fields that
could contain sensitive information, such as 'name', 'description', 'location', etc.
"""
sensitive_fields = [
'location', 'attendee_ids', 'partner_ids', 'description',
'videocall_location', 'categ_ids', 'message_ids',
]
# Create event with all sensitive fields defined on it.
event_type = self.env['calendar.event.type'].create({'name': 'type'})
john_private_evt = self.create_event(
self.john,
name='private-event',
privacy='private',
location='private-location',
description='private-description',
attendee_status='accepted',
partner_ids=[self.john.partner_id.id, self.raoul.partner_id.id],
categ_ids=[event_type.id],
videocall_location='private-url.com'
)
john_private_evt.message_post(body="Message to be hidden.")
# Read the event as an uninvited administrator and ensure that the sensitive fields were hidden.
# Do the same for the search_read method: the information of sensitive fields must be hidden.
private_event_domain = ('id', '=', john_private_evt.id)
readed_event = john_private_evt.with_user(self.admin_user).read(sensitive_fields + ['name'])
search_readed_event = self.env['calendar.event'].with_user(self.admin_user).search_read([private_event_domain])
for event in [readed_event, search_readed_event]:
self.assertEqual(len(event), 1, "The event itself must be fetched since the record is not hidden from uninvited admins.")
self.assertEqual(event[0]['name'], "Busy", "Event name must be 'Busy', hiding the information from uninvited administrators.")
for field in sensitive_fields:
self.assertFalse(event[0][field], "Field %s contains private information, it must be hidden from uninvited administrators." % field)
# Ensure that methods like 'mapped', 'filtered', 'filtered_domain', '_search' and 'read_group' do not
# bypass the override of read, which will hide the private information of the events from uninvited administrators.
sensitive_stored_fields = ['name', 'location', 'description', 'videocall_location']
searched_event = self.env['calendar.event'].with_user(self.admin_user).search([private_event_domain])
for field in sensitive_stored_fields:
# For each method, fetch the information of the private event as an uninvited administrator.
check_mapped_event = searched_event.with_user(self.admin_user).mapped(field)
check_filtered_event = searched_event.with_user(self.admin_user).filtered(lambda ev: ev.id == john_private_evt.id)
check_filtered_domain = searched_event.with_user(self.admin_user).filtered_domain([private_event_domain])
check_search_query = self.env['calendar.event'].with_user(self.admin_user)._search([private_event_domain])
check_search_object = self.env['calendar.event'].with_user(self.admin_user).browse(check_search_query)
check_read_group = self.env['calendar.event'].with_user(self.admin_user).read_group([private_event_domain], [field], [field])
if field == 'name':
# The 'name' field is manually changed to 'Busy' by default. We need to ensure it is shown as 'Busy' in all following methods.
self.assertEqual(check_mapped_event, ['Busy'], 'Private event name should be shown as Busy using the mapped function.')
self.assertEqual(check_filtered_event.name, 'Busy', 'Private event name should be shown as Busy using the filtered function.')
self.assertEqual(check_filtered_domain.name, 'Busy', 'Private event name should be shown as Busy using the filtered_domain function.')
self.assertEqual(check_search_object.name, 'Busy', 'Private event name should be shown as Busy using the _search function.')
else:
# The remaining private fields should be falsy for uninvited administrators.
self.assertFalse(check_mapped_event[0], 'Private event field "%s" should be hidden when using the mapped function.' % field)
self.assertFalse(check_filtered_event[field], 'Private event field "%s" should be hidden when using the filtered function.' % field)
self.assertFalse(check_filtered_domain[field], 'Private event field "%s" should be hidden when using the filtered_domain function.' % field)
self.assertFalse(check_search_object[field], 'Private event field "%s" should be hidden when using the _search function.' % field)
# Private events are excluded from read_group by default, ensure that we do not fetch it.
self.assertFalse(len(check_read_group), 'Private event should be hidden using the function _read_group.')

View file

@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo.tests.common import TransactionCase, new_test_user, Form
from odoo import fields, Command
from freezegun import freeze_time
class TestEventNotifications(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user = new_test_user(cls.env, 'xav', email='em@il.com', notification_type='inbox')
cls.event = cls.env['calendar.event'].with_user(cls.user).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.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_create_with_specific_states(self):
"""
When an event is created from an external calendar account (such as Google) which is not linked to an
Odoo account, attendee info such as email and state are given at sync.
In this case, attendee_ids should be created accordingly.
"""
organizer_partner = self.env['res.partner'].create({'name': "orga", "email": "orga@google.com"})
event = self.env['calendar.event'].with_user(self.user).create({
'name': "Doom's day",
'start': datetime(2019, 10, 25, 8, 0),
'stop': datetime(2019, 10, 27, 18, 0),
'attendee_ids': [
(0, 0, {'partner_id': self.partner.id, 'state': 'needsAction'}),
(0, 0, {'partner_id': organizer_partner.id, 'state': 'accepted'})
],
'partner_ids': [(4, self.partner.id), (4, organizer_partner.id)],
})
attendees_info = [(a.email, a.state) for a in event.attendee_ids]
self.assertEqual(len(event.attendee_ids), 2)
self.assertIn((self.partner.email, "needsAction"), attendees_info)
self.assertIn((organizer_partner.email, "accepted"), attendees_info)
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_attendee_added_write(self):
"""Test that writing ids directly on partner_ids instead of commands is handled."""
self.event.write({'partner_ids': [self.partner.id]})
self.assertEqual(self.event.attendee_ids.partner_id, self.partner, "It should be linked to the partner")
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")
def test_attendee_without_email(self):
self.partner.email = False
self.event.partner_ids = self.partner
self.assertTrue(self.event.attendee_ids)
self.assertEqual(self.event.attendee_ids.partner_id, self.partner)
self.assertTrue(self.event.invalid_email_partner_ids)
self.assertEqual(self.event.invalid_email_partner_ids, self.partner)
def test_attendee_with_invalid_email(self):
self.partner.email = "I'm an invalid email"
self.event.partner_ids = self.partner
self.assertTrue(self.event.attendee_ids)
self.assertEqual(self.event.attendee_ids.partner_id, self.partner)
self.assertTrue(self.event.invalid_email_partner_ids)
self.assertEqual(self.event.invalid_email_partner_ids, self.partner)
def test_default_attendee(self):
"""
Check if priority list id correctly followed
1) vals_list[0]['attendee_ids']
2) vals_list[0]['partner_ids']
3) context.get('default_attendee_ids')
"""
partner_bis = self.env['res.partner'].create({'name': "Xavier"})
event = self.env['calendar.event'].with_user(
self.user
).with_context(
default_attendee_ids=[(0, 0, {'partner_id': partner_bis.id})]
).create({
'name': "Doom's day",
'partner_ids': [(4, self.partner.id)],
'start': datetime(2019, 10, 25, 8, 0),
'stop': datetime(2019, 10, 27, 18, 0),
})
self.assertIn(self.partner, event.attendee_ids.partner_id, "Partner should be in attendee")
self.assertNotIn(partner_bis, event.attendee_ids.partner_id, "Partner bis should not be in attendee")
def test_push_meeting_start(self):
"""
Checks that you can push the start date of an all day meeting.
"""
attendee = self.env['res.partner'].create({
'name': "Xavier",
'email': "xavier@example.com",
})
event = self.env['calendar.event'].create({
'name': "Doom's day",
'attendee_ids': [Command.create({'partner_id': attendee.id})],
'allday': True,
'start_date': fields.Date.today(),
'stop_date': fields.Date.today(),
})
initial_start = event.start
with Form(event) as event_form:
event_form.stop_date = datetime.today() + relativedelta(days=1)
event_form.start_date = datetime.today() + relativedelta(days=1)
self.assertFalse(initial_start == event.start)
@freeze_time("2019-10-24 09:00:00", tick=True)
def test_multi_attendee_mt_note_default(self):
mt_note = self.env.ref("mail.mt_note")
mt_note.default = True
user_exta = new_test_user(self.env, "extra", email="extra@il.com")
partner_extra = user_exta.partner_id
event = self.env["calendar.event"].create({
"name": "Team meeting",
"attendee_ids": [
(0, 0, {"partner_id": self.partner.id}),
(0, 0, {"partner_id": partner_extra.id})
],
"start": datetime(2019, 10, 25, 8, 0),
"stop": datetime(2019, 10, 25, 10, 0),
})
messages = self.env["mail.message"].search([
("model", "=", event._name),
("res_id", "=", event.id),
("message_type", "=", "user_notification")
])
self.assertEqual(len(messages), 2)
mesage_user = messages.filtered(lambda x: self.partner in x.partner_ids)
self.assertNotIn(partner_extra, mesage_user.notified_partner_ids)
mesage_user_extra = messages.filtered(lambda x: partner_extra in x.partner_ids)
self.assertNotIn(self.partner, mesage_user_extra.notified_partner_ids)

View file

@ -0,0 +1,554 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from datetime import date, datetime, timedelta
from odoo import fields, Command
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
from odoo.tests import Form, tagged, new_test_user
from odoo.addons.base.tests.common import SavepointCaseWithUserDemo
import pytz
import re
import base64
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 & <br> 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_activity_event_multiple_meetings(self):
# Creating multiple meetings from an activity creates additional activities
# 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',
})
activity_id = self.env['mail.activity'].create({
'summary': 'Meeting with partner',
'activity_type_id': meeting_act_type.id,
'res_model_id': self.env['ir.model']._get_id('res.partner'),
'res_id': test_record.id,
})
calendar_action = activity_id.with_context(default_res_model='res.partner', default_res_id=test_record.id).action_create_calendar_event()
event_1 = self.env['calendar.event'].with_context(calendar_action['context']).create({
'name': 'Meeting 1',
'start': datetime(2025, 3, 10, 17),
'stop': datetime(2025, 3, 10, 22),
})
self.assertEqual(event_1.activity_ids, activity_id)
total_activities = self.env['mail.activity'].search_count(domain=[])
event_2 = self.env['calendar.event'].with_context(calendar_action['context']).create({
'name': 'Meeting 2',
'start': datetime(2025, 3, 11, 17),
'stop': datetime(2025, 3, 11, 22),
})
self.assertEqual(event_1.activity_ids, activity_id, "Event 1's activity should still be the first activity")
self.assertEqual(activity_id.calendar_event_id, event_1, "The first activity's event should still be event 1")
self.assertEqual(total_activities + 1, self.env['mail.activity'].search_count(domain=[]), "1 more activity record should have been created (by event 2)")
self.assertNotEqual(event_2.activity_ids, activity_id, "Event 2's activity should not be the first activity")
self.assertEqual(event_2.activity_ids.activity_type_id, activity_id.activity_type_id, "Event 2's activity should be the same activity type as the first activity")
self.assertEqual(test_record.activity_ids, activity_id | event_2.activity_ids, "Resource record should now have both activities")
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,
})
self.env.invalidate_all()
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_recurring_ny(self):
self.env.user.tz = 'America/New_York'
f = Form(self.CalendarEvent.with_context(tz='America/New_York'))
f.name = 'test'
f.start = '2022-07-07 01:00:00' # This is in UTC. In NY, it corresponds to the 6th of july at 9pm.
f.recurrency = True
self.assertEqual(f.weekday, 'WED')
self.assertEqual(f.event_tz, 'America/New_York', "The value should correspond to the user tz")
self.assertEqual(f.count, 1, "The default value should be displayed")
self.assertEqual(f.interval, 1, "The default value should be displayed")
self.assertEqual(f.month_by, "date", "The default value should be displayed")
self.assertEqual(f.end_type, "count", "The default value should be displayed")
self.assertEqual(f.rrule_type, "weekly", "The default value should be displayed")
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']._get_id('res.partner'),
'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']._get_id('res.partner'),
'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
Check that mail have extra attachement added by the user
"""
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)
def _test_emails_has_attachment(self, partners):
# check that every email has an attachment
for partner in partners:
mail = self.env['mail.message'].sudo().search([
('notified_partner_ids', 'in', partner.id),
])
extra_attachment = mail.attachment_ids.filtered(lambda attachment: attachment.name == "fileText_attachment.txt")
self.assertEqual(len(extra_attachment), 1)
attachment = self.env['ir.attachment'].create({
'datas': base64.b64encode(bytes("Event Attachment", 'utf-8')),
'name': 'fileText_attachment.txt',
'mimetype': 'text/plain'
})
self.env.ref('calendar.calendar_template_meeting_invitation').attachment_ids = attachment
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)
_test_emails_has_attachment(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_internal_user_invitation_ics(self):
""" Check that internal user can read invitation.ics attachment """
internal_user = new_test_user(self.env, login='internal_user', groups='base.group_user')
partner = internal_user.partner_id
self.event_tech_presentation.write({
'partner_ids': [(4, partner.id)],
})
msg = self.env['mail.message'].search([
('notified_partner_ids', 'in', partner.id),
])
msg.invalidate_recordset()
# internal user can read the attachment without errors
self.assertEqual(msg.with_user(internal_user).attachment_ids.name, 'invitation.ics')
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)),
})
def test_meeting_creation_from_partner_form(self):
""" When going from a partner to the Calendar and adding a meeting, both current user and partner
should be attendees of the event """
calendar_action = self.partner_demo.schedule_meeting()
event = self.env['calendar.event'].with_context(calendar_action['context']).create({
'name': 'Super Meeting',
'start': datetime(2020, 12, 13, 17),
'stop': datetime(2020, 12, 13, 22),
})
self.assertEqual(len(event.attendee_ids), 2)
self.assertTrue(self.partner_demo in event.attendee_ids.mapped('partner_id'))
self.assertTrue(self.env.user.partner_id in event.attendee_ids.mapped('partner_id'))
def test_discuss_videocall(self):
self.event_tech_presentation._set_discuss_videocall_location()
self.assertFalse(self.event_tech_presentation.videocall_channel_id.id, 'No channel should be set before the route is accessed')
# create the first channel
self.event_tech_presentation._create_videocall_channel()
self.assertNotEqual(self.event_tech_presentation.videocall_channel_id.id, False)
partner1 = self.env['res.partner'].create({'name': 'Bob', 'email': u'bob@gm.co'})
partner2 = self.env['res.partner'].create({'name': 'Jack', 'email': u'jack@gm.co'})
new_partners = [partner1.id, partner2.id]
# invite partners to meeting
self.event_tech_presentation.write({
'partner_ids': [Command.link(new_partner) for new_partner in new_partners]
})
self.assertTrue(set(new_partners) == set(self.event_tech_presentation.videocall_channel_id.channel_partner_ids.ids), 'new partners must be invited to the channel')
@tagged('post_install', '-at_install')
class TestCalendarTours(HttpCaseWithUserDemo):
def test_calendar_month_view_start_hour_displayed(self):
""" Test that the time is displayed in the month view. """
self.start_tour("/web", 'calendar_appointments_hour_tour', login="demo")
def test_calendar_delete_tour(self):
"""
Check that we can delete events with the "Everybody's calendars" filter.
"""
user_admin = self.env.ref('base.user_admin')
start = datetime.combine(date.today(), datetime.min.time()).replace(hour=9)
stop = datetime.combine(date.today(), datetime.min.time()).replace(hour=12)
event = self.env['calendar.event'].with_user(user_admin).create({
'name': 'Test Event',
'description': 'Test Description',
'start': start.strftime("%Y-%m-%d %H:%M:%S"),
'stop': stop.strftime("%Y-%m-%d %H:%M:%S"),
'duration': 3,
'location': 'Odoo S.A.',
'privacy': 'public',
'show_as': 'busy',
})
action_id = self.env.ref('calendar.action_calendar_event')
url = "/web#action=" + str(action_id.id) + '&view_type=calendar'
self.start_tour(url, 'test_calendar_delete_tour', login='admin')
event = self.env['calendar.event'].search([('name', '=', 'Test Event')])
self.assertFalse(event) # Check if the event has been correctly deleted
def test_calendar_decline_tour(self):
"""
Check that we can decline events.
"""
user_admin = self.env.ref('base.user_admin')
user_demo = self.user_demo
start = datetime.combine(date.today(), datetime.min.time()).replace(hour=9)
stop = datetime.combine(date.today(), datetime.min.time()).replace(hour=12)
event = self.env['calendar.event'].with_user(user_admin).create({
'name': 'Test Event',
'description': 'Test Description',
'start': start.strftime("%Y-%m-%d %H:%M:%S"),
'stop': stop.strftime("%Y-%m-%d %H:%M:%S"),
'duration': 3,
'location': 'Odoo S.A.',
'privacy': 'public',
'show_as': 'busy',
})
event.partner_ids = [Command.link(user_demo.partner_id.id)]
action_id = self.env.ref('calendar.action_calendar_event')
url = "/web#action=" + str(action_id.id) + '&view_type=calendar'
self.start_tour(url, 'test_calendar_decline_tour', login='demo')
attendee = self.env['calendar.attendee'].search([('event_id', '=', event.id), ('partner_id', '=', user_demo.partner_id.id)])
self.assertEqual(attendee.state, 'declined') # Check if the event has been correctly declined
def test_calendar_decline_with_everybody_filter_tour(self):
"""
Check that we can decline events with the "Everybody's calendars" filter.
"""
user_admin = self.env.ref('base.user_admin')
user_demo = self.user_demo
start = datetime.combine(date.today(), datetime.min.time()).replace(hour=9)
stop = datetime.combine(date.today(), datetime.min.time()).replace(hour=12)
event = self.env['calendar.event'].with_user(user_admin).create({
'name': 'Test Event',
'description': 'Test Description',
'start': start.strftime("%Y-%m-%d %H:%M:%S"),
'stop': stop.strftime("%Y-%m-%d %H:%M:%S"),
'duration': 3,
'location': 'Odoo S.A.',
'privacy': 'public',
'show_as': 'busy',
})
event.partner_ids = [Command.link(user_demo.partner_id.id)]
action_id = self.env.ref('calendar.action_calendar_event')
url = "/web#action=" + str(action_id.id) + '&view_type=calendar'
self.start_tour(url, 'test_calendar_decline_with_everybody_filter_tour', login='demo')
attendee = self.env['calendar.attendee'].search([('event_id', '=', event.id), ('partner_id', '=', user_demo.partner_id.id)])
self.assertEqual(attendee.state, 'declined') # Check if the event has been correctly declined

View file

@ -0,0 +1,49 @@
# -*- 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)]})
attendee = self.event.attendee_ids.filtered(lambda att: att.partner_id.id == self.other_user.partner_id.id)
token = attendee.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.env.invalidate_all()
self.assertEqual(attendee.state, "accepted", "Attendee should have accepted")
def test_accept_meeting_authenticated(self):
self.event.write({"partner_ids": [(4, self.other_user.partner_id.id)]})
attendee = self.event.attendee_ids.filtered(lambda att: att.partner_id.id == self.other_user.partner_id.id)
token = attendee.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.env.invalidate_all()
self.assertEqual(attendee.state, "accepted", "Attendee should have accepted")

View file

@ -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',
'fri': True,
'mon': True,
'thu': True,
'tue': True,
'wed': 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 !')

View file

@ -0,0 +1,281 @@
# 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 freezegun import freeze_time
from odoo import fields
from odoo.tests.common import TransactionCase, new_test_user
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
from odoo.addons.mail.tests.common import MailCase
class TestEventNotifications(TransactionCase, MailCase, CronMixinCase):
@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_allday(self):
with self.assertSinglePostNotifications([{'partner': self.partner, 'type': 'inbox'}], {
'message_type': 'user_notification',
'subtype': 'mail.mt_note',
}):
self.env['calendar.event'].with_context(mail_create_nolog=True).create([{
'name': 'Meeting',
'allday': True,
'start_date': fields.Date.today() + relativedelta(days=7),
'stop_date': fields.Date.today() + relativedelta(days=8),
'partner_ids': [(4, self.partner.id)],
}])
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, 'res.partner', self.partner.id)], [
{
"type": "calendar.alarm",
"payload": [{
"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)),
}],
},
]):
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)]
})
def test_email_alarm(self):
now = fields.Datetime.now()
with self.capture_triggers('calendar.ir_cron_scheduler_alarm') as capt:
alarm = self.env['calendar.alarm'].create({
'name': 'Alarm',
'alarm_type': 'email',
'interval': 'minutes',
'duration': 20,
})
self.event.write({
'name': 'test event',
'start': now + relativedelta(minutes=15),
'stop': now + relativedelta(minutes=18),
'partner_ids': [fields.Command.link(self.partner.id)],
'alarm_ids': [fields.Command.link(alarm.id)],
})
self.env.flush_all() # flush is required to make partner_ids be present in the event
capt.records.ensure_one()
self.assertLessEqual(capt.records.call_at, now)
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.event.user_id = self.user
old_messages = self.event.message_ids
self.env['calendar.alarm_manager'].with_context(lastcall=now - relativedelta(minutes=15))._send_reminder()
messages = self.env["mail.message"].search([
("model", "=", self.event._name),
("res_id", "=", self.event.id),
("message_type", "=", "user_notification")
])
new_messages = messages - old_messages
user_message = new_messages.filtered(lambda x: self.event.user_id.partner_id in x.partner_ids)
self.assertTrue(user_message.notification_ids, "Organizer must receive a reminder")
def test_notification_event_timezone(self):
"""
Check the domain that decides when calendar events should be notified to the user.
"""
def search_event():
return self.env['calendar.event'].search(self.env['res.users']._systray_get_calendar_event_domain())
self.env.user.tz = 'Europe/Brussels' # UTC +1 15th November 2023
events = self.env['calendar.event'].create([{
'name': "Meeting",
'start': datetime(2023, 11, 15, 18, 0), # 19:00
'stop': datetime(2023, 11, 15, 19, 0), # 20:00
},
{
'name': "Tomorrow meeting",
'start': datetime(2023, 11, 15, 23, 0), # 00:00 next day
'stop': datetime(2023, 11, 16, 0, 0), # 01:00 next day
}
]).with_context(mail_notrack=True)
with freeze_time('2023-11-15 17:30:00'): # 18:30 before event
self.assertEqual(search_event(), events[0])
with freeze_time('2023-11-15 18:00:00'): # 19:00 during event
self.assertEqual(search_event(), events[0])
with freeze_time('2023-11-15 18:30:00'): # 19:30 during event
self.assertEqual(search_event(), events[0])
with freeze_time('2023-11-15 19:00:00'): # 20:00 during event
self.assertEqual(search_event(), events[0])
with freeze_time('2023-11-15 19:30:00'): # 20:30 after event
self.assertEqual(len(search_event()), 0)
events.unlink()
self.env.user.tz = 'America/Lima' # UTC -5 15th November 2023
event = self.env['calendar.event'].create({
'name': "Meeting",
'start': datetime(2023, 11, 16, 0, 0), # 19:00 15th November
'stop': datetime(2023, 11, 16, 1, 0), # 20:00 15th November
}).with_context(mail_notrack=True)
with freeze_time('2023-11-15 23:30:00'): # 18:30 before event
self.assertEqual(search_event(), event)
with freeze_time('2023-11-16 00:00:00'): # 19:00 during event
self.assertEqual(search_event(), event)
with freeze_time('2023-11-16 00:30:00'): # 19:30 during event
self.assertEqual(search_event(), event)
with freeze_time('2023-11-16 01:00:00'): # 20:00 during event
self.assertEqual(search_event(), event)
with freeze_time('2023-11-16 01:30:00'): # 20:30 after event
self.assertEqual(len(search_event()), 0)
event.unlink()
event = self.env['calendar.event'].create({
'name': "Meeting",
'start': datetime(2023, 11, 16, 21, 0), # 16:00 16th November
'stop': datetime(2023, 11, 16, 22, 0), # 27:00 16th November
}).with_context(mail_notrack=True)
with freeze_time('2023-11-15 19:00:00'): # 14:00 the day before event
self.assertEqual(len(search_event()), 0)
event.unlink()
self.env.user.tz = 'Asia/Manila' # UTC +8 15th November 2023
events = self.env['calendar.event'].create([{
'name': "Very early meeting",
'start': datetime(2023, 11, 14, 16, 30), # 0:30
'stop': datetime(2023, 11, 14, 17, 0), # 1:00
},
{
'name': "Meeting on 2 days",
'start': datetime(2023, 11, 15, 15, 30), # 23:30
'stop': datetime(2023, 11, 15, 16, 30), # 0:30 next day
},
{
'name': "Early meeting tomorrow",
'start': datetime(2023, 11, 15, 23, 0), # 00:00 next day
'stop': datetime(2023, 11, 16, 0, 0), # 01:00 next day
},
{
'name': "All day meeting",
'allday': True,
'start': "2023-11-15",
}
]).with_context(mail_notrack=True)
with freeze_time('2023-11-15 16:00:00'):
self.assertEqual(len(search_event()), 3)
events.unlink()

View file

@ -0,0 +1,900 @@
# -*- 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 TransactionCase
from freezegun import freeze_time
class TestRecurrentEvents(TransactionCase):
@classmethod
def setUpClass(cls):
super(TestRecurrentEvents, cls).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, 21, 8, 0),
'stop': datetime(2019, 10, 23, 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',
'tue': 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',
'tue': 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',
'tue': 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',
'tue': 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': 27,
'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, 27, 8, 0), datetime(2019, 10, 29, 18, 0)),
(datetime(2019, 12, 27, 8, 0), datetime(2019, 12, 29, 18, 0)),
(datetime(2020, 2, 27, 8, 0), datetime(2020, 2, 29, 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 27th March 2020 """
self.event.start = datetime(2019, 10, 1, 8, 0)
self.event.stop = datetime(2019, 10, 3, 18, 0)
self.event._apply_recurrence_values({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'day',
'byday': '3',
'weekday': 'TUE',
'end_type': 'end_date',
'until': date(2020, 3, 27),
'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': 'WED',
'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, 28, 10, 0)
self.event.stop = datetime(2002, 10, 28, 12, 0)
self.event._apply_recurrence_values({
'interval': 2,
'rrule_type': 'weekly',
'mon': True,
'count': '2',
'event_tz': 'America/New_York', # 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, 28, 10, 0), datetime(2002, 10, 28, 12, 0)),
(datetime(2002, 11, 11, 10, 0), datetime(2002, 11, 11, 12, 0)),
])
def test_ambiguous_dst_time_winter(self):
""" Test hours stays the same, regardless of DST changes """
eastern = pytz.timezone('America/New_York')
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 America/New_York
# 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',
'sun': True,
'count': '2',
'event_tz': 'America/New_York' # 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('America/New_York')
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
# America/New_York 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',
'sun': True,
'count': '2',
'event_tz': 'America/New_York' # 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',
'mon': 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)),
])
def test_videocall_recurrency(self):
self.event._set_discuss_videocall_location()
self.event._apply_recurrence_values({
'interval': 1,
'rrule_type': 'weekly',
'mon': True,
'count': 2,
})
recurrent_events = self.event.recurrence_id.calendar_event_ids
detached_events = self.event.recurrence_id.calendar_event_ids - self.event
rec_events_videocall_locations = recurrent_events.mapped('videocall_location')
self.assertEqual(len(rec_events_videocall_locations), len(set(rec_events_videocall_locations)), 'Recurrent events should have different videocall locations')
self.assertEqual(not any(recurrent_events.videocall_channel_id), True, 'No channel should be set before the route is accessed')
# create the first channel
detached_events[0]._create_videocall_channel()
# after channel is created, all other events should have the same channel
self.assertEqual(detached_events[0].videocall_channel_id.id, self.event.videocall_channel_id.id)
@freeze_time('2023-03-27')
def test_backward_pass_dst(self):
"""
When we apply the rule to compute the period of the recurrence,
we take an earlier date (in `_get_start_of_period` method).
However, it is possible that this earlier date has a different DST.
This causes time difference problems.
"""
# In Europe/Brussels: 26 March 2023 from winter to summer (from no DST to DST)
# We are in the case where we create a recurring event after the time change (there is the DST).
timezone = 'Europe/Brussels'
tz = pytz.timezone(timezone)
dt = tz.localize(datetime(2023, 3, 27, 9, 0, 00)).astimezone(pytz.utc).replace(tzinfo=None)
self.event.start = dt
self.event.stop = dt + relativedelta(hours=1)
# Check before apply the recurrence
self.assertEqual(self.event.start, datetime(2023, 3, 27, 7, 0, 00)) # Because 2023-03-27 in Europe/Brussels is UTC+2
self.event._apply_recurrence_values({
'rrule_type': 'monthly', # Because we will take the first day of the month (jump back)
'interval': 1,
'end_type': 'count',
'count': 2, # To have the base event and the unique recurrence event
'month_by': 'date',
'day': 27,
'event_tz': timezone,
})
# What we expect:
# - start date of base event: datetime(2023, 3, 27, 7, 0, 00)
# - start date of the unique recurrence event: datetime(2023, 4, 27, 7, 0, 00)
# With the FIX, we replace the following lines with
# `events = self.event.recurrence_id.calendar_event_ids`
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.assertIn(self.event, events)
self.assertEventDates(events, [
(datetime(2023, 3, 27, 7, 00), datetime(2023, 3, 27, 8, 00)),
(datetime(2023, 4, 27, 7, 00), datetime(2023, 4, 27, 8, 00)),
])
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',
'tue': 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.tue)
self.assertTrue(new_recurrence.sat)
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.tue)
self.assertTrue(new_recurrence.sat)
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]
self.assertEventDates(event.recurrence_id.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)),
(datetime(2019, 11, 5, 1, 0), datetime(2019, 11, 7, 18, 0)),
])
event.write({
'recurrence_update': 'all_events',
'tue': False,
'fri': False,
'sat': True,
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
recurrence = self.env['calendar.recurrence'].search([])
self.assertEventDates(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_stop_all(self):
# testing the case where we only want to update the stop time
event = self.events[0]
event.write({
'recurrence_update': 'all_events',
'stop': event.stop + relativedelta(hours=1),
})
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 10, 22, 2, 0), datetime(2019, 10, 24, 19, 0)),
(datetime(2019, 10, 29, 2, 0), datetime(2019, 10, 31, 19, 0)),
(datetime(2019, 11, 5, 2, 0), datetime(2019, 11, 7, 19, 0)),
])
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.tue)
self.assertTrue(self.recurrence.wed)
def test_rrule_x_params(self):
self.recurrence.rrule = 'RRULE;X-EVOLUTION-ENDDATE=20191112;X-OTHER-PARAM=0:X-AMAZING=1;FREQ=WEEKLY;COUNT=3;X-MAIL-special=1;BYDAY=WE;X-RELATIVE=True'
self.assertFalse(self.recurrence.tue)
self.assertTrue(self.recurrence.wed)
def test_rrule_x_params_no_rrule_prefix(self):
self.recurrence.rrule = 'X-EVOLUTION-ENDDATE=20371102T114500Z:FREQ=WEEKLY;COUNT=720;BYDAY=MO'
self.assertFalse(self.recurrence.tue)
self.assertTrue(self.recurrence.mon)
self.assertEqual(self.recurrence.count, 720)
self.assertEqual(self.recurrence.rrule_type, 'weekly')
def test_shift_all_base_inactive(self):
self.recurrence.base_event_id.active = False
event = self.events[1]
event.write({
'recurrence_update': 'all_events',
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=5),
})
self.assertFalse(self.recurrence.calendar_event_ids, "Inactive event should not create recurrent events")
def test_shift_all_with_outlier(self):
outlier = self.events[1]
outlier.write({
'recurrence_update': 'self_only',
'start': datetime(2019, 10, 31, 1, 0), # Thursday
'stop': datetime(2019, 10, 31, 18, 0),
})
event = self.events[0]
event.write({
'recurrence_update': 'all_events',
'tue': False,
'fri': False,
'sat': True,
'start': event.start + relativedelta(days=4),
'stop': event.stop + relativedelta(days=4),
})
self.assertEventDates(event.recurrence_id.calendar_event_ids, [
(datetime(2019, 10, 26, 1, 0), datetime(2019, 10, 28, 18, 0)),
(datetime(2019, 11, 2, 1, 0), datetime(2019, 11, 4, 18, 0)),
(datetime(2019, 11, 9, 1, 0), datetime(2019, 11, 11, 18, 0))
])
self.assertTrue(outlier.exists(), 'The outlier should have its date and time updated according to the change.')
def test_update_recurrence_future(self):
event = self.events[1]
event.write({
'recurrence_update': 'future_events',
'fri': 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].start, self.events[2].start, "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.tue)
self.assertTrue(event.recurrence_id.fri)
def test_update_name_future(self):
# update regular event (not the base event)
old_events = self.events[1:]
old_events[0].write({
'name': 'New name',
'recurrence_update': 'future_events',
'rrule_type': 'daily',
'count': 5,
})
new_recurrence = self.env['calendar.recurrence'].search([('id', '>', self.events[0].recurrence_id.id)])
self.assertTrue(self.events[0].recurrence_id.exists())
self.assertEqual(new_recurrence.count, 5)
self.assertFalse(any(old_event.active for old_event in old_events - old_events[0]))
for event in new_recurrence.calendar_event_ids:
self.assertEqual(event.name, 'New name')
# update the base event
new_events = new_recurrence.calendar_event_ids.sorted('start')
new_events[0].write({
'name': 'Old name',
'recurrence_update': 'future_events'
})
self.assertTrue(new_recurrence.exists())
for event in new_recurrence.calendar_event_ids:
self.assertEqual(event.name, 'Old name')
def test_update_recurrence_all(self):
self.events[1].write({
'recurrence_update': 'all_events',
'mon': True, # recurrence is now Tuesday AND Monday
})
recurrence = self.env['calendar.recurrence'].search([])
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 22, 1, 0), datetime(2019, 10, 24, 18, 0)),
(datetime(2019, 10, 28, 1, 0), datetime(2019, 10, 30, 18, 0)),
(datetime(2019, 10, 29, 1, 0), datetime(2019, 10, 31, 18, 0)),
])
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',
'tue': 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.tue)
self.assertTrue(new_recurrence.sat)
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)),
])
def test_update_name_all(self):
old_recurrence = self.events[0].recurrence_id
old_events = old_recurrence.calendar_event_ids - self.events[0]
self.events[0].write({
'name': 'New name',
'recurrence_update': 'all_events',
'count': '5'
})
new_recurrence = self.env['calendar.recurrence'].search([('id', '>', old_recurrence.id)])
self.assertFalse(old_recurrence.exists())
self.assertEqual(new_recurrence.count, 5)
self.assertFalse(any(old_event.active for old_event in old_events))
for event in new_recurrence.calendar_event_ids:
self.assertEqual(event.name, 'New name')
def test_archive_recurrence_all(self):
self.events[1].action_mass_archive('all_events')
self.assertEqual([False, False, False], self.events.mapped('active'))
def test_archive_recurrence_future(self):
event = self.events[1]
event.action_mass_archive('future_events')
self.assertEqual([True, False, False], self.events.mapped('active'))
def test_unlink_recurrence_all(self):
event = self.events[1]
event.action_mass_deletion('all_events')
self.assertFalse(self.recurrence.exists())
self.assertFalse(self.events.exists())
def test_unlink_recurrence_future(self):
event = self.events[1]
event.action_mass_deletion('future_events')
self.assertTrue(self.recurrence)
self.assertEqual(self.events.exists(), self.events[0])
def test_recurrence_update_all_first_archived(self):
"""Test to check the flow when a calendar event is
created from a day that does not belong to the recurrence.
"""
event = self.env['calendar.event'].create({
'name': 'Recurrent Event',
'start': datetime(2019, 10, 22, 1, 0),
'stop': datetime(2019, 10, 22, 2, 0),
'recurrency': True,
'rrule_type': 'weekly',
'tue': False,
'wed': True,
'fri': True,
'interval': 1,
'count': 3,
'event_tz': 'Etc/GMT-4',
})
# Tuesday datetime(2019, 10, 22, 1, 0) - Archived
# Wednesday datetime(2019, 10, 23, 1, 0)
# Friday datetime(2019, 10, 25, 1, 0)
# Wednesday datetime(2019, 10, 30, 1, 0)
recurrence = self.env['calendar.recurrence'].search([('id', '!=', self.recurrence.id)])
events = recurrence.calendar_event_ids.sorted('start')
# Check first event is archived
self.assertFalse(event.active)
# Check base_event is different than archived and it is first active event
self.assertNotEqual(recurrence.base_event_id, event)
self.assertEqual(recurrence.base_event_id, events[0])
# Update all events to check that error is not thrown
events[0].write({
'recurrence_update': 'all_events',
'fri': False,
})
events = self.env['calendar.recurrence'].search(
[('id', '!=', self.recurrence.id)]
).calendar_event_ids.sorted('start')
self.assertEventDates(events, [
(datetime(2019, 10, 23, 1, 0), datetime(2019, 10, 23, 2, 0)),
(datetime(2019, 10, 30, 1, 0), datetime(2019, 10, 30, 2, 0)),
(datetime(2019, 11, 6, 1, 0), datetime(2019, 11, 6, 2, 0)),
])
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',
'tue': True,
'fri': 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
# We go from 2 days a week Thuesday and Friday to one day a week, Thursday
event.write({
'recurrence_update': 'all_events',
'tue': False,
'thu': True,
'fri': False,
'start': event.start + relativedelta(days=2),
'stop': event.stop + relativedelta(days=2),
})
recurrence = self.env['calendar.recurrence'].search([])
# We don't try to do magic tricks. First event is moved, other remain
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 24, 1, 0), datetime(2019, 10, 26, 18, 0)),
(datetime(2019, 10, 31, 1, 0), datetime(2019, 11, 2, 18, 0)),
(datetime(2019, 11, 7, 1, 0), datetime(2019, 11, 9, 18, 0)),
])
def test_shift_all_multiple_weekdays_duration(self):
event = self.events[0] # Tuesday
event.write({
'recurrence_update': 'all_events',
'tue': False,
'thu': True,
'fri': False,
'start': event.start + relativedelta(days=2),
'stop': event.stop + relativedelta(days=3),
})
recurrence = self.env['calendar.recurrence'].search([])
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 24, 1, 0), datetime(2019, 10, 27, 18, 0)),
(datetime(2019, 10, 31, 1, 0), datetime(2019, 11, 3, 18, 0)),
(datetime(2019, 11, 7, 1, 0), datetime(2019, 11, 10, 18, 0)),
])
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.fri)
self.assertTrue(self.recurrence.tue)
self.assertTrue(event.recurrence_id.tue)
self.assertTrue(event.recurrence_id.mon)
self.assertFalse(event.recurrence_id.fri)
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': 'TUE',
'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]
event.write({
'recurrence_update': 'all_events',
'start': event.start + relativedelta(hours=5),
'stop': event.stop + relativedelta(hours=5),
})
recurrence = self.env['calendar.recurrence'].search([])
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 15, 6, 0), datetime(2019, 10, 16, 23, 0)),
(datetime(2019, 11, 19, 6, 0), datetime(2019, 11, 20, 23, 0)),
(datetime(2019, 12, 17, 6, 0), datetime(2019, 12, 18, 23, 0)),
])
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_update_all(self):
event = self.events[1]
event.write({
'recurrence_update': 'all_events',
'day': 25,
})
recurrence = self.env['calendar.recurrence'].search([('day', '=', 25)])
self.assertEventDates(recurrence.calendar_event_ids, [
(datetime(2019, 10, 25, 1, 0), datetime(2019, 10, 27, 18, 0)),
(datetime(2019, 11, 25, 1, 0), datetime(2019, 11, 27, 18, 0)),
(datetime(2019, 12, 25, 1, 0), datetime(2019, 12, 27, 18, 0)),
])

View file

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, time
from dateutil.relativedelta import relativedelta
import pytz
from odoo import Command
from odoo import tests
from odoo.addons.mail.tests.common import MailCommon
@tests.tagged('mail_activity_mixin')
class TestMailActivityMixin(MailCommon):
@classmethod
def setUpClass(cls):
super(TestMailActivityMixin, cls).setUpClass()
# using res.partner as the model inheriting from mail.activity.mixin
cls.test_record = cls.env['res.partner'].with_context(cls._test_context).create({'name': 'Test'})
cls.activity_type_1 = cls.env['mail.activity.type'].create({
'name': 'Calendar Activity Test Default',
'summary': 'default activity',
'res_model': 'res.partner',
})
cls.env['ir.model.data'].create({
'name': cls.activity_type_1.name.lower().replace(' ', '_'),
'module': 'calendar',
'model': cls.activity_type_1._name,
'res_id': cls.activity_type_1.id,
})
# reset ctx
cls._reset_mail_context(cls.test_record)
def test_activity_calendar_event_id(self):
"""Test the computed field "activity_calendar_event_id" which is the event of the
next activity. It must evaluate to False if the next activity is not related to an event"""
def create_event(name, event_date):
return self.env['calendar.event'].create({
'name': name,
'start': datetime.combine(event_date, time(12, 0, 0)),
'stop': datetime.combine(event_date, time(14, 0, 0)),
})
def schedule_meeting_activity(record, date_deadline, calendar_event=False):
meeting = record.activity_schedule('calendar.calendar_activity_test_default', date_deadline=date_deadline)
meeting.calendar_event_id = calendar_event
return meeting
group_partner_manager = self.env['ir.model.data']._xmlid_to_res_id('base.group_partner_manager')
self.user_employee.write({
'tz': self.user_admin.tz,
'groups_id': [Command.link(group_partner_manager)]
})
with self.with_user('employee'):
test_record = self.env['res.partner'].browse(self.test_record.id)
self.assertEqual(test_record.activity_ids, self.env['mail.activity'])
now_utc = datetime.now(pytz.UTC)
now_user = now_utc.astimezone(pytz.timezone(self.env.user.tz or 'UTC'))
today_user = now_user.date()
date1 = today_user + relativedelta(days=1)
date2 = today_user + relativedelta(days=2)
ev1 = create_event('ev1', date1)
ev2 = create_event('ev2', date2)
act1 = schedule_meeting_activity(test_record, date1)
schedule_meeting_activity(test_record, date2, ev2)
self.assertFalse(test_record.activity_calendar_event_id, "The next activity does not have a calendar event")
act1.calendar_event_id = ev1
self.assertEqual(test_record.activity_calendar_event_id.name, ev1.name, "This should be the calendar event of the next activity")
act1._action_done(feedback="Mark activity as done with text")
self.assertFalse(act1.exists(), "activity marked as done should be deleted")
self.assertTrue(ev1.exists(), "event of done activity must not be deleted")

View file

@ -0,0 +1,175 @@
from datetime import datetime
from odoo.tests.common import TransactionCase
class TestRecurrenceRule(TransactionCase):
def test_daily_count(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'daily',
'interval': 2,
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Days for 3 events')
def test_daily_until(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'daily',
'interval': 2,
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Days until 2024-11-15')
def test_daily_none(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'daily',
'interval': 2,
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Days')
def test_weekly_count(self):
""" Every week, on Tuesdays, for 3 occurences """
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'weekly',
'tue': True,
'wed': True,
'interval': 2,
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Weeks on Tuesday, Wednesday for 3 events')
def test_weekly_until(self):
""" Every week, on Tuesdays, for 3 occurences """
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'weekly',
'tue': True,
'wed': True,
'interval': 2,
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Weeks on Tuesday, Wednesday until 2024-11-15')
def test_weekly_none(self):
""" Every week, on Tuesdays, for 3 occurences """
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'weekly',
'tue': True,
'wed': True,
'interval': 2,
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Weeks on Tuesday, Wednesday')
def test_monthly_count_by_day(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'day',
'byday': '1',
'weekday': 'MON',
'end_type': 'count',
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months on the First Monday for 3 events')
def test_monthly_until_by_day(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'day',
'byday': '1',
'weekday': 'MON',
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months on the First Monday until 2024-11-15')
def test_monthly_none_by_day(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'day',
'byday': '1',
'weekday': 'MON',
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months on the First Monday')
def test_monthly_count_by_date(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'date',
'day': 27,
'weekday': 'MON',
'end_type': 'count',
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months day 27 for 3 events')
def test_monthly_until_by_date(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'date',
'day': 27,
'weekday': 'MON',
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months day 27 until 2024-11-15')
def test_monthly_none_by_date(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'date',
'day': 27,
'weekday': 'MON',
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months day 27')
def test_yearly_count(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'yearly',
'interval': 2,
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Years for 3 events')
def test_yearly_until(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'yearly',
'interval': 2,
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Years until 2024-11-15')
def test_yearly_none(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'yearly',
'interval': 2,
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Years')

View file

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase
from odoo.tests.common import new_test_user
class TestResPartner(TransactionCase):
def test_meeting_count(self):
test_user = new_test_user(self.env, login='test_user', groups='base.group_user, base.group_partner_manager')
Partner = self.env['res.partner'].with_user(test_user)
Event = self.env['calendar.event'].with_user(test_user)
# Partner hierarchy
# 1 5
# /|
# 2 3
# |
# 4
test_partner_1 = Partner.create({'name': 'test_partner_1'})
test_partner_2 = Partner.create({'name': 'test_partner_2', 'parent_id': test_partner_1.id})
test_partner_3 = Partner.create({'name': 'test_partner_3', 'parent_id': test_partner_1.id})
test_partner_4 = Partner.create({'name': 'test_partner_4', 'parent_id': test_partner_3.id})
test_partner_5 = Partner.create({'name': 'test_partner_5'})
test_partner_6 = Partner.create({'name': 'test_partner_6'})
test_partner_7 = Partner.create({'name': 'test_partner_7', 'parent_id': test_partner_6.id})
Event.create({'name': 'event_1',
'partner_ids': [(6, 0, [test_partner_1.id,
test_partner_2.id,
test_partner_3.id,
test_partner_4.id])]})
Event.create({'name': 'event_2',
'partner_ids': [(6, 0, [test_partner_1.id,
test_partner_3.id])]})
Event.create({'name': 'event_2',
'partner_ids': [(6, 0, [test_partner_2.id,
test_partner_3.id])]})
Event.create({'name': 'event_3',
'partner_ids': [(6, 0, [test_partner_3.id,
test_partner_4.id])]})
Event.create({'name': 'event_4',
'partner_ids': [(6, 0, [test_partner_1.id])]})
Event.create({'name': 'event_5',
'partner_ids': [(6, 0, [test_partner_3.id])]})
Event.create({'name': 'event_6',
'partner_ids': [(6, 0, [test_partner_4.id])]})
Event.create({'name': 'event_7',
'partner_ids': [(6, 0, [test_partner_5.id])]})
Event.create({'name': 'event_8',
'partner_ids': [(6, 0, [test_partner_5.id,
test_partner_7.id])]})
#Test rule to see if ir.rules are applied
calendar_event_model_id = self.env['ir.model']._get('calendar.event').id
self.env['ir.rule'].create({'name': 'test_rule',
'model_id': calendar_event_model_id,
'domain_force': [('name', 'not in', ['event_9', 'event_10'])],
'perm_read': True,
'perm_create': False,
'perm_write': False})
Event.create({'name': 'event_9',
'partner_ids': [(6, 0, [test_partner_2.id,
test_partner_3.id])]})
Event.create({'name': 'event_10',
'partner_ids': [(6, 0, [test_partner_5.id])]})
self.assertEqual(test_partner_1.meeting_count, 7)
self.assertEqual(test_partner_2.meeting_count, 2)
self.assertEqual(test_partner_3.meeting_count, 6)
self.assertEqual(test_partner_4.meeting_count, 3)
self.assertEqual(test_partner_5.meeting_count, 2)
self.assertEqual(test_partner_6.meeting_count, 1)
self.assertEqual(test_partner_7.meeting_count, 1)