mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-19 23:52:04 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
22
odoo-bringout-oca-ocb-mail/mail/tests/__init__.py
Normal file
22
odoo-bringout-oca-ocb-mail/mail/tests/__init__.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_discuss_controller
|
||||
from . import test_get_model_definitions
|
||||
from . import test_link_preview
|
||||
from . import test_mail_channel
|
||||
from . import test_mail_channel_as_guest
|
||||
from . import test_mail_channel_member
|
||||
from . import test_mail_composer
|
||||
from . import test_mail_full_composer
|
||||
from . import test_mail_mail
|
||||
from . import test_mail_mail_stable_selection
|
||||
from . import test_mail_render
|
||||
from . import test_mail_template
|
||||
from . import test_mail_tools
|
||||
from . import test_res_partner
|
||||
from . import test_res_users
|
||||
from . import test_res_users_settings
|
||||
from . import test_rtc
|
||||
from . import test_uninstall
|
||||
from . import test_update_notification
|
||||
from . import test_user_modify_own_profile
|
||||
1434
odoo-bringout-oca-ocb-mail/mail/tests/common.py
Normal file
1434
odoo-bringout-oca-ocb-mail/mail/tests/common.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,99 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
|
||||
try:
|
||||
import websocket as ws
|
||||
except ImportError:
|
||||
ws = None
|
||||
|
||||
from odoo.tests import tagged, new_test_user
|
||||
from odoo.addons.bus.tests.common import WebsocketCase
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.addons.bus.models.bus import channel_with_db, json_dump
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestBusPresence(WebsocketCase, MailCommon):
|
||||
def _receive_presence(self, sender, recipient):
|
||||
self._reset_bus()
|
||||
sent_from_user = isinstance(sender, self.env.registry["res.users"])
|
||||
receive_to_user = isinstance(recipient, self.env.registry["res.users"])
|
||||
if receive_to_user:
|
||||
session = self.authenticate(recipient.login, recipient.login)
|
||||
auth_cookie = f"session_id={session.sid};"
|
||||
else:
|
||||
self.authenticate(None, None)
|
||||
auth_cookie = f"{recipient._cookie_name}={recipient._format_auth_cookie()};"
|
||||
websocket = self.websocket_connect(cookie=auth_cookie)
|
||||
sender_bus_target = sender.partner_id if sent_from_user else sender
|
||||
self.subscribe(
|
||||
websocket,
|
||||
[f"odoo-presence-{sender_bus_target._name}_{sender_bus_target.id}"],
|
||||
self.env["bus.bus"]._bus_last_id(),
|
||||
)
|
||||
self.env["bus.presence"].create(
|
||||
{"user_id" if sent_from_user else "guest_id": sender.id, "status": "online"}
|
||||
)
|
||||
self.trigger_notification_dispatching([(sender_bus_target, "presence")])
|
||||
notifications = json.loads(websocket.recv())
|
||||
self._close_websockets()
|
||||
bus_record = self.env["bus.bus"].search([("id", "=", int(notifications[0]["id"]))])
|
||||
self.assertEqual(
|
||||
bus_record.channel,
|
||||
json_dump(channel_with_db(self.env.cr.dbname, (sender_bus_target, "presence"))),
|
||||
)
|
||||
self.assertEqual(notifications[0]["message"]["type"], "bus.bus/im_status_updated")
|
||||
self.assertEqual(notifications[0]["message"]["payload"]["im_status"], "online")
|
||||
self.assertEqual(notifications[0]["message"]["payload"]["presence_status"], "online")
|
||||
self.assertEqual(
|
||||
notifications[0]["message"]["payload"]["partner_id" if sent_from_user else "guest_id"],
|
||||
sender_bus_target.id,
|
||||
)
|
||||
|
||||
def test_receive_presences_as_guest(self):
|
||||
guest = self.env["mail.guest"].create({"name": "Guest"})
|
||||
bob = new_test_user(self.env, login="bob_user", groups="base.group_user")
|
||||
# Guest should not receive users's presence: no common channel.
|
||||
with self.assertRaises(ws._exceptions.WebSocketTimeoutException):
|
||||
self._receive_presence(sender=bob, recipient=guest)
|
||||
channel = self.env["discuss.channel"].channel_create(group_id=None, name="General")
|
||||
channel.add_members(guest_ids=[guest.id], partner_ids=[bob.partner_id.id])
|
||||
# Now that they share a channel, guest should receive users's presence.
|
||||
self._receive_presence(sender=bob, recipient=guest)
|
||||
|
||||
other_guest = self.env["mail.guest"].create({"name": "OtherGuest"})
|
||||
# Guest should not receive guest's presence: no common channel.
|
||||
with self.assertRaises(ws._exceptions.WebSocketTimeoutException):
|
||||
self._receive_presence(sender=other_guest, recipient=guest)
|
||||
channel.add_members(guest_ids=[other_guest.id])
|
||||
# Now that they share a channel, guest should receive guest's presence.
|
||||
self._receive_presence(sender=other_guest, recipient=guest)
|
||||
|
||||
def test_receive_presences_as_portal(self):
|
||||
portal = new_test_user(self.env, login="portal_user", groups="base.group_portal")
|
||||
bob = new_test_user(self.env, login="bob_user", groups="base.group_user")
|
||||
# Portal should not receive users's presence: no common channel.
|
||||
with self.assertRaises(ws._exceptions.WebSocketTimeoutException):
|
||||
self._receive_presence(sender=bob, recipient=portal)
|
||||
channel = self.env["discuss.channel"].channel_create(group_id=None, name="General")
|
||||
channel.add_members(partner_ids=[portal.partner_id.id, bob.partner_id.id])
|
||||
# Now that they share a channel, portal should receive users's presence.
|
||||
self._receive_presence(sender=bob, recipient=portal)
|
||||
|
||||
guest = self.env["mail.guest"].create({"name": "Guest"})
|
||||
# Portal should not receive guest's presence: no common channel.
|
||||
with self.assertRaises(ws._exceptions.WebSocketTimeoutException):
|
||||
self._receive_presence(sender=guest, recipient=portal)
|
||||
channel.add_members(guest_ids=[guest.id])
|
||||
# Now that they share a channel, portal should receive guest's presence.
|
||||
self._receive_presence(sender=guest, recipient=portal)
|
||||
|
||||
def test_receive_presences_as_internal(self):
|
||||
internal = new_test_user(self.env, login="internal_user", groups="base.group_user")
|
||||
guest = self.env["mail.guest"].create({"name": "Guest"})
|
||||
# Internal can access guest's presence regardless of their channels.
|
||||
self._receive_presence(sender=guest, recipient=internal)
|
||||
# Internal can access users's presence regardless of their channels.
|
||||
bob = new_test_user(self.env, login="bob_user", groups="base.group_user")
|
||||
self._receive_presence(sender=bob, recipient=internal)
|
||||
209
odoo-bringout-oca-ocb-mail/mail/tests/test_discuss_controller.py
Normal file
209
odoo-bringout-oca-ocb-mail/mail/tests/test_discuss_controller.py
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
|
||||
import odoo
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@odoo.tests.tagged("-at_install", "post_install")
|
||||
class TestDiscussController(HttpCaseWithUserDemo):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.channel = cls.env["mail.channel"].create(
|
||||
{
|
||||
"group_public_id": None,
|
||||
"name": "Test channel",
|
||||
}
|
||||
)
|
||||
cls.public_user = cls.env.ref("base.public_user")
|
||||
cls.attachments = (
|
||||
cls.env["ir.attachment"]
|
||||
.with_user(cls.public_user)
|
||||
.sudo()
|
||||
.create(
|
||||
[
|
||||
{
|
||||
"access_token": cls.env["ir.attachment"]._generate_access_token(),
|
||||
"name": "File 1",
|
||||
"res_id": 0,
|
||||
"res_model": "mail.compose.message",
|
||||
},
|
||||
{
|
||||
"access_token": cls.env["ir.attachment"]._generate_access_token(),
|
||||
"name": "File 2",
|
||||
"res_id": 0,
|
||||
"res_model": "mail.compose.message",
|
||||
},
|
||||
]
|
||||
)
|
||||
)
|
||||
cls.guest = cls.env["mail.guest"].create({"name": "Guest"})
|
||||
cls.channel.add_members(guest_ids=cls.guest.ids)
|
||||
|
||||
@mute_logger("odoo.addons.http_routing.models.ir_http", "odoo.http")
|
||||
def test_channel_message_attachments(self):
|
||||
self.authenticate(None, None)
|
||||
self.opener.cookies[
|
||||
self.guest._cookie_name
|
||||
] = f"{self.guest.id}{self.guest._cookie_separator}{self.guest.access_token}"
|
||||
# test message post: token error
|
||||
res1 = self.url_open(
|
||||
url="/mail/message/post",
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"thread_model": self.channel._name,
|
||||
"thread_id": self.channel.id,
|
||||
"post_data": {
|
||||
"body": "test",
|
||||
"attachment_ids": [self.attachments[0].id],
|
||||
"attachment_tokens": ["wrong token"],
|
||||
},
|
||||
},
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(res1.status_code, 200)
|
||||
self.assertIn(
|
||||
f"The attachment {self.attachments[0].id} does not exist or you do not have the rights to access it",
|
||||
res1.text,
|
||||
"guest should not be allowed to add attachment without token when posting message",
|
||||
)
|
||||
# test message post: token ok
|
||||
res2 = self.url_open(
|
||||
url="/mail/message/post",
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"thread_model": self.channel._name,
|
||||
"thread_id": self.channel.id,
|
||||
"post_data": {
|
||||
"body": "test",
|
||||
"attachment_ids": [self.attachments[0].id],
|
||||
"attachment_tokens": [self.attachments[0].access_token],
|
||||
"message_type": "comment",
|
||||
},
|
||||
},
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(res2.status_code, 200)
|
||||
message_format1 = res2.json()["result"]
|
||||
self.assertEqual(
|
||||
message_format1["attachment_ids"],
|
||||
json.loads(json.dumps(self.attachments[0]._attachment_format())),
|
||||
"guest should be allowed to add attachment with token when posting message",
|
||||
)
|
||||
# test message update: token error
|
||||
res3 = self.url_open(
|
||||
url="/mail/message/update_content",
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"message_id": message_format1["id"],
|
||||
"body": "test",
|
||||
"attachment_ids": [self.attachments[1].id],
|
||||
"attachment_tokens": ["wrong token"],
|
||||
},
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(res3.status_code, 200)
|
||||
self.assertIn(
|
||||
f"The attachment {self.attachments[1].id} does not exist or you do not have the rights to access it",
|
||||
res3.text,
|
||||
"guest should not be allowed to add attachment without token when updating message",
|
||||
)
|
||||
# test message update: token ok
|
||||
res4 = self.url_open(
|
||||
url="/mail/message/update_content",
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"message_id": message_format1["id"],
|
||||
"body": "test",
|
||||
"attachment_ids": [self.attachments[1].id],
|
||||
"attachment_tokens": [self.attachments[1].access_token],
|
||||
},
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(res4.status_code, 200)
|
||||
message_format2 = res4.json()["result"]
|
||||
self.assertEqual(
|
||||
message_format2["attachments"],
|
||||
json.loads(json.dumps(self.attachments.sorted()._attachment_format())),
|
||||
"guest should be allowed to add attachment with token when updating message",
|
||||
)
|
||||
# test message update: own attachment ok
|
||||
res5 = self.url_open(
|
||||
url="/mail/message/update_content",
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"message_id": message_format2["id"],
|
||||
"body": "test",
|
||||
"attachment_ids": [self.attachments[1].id],
|
||||
},
|
||||
}
|
||||
),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
self.assertEqual(res5.status_code, 200)
|
||||
message_format3 = res5.json()["result"]
|
||||
self.assertEqual(
|
||||
message_format3["attachments"],
|
||||
json.loads(json.dumps(self.attachments.sorted()._attachment_format())),
|
||||
"guest should be allowed to add own attachment without token when updating message",
|
||||
)
|
||||
|
||||
@mute_logger("odoo.addons.http_routing.models.ir_http", "odoo.http")
|
||||
def test_attachment_hijack(self):
|
||||
att = self.env["ir.attachment"].create(
|
||||
[
|
||||
{
|
||||
"name": "arguments_for_firing_marc_demo",
|
||||
"res_id": 0,
|
||||
"res_model": "mail.compose.message",
|
||||
},
|
||||
]
|
||||
)
|
||||
demo = self.authenticate("demo", "demo")
|
||||
channel = self.env["mail.channel"].create({"group_public_id": None, "name": "public_channel"})
|
||||
channel.add_members(
|
||||
self.env["res.users"].browse(demo.uid).partner_id.ids
|
||||
) # don't care, we just need a channel where demo is follower
|
||||
no_access_request = self.url_open("/web/content/" + str(att.id))
|
||||
self.assertFalse(
|
||||
no_access_request.ok
|
||||
) # if this test breaks, it might be due to a change in /web/content, or the default rules for accessing an attachment. This is not an issue but it makes this test irrelevant.
|
||||
response = self.url_open(
|
||||
url="/mail/message/post",
|
||||
headers={"Content-Type": "application/json"}, # route called as demo
|
||||
data=json.dumps(
|
||||
{
|
||||
"params": {
|
||||
"post_data": {
|
||||
"attachment_ids": [att.id], # demo does not have access to this attachment id
|
||||
"body": "",
|
||||
"message_type": "comment",
|
||||
"partner_ids": [],
|
||||
"subtype_xmlid": "mail.mt_comment",
|
||||
},
|
||||
"thread_id": channel.id,
|
||||
"thread_model": "mail.channel",
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
self.assertNotIn(
|
||||
"arguments_for_firing_marc_demo", response.text
|
||||
) # demo should not be able to see the name of the document
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo
|
||||
from odoo.tests import HttpCase
|
||||
|
||||
|
||||
@odoo.tests.tagged('-at_install', 'post_install')
|
||||
class TestGetModelDefinitions(HttpCase):
|
||||
def test_access_cr(self):
|
||||
""" Checks that get_model_definitions does not return anything else than models """
|
||||
with self.assertRaises(KeyError):
|
||||
self.env['ir.model']._get_model_definitions(['res.users', 'cr'])
|
||||
|
||||
def test_access_all_model_fields(self):
|
||||
"""
|
||||
Check that get_model_definitions return all the models
|
||||
and their fields
|
||||
"""
|
||||
model_definitions = self.env['ir.model']._get_model_definitions([
|
||||
'res.users', 'res.partner'
|
||||
])
|
||||
# models are retrieved
|
||||
self.assertIn('res.users', model_definitions)
|
||||
self.assertIn('res.partner', model_definitions)
|
||||
# check that model fields are retrieved
|
||||
self.assertTrue(
|
||||
all(fname in model_definitions['res.users'].keys() for fname in ['email', 'name', 'partner_id'])
|
||||
)
|
||||
self.assertTrue(
|
||||
all(fname in model_definitions['res.partner'].keys() for fname in ['active', 'date', 'name'])
|
||||
)
|
||||
|
||||
def test_relational_fields_with_missing_model(self):
|
||||
"""
|
||||
Check that get_model_definitions only returns relational fields
|
||||
if the model is requested
|
||||
"""
|
||||
model_definitions = self.env['ir.model']._get_model_definitions([
|
||||
'res.partner'
|
||||
])
|
||||
# since res.country is not requested, country_id shouldn't be in
|
||||
# the model definition fields
|
||||
self.assertNotIn('country_id', model_definitions['res.partner'])
|
||||
|
||||
model_definitions = self.env['ir.model']._get_model_definitions([
|
||||
'res.partner', 'res.country',
|
||||
])
|
||||
# res.country is requested, country_id should be present on res.partner
|
||||
self.assertIn('country_id', model_definitions['res.partner'])
|
||||
107
odoo-bringout-oca-ocb-mail/mail/tests/test_link_preview.py
Normal file
107
odoo-bringout-oca-ocb-mail/mail/tests/test_link_preview.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from functools import partial
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from unittest.mock import patch
|
||||
import requests
|
||||
|
||||
mail_channel_new_test_user = partial(mail_new_test_user, context={'mail_channel_nosubscribe': False})
|
||||
|
||||
|
||||
def _patched_get_html(*args, **kwargs):
|
||||
response = requests.Response()
|
||||
response.status_code = 200
|
||||
response._content = b"""
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:title" content="Test title">
|
||||
<meta property="og:description" content="Test description">
|
||||
</head>
|
||||
</html>
|
||||
"""
|
||||
response.headers["Content-Type"] = 'text/html'
|
||||
return response
|
||||
|
||||
def _patch_head_html(*args, **kwargs):
|
||||
response = requests.Response()
|
||||
response.status_code = 200
|
||||
response.headers["Content-Type"] = 'text/html'
|
||||
return response
|
||||
|
||||
|
||||
def _patch_no_content_type(*args, **kwargs):
|
||||
response = requests.Response()
|
||||
response.status_code = 200
|
||||
return response
|
||||
|
||||
|
||||
class TestLinkPreview(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.user_1 = mail_channel_new_test_user(
|
||||
cls.env, login='user_1',
|
||||
name='User 1',
|
||||
groups='base.group_user')
|
||||
|
||||
cls.public_channel = cls.env['mail.channel'].create({
|
||||
'name': 'Public channel of user 1',
|
||||
'channel_type': 'channel',
|
||||
})
|
||||
cls.public_channel.channel_member_ids.unlink()
|
||||
|
||||
def test_01_link_preview_throttle(self):
|
||||
with patch.object(requests.Session, 'get', _patched_get_html), patch.object(requests.Session, 'head', _patch_head_html):
|
||||
throttle = int(self.env['ir.config_parameter'].sudo().get_param('mail.link_preview_throttle', 99))
|
||||
link_previews = []
|
||||
for _ in range(throttle):
|
||||
link_previews.append({'source_url': 'https://thisdomainedoentexist.nothing', 'message_id': 1})
|
||||
self.env['mail.link.preview'].create(link_previews)
|
||||
message = self.env['mail.message'].create({
|
||||
'model': 'mail.channel',
|
||||
'res_id': self.public_channel.id,
|
||||
'body': '<a href="https://thisdomainedoentexist.nothing">Nothing link</a>',
|
||||
})
|
||||
self.env['mail.link.preview']._create_link_previews(message)
|
||||
link_preview_count = self.env['mail.link.preview'].search_count([('source_url', '=', 'https://thisdomainedoentexist.nothing')])
|
||||
self.assertEqual(link_preview_count, throttle + 1)
|
||||
|
||||
def test_02_link_preview_create(self):
|
||||
with patch.object(requests.Session, 'get', _patched_get_html), patch.object(requests.Session, 'head', _patch_head_html):
|
||||
message = self.env['mail.message'].create({
|
||||
'model': 'mail.channel',
|
||||
'res_id': self.public_channel.id,
|
||||
'body': '<a href="https://thisdomainedoentexist.nothing">Nothing link</a>',
|
||||
})
|
||||
self.env['mail.link.preview']._create_link_previews(message)
|
||||
self.assertBusNotifications(
|
||||
[(self.cr.dbname, 'mail.channel', self.public_channel.id)],
|
||||
message_items=[{
|
||||
'type': 'mail.link.preview/insert',
|
||||
'payload': [{
|
||||
'id': link_preview.id,
|
||||
'message': {'id': message.id},
|
||||
'image_mimetype': False,
|
||||
'og_description': 'Test description',
|
||||
'og_image': False,
|
||||
'og_mimetype': False,
|
||||
'og_title': 'Test title',
|
||||
'og_type': False,
|
||||
'source_url': 'https://thisdomainedoentexist.nothing',
|
||||
} for link_preview in message.link_preview_ids]
|
||||
}]
|
||||
)
|
||||
|
||||
def test_03_link_preview_create_no_content_type(self):
|
||||
with patch.object(requests.Session, 'request', _patch_no_content_type):
|
||||
message = self.env['mail.message'].create({
|
||||
'model': 'mail.channel',
|
||||
'res_id': self.public_channel.id,
|
||||
'body': '<a href="https://thisdomainedoentexist.nothing">Nothing link</a>',
|
||||
})
|
||||
self.env['mail.link.preview']._create_link_previews(message)
|
||||
link_preview_count = self.env['mail.link.preview'].search_count([('source_url', '=', 'https://thisdomainedoentexist.nothing')])
|
||||
self.assertEqual(link_preview_count, 0)
|
||||
578
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_channel.py
Normal file
578
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_channel.py
Normal file
|
|
@ -0,0 +1,578 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import Command, fields
|
||||
from odoo.addons.mail.models.mail_channel import channel_avatar, group_avatar
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.tests import tagged, Form
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import html_escape, mute_logger
|
||||
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
|
||||
@tagged('mail_channel')
|
||||
class TestChannelAccessRights(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestChannelAccessRights, cls).setUpClass()
|
||||
|
||||
cls.user_employee_1 = mail_new_test_user(cls.env, login='user_employee_1', groups='base.group_user', name='Tao Lee')
|
||||
cls.user_public = mail_new_test_user(cls.env, login='user_public', groups='base.group_public', name='Bert Tartignole')
|
||||
cls.user_portal = mail_new_test_user(cls.env, login='user_portal', groups='base.group_portal', name='Chell Gladys')
|
||||
|
||||
# Channel for certain group
|
||||
cls.group_restricted_channel = cls.env['mail.channel'].browse(cls.env['mail.channel'].channel_create(name='Channel for Groups', group_id=cls.env.ref('base.group_user').id)['id'])
|
||||
# Public Channel
|
||||
cls.public_channel = cls.env['mail.channel'].browse(cls.env['mail.channel'].channel_create(name='Public Channel', group_id=None)['id'])
|
||||
# Group
|
||||
cls.private_group = cls.env['mail.channel'].browse(cls.env['mail.channel'].create_group(partners_to=cls.user_employee.partner_id.ids, name="Group")['id'])
|
||||
# Chat
|
||||
cls.chat_user_employee = cls.env['mail.channel'].browse(cls.env['mail.channel'].channel_get(cls.user_employee.partner_id.ids)['id'])
|
||||
cls.chat_user_employee_1 = cls.env['mail.channel'].browse(cls.env['mail.channel'].channel_get(cls.user_employee_1.partner_id.ids)['id'])
|
||||
cls.chat_user_portal = cls.env['mail.channel'].browse(cls.env['mail.channel'].channel_get(cls.user_portal.partner_id.ids)['id'])
|
||||
cls.chat_user_public = cls.env['mail.channel'].browse(cls.env['mail.channel'].channel_get(cls.user_public.partner_id.ids)['id'])
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_rule', 'odoo.addons.base.models.ir_model', 'odoo.models')
|
||||
@users('user_public')
|
||||
def test_access_public(self):
|
||||
# Read public channel -> ok
|
||||
self.env['mail.channel'].browse(self.public_channel.id).read()
|
||||
|
||||
# Read group restricted channel -> ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.group_restricted_channel.id).read()
|
||||
# Read group -> ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.private_group.id).read()
|
||||
|
||||
# Being a member of public channel: -> ok
|
||||
self.public_channel.add_members(self.user_public.partner_id.id)
|
||||
# Being a member of group restricted channel: -> ko, no access rights
|
||||
with self.assertRaises(UserError):
|
||||
self.group_restricted_channel.add_members(self.user_public.partner_id.id)
|
||||
# Being a group member: -> ok
|
||||
self.private_group.add_members(self.user_public.partner_id.id)
|
||||
|
||||
# Read a group when being a member: -> ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.private_group.id).read()
|
||||
# Read a chat when being a member: -> ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.chat_user_public.id).read()
|
||||
|
||||
# Create channel/group/chat: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].create({'name': 'Test', 'channel_type': 'channel'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].create({'name': 'Test', 'channel_type': 'group'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].create({'name': 'Test', 'channel_type': 'chat'})
|
||||
|
||||
# Update channel/group/chat: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.public_channel.id).write({'name': 'modified'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.group_restricted_channel.id).write({'name': 'modified'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.private_group.id).write({'name': 'modified'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.chat_user_public.id).write({'name': 'modified'})
|
||||
|
||||
# Unlink channel/group/chat: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.public_channel.id).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.group_restricted_channel.id).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.private_group.id).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.chat_user_public.id).unlink()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_rule', 'odoo.addons.base.models.ir_model', 'odoo.models')
|
||||
@users('employee')
|
||||
def test_access_employee(self):
|
||||
# Read public channel -> ok
|
||||
self.env['mail.channel'].browse(self.public_channel.id).read()
|
||||
# Read group restricted channel -> ok
|
||||
self.env['mail.channel'].browse(self.group_restricted_channel.id).read()
|
||||
# Read chat when not being a member: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.chat_user_employee_1.id).read()
|
||||
|
||||
# Update chat when not being a member: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.chat_user_employee_1.id).write({'name': 'modified'})
|
||||
|
||||
# Being a channel/group member: -> ok
|
||||
self.public_channel.add_members(self.user_public.partner_id.id)
|
||||
self.group_restricted_channel.add_members(self.env.user.partner_id.id)
|
||||
self.private_group.add_members(self.env.user.partner_id.id)
|
||||
|
||||
# Read a group when being a member: ok
|
||||
self.env['mail.channel'].browse(self.private_group.id).read()
|
||||
# Read a chat when being a member: ok
|
||||
self.env['mail.channel'].browse(self.chat_user_employee.id).read()
|
||||
|
||||
# Update channel/group/chat when being a member: ok
|
||||
self.env['mail.channel'].browse(self.public_channel.id).write({'name': 'modified again'})
|
||||
self.env['mail.channel'].browse(self.group_restricted_channel.id).write({'name': 'modified again'})
|
||||
self.env['mail.channel'].browse(self.private_group.id).write({'name': 'modified again'})
|
||||
self.env['mail.channel'].browse(self.chat_user_employee.id).write({'name': 'modified again'})
|
||||
|
||||
# Create channel/group/chat: ok
|
||||
new_channel = self.env['mail.channel'].create(
|
||||
{'name': 'Test', 'channel_type': 'channel'})
|
||||
new_group = self.env['mail.channel'].create(
|
||||
{'name': 'Test', 'channel_type': 'group'})
|
||||
new_chat = self.env['mail.channel'].create(
|
||||
{'name': 'Test', 'channel_type': 'chat'})
|
||||
|
||||
# Employee should be inside the created chat/group/chat
|
||||
self.assertIn(new_channel.channel_partner_ids, self.partner_employee)
|
||||
self.assertIn(new_group.channel_partner_ids, self.partner_employee)
|
||||
self.assertIn(new_chat.channel_partner_ids, self.partner_employee)
|
||||
|
||||
# Unlink channel/group/chat: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.public_channel.id).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.group_restricted_channel.id).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.private_group.id).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.chat_user_employee.id).unlink()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_rule', 'odoo.addons.base.models.ir_model', 'odoo.models')
|
||||
@users('user_portal')
|
||||
def test_access_portal(self):
|
||||
# Read public channel -> ok
|
||||
self.env['mail.channel'].browse(self.public_channel.id).read()
|
||||
# Read group restricted channel/group -> ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.group_restricted_channel.id).read()
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.private_group.id).read()
|
||||
|
||||
# Being a group member: -> ok
|
||||
self.private_group.add_members(self.user_portal.partner_id.id)
|
||||
|
||||
# Read a group/chat when being a member: ok
|
||||
self.env['mail.channel'].browse(self.private_group.id).read()
|
||||
self.env['mail.channel'].browse(self.chat_user_portal.id).read()
|
||||
|
||||
# Update group/chat when being a member: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.private_group.id).write({'name': 'modified'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.chat_user_portal.id).write({'name': 'modified'})
|
||||
|
||||
# Create group/chat: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].create({'name': 'Test', 'channel_type': 'group'})
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].create({'name': 'Test', 'channel_type': 'chat'})
|
||||
|
||||
# Unlink group/chat: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.private_group.id).unlink()
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel'].browse(self.chat_user_portal.id).unlink()
|
||||
|
||||
# Read message from group/chat: ok
|
||||
group_portal = self.env['mail.channel'].browse(self.private_group.id)
|
||||
for message in group_portal.message_ids:
|
||||
message.read(['subject'])
|
||||
chat_portal = self.env['mail.channel'].browse(self.chat_user_portal.id)
|
||||
for message in chat_portal.message_ids:
|
||||
message.read(['subject'])
|
||||
|
||||
# Read partner list from group: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
group_portal.message_partner_ids
|
||||
for partner in self.private_group.message_partner_ids:
|
||||
if partner.id == self.user_portal.partner_id.id:
|
||||
# Portal user can read their own partner record
|
||||
continue
|
||||
with self.assertRaises(AccessError):
|
||||
partner.with_user(self.user_portal).name
|
||||
|
||||
# Read partner list from chat: ko, no access rights
|
||||
with self.assertRaises(AccessError):
|
||||
chat_portal.message_partner_ids
|
||||
for partner in self.chat_user_portal.message_partner_ids:
|
||||
if partner.id == self.user_portal.partner_id.id:
|
||||
# Portal user can read their own partner record
|
||||
continue
|
||||
with self.assertRaises(AccessError):
|
||||
partner.with_user(self.user_portal).name
|
||||
|
||||
|
||||
@tagged('mail_channel')
|
||||
class TestChannelInternals(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestChannelInternals, cls).setUpClass()
|
||||
cls.test_channel = cls.env['mail.channel'].browse(cls.env['mail.channel'].with_context(cls._test_context).channel_create(name='Channel', group_id=None)['id'])
|
||||
cls.test_partner = cls.env['res.partner'].with_context(cls._test_context).create({
|
||||
'name': 'Test Partner',
|
||||
'email': 'test_customer@example.com',
|
||||
})
|
||||
cls.user_employee_nomail = mail_new_test_user(
|
||||
cls.env, login='employee_nomail',
|
||||
email=False,
|
||||
groups='base.group_user',
|
||||
company_id=cls.company_admin.id,
|
||||
name='Evita Employee NoEmail',
|
||||
notification_type='email',
|
||||
signature='--\nEvite'
|
||||
)
|
||||
cls.partner_employee_nomail = cls.user_employee_nomail.partner_id
|
||||
|
||||
@users('employee')
|
||||
def test_channel_members(self):
|
||||
channel = self.env['mail.channel'].browse(self.test_channel.ids)
|
||||
self.assertEqual(channel.message_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(channel.channel_partner_ids, self.env['res.partner'])
|
||||
|
||||
channel.add_members(self.test_partner.ids)
|
||||
self.assertEqual(channel.message_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(channel.channel_partner_ids, self.test_partner)
|
||||
|
||||
self.env['mail.channel.member'].sudo().search([
|
||||
('partner_id', 'in', self.test_partner.ids),
|
||||
('channel_id', 'in', channel.ids)
|
||||
]).unlink()
|
||||
self.assertEqual(channel.message_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(channel.channel_partner_ids, self.env['res.partner'])
|
||||
|
||||
channel.message_post(body='Test', message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
self.assertEqual(channel.message_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(channel.channel_partner_ids, self.env['res.partner'])
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
def test_channel_chat_message_post_should_update_last_interest_dt(self):
|
||||
channel_info = self.env['mail.channel'].with_user(self.user_admin).channel_get((self.partner_employee | self.user_admin.partner_id).ids)
|
||||
chat = self.env['mail.channel'].with_user(self.user_admin).browse(channel_info['id'])
|
||||
post_time = fields.Datetime.now()
|
||||
# Mocks the return value of field.Datetime.now(),
|
||||
# so we can see if the `last_interest_dt` is updated correctly
|
||||
with patch.object(fields.Datetime, 'now', lambda: post_time):
|
||||
chat.message_post(body="Test", message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
channel_member_employee = self.env['mail.channel.member'].search([
|
||||
('partner_id', '=', self.partner_employee.id),
|
||||
('channel_id', '=', chat.id),
|
||||
])
|
||||
channel_member_admin = self.env['mail.channel.member'].search([
|
||||
('partner_id', '=', self.partner_admin.id),
|
||||
('channel_id', '=', chat.id),
|
||||
])
|
||||
self.assertEqual(channel_member_employee.last_interest_dt, post_time)
|
||||
self.assertEqual(channel_member_admin.last_interest_dt, post_time)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
def test_channel_recipients_channel(self):
|
||||
""" Posting a message on a channel should not send emails """
|
||||
channel = self.env['mail.channel'].browse(self.test_channel.ids)
|
||||
channel.add_members((self.partner_employee | self.partner_admin | self.test_partner).ids)
|
||||
with self.mock_mail_gateway():
|
||||
new_msg = channel.message_post(body="Test", message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
self.assertNotSentEmail()
|
||||
self.assertEqual(new_msg.model, self.test_channel._name)
|
||||
self.assertEqual(new_msg.res_id, self.test_channel.id)
|
||||
self.assertEqual(new_msg.partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(new_msg.notified_partner_ids, self.env['res.partner'])
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
def test_channel_recipients_chat(self):
|
||||
""" Posting a message on a chat should not send emails """
|
||||
channel_info = self.env['mail.channel'].with_user(self.user_admin).channel_get((self.partner_employee | self.user_admin.partner_id).ids)
|
||||
chat = self.env['mail.channel'].with_user(self.user_admin).browse(channel_info['id'])
|
||||
with self.mock_mail_gateway():
|
||||
with self.with_user('employee'):
|
||||
new_msg = chat.message_post(body="Test", message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
self.assertNotSentEmail()
|
||||
self.assertEqual(new_msg.model, chat._name)
|
||||
self.assertEqual(new_msg.res_id, chat.id)
|
||||
self.assertEqual(new_msg.partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(new_msg.notified_partner_ids, self.env['res.partner'])
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
def test_channel_recipients_mention(self):
|
||||
""" Posting a message on a classic channel should support mentioning somebody """
|
||||
with self.mock_mail_gateway():
|
||||
self.test_channel.message_post(
|
||||
body="Test", partner_ids=self.test_partner.ids,
|
||||
message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
self.assertSentEmail(self.test_channel.env.user.partner_id, [self.test_partner])
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_channel_user_synchronize(self):
|
||||
"""Archiving / deleting a user should automatically unsubscribe related partner from group restricted channels"""
|
||||
group_restricted_channel = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='Sic Mundus', group_id=self.env.ref('base.group_user').id)['id'])
|
||||
|
||||
self.test_channel.add_members((self.partner_employee | self.partner_employee_nomail).ids)
|
||||
group_restricted_channel.add_members((self.partner_employee | self.partner_employee_nomail).ids)
|
||||
|
||||
# Unsubscribe archived user from the private channels, but not from public channels
|
||||
self.user_employee.active = False
|
||||
self.assertEqual(group_restricted_channel.channel_partner_ids, self.partner_employee_nomail)
|
||||
self.assertEqual(self.test_channel.channel_partner_ids, self.user_employee.partner_id | self.partner_employee_nomail)
|
||||
|
||||
# Unsubscribe deleted user from the private channels, but not from public channels
|
||||
self.user_employee_nomail.unlink()
|
||||
self.assertEqual(group_restricted_channel.channel_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(self.test_channel.channel_partner_ids, self.user_employee.partner_id | self.partner_employee_nomail)
|
||||
|
||||
@users('employee_nomail')
|
||||
def test_channel_info_get(self):
|
||||
# `channel_get` should return a new channel the first time a partner is given
|
||||
initial_channel_info = self.env['mail.channel'].channel_get(partners_to=self.test_partner.ids)
|
||||
# shape of channelMembers is [('insert', data...)], [0][1] accesses the data
|
||||
self.assertEqual(set(m['persona']['partner']['id'] for m in initial_channel_info['channel']['channelMembers'][0][1]), {self.partner_employee_nomail.id, self.test_partner.id})
|
||||
|
||||
# `channel_get` should return the existing channel every time the same partner is given
|
||||
same_channel_info = self.env['mail.channel'].channel_get(partners_to=self.test_partner.ids)
|
||||
self.assertEqual(same_channel_info['id'], initial_channel_info['id'])
|
||||
|
||||
# `channel_get` should return the existing channel when the current partner is given together with the other partner
|
||||
together_channel_info = self.env['mail.channel'].channel_get(partners_to=(self.partner_employee_nomail + self.test_partner).ids)
|
||||
self.assertEqual(together_channel_info['id'], initial_channel_info['id'])
|
||||
|
||||
# `channel_get` should return a new channel the first time just the current partner is given,
|
||||
# even if a channel containing the current partner together with other partners already exists
|
||||
solo_channel_info = self.env['mail.channel'].channel_get(partners_to=self.partner_employee_nomail.ids)
|
||||
self.assertNotEqual(solo_channel_info['id'], initial_channel_info['id'])
|
||||
# shape of channelMembers is [('insert', data...)], [0][1] accesses the data
|
||||
self.assertEqual(set(m['persona']['partner']['id'] for m in solo_channel_info['channel']['channelMembers'][0][1]), {self.partner_employee_nomail.id})
|
||||
|
||||
# `channel_get` should return the existing channel every time the current partner is given
|
||||
same_solo_channel_info = self.env['mail.channel'].channel_get(partners_to=self.partner_employee_nomail.ids)
|
||||
self.assertEqual(same_solo_channel_info['id'], solo_channel_info['id'])
|
||||
|
||||
# `channel_get` will pin the channel by default and thus last interest will be updated.
|
||||
@users('employee')
|
||||
def test_channel_info_get_should_update_last_interest_dt(self):
|
||||
# create the channel via `channel_get`
|
||||
self.env['mail.channel'].channel_get(partners_to=self.partner_admin.ids)
|
||||
|
||||
retrieve_time = datetime(2021, 1, 1, 0, 0)
|
||||
with patch.object(fields.Datetime, 'now', lambda: retrieve_time):
|
||||
# `last_interest_dt` should be updated again when `channel_get` is called
|
||||
# because `channel_pin` is called.
|
||||
channel_info = self.env['mail.channel'].channel_get(partners_to=self.partner_admin.ids)
|
||||
self.assertEqual(channel_info['last_interest_dt'], retrieve_time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
|
||||
@users('employee')
|
||||
def test_channel_info_seen(self):
|
||||
""" In case of concurrent channel_seen RPC, ensure the oldest call has no effect. """
|
||||
channel_info = self.env['mail.channel'].with_user(self.user_admin).channel_get((self.partner_employee | self.user_admin.partner_id).ids)
|
||||
chat = self.env['mail.channel'].with_user(self.user_admin).browse(channel_info['id'])
|
||||
msg_1 = self._add_messages(chat, 'Body1', author=self.user_employee.partner_id)
|
||||
msg_2 = self._add_messages(chat, 'Body2', author=self.user_employee.partner_id)
|
||||
|
||||
chat._channel_seen(msg_2.id)
|
||||
self.assertEqual(
|
||||
chat.channel_info()[0]['seen_partners_info'][0]['seen_message_id'],
|
||||
msg_2.id,
|
||||
"Last message id should have been updated"
|
||||
)
|
||||
|
||||
chat._channel_seen(msg_1.id)
|
||||
self.assertEqual(
|
||||
chat.channel_info()[0]['seen_partners_info'][0]['seen_message_id'],
|
||||
msg_2.id,
|
||||
"Last message id should stay the same after mark channel as seen with an older message"
|
||||
)
|
||||
|
||||
def test_channel_message_post_should_not_allow_adding_wrong_parent(self):
|
||||
channels = self.env['mail.channel'].create([{'name': '1'}, {'name': '2'}])
|
||||
message = self._add_messages(channels[0], 'Body1')
|
||||
message_format2 = channels[1].message_post(body='Body2', parent_id=message.id)
|
||||
self.assertFalse(message_format2['parent_id'], "should not allow parent from wrong thread")
|
||||
message_format3 = channels[1].message_post(body='Body3', parent_id=message.id + 100)
|
||||
self.assertFalse(message_format3['parent_id'], "should not allow non-existing parent")
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_channel_unsubscribe_auto(self):
|
||||
""" Archiving / deleting a user should automatically unsubscribe related
|
||||
partner from private channels """
|
||||
test_user = self.env['res.users'].create({
|
||||
"login": "adam",
|
||||
"name": "Jonas",
|
||||
})
|
||||
test_partner = test_user.partner_id
|
||||
group_restricted_channel = self.env['mail.channel'].with_context(self._test_context).create({
|
||||
'name': 'Sic Mundus',
|
||||
'group_public_id': self.env.ref('base.group_user').id,
|
||||
'channel_partner_ids': [Command.link(self.user_employee.partner_id.id), Command.link(test_partner.id)],
|
||||
})
|
||||
self.test_channel.with_context(self._test_context).write({
|
||||
'channel_partner_ids': [Command.link(self.user_employee.partner_id.id), Command.link(test_partner.id)],
|
||||
})
|
||||
private_group = self.env['mail.channel'].with_user(self.user_employee).with_context(self._test_context).create({
|
||||
'name': 'test',
|
||||
'channel_type': 'group',
|
||||
'channel_partner_ids': [Command.link(self.user_employee.partner_id.id), Command.link(test_partner.id)],
|
||||
})
|
||||
|
||||
# Unsubscribe archived user from the private channels, but not from public channels and not from group
|
||||
self.user_employee.active = False
|
||||
(private_group | self.test_channel).invalidate_recordset(['channel_partner_ids'])
|
||||
self.assertEqual(group_restricted_channel.channel_partner_ids, test_partner)
|
||||
self.assertEqual(self.test_channel.channel_partner_ids, self.user_employee.partner_id | test_partner)
|
||||
self.assertEqual(private_group.channel_partner_ids, self.user_employee.partner_id | test_partner)
|
||||
|
||||
# Unsubscribe deleted user from the private channels, but not from public channels and not from group
|
||||
test_user.unlink()
|
||||
self.assertEqual(group_restricted_channel.channel_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(self.test_channel.channel_partner_ids, self.user_employee.partner_id | test_partner)
|
||||
self.assertEqual(private_group.channel_partner_ids, self.user_employee.partner_id | test_partner)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_channel_private_unfollow(self):
|
||||
""" Test that a partner can leave (unfollow) a channel/group/chat. """
|
||||
group_restricted_channel = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='Channel for Groups', group_id=self.env.ref('base.group_user').id)['id'])
|
||||
public_channel = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='Channel for Everyone', group_id=None)['id'])
|
||||
private_group = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids, name="Group")['id'])
|
||||
chat_user_current = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get(self.env.user.partner_id.ids)['id'])
|
||||
|
||||
group_restricted_channel.add_members(self.env.user.partner_id.id)
|
||||
public_channel.add_members(self.env.user.partner_id.id)
|
||||
|
||||
group_restricted_channel.action_unfollow()
|
||||
public_channel.action_unfollow()
|
||||
private_group.action_unfollow()
|
||||
chat_user_current.action_unfollow()
|
||||
|
||||
self.assertEqual(group_restricted_channel.channel_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(public_channel.channel_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(private_group.channel_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(chat_user_current.channel_partner_ids, self.env['res.partner'])
|
||||
|
||||
def test_channel_unfollow_should_not_post_message_if_the_partner_has_been_removed(self):
|
||||
'''
|
||||
When a partner leaves a channel, the system will help post a message under
|
||||
that partner's name in the channel to notify others if `email_sent` is set `False`.
|
||||
The message should only be posted when the partner is still a member of the channel
|
||||
before method `_action_unfollow()` is called.
|
||||
If the partner has been removed earlier, no more messages will be posted
|
||||
even if `_action_unfollow()` is called again.
|
||||
'''
|
||||
channel = self.env['mail.channel'].browse(self.test_channel.id)
|
||||
channel.add_members(self.test_partner.ids)
|
||||
|
||||
# no message should be posted under test_partner's name
|
||||
messages_0 = self.env['mail.message'].search([
|
||||
('model', '=', 'mail.channel'),
|
||||
('res_id', '=', channel.id),
|
||||
('author_id', '=', self.test_partner.id)
|
||||
])
|
||||
self.assertEqual(len(messages_0), 0)
|
||||
|
||||
# a message should be posted to notify others when a partner is about to leave
|
||||
channel._action_unfollow(self.test_partner)
|
||||
messages_1 = self.env['mail.message'].search([
|
||||
('model', '=', 'mail.channel'),
|
||||
('res_id', '=', channel.id),
|
||||
('author_id', '=', self.test_partner.id)
|
||||
])
|
||||
self.assertEqual(len(messages_1), 1)
|
||||
|
||||
# no more messages should be posted if the partner has been removed before.
|
||||
channel._action_unfollow(self.test_partner)
|
||||
messages_2 = self.env['mail.message'].search([
|
||||
('model', '=', 'mail.channel'),
|
||||
('res_id', '=', channel.id),
|
||||
('author_id', '=', self.test_partner.id)
|
||||
])
|
||||
self.assertEqual(len(messages_2), 1)
|
||||
self.assertEqual(messages_1, messages_2)
|
||||
|
||||
def test_channel_should_generate_correct_default_avatar(self):
|
||||
test_channel = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='Channel', group_id=self.env.ref('base.group_user').id)['id'])
|
||||
test_channel.uuid = 'channel-uuid'
|
||||
private_group = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
|
||||
private_group.uuid = 'group-uuid'
|
||||
bgcolor_channel = html_escape('hsl(316, 61%, 45%)') # depends on uuid
|
||||
bgcolor_group = html_escape('hsl(17, 60%, 45%)') # depends on uuid
|
||||
expceted_avatar_channel = (channel_avatar.replace('fill="#875a7b"', f'fill="{bgcolor_channel}"')).encode()
|
||||
expected_avatar_group = (group_avatar.replace('fill="#875a7b"', f'fill="{bgcolor_group}"')).encode()
|
||||
|
||||
self.assertEqual(base64.b64decode(test_channel.avatar_128), expceted_avatar_channel)
|
||||
self.assertEqual(base64.b64decode(private_group.avatar_128), expected_avatar_group)
|
||||
|
||||
test_channel.image_128 = base64.b64encode(("<svg/>").encode())
|
||||
self.assertEqual(test_channel.avatar_128, test_channel.image_128)
|
||||
|
||||
def test_channel_write_should_send_notification_if_image_128_changed(self):
|
||||
channel = self.env['mail.channel'].create({'name': '', 'uuid': 'test-uuid'})
|
||||
# do the operation once before the assert to grab the value to expect
|
||||
channel.image_128 = base64.b64encode(("<svg/>").encode())
|
||||
avatar_cache_key = channel._get_avatar_cache_key()
|
||||
channel.image_128 = False
|
||||
self.env['bus.bus'].search([]).unlink()
|
||||
with self.assertBus(
|
||||
[(self.cr.dbname, 'mail.channel', channel.id)],
|
||||
[{
|
||||
"type": "mail.channel/insert",
|
||||
"payload": {
|
||||
"avatarCacheKey": avatar_cache_key,
|
||||
"id": channel.id,
|
||||
},
|
||||
}]
|
||||
):
|
||||
channel.image_128 = base64.b64encode(("<svg/>").encode())
|
||||
|
||||
def test_mail_message_starred_group(self):
|
||||
""" Test starred message computation for a group. A starred
|
||||
message in a group should be considered only if:
|
||||
- It's our message
|
||||
- OR we have access to the channel
|
||||
"""
|
||||
self.assertEqual(self.user_employee._init_messaging()['starred_counter'], 0)
|
||||
test_group = self.env['mail.channel'].create({
|
||||
'name': 'Private Channel',
|
||||
'channel_type': 'group',
|
||||
'channel_partner_ids': [(6, 0, self.partner_employee.id)]
|
||||
})
|
||||
|
||||
test_group_own_message = test_group.with_user(self.user_employee.id).message_post(body='TestingMessage')
|
||||
test_group_own_message.write({'starred_partner_ids': [(6, 0, self.partner_employee.ids)]})
|
||||
self.assertEqual(self.user_employee.with_user(self.user_employee)._init_messaging()['starred_counter'], 1)
|
||||
|
||||
test_group_message = test_group.message_post(body='TestingMessage')
|
||||
test_group_message.write({'starred_partner_ids': [(6, 0, self.partner_employee.ids)]})
|
||||
self.assertEqual(self.user_employee.with_user(self.user_employee)._init_messaging()['starred_counter'], 2)
|
||||
|
||||
test_group.write({'channel_partner_ids': False})
|
||||
self.assertEqual(self.user_employee.with_user(self.user_employee)._init_messaging()['starred_counter'], 1)
|
||||
|
||||
def test_multi_company_chat(self):
|
||||
self._activate_multi_company()
|
||||
self.assertEqual(self.env.user.company_id, self.company_admin)
|
||||
|
||||
with self.with_user('employee'):
|
||||
initial_channel_info = self.env['mail.channel'].with_context(
|
||||
allowed_company_ids=self.company_admin.ids
|
||||
).channel_get(self.partner_employee_c2.ids)
|
||||
self.assertTrue(initial_channel_info, 'should be able to chat with multi company user')
|
||||
|
||||
@users('employee')
|
||||
def test_create_chat_channel_should_only_pin_the_channel_for_the_current_user(self):
|
||||
chat = self.env['mail.channel'].channel_get(partners_to=self.test_partner.ids)
|
||||
member_of_current_user = self.env['mail.channel.member'].search([('channel_id', '=', chat['id']), ('partner_id', '=', self.env.user.partner_id.id)])
|
||||
member_of_correspondent = self.env['mail.channel.member'].search([('channel_id', '=', chat['id']), ('partner_id', '=', self.test_partner.id)])
|
||||
self.assertTrue(member_of_current_user.is_pinned)
|
||||
self.assertFalse(member_of_correspondent.is_pinned)
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserPortal, HttpCaseWithUserDemo
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
|
||||
|
||||
@odoo.tests.tagged('-at_install', 'post_install', 'is_tour')
|
||||
class TestMailPublicPage(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
||||
"""Checks that the invite page redirects to the channel and that all
|
||||
modules load correctly on the welcome and channel page when authenticated as various users"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
portal_user = mail_new_test_user(
|
||||
self.env,
|
||||
name='Portal Bowser',
|
||||
login='portal_bowser',
|
||||
email='portal_bowser@example.com',
|
||||
groups='base.group_portal',
|
||||
)
|
||||
internal_user = mail_new_test_user(
|
||||
self.env,
|
||||
name='Internal Luigi',
|
||||
login='internal_luigi',
|
||||
email='internal_luigi@example.com',
|
||||
groups='base.group_user',
|
||||
)
|
||||
guest = self.env['mail.guest'].create({'name': 'Guest Mario'})
|
||||
|
||||
self.channel = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(group_id=None, name='Test channel')['id'])
|
||||
self.channel.add_members(portal_user.partner_id.ids)
|
||||
self.channel.add_members(internal_user.partner_id.ids)
|
||||
self.channel.add_members(guest_ids=[guest.id])
|
||||
|
||||
self.group = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=(internal_user + portal_user).partner_id.ids, name="Test group")['id'])
|
||||
self.group.add_members(guest_ids=[guest.id])
|
||||
|
||||
self.tour = "mail/static/tests/tours/discuss_public_tour.js"
|
||||
|
||||
def _open_channel_page_as_user(self, login):
|
||||
self.start_tour(self.channel.invitation_url, self.tour, login=login)
|
||||
# Second run of the tour as the first call has side effects, like creating user settings or adding members to
|
||||
# the channel, so we need to run it again to test different parts of the code.
|
||||
self.start_tour(self.channel.invitation_url, self.tour, login=login)
|
||||
|
||||
def _open_group_page_as_user(self, login):
|
||||
self.start_tour(self.group.invitation_url, self.tour, login=login)
|
||||
# Second run of the tour as the first call has side effects, like creating user settings or adding members to
|
||||
# the channel, so we need to run it again to test different parts of the code.
|
||||
self.start_tour(self.group.invitation_url, self.tour, login=login)
|
||||
|
||||
def test_mail_channel_public_page_as_admin(self):
|
||||
self._open_channel_page_as_user('admin')
|
||||
|
||||
def test_mail_group_public_page_as_admin(self):
|
||||
self._open_group_page_as_user('admin')
|
||||
|
||||
def test_mail_channel_public_page_as_guest(self):
|
||||
self.start_tour(self.channel.invitation_url, "mail/static/tests/tours/mail_channel_as_guest_tour.js")
|
||||
guest = self.env['mail.guest'].search([('channel_ids', 'in', self.channel.id)], limit=1, order='id desc')
|
||||
self.start_tour(self.channel.invitation_url, self.tour, cookies={guest._cookie_name: f"{guest.id}{guest._cookie_separator}{guest.access_token}"})
|
||||
|
||||
def test_mail_group_public_page_as_guest(self):
|
||||
self.start_tour(self.group.invitation_url, "mail/static/tests/tours/mail_channel_as_guest_tour.js")
|
||||
guest = self.env['mail.guest'].search([('channel_ids', 'in', self.channel.id)], limit=1, order='id desc')
|
||||
self.start_tour(self.group.invitation_url, self.tour, cookies={guest._cookie_name: f"{guest.id}{guest._cookie_separator}{guest.access_token}"})
|
||||
|
||||
def test_mail_channel_public_page_as_internal(self):
|
||||
self._open_channel_page_as_user('demo')
|
||||
|
||||
def test_mail_group_public_page_as_internal(self):
|
||||
self._open_group_page_as_user('demo')
|
||||
|
||||
def test_mail_channel_public_page_as_portal(self):
|
||||
self._open_channel_page_as_user('portal')
|
||||
|
||||
def test_mail_group_public_page_as_portal(self):
|
||||
self._open_group_page_as_user('portal')
|
||||
|
||||
def test_chat_from_token_as_guest(self):
|
||||
self.env['ir.config_parameter'].set_param('mail.chat_from_token', True)
|
||||
self.url_open('/chat/xyz')
|
||||
channel = self.env['mail.channel'].search([('uuid', '=', 'xyz')])
|
||||
self.assertEqual(len(channel), 1)
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from functools import partial
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
|
||||
mail_channel_new_test_user = partial(mail_new_test_user, context={'mail_channel_nosubscribe': False})
|
||||
|
||||
|
||||
class TestMailChannelMembers(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailChannelMembers, cls).setUpClass()
|
||||
|
||||
cls.secret_group = cls.env['res.groups'].create({
|
||||
'name': 'Secret User Group',
|
||||
})
|
||||
cls.env['ir.model.data'].create({
|
||||
'name': 'secret_group',
|
||||
'module': 'mail',
|
||||
'model': cls.secret_group._name,
|
||||
'res_id': cls.secret_group.id,
|
||||
})
|
||||
|
||||
cls.user_1 = mail_channel_new_test_user(
|
||||
cls.env, login='user_1',
|
||||
name='User 1',
|
||||
groups='base.group_user,mail.secret_group')
|
||||
cls.user_2 = mail_channel_new_test_user(
|
||||
cls.env, login='user_2',
|
||||
name='User 2',
|
||||
groups='base.group_user,mail.secret_group')
|
||||
cls.user_3 = mail_channel_new_test_user(
|
||||
cls.env, login='user_3',
|
||||
name='User 3',
|
||||
groups='base.group_user,mail.secret_group')
|
||||
cls.user_portal = mail_channel_new_test_user(
|
||||
cls.env, login='user_portal',
|
||||
name='User Portal',
|
||||
groups='base.group_portal')
|
||||
cls.user_public = mail_channel_new_test_user(
|
||||
cls.env, login='user_ublic',
|
||||
name='User Public',
|
||||
groups='base.group_public')
|
||||
|
||||
cls.group = cls.env['mail.channel'].create({
|
||||
'name': 'Group',
|
||||
'channel_type': 'group',
|
||||
})
|
||||
cls.group_restricted_channel = cls.env['mail.channel'].create({
|
||||
'name': 'Group restricted channel',
|
||||
'channel_type': 'channel',
|
||||
'group_public_id': cls.secret_group.id,
|
||||
})
|
||||
cls.public_channel = cls.env['mail.channel'].browse(cls.env['mail.channel'].channel_create(group_id=None, name='Public channel of user 1')['id'])
|
||||
(cls.group | cls.group_restricted_channel | cls.public_channel).channel_member_ids.unlink()
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# GROUP
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def test_group_01(self):
|
||||
"""Test access on group."""
|
||||
res = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id)])
|
||||
self.assertFalse(res)
|
||||
|
||||
# User 1 can join group with SUDO
|
||||
self.group.with_user(self.user_1).sudo().add_members(self.user_1.partner_id.ids)
|
||||
res = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id)])
|
||||
self.assertEqual(res.partner_id, self.user_1.partner_id)
|
||||
|
||||
# User 2 can not join group
|
||||
with self.assertRaises(AccessError):
|
||||
self.group.with_user(self.user_2).add_members(self.user_2.partner_id.ids)
|
||||
|
||||
# User 2 can not create a `mail.channel.member` to join the group
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel.member'].with_user(self.user_2).create({
|
||||
'partner_id': self.user_2.partner_id.id,
|
||||
'channel_id': self.group.id,
|
||||
})
|
||||
|
||||
# User 2 can not write on `mail.channel.member` to join the group
|
||||
channel_member = self.env['mail.channel.member'].with_user(self.user_2).search([('partner_id', '=', self.user_2.partner_id.id)])[0]
|
||||
with self.assertRaises(AccessError):
|
||||
channel_member.channel_id = self.group.id
|
||||
with self.assertRaises(AccessError):
|
||||
channel_member.write({'channel_id': self.group.id})
|
||||
|
||||
# Even with SUDO, channel_id of channel.member should not be changed.
|
||||
with self.assertRaises(AccessError):
|
||||
channel_member.sudo().channel_id = self.group.id
|
||||
|
||||
# User 2 can not write on the `partner_id` of `mail.channel.member`
|
||||
# of an other partner to join a group
|
||||
channel_member_1 = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id), ('partner_id', '=', self.user_1.partner_id.id)])
|
||||
with self.assertRaises(AccessError):
|
||||
channel_member_1.with_user(self.user_2).partner_id = self.user_2.partner_id
|
||||
self.assertEqual(channel_member_1.partner_id, self.user_1.partner_id)
|
||||
|
||||
# Even with SUDO, partner_id of channel.member should not be changed.
|
||||
with self.assertRaises(AccessError):
|
||||
channel_member_1.with_user(self.user_2).sudo().partner_id = self.user_2.partner_id
|
||||
|
||||
def test_group_members(self):
|
||||
"""Test invitation in group part 1 (invite using crud methods)."""
|
||||
self.group.with_user(self.user_1).sudo().add_members(self.user_1.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id)])
|
||||
self.assertEqual(len(channel_members), 1)
|
||||
|
||||
# User 2 is not in the group, they can not invite user 3
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.channel.member'].with_user(self.user_2).create({
|
||||
'partner_id': self.user_portal.partner_id.id,
|
||||
'channel_id': self.group.id,
|
||||
})
|
||||
|
||||
# User 1 is in the group, they can invite other users
|
||||
self.env['mail.channel.member'].with_user(self.user_1).create({
|
||||
'partner_id': self.user_portal.partner_id.id,
|
||||
'channel_id': self.group.id,
|
||||
})
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id | self.user_portal.partner_id)
|
||||
|
||||
# But User 3 can not write on the `mail.channel.member` of other user
|
||||
channel_member_1 = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id), ('partner_id', '=', self.user_1.partner_id.id)])
|
||||
channel_member_3 = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id), ('partner_id', '=', self.user_portal.partner_id.id)])
|
||||
channel_member_3.with_user(self.user_portal).custom_channel_name = 'Test'
|
||||
with self.assertRaises(AccessError):
|
||||
channel_member_1.with_user(self.user_2).custom_channel_name = 'Blabla'
|
||||
self.assertNotEqual(channel_member_1.custom_channel_name, 'Blabla')
|
||||
|
||||
def test_group_invite(self):
|
||||
"""Test invitation in group part 2 (use `invite` action)."""
|
||||
self.group.with_user(self.user_1).sudo().add_members(self.user_1.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id)
|
||||
|
||||
# User 2 is not in the group, they can not invite user_portal
|
||||
with self.assertRaises(AccessError):
|
||||
self.group.with_user(self.user_2).add_members(self.user_portal.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id)
|
||||
|
||||
# User 1 is in the group, they can invite user_portal
|
||||
self.group.with_user(self.user_1).add_members(self.user_portal.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id | self.user_portal.partner_id)
|
||||
|
||||
def test_group_leave(self):
|
||||
"""Test kick/leave channel."""
|
||||
self.group.with_user(self.user_1).sudo().add_members(self.user_1.partner_id.ids)
|
||||
self.group.with_user(self.user_portal).sudo().add_members(self.user_portal.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group.id)])
|
||||
self.assertEqual(len(channel_members), 2)
|
||||
|
||||
# User 2 is not in the group, they can not kick user 1
|
||||
with self.assertRaises(AccessError):
|
||||
channel_members.with_user(self.user_2).unlink()
|
||||
|
||||
# User 3 is in the group, they can kick user 1
|
||||
channel_members.with_user(self.user_portal).unlink()
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# GROUP BASED CHANNELS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def test_group_restricted_channel(self):
|
||||
"""Test basics on group channel."""
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group_restricted_channel.id)])
|
||||
self.assertFalse(channel_members)
|
||||
|
||||
# user 1 is in the channel, they can join the channel
|
||||
self.group_restricted_channel.with_user(self.user_1).add_members(self.user_1.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group_restricted_channel.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id)
|
||||
|
||||
# user 3 is not in the channel, they can not join
|
||||
with self.assertRaises(AccessError):
|
||||
self.group_restricted_channel.with_user(self.user_portal).add_members(self.user_portal.partner_id.ids)
|
||||
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group_restricted_channel.id)])
|
||||
with self.assertRaises(AccessError):
|
||||
channel_members.with_user(self.user_portal).partner_id = self.user_portal.partner_id
|
||||
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group_restricted_channel.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id)
|
||||
|
||||
# user 1 can not invite user 3 because they are not in the channel
|
||||
with self.assertRaises(UserError):
|
||||
self.group_restricted_channel.with_user(self.user_1).add_members(self.user_portal.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group_restricted_channel.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id)
|
||||
|
||||
# but user 2 is in the channel and can be invited by user 1
|
||||
self.group_restricted_channel.with_user(self.user_1).add_members(self.user_2.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.group_restricted_channel.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id | self.user_2.partner_id)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PUBLIC CHANNELS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def test_public_channel(self):
|
||||
""" Test access on public channels """
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.public_channel.id)])
|
||||
self.assertFalse(channel_members)
|
||||
|
||||
self.public_channel.with_user(self.user_1).add_members(self.user_1.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.public_channel.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id)
|
||||
|
||||
self.public_channel.with_user(self.user_2).add_members(self.user_2.partner_id.ids)
|
||||
channel_members = self.env['mail.channel.member'].search([('channel_id', '=', self.public_channel.id)])
|
||||
self.assertEqual(channel_members.mapped('partner_id'), self.user_1.partner_id | self.user_2.partner_id)
|
||||
|
||||
# portal/public users still cannot join a public channel, should go through dedicated controllers
|
||||
with self.assertRaises(AccessError):
|
||||
self.public_channel.with_user(self.user_portal).add_members(self.user_portal.partner_id.ids)
|
||||
with self.assertRaises(AccessError):
|
||||
self.public_channel.with_user(self.user_public).add_members(self.user_public.partner_id.ids)
|
||||
|
||||
def test_channel_member_invite_with_guest(self):
|
||||
guest = self.env['mail.guest'].create({'name': 'Guest'})
|
||||
partner = self.env['res.partner'].create({
|
||||
'name': 'ToInvite',
|
||||
'active': True,
|
||||
'type': 'contact',
|
||||
'user_ids': self.user_1,
|
||||
})
|
||||
self.public_channel.add_members(guest_ids=[guest.id])
|
||||
search = self.env['res.partner'].search_for_channel_invite(partner.name, channel_id=self.public_channel.id)
|
||||
self.assertEqual(len(search['partners']), 1)
|
||||
self.assertEqual(search['partners'][0]['id'], partner.id)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# UNREAD COUNTER TESTS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def test_unread_counter_with_message_post(self):
|
||||
channel_as_user_1 = self.env['mail.channel'].browse(self.env['mail.channel'].with_user(self.user_1).channel_create(group_id=None, name='Public channel')['id'])
|
||||
channel_as_user_1.with_user(self.user_1).add_members(self.user_1.partner_id.ids)
|
||||
channel_as_user_1.with_user(self.user_1).add_members(self.user_2.partner_id.ids)
|
||||
channel_1_rel_user_2 = self.env['mail.channel.member'].search([
|
||||
('channel_id', '=', channel_as_user_1.id),
|
||||
('partner_id', '=', self.user_2.partner_id.id)
|
||||
])
|
||||
self.assertEqual(channel_1_rel_user_2.message_unread_counter, 0, "should not have unread message initially as notification type is ignored")
|
||||
|
||||
channel_as_user_1.message_post(body='Test', message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
channel_1_rel_user_2 = self.env['mail.channel.member'].search([
|
||||
('channel_id', '=', channel_as_user_1.id),
|
||||
('partner_id', '=', self.user_2.partner_id.id)
|
||||
])
|
||||
self.assertEqual(channel_1_rel_user_2.message_unread_counter, 1, "should have 1 unread message after someone else posted a message")
|
||||
|
||||
def test_unread_counter_with_message_post_multi_channel(self):
|
||||
channel_1_as_user_1 = self.env['mail.channel'].with_user(self.user_1).browse(self.env['mail.channel'].with_user(self.user_1).channel_create(group_id=None, name='wololo channel')['id'])
|
||||
channel_2_as_user_2 = self.env['mail.channel'].with_user(self.user_2).browse(self.env['mail.channel'].with_user(self.user_2).channel_create(group_id=None, name='walala channel')['id'])
|
||||
channel_1_as_user_1.add_members(self.user_2.partner_id.ids)
|
||||
channel_2_as_user_2.add_members(self.user_1.partner_id.ids)
|
||||
channel_2_as_user_2.add_members(self.user_3.partner_id.ids)
|
||||
channel_1_as_user_1.message_post(body='Test', message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
channel_1_as_user_1.message_post(body='Test 2', message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
channel_2_as_user_2.message_post(body='Test', message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
members = self.env['mail.channel.member'].search([('channel_id', 'in', (channel_1_as_user_1 + channel_2_as_user_2).ids)], order="id")
|
||||
self.assertEqual(members.mapped('message_unread_counter'), [
|
||||
0, # channel 1 user 1: posted last message
|
||||
0, # channel 2 user 2: posted last message
|
||||
2, # channel 1 user 2: received 2 messages (from message post)
|
||||
1, # channel 2 user 1: received 1 message (from message post)
|
||||
1, # channel 2 user 3: received 1 message (from message post)
|
||||
])
|
||||
263
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_composer.py
Normal file
263
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_composer.py
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests import Form, tagged, users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mail_composer')
|
||||
class TestMailComposer(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailComposer, cls).setUpClass()
|
||||
cls.env['ir.config_parameter'].set_param('mail.restrict.template.rendering', True)
|
||||
cls.user_employee.groups_id -= cls.env.ref('mail.group_mail_template_editor')
|
||||
cls.test_record = cls.env['res.partner'].with_context(cls._test_context).create({
|
||||
'name': 'Test',
|
||||
})
|
||||
cls.body_html = """<div>
|
||||
<h1>Hello sir!</h1>
|
||||
<p>Here! <a href="https://www.example.com">
|
||||
<!--[if mso]>
|
||||
<i style="letter-spacing: 25px; mso-font-width: -100%; mso-text-raise: 30pt;"> </i>
|
||||
<![endif]-->
|
||||
A link for you! <!-- my favorite example -->
|
||||
<!--[if mso]>
|
||||
<i style="letter-spacing: 25px; mso-font-width: -100%;"> </i>
|
||||
<![endif]-->
|
||||
</a> Make good use of it.</p>
|
||||
</div>"""
|
||||
|
||||
cls.mail_template = cls.env['mail.template'].create({
|
||||
'auto_delete': True,
|
||||
'body_html': cls.body_html,
|
||||
'lang': '{{ object.lang }}',
|
||||
'model_id': cls.env['ir.model']._get_id('res.partner'),
|
||||
'subject': 'MSO FTW',
|
||||
'name': 'Test template with mso conditionals',
|
||||
})
|
||||
|
||||
|
||||
@tagged('mail_composer')
|
||||
class TestMailComposerForm(TestMailComposer):
|
||||
""" Test mail composer form view usage. """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailComposerForm, cls).setUpClass()
|
||||
|
||||
cls.user_employee.write({'groups_id': [
|
||||
(4, cls.env.ref('base.group_private_addresses').id),
|
||||
(4, cls.env.ref('base.group_partner_manager').id),
|
||||
]})
|
||||
cls.partner_private, cls.partner_private_2, cls.partner_classic = cls.env['res.partner'].create([
|
||||
{
|
||||
'email': 'private.customer@text.example.com',
|
||||
'phone': '0032455112233',
|
||||
'name': 'Private Customer',
|
||||
'type': 'private',
|
||||
},
|
||||
{
|
||||
'email': 'private.customer.2@test.example.com',
|
||||
'phone': '0032455445566',
|
||||
'name': 'Private Customer 2',
|
||||
'type': 'private',
|
||||
},
|
||||
{
|
||||
'email': 'not.private@test.example.com',
|
||||
'phone': '0032455778899',
|
||||
'name': 'Classic Customer',
|
||||
'type': 'contact',
|
||||
}
|
||||
])
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
@users('employee')
|
||||
def test_composer_default_recipients(self):
|
||||
""" Test usage of a private partner in composer, as default value """
|
||||
partner_classic = self.partner_classic.with_env(self.env)
|
||||
test_record = self.test_record.with_env(self.env)
|
||||
|
||||
form = Form(self.env['mail.compose.message'].with_context({
|
||||
'default_partner_ids': partner_classic.ids,
|
||||
'default_model': test_record._name,
|
||||
'default_res_id': test_record.id,
|
||||
}))
|
||||
form.body = '<p>Hello</p>'
|
||||
self.assertEqual(
|
||||
form.partner_ids._get_ids(), partner_classic.ids,
|
||||
'Default populates the field'
|
||||
)
|
||||
saved_form = form.save()
|
||||
self.assertEqual(
|
||||
saved_form.partner_ids, partner_classic,
|
||||
'Default value is kept at save'
|
||||
)
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
saved_form._action_send_mail()
|
||||
|
||||
message = self.test_record.message_ids[0]
|
||||
self.assertEqual(message.body, '<p>Hello</p>')
|
||||
self.assertEqual(message.partner_ids, partner_classic)
|
||||
self.assertEqual(message.subject, f'Re: {test_record.name}')
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
@users('employee')
|
||||
def test_composer_default_recipients_private(self):
|
||||
""" Test usage of a private partner in composer, as default value """
|
||||
partner_private = self.partner_private.with_env(self.env)
|
||||
partner_classic = self.partner_classic.with_env(self.env)
|
||||
test_record = self.test_record.with_env(self.env)
|
||||
|
||||
form = Form(self.env['mail.compose.message'].with_context({
|
||||
'default_partner_ids': (partner_private + partner_classic).ids,
|
||||
'default_model': test_record._name,
|
||||
'default_res_id': test_record.id,
|
||||
}))
|
||||
form.body = '<p>Hello</p>'
|
||||
self.assertEqual(
|
||||
sorted(form.partner_ids._get_ids()),
|
||||
sorted((partner_private + partner_classic).ids),
|
||||
'Default populates the field'
|
||||
)
|
||||
saved_form = form.save()
|
||||
self.assertEqual(
|
||||
saved_form.partner_ids, partner_private + partner_classic,
|
||||
'Default value is kept at save'
|
||||
)
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
saved_form._action_send_mail()
|
||||
|
||||
message = self.test_record.message_ids[0]
|
||||
self.assertEqual(message.body, '<p>Hello</p>')
|
||||
self.assertEqual(message.partner_ids, partner_private + partner_classic)
|
||||
self.assertEqual(message.subject, f'Re: {test_record.name}')
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_rule', 'odoo.addons.mail.models.mail_mail')
|
||||
@users('employee')
|
||||
def test_composer_default_recipients_private_norights(self):
|
||||
""" Test usage of a private partner in composer when not having the
|
||||
rights to see them, as default value """
|
||||
self.user_employee.write({'groups_id': [
|
||||
(3, self.env.ref('base.group_private_addresses').id),
|
||||
]})
|
||||
with self.assertRaises(AccessError):
|
||||
_name = self.partner_private.with_env(self.env).name
|
||||
|
||||
partner_classic = self.partner_classic.with_env(self.env)
|
||||
test_record = self.test_record.with_env(self.env)
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
_form = Form(self.env['mail.compose.message'].with_context({
|
||||
'default_partner_ids': (self.partner_private + partner_classic).ids,
|
||||
'default_model': test_record._name,
|
||||
'default_res_id': test_record.id,
|
||||
}))
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
@users('employee')
|
||||
def test_composer_template_recipients_private(self):
|
||||
""" Test usage of a private partner in composer, comint from template
|
||||
value """
|
||||
email_to_new = 'new.customer@test.example.com'
|
||||
self.mail_template.write({
|
||||
'email_to': f'{self.partner_private_2.email_formatted}, {email_to_new}',
|
||||
'partner_to': f'{self.partner_private.id},{self.partner_classic.id}',
|
||||
})
|
||||
template = self.mail_template.with_env(self.env)
|
||||
partner_private = self.partner_private.with_env(self.env)
|
||||
partner_private_2 = self.partner_private_2.with_env(self.env)
|
||||
partner_classic = self.partner_classic.with_env(self.env)
|
||||
test_record = self.test_record.with_env(self.env)
|
||||
|
||||
form = Form(self.env['mail.compose.message'].with_context({
|
||||
'default_model': test_record._name,
|
||||
'default_res_id': test_record.id,
|
||||
'default_template_id': template.id,
|
||||
}))
|
||||
|
||||
# transformation from email_to into partner_ids: find or create
|
||||
existing_partner = self.env['res.partner'].search(
|
||||
[('email_normalized', '=', self.partner_private_2.email_normalized)]
|
||||
)
|
||||
self.assertEqual(existing_partner, partner_private_2, 'Should find existing private contact')
|
||||
new_partner = self.env['res.partner'].search(
|
||||
[('email_normalized', '=', email_to_new)]
|
||||
)
|
||||
self.assertEqual(new_partner.type, 'contact', 'Should create a new contact')
|
||||
|
||||
self.assertEqual(
|
||||
sorted(form.partner_ids._get_ids()),
|
||||
sorted((partner_private + partner_classic + partner_private_2 + new_partner).ids),
|
||||
'Template populates the field with both email_to and partner_to'
|
||||
)
|
||||
saved_form = form.save()
|
||||
self.assertEqual(
|
||||
# saved_form.partner_ids, partner_private + partner_classic + partner_private_2 + new_partner,
|
||||
saved_form.partner_ids, partner_classic + new_partner,
|
||||
'Template value is kept at save (FIXME: loosing private partner)'
|
||||
)
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
saved_form._action_send_mail()
|
||||
|
||||
message = self.test_record.message_ids[0]
|
||||
self.assertIn('<h1>Hello sir!</h1>', message.body)
|
||||
# self.assertEqual(message.partner_ids, partner_private + partner_classic + partner_private_2 + new_partner)
|
||||
self.assertEqual(
|
||||
message.partner_ids, partner_classic + new_partner,
|
||||
'FIXME: loosing private partner'
|
||||
)
|
||||
self.assertEqual(message.subject, 'MSO FTW')
|
||||
|
||||
|
||||
@tagged('mail_composer')
|
||||
class TestMailComposerRendering(TestMailComposer):
|
||||
""" Test rendering and support of various html tweaks in composer """
|
||||
|
||||
@users('employee')
|
||||
def test_mail_mass_mode_template_with_mso(self):
|
||||
mail_compose_message = self.env['mail.compose.message'].create({
|
||||
'composition_mode': 'mass_mail',
|
||||
'model': 'res.partner',
|
||||
'template_id': self.mail_template.id,
|
||||
'subject': 'MSO FTW',
|
||||
})
|
||||
|
||||
values = mail_compose_message.get_mail_values(self.partner_employee.ids)
|
||||
|
||||
self.assertIn(
|
||||
self.body_html,
|
||||
values[self.partner_employee.id]['body_html'],
|
||||
'We must preserve (mso) comments in email html'
|
||||
)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
@users('employee')
|
||||
def test_mail_mass_mode_compose_with_mso(self):
|
||||
composer = self.env['mail.compose.message'].with_context({
|
||||
'default_model': self.test_record._name,
|
||||
'default_composition_mode': 'mass_mail',
|
||||
'active_ids': [self.test_record.id],
|
||||
'active_model': self.test_record._name,
|
||||
'active_id': self.test_record.id
|
||||
}).create({
|
||||
'body': self.body_html,
|
||||
'partner_ids': [(4, self.partner_employee.id)],
|
||||
'composition_mode': 'mass_mail',
|
||||
})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
composer._action_send_mail()
|
||||
|
||||
values = composer.get_mail_values(self.partner_employee.ids)
|
||||
|
||||
self.assertIn(
|
||||
self.body_html,
|
||||
values[self.partner_employee.id]['body_html'],
|
||||
'We must preserve (mso) comments in email html'
|
||||
)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests.common import tagged, HttpCase
|
||||
from odoo import Command
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', 'mail_composer')
|
||||
class TestMailFullComposer(MailCommon, HttpCase):
|
||||
|
||||
def test_full_composer_tour(self):
|
||||
self.env['mail.template'].create({
|
||||
'name': 'Test template',
|
||||
'partner_to': '{{ object.id }}',
|
||||
'lang': '{{ object.lang }}',
|
||||
'auto_delete': True,
|
||||
'model_id': self.ref('base.model_res_partner'),
|
||||
})
|
||||
user = self.env['res.users'].create({
|
||||
'email': 'testuser@testuser.com',
|
||||
'groups_id': [Command.set([self.ref('base.group_user'), self.ref('base.group_partner_manager')])],
|
||||
'name': 'Test User',
|
||||
'login': 'testuser',
|
||||
'password': 'testuser',
|
||||
})
|
||||
partner = self.env["res.partner"].create({"name": "Jane", "email": "jane@example.com"})
|
||||
with self.mock_mail_app():
|
||||
self.start_tour(f"/web#id={partner.id}&model=res.partner", 'mail/static/tests/tours/mail_full_composer_test_tour.js', login='testuser')
|
||||
message = self._new_msgs.filtered(lambda message: message.author_id == user.partner_id)
|
||||
self.assertEqual(len(message), 1)
|
||||
35
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_mail.py
Normal file
35
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_mail.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
from odoo.tests import TransactionCase
|
||||
from unittest import mock
|
||||
import smtplib
|
||||
|
||||
|
||||
class MailCase(TransactionCase):
|
||||
|
||||
def test_mail_send_non_connected_smtp_session(self):
|
||||
"""Check to avoid SMTPServerDisconnected error while trying to
|
||||
disconnect smtp session that is not connected.
|
||||
|
||||
This used to happens while trying to connect to a
|
||||
google smtp server with an expired token.
|
||||
|
||||
Or here testing non recipients emails with non connected
|
||||
smtp session, we won't get SMTPServerDisconnected that would
|
||||
hide the other error that is raised earlier.
|
||||
"""
|
||||
disconnected_smtpsession = mock.MagicMock()
|
||||
disconnected_smtpsession.quit.side_effect = smtplib.SMTPServerDisconnected
|
||||
mail = self.env["mail.mail"].create({})
|
||||
with mock.patch("odoo.addons.base.models.ir_mail_server.IrMailServer.connect", return_value=disconnected_smtpsession):
|
||||
with mock.patch("odoo.addons.mail.models.mail_mail._logger.info") as mock_logging_info:
|
||||
mail.send()
|
||||
disconnected_smtpsession.quit.assert_called_once()
|
||||
mock_logging_info.assert_any_call(
|
||||
"Ignoring SMTPServerDisconnected while trying to quit non open session"
|
||||
)
|
||||
# if we get here SMTPServerDisconnected was not raised
|
||||
self.assertEqual(mail.state, "exception")
|
||||
self.assertEqual(
|
||||
mail.failure_reason,
|
||||
"Error without exception. Probably due to sending "
|
||||
"an email without computed recipients."
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
class TestMailMailStableSelection(TransactionCase):
|
||||
"""Only relevant in stable as a hotfix. May be removed in master."""
|
||||
|
||||
def test_mail_mail_stable_selection(self):
|
||||
# remove all selections
|
||||
message_type_selections = self.env['ir.model.fields']._get('mail.message', 'message_type').selection_ids
|
||||
message_type_selections.filtered(lambda s: s.value == 'auto_comment').unlink()
|
||||
self.env['mail.mail']._fields_get_message_type_update_selection(self.env['mail.message']._fields['message_type'].selection)
|
||||
# force convert to cache with specific language so it has to fetch related from DB
|
||||
mail = self.env['mail.mail'].create({'subject': 'test', 'message_type': 'auto_comment'})
|
||||
mail.invalidate_recordset(['message_type'])
|
||||
self.assertEqual(mail.with_context(lang="en_US").message_type, 'auto_comment')
|
||||
505
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_render.py
Normal file
505
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_render.py
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from markupsafe import Markup
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.mail.tests import common
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
|
||||
class TestMailRenderCommon(common.MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailRenderCommon, cls).setUpClass()
|
||||
|
||||
# activate multi language support
|
||||
cls.env['res.lang']._activate_lang('fr_FR')
|
||||
cls.user_admin.write({'lang': 'en_US'})
|
||||
|
||||
# test records
|
||||
cls.render_object = cls.env['res.partner'].create({
|
||||
'name': 'TestRecord',
|
||||
'lang': 'en_US',
|
||||
})
|
||||
cls.render_object_fr = cls.env['res.partner'].create({
|
||||
'name': 'Element de Test',
|
||||
'lang': 'fr_FR',
|
||||
})
|
||||
|
||||
# some jinja templates
|
||||
cls.base_inline_template_bits = [
|
||||
'<p>Hello</p>',
|
||||
'<p>Hello {{ object.name }}</p>',
|
||||
"""<p>
|
||||
{{ '<span>English Speaker</span>' if object.lang == 'en_US' else '<span>Other Speaker</span>' }}
|
||||
</p>""",
|
||||
"""
|
||||
<p>{{ 13 + 13 }}</p>
|
||||
<h1>This is a test</h1>
|
||||
""",
|
||||
"""<b>Test</b>{{ '' if True else '<b>Code not executed</b>' }}""",
|
||||
]
|
||||
cls.base_inline_template_bits_fr = [
|
||||
'<p>Bonjour</p>',
|
||||
'<p>Bonjour {{ object.name }}</p>',
|
||||
"""<p>
|
||||
{{ '<span>Narrateur Anglais</span>' if object.lang == 'en_US' else '<span>Autre Narrateur</span>' }}
|
||||
</p>"""
|
||||
]
|
||||
|
||||
# some qweb templates, their views and their xml ids
|
||||
cls.base_qweb_bits = [
|
||||
'<p>Hello</p>',
|
||||
'<p>Hello <t t-esc="object.name"/></p>',
|
||||
"""<p>
|
||||
<span t-if="object.lang == 'en_US'">English Speaker</span>
|
||||
<span t-else="">Other Speaker</span>
|
||||
</p>"""
|
||||
]
|
||||
cls.base_qweb_bits_fr = [
|
||||
'<p>Bonjour</p>',
|
||||
'<p>Bonjour <t t-esc="object.name"/></p>',
|
||||
"""<p>
|
||||
<span t-if="object.lang == 'en_US'">Narrateur Anglais</span>
|
||||
<span t-else="">Autre Narrateur</span>
|
||||
</p>"""
|
||||
]
|
||||
cls.base_qweb_templates = cls.env['ir.ui.view'].create([
|
||||
{'name': 'TestRender%d' % index,
|
||||
'type': 'qweb',
|
||||
'arch': qweb_content,
|
||||
} for index, qweb_content in enumerate(cls.base_qweb_bits)
|
||||
])
|
||||
cls.base_qweb_templates_data = cls.env['ir.model.data'].create([
|
||||
{'name': template.name, 'module': 'mail',
|
||||
'model': template._name, 'res_id': template.id,
|
||||
} for template in cls.base_qweb_templates
|
||||
])
|
||||
cls.base_qweb_templates_xmlids = [
|
||||
model_data.complete_name
|
||||
for model_data in cls.base_qweb_templates_data
|
||||
]
|
||||
|
||||
# render result
|
||||
cls.base_rendered = [
|
||||
'<p>Hello</p>',
|
||||
'<p>Hello %s</p>' % cls.render_object.name,
|
||||
"""<p>
|
||||
<span>English Speaker</span>
|
||||
</p>"""
|
||||
]
|
||||
cls.base_rendered_fr = [
|
||||
'<p>Bonjour</p>',
|
||||
'<p>Bonjour %s</p>' % cls.render_object_fr.name,
|
||||
"""<p>
|
||||
<span>Autre Narrateur</span>
|
||||
</p>"""
|
||||
]
|
||||
|
||||
# link to mail template
|
||||
cls.test_template = cls.env['mail.template'].create({
|
||||
'name': 'Test Template',
|
||||
'subject': cls.base_inline_template_bits[0],
|
||||
'body_html': cls.base_qweb_bits[1],
|
||||
'model_id': cls.env['ir.model']._get('res.partner').id,
|
||||
'lang': '{{ object.lang }}'
|
||||
})
|
||||
|
||||
# some translations
|
||||
cls.test_template.with_context(lang='fr_FR').subject = cls.base_qweb_bits_fr[0]
|
||||
cls.test_template.with_context(lang='fr_FR').body_html = cls.base_qweb_bits_fr[1]
|
||||
|
||||
cls.env['ir.model.data'].create({
|
||||
'name': 'test_template_xmlid',
|
||||
'module': 'mail',
|
||||
'model': cls.test_template._name,
|
||||
'res_id': cls.test_template.id,
|
||||
})
|
||||
|
||||
# Enable group-based template management
|
||||
cls.env['ir.config_parameter'].set_param('mail.restrict.template.rendering', True)
|
||||
|
||||
# User without the group "mail.group_mail_template_editor"
|
||||
cls.user_rendering_restricted = common.mail_new_test_user(
|
||||
cls.env, login='user_rendering_restricted',
|
||||
groups='base.group_user',
|
||||
company_id=cls.company_admin.id,
|
||||
name='Code Template Restricted User',
|
||||
notification_type='inbox',
|
||||
signature='--\nErnest'
|
||||
)
|
||||
cls.user_rendering_restricted.groups_id -= cls.env.ref('mail.group_mail_template_editor')
|
||||
cls.user_employee.groups_id += cls.env.ref('mail.group_mail_template_editor')
|
||||
|
||||
|
||||
@tagged('mail_render')
|
||||
class TestMailRender(TestMailRenderCommon):
|
||||
|
||||
@users('employee')
|
||||
def test_evaluation_context(self):
|
||||
""" Test evaluation context and various ways of tweaking it. """
|
||||
partner = self.env['res.partner'].browse(self.render_object.ids)
|
||||
MailRenderMixin = self.env['mail.render.mixin']
|
||||
|
||||
custom_ctx = {'custom_ctx': 'Custom Context Value'}
|
||||
add_context = {
|
||||
'custom_value': 'Custom Render Value'
|
||||
}
|
||||
srces = [
|
||||
'<b>I am {{ user.name }}</b>',
|
||||
'<span>Datetime is {{ format_datetime(datetime.datetime(2021, 6, 1), dt_format="MM - d - YYY") }}</span>',
|
||||
'<span>Context {{ ctx.get("custom_ctx") }}, value {{ custom_value }}</span>',
|
||||
]
|
||||
results = [
|
||||
'<b>I am %s</b>' % self.env.user.name,
|
||||
'<span>Datetime is 06 - 1 - 2021</span>',
|
||||
'<span>Context Custom Context Value, value Custom Render Value</span>'
|
||||
]
|
||||
for src, expected in zip(srces, results):
|
||||
for engine in ['inline_template']:
|
||||
result = MailRenderMixin.with_context(**custom_ctx)._render_template(
|
||||
src, partner._name, partner.ids,
|
||||
engine=engine, add_context=add_context
|
||||
)[partner.id]
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@users('employee')
|
||||
def test_prepend_preview_inline_template_to_qweb(self):
|
||||
body = 'body'
|
||||
preview = 'foo{{"false" if 1 > 2 else "true"}}bar'
|
||||
result = self.env['mail.render.mixin']._prepend_preview(Markup(body), preview)
|
||||
self.assertEqual(result, '''<div style="display:none;font-size:1px;height:0px;width:0px;opacity:0;">
|
||||
foo<t t-out=""false" if 1 > 2 else "true""/>bar
|
||||
</div>body''')
|
||||
|
||||
@users('employee')
|
||||
def test_render_field(self):
|
||||
template = self.env['mail.template'].browse(self.test_template.ids)
|
||||
partner = self.env['res.partner'].browse(self.render_object.ids)
|
||||
for fname, expected in zip(['subject', 'body_html'], self.base_rendered):
|
||||
rendered = template._render_field(
|
||||
fname,
|
||||
partner.ids,
|
||||
compute_lang=True
|
||||
)[partner.id]
|
||||
self.assertEqual(rendered, expected)
|
||||
|
||||
@users('employee')
|
||||
def test_render_field_lang(self):
|
||||
""" Test translation in french """
|
||||
template = self.env['mail.template'].browse(self.test_template.ids)
|
||||
partner = self.env['res.partner'].browse(self.render_object_fr.ids)
|
||||
for fname, expected in zip(['subject', 'body_html'], self.base_rendered_fr):
|
||||
rendered = template._render_field(
|
||||
fname,
|
||||
partner.ids,
|
||||
compute_lang=True
|
||||
)[partner.id]
|
||||
self.assertEqual(rendered, expected)
|
||||
|
||||
@users('employee')
|
||||
def test_render_template_inline_template(self):
|
||||
partner = self.env['res.partner'].browse(self.render_object.ids)
|
||||
for source, expected in zip(self.base_inline_template_bits, self.base_rendered):
|
||||
rendered = self.env['mail.render.mixin']._render_template(
|
||||
source,
|
||||
partner._name,
|
||||
partner.ids,
|
||||
engine='inline_template',
|
||||
)[partner.id]
|
||||
self.assertEqual(rendered, expected)
|
||||
|
||||
@users('employee')
|
||||
def test_render_template_inline_template_w_post_process_custom_local_links(self):
|
||||
def _mock_get_base_url(recordset):
|
||||
return f"http://www.render-object-{recordset._name}-{recordset.id}-{recordset.display_name}.com"
|
||||
partner_ids = self.env['res.partner'].sudo().create([{
|
||||
'name': f'test partner {n}'
|
||||
} for n in range(20)]).ids
|
||||
with patch('odoo.models.Model.get_base_url', new=_mock_get_base_url), self.assertQueryCount(7):
|
||||
# make sure name isn't already in cache
|
||||
self.env['res.partner'].browse(partner_ids).invalidate_recordset(['name', 'display_name'])
|
||||
render_results = self.env['mail.render.mixin']._render_template(
|
||||
'<a href="/test/destination"><img src="/test/image"></a>',
|
||||
'res.partner',
|
||||
partner_ids,
|
||||
engine='inline_template',
|
||||
post_process=True,
|
||||
)
|
||||
Partner = self.env['res.partner'].with_prefetch(partner_ids)
|
||||
for partner_id, render_result in render_results.items():
|
||||
partner = Partner.browse(partner_id)
|
||||
expected_base_url = f"http://www.render-object-{partner._name}-{partner.id}-{partner.name}.com"
|
||||
self.assertEqual(render_result, f'<a href="{expected_base_url}/test/destination"><img src="{expected_base_url}/test/image"></a>')
|
||||
|
||||
@users('employee')
|
||||
def test_render_template_qweb(self):
|
||||
partner = self.env['res.partner'].browse(self.render_object.ids)
|
||||
for source, expected in zip(self.base_qweb_bits, self.base_rendered):
|
||||
rendered = self.env['mail.render.mixin']._render_template(
|
||||
source,
|
||||
partner._name,
|
||||
partner.ids,
|
||||
engine='qweb',
|
||||
)[partner.id]
|
||||
self.assertEqual(rendered, expected)
|
||||
|
||||
@users('employee')
|
||||
def test_render_template_qweb_view(self):
|
||||
partner = self.env['res.partner'].browse(self.render_object.ids)
|
||||
for source, expected in zip(self.base_qweb_templates_xmlids, self.base_rendered):
|
||||
rendered = self.env['mail.render.mixin']._render_template(
|
||||
source,
|
||||
partner._name,
|
||||
partner.ids,
|
||||
engine='qweb_view',
|
||||
)[partner.id]
|
||||
self.assertEqual(rendered, expected)
|
||||
|
||||
@users('employee')
|
||||
def test_render_template_various(self):
|
||||
""" Test static rendering """
|
||||
partner = self.env['res.partner'].browse(self.render_object.ids)
|
||||
MailRenderMixin = self.env['mail.render.mixin']
|
||||
|
||||
# static string
|
||||
src = 'This is a string'
|
||||
expected = 'This is a string'
|
||||
for engine in ['inline_template']:
|
||||
result = MailRenderMixin._render_template(
|
||||
src, partner._name, partner.ids, engine=engine,
|
||||
)[partner.id]
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# code string
|
||||
src = 'This is a string with a number {{ 13+13 }}'
|
||||
expected = 'This is a string with a number 26'
|
||||
for engine in ['inline_template']:
|
||||
result = MailRenderMixin._render_template(
|
||||
src, partner._name, partner.ids, engine=engine,
|
||||
)[partner.id]
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# block string
|
||||
src = "This is a string with a block {{ 'hidden' if False else 'displayed' }}"
|
||||
expected = 'This is a string with a block displayed'
|
||||
for engine in ['inline_template']:
|
||||
result = MailRenderMixin._render_template(
|
||||
src, partner._name, partner.ids, engine=engine,
|
||||
)[partner.id]
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# static xml
|
||||
src = '<p class="text-muted"><span>This is a string</span></p>'
|
||||
expected = '<p class="text-muted"><span>This is a string</span></p>'
|
||||
for engine in ['inline_template', 'qweb']:
|
||||
result = MailRenderMixin._render_template(
|
||||
src, partner._name, partner.ids, engine=engine,
|
||||
)[partner.id]
|
||||
self.assertEqual(expected, result) # tde: checkme
|
||||
|
||||
# code xml
|
||||
srces = [
|
||||
'<p class="text-muted"><span>This is a string with a number {{ 13+13 }}</span></p>',
|
||||
'<p class="text-muted"><span>This is a string with a number <t t-out="13+13"/></span></p>',
|
||||
]
|
||||
expected = '<p class="text-muted"><span>This is a string with a number 26</span></p>'
|
||||
for engine, src in zip(['inline_template', 'qweb'], srces):
|
||||
result = MailRenderMixin._render_template(
|
||||
src, partner._name, partner.ids, engine=engine,
|
||||
)[partner.id]
|
||||
self.assertEqual(expected, str(result))
|
||||
src = """<p>
|
||||
<t t-set="line_statement_variable" t-value="3" />
|
||||
<span>We have <t t-out="line_statement_variable" /> cookies in stock</span>
|
||||
<span>We have <t t-set="block_variable" t-value="4" /><t t-out="block_variable" /> cookies in stock</span>
|
||||
</p>"""
|
||||
expected = """<p>
|
||||
<span>We have 3 cookies in stock</span>
|
||||
<span>We have 4 cookies in stock</span>
|
||||
</p>"""
|
||||
for engine in ['qweb']:
|
||||
result = MailRenderMixin._render_template(
|
||||
src, partner._name, partner.ids, engine=engine,
|
||||
)[partner.id]
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
@users('employee')
|
||||
def test_replace_local_links(self):
|
||||
local_links_template_bits = [
|
||||
'<a href="/web/path?a=a&b=b"/>',
|
||||
'<img src="/web/path?a=a&b=b"/>',
|
||||
'<v:fill src="/web/path?a=a&b=b"/>',
|
||||
'<v:image src="/web/path?a=a&b=b"/>',
|
||||
'<div style="background-image:url(/web/path?a=a&b=b);"/>',
|
||||
'<div style="background-image:url(\'/web/path?a=a&b=b\');"/>',
|
||||
'<div style="background-image:url("/web/path?a=a&b=b");"/>',
|
||||
'<div background="/web/path?a=a&b=b"/>',
|
||||
]
|
||||
base_url = self.env['mail.render.mixin'].get_base_url()
|
||||
rendered_local_links = [
|
||||
'<a href="%s/web/path?a=a&b=b"/>' % base_url,
|
||||
'<img src="%s/web/path?a=a&b=b"/>' % base_url,
|
||||
'<v:fill src="%s/web/path?a=a&b=b"/>' % base_url,
|
||||
'<v:image src="%s/web/path?a=a&b=b"/>' % base_url,
|
||||
'<div style="background-image:url(%s/web/path?a=a&b=b);"/>' % base_url,
|
||||
'<div style="background-image:url(\'%s/web/path?a=a&b=b\');"/>' % base_url,
|
||||
'<div style="background-image:url("%s/web/path?a=a&b=b");"/>' % base_url,
|
||||
'<div background="%s/web/path?a=a&b=b"/>' % base_url,
|
||||
]
|
||||
for source, expected in zip(local_links_template_bits, rendered_local_links):
|
||||
rendered = self.env['mail.render.mixin']._replace_local_links(source)
|
||||
self.assertEqual(rendered, expected)
|
||||
|
||||
|
||||
@tagged('mail_render')
|
||||
class TestMailRenderSecurity(TestMailRenderCommon):
|
||||
""" Test security of rendering, based on qweb finding + restricted rendering
|
||||
group usage. """
|
||||
|
||||
@users('employee')
|
||||
def test_render_inline_template_impersonate(self):
|
||||
""" Test that the use of SUDO do not change the current user. """
|
||||
partner = self.env['res.partner'].browse(self.render_object.ids)
|
||||
src = '{{ user.name }} - {{ object.name }}'
|
||||
expected = '%s - %s' % (self.env.user.name, partner.name)
|
||||
result = self.env['mail.render.mixin'].sudo()._render_template_inline_template(
|
||||
src, partner._name, partner.ids
|
||||
)[partner.id]
|
||||
self.assertIn(expected, result)
|
||||
|
||||
@users('user_rendering_restricted')
|
||||
def test_render_inline_template_restricted(self):
|
||||
"""Test if we correctly detect static template."""
|
||||
res_ids = self.env['res.partner'].search([], limit=1).ids
|
||||
with self.assertRaises(AccessError, msg='Simple user should not be able to render dynamic code'):
|
||||
self.env['mail.render.mixin']._render_template_inline_template(
|
||||
self.base_inline_template_bits[3],
|
||||
'res.partner',
|
||||
res_ids
|
||||
)
|
||||
|
||||
src = """<h1>This is a static template</h1>"""
|
||||
result = self.env['mail.render.mixin']._render_template_inline_template(
|
||||
src,
|
||||
'res.partner',
|
||||
res_ids
|
||||
)[res_ids[0]]
|
||||
self.assertEqual(src, str(result))
|
||||
|
||||
@users('user_rendering_restricted')
|
||||
def test_render_inline_template_restricted_static(self):
|
||||
"""Test that we render correctly static templates (without placeholders)."""
|
||||
model = 'res.partner'
|
||||
res_ids = self.env[model].search([], limit=1).ids
|
||||
MailRenderMixin = self.env['mail.render.mixin']
|
||||
|
||||
result = MailRenderMixin._render_template_inline_template(
|
||||
self.base_inline_template_bits[0],
|
||||
model,
|
||||
res_ids
|
||||
)[res_ids[0]]
|
||||
self.assertEqual(result, self.base_inline_template_bits[0])
|
||||
|
||||
@users('employee')
|
||||
def test_render_inline_template_unrestricted(self):
|
||||
""" Test if we correctly detect static template. """
|
||||
res_ids = self.env['res.partner'].search([], limit=1).ids
|
||||
result = self.env['mail.render.mixin']._render_template_inline_template(
|
||||
self.base_inline_template_bits[3],
|
||||
'res.partner',
|
||||
res_ids
|
||||
)[res_ids[0]]
|
||||
self.assertIn('26', result, 'Template Editor should be able to render inline_template code')
|
||||
|
||||
@users('user_rendering_restricted')
|
||||
def test_render_template_qweb_restricted(self):
|
||||
model = 'res.partner'
|
||||
res_ids = self.env[model].search([], limit=1).ids
|
||||
partner = self.env[model].browse(res_ids)
|
||||
|
||||
src = """<h1>This is a static template</h1>"""
|
||||
|
||||
result = self.env['mail.render.mixin']._render_template_qweb(src, model, res_ids)[
|
||||
partner.id]
|
||||
self.assertEqual(src, str(result))
|
||||
|
||||
@users('user_rendering_restricted')
|
||||
def test_security_function_call(self):
|
||||
"""Test the case when the template call a custom function.
|
||||
|
||||
This function should not be called when the template is not rendered.
|
||||
"""
|
||||
model = 'res.partner'
|
||||
res_ids = self.env[model].search([], limit=1).ids
|
||||
partner = self.env[model].browse(res_ids)
|
||||
MailRenderMixin = self.env['mail.render.mixin']
|
||||
|
||||
def cust_function():
|
||||
# Can not use "MagicMock" in a Jinja sand-boxed environment
|
||||
# so create our own function
|
||||
cust_function.call = True
|
||||
return 'return value'
|
||||
|
||||
cust_function.call = False
|
||||
|
||||
src = """<h1>This is a test</h1>
|
||||
<p>{{ cust_function() }}</p>"""
|
||||
expected = """<h1>This is a test</h1>
|
||||
<p>return value</p>"""
|
||||
context = {'cust_function': cust_function}
|
||||
|
||||
result = self.env['mail.render.mixin'].with_user(self.user_admin)._render_template_inline_template(
|
||||
src, partner._name, partner.ids,
|
||||
add_context=context
|
||||
)[partner.id]
|
||||
self.assertEqual(expected, result)
|
||||
self.assertTrue(cust_function.call)
|
||||
|
||||
with self.assertRaises(AccessError, msg='Simple user should not be able to render dynamic code'):
|
||||
MailRenderMixin._render_template_inline_template(src, model, res_ids, add_context=context)
|
||||
|
||||
@users('user_rendering_restricted')
|
||||
def test_security_inline_template_restricted(self):
|
||||
"""Test if we correctly detect condition block (which might contains code)."""
|
||||
res_ids = self.env['res.partner'].search([], limit=1).ids
|
||||
with self.assertRaises(AccessError, msg='Simple user should not be able to render dynamic code'):
|
||||
self.env['mail.render.mixin']._render_template_inline_template(self.base_inline_template_bits[4], 'res.partner', res_ids)
|
||||
|
||||
@users('employee')
|
||||
def test_security_inline_template_unrestricted(self):
|
||||
"""Test if we correctly detect condition block (which might contains code)."""
|
||||
res_ids = self.env['res.partner'].search([], limit=1).ids
|
||||
result = self.env['mail.render.mixin']._render_template_inline_template(self.base_inline_template_bits[4], 'res.partner', res_ids)[res_ids[0]]
|
||||
self.assertNotIn('Code not executed', result, 'The condition block did not work')
|
||||
|
||||
@users('user_rendering_restricted')
|
||||
def test_security_qweb_template_restricted(self):
|
||||
"""Test if we correctly detect condition block (which might contains code)."""
|
||||
res_ids = self.env['res.partner'].search([], limit=1).ids
|
||||
with self.assertRaises(AccessError, msg='Simple user should not be able to render qweb code'):
|
||||
self.env['mail.render.mixin']._render_template_qweb(self.base_qweb_bits[1], 'res.partner', res_ids)
|
||||
|
||||
@users('user_rendering_restricted')
|
||||
def test_security_qweb_template_restricted_cached(self):
|
||||
"""Test if we correctly detect condition block (which might contains code)."""
|
||||
res_ids = self.env['res.partner'].search([], limit=1).ids
|
||||
|
||||
# Render with the admin first to fill the cache
|
||||
self.env['mail.render.mixin'].with_user(self.user_admin)._render_template_qweb(
|
||||
self.base_qweb_bits[1], 'res.partner', res_ids)
|
||||
|
||||
# Check that it raise even when rendered previously by an admin
|
||||
with self.assertRaises(AccessError, msg='Simple user should not be able to render qweb code'):
|
||||
self.env['mail.render.mixin']._render_template_qweb(
|
||||
self.base_qweb_bits[1], 'res.partner', res_ids)
|
||||
|
||||
@users('employee')
|
||||
def test_security_qweb_template_unrestricted(self):
|
||||
"""Test if we correctly detect condition block (which might contains code)."""
|
||||
res_ids = self.env['res.partner'].search([], limit=1).ids
|
||||
result = self.env['mail.render.mixin']._render_template_qweb(self.base_qweb_bits[1], 'res.partner', res_ids)[res_ids[0]]
|
||||
self.assertNotIn('Code not executed', result, 'The condition block did not work')
|
||||
249
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_template.py
Normal file
249
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_template.py
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from markupsafe import Markup
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.modules.module import get_module_resource
|
||||
from odoo.tests import Form, tagged, users
|
||||
from odoo.tools import convert_file
|
||||
|
||||
|
||||
@tagged('mail_template')
|
||||
class TestMailTemplate(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailTemplate, cls).setUpClass()
|
||||
# Enable the Jinja rendering restriction
|
||||
cls.env['ir.config_parameter'].set_param('mail.restrict.template.rendering', True)
|
||||
cls.user_employee.groups_id -= cls.env.ref('mail.group_mail_template_editor')
|
||||
|
||||
cls.mail_template = cls.env['mail.template'].create({
|
||||
'name': 'Test template',
|
||||
'subject': '{{ 1 + 5 }}',
|
||||
'body_html': '<t t-out="4 + 9"/>',
|
||||
'lang': '{{ object.lang }}',
|
||||
'auto_delete': True,
|
||||
'model_id': cls.env.ref('base.model_res_partner').id,
|
||||
})
|
||||
|
||||
@users('employee')
|
||||
def test_mail_compose_message_content_from_template(self):
|
||||
form = Form(self.env['mail.compose.message'])
|
||||
form.template_id = self.mail_template
|
||||
mail_compose_message = form.save()
|
||||
|
||||
self.assertEqual(mail_compose_message.subject, '6', 'We must trust mail template values')
|
||||
|
||||
@users('employee')
|
||||
def test_mail_compose_message_content_from_template_mass_mode(self):
|
||||
mail_compose_message = self.env['mail.compose.message'].create({
|
||||
'composition_mode': 'mass_mail',
|
||||
'model': 'res.partner',
|
||||
'template_id': self.mail_template.id,
|
||||
'subject': '{{ 1 + 5 }}',
|
||||
})
|
||||
|
||||
values = mail_compose_message.get_mail_values(self.partner_employee.ids)
|
||||
|
||||
self.assertEqual(values[self.partner_employee.id]['subject'], '6', 'We must trust mail template values')
|
||||
self.assertIn('13', values[self.partner_employee.id]['body_html'], 'We must trust mail template values')
|
||||
|
||||
def test_mail_template_acl(self):
|
||||
# Sanity check
|
||||
self.assertTrue(self.user_admin.has_group('mail.group_mail_template_editor'))
|
||||
self.assertTrue(self.user_admin.has_group('base.group_sanitize_override'))
|
||||
self.assertFalse(self.user_employee.has_group('mail.group_mail_template_editor'))
|
||||
self.assertFalse(self.user_employee.has_group('base.group_sanitize_override'))
|
||||
|
||||
# Group System can create / write / unlink mail template
|
||||
mail_template = self.env['mail.template'].with_user(self.user_admin).create({'name': 'Test template'})
|
||||
self.assertEqual(mail_template.name, 'Test template')
|
||||
|
||||
mail_template.with_user(self.user_admin).name = 'New name'
|
||||
self.assertEqual(mail_template.name, 'New name')
|
||||
|
||||
# Standard employee can create and edit non-dynamic templates
|
||||
employee_template = self.env['mail.template'].with_user(self.user_employee).create({'body_html': '<p>foo</p>'})
|
||||
|
||||
employee_template.with_user(self.user_employee).body_html = '<p>bar</p>'
|
||||
|
||||
employee_template = self.env['mail.template'].with_user(self.user_employee).create({'email_to': 'foo@bar.com'})
|
||||
|
||||
employee_template.with_user(self.user_employee).email_to = 'bar@foo.com'
|
||||
|
||||
# Standard employee cannot create and edit templates with dynamic qweb
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.template'].with_user(self.user_employee).create({'body_html': '<p t-esc="\'foo\'"></p>'})
|
||||
|
||||
# Standard employee cannot edit templates from another user, non-dynamic and dynamic
|
||||
with self.assertRaises(AccessError):
|
||||
mail_template.with_user(self.user_employee).body_html = '<p>foo</p>'
|
||||
with self.assertRaises(AccessError):
|
||||
mail_template.with_user(self.user_employee).body_html = '<p t-esc="\'foo\'"></p>'
|
||||
|
||||
# Standard employee can edit his own templates if not dynamic
|
||||
employee_template.with_user(self.user_employee).body_html = '<p>foo</p>'
|
||||
|
||||
# Standard employee cannot create and edit templates with dynamic inline fields
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.template'].with_user(self.user_employee).create({'email_to': '{{ object.partner_id.email }}'})
|
||||
|
||||
# Standard employee cannot edit his own templates if dynamic
|
||||
with self.assertRaises(AccessError):
|
||||
employee_template.with_user(self.user_employee).body_html = '<p t-esc="\'foo\'"></p>'
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
employee_template.with_user(self.user_employee).email_to = '{{ object.partner_id.email }}'
|
||||
|
||||
def test_mail_template_acl_translation(self):
|
||||
''' Test that a user that doenn't have the group_mail_template_editor cannot create / edit
|
||||
translation with dynamic code if he cannot write dynamic code on the related record itself.
|
||||
'''
|
||||
|
||||
self.env.ref('base.lang_fr').sudo().active = True
|
||||
|
||||
employee_template = self.env['mail.template'].with_user(self.user_employee).create({
|
||||
'model_id': self.env.ref('base.model_res_partner').id,
|
||||
'subject': 'The subject',
|
||||
'body_html': '<p>foo</p>',
|
||||
})
|
||||
|
||||
### check qweb dynamic
|
||||
# write on translation for template without dynamic code is allowed
|
||||
employee_template.with_context(lang='fr_FR').body_html = 'non-qweb'
|
||||
|
||||
# cannot write dynamic code on mail_template translation for employee without the group mail_template_editor.
|
||||
with self.assertRaises(AccessError):
|
||||
employee_template.with_context(lang='fr_FR').body_html = '<t t-esc="foo"/>'
|
||||
|
||||
employee_template.with_context(lang='fr_FR').sudo().body_html = '<t t-esc="foo"/>'
|
||||
|
||||
# reset the body_html to static
|
||||
employee_template.body_html = False
|
||||
employee_template.body_html = '<p>foo</p>'
|
||||
|
||||
### check qweb inline dynamic
|
||||
# write on translation for template without dynamic code is allowed
|
||||
employee_template.with_context(lang='fr_FR').subject = 'non-qweb'
|
||||
|
||||
# cannot write dynamic code on mail_template translation for employee without the group mail_template_editor.
|
||||
with self.assertRaises(AccessError):
|
||||
employee_template.with_context(lang='fr_FR').subject = '{{ object.foo }}'
|
||||
|
||||
employee_template.with_context(lang='fr_FR').sudo().subject = '{{ object.foo }}'
|
||||
|
||||
def test_server_archived_usage_protection(self):
|
||||
""" Test the protection against using archived server (servers used cannot be archived) """
|
||||
IrMailServer = self.env['ir.mail_server']
|
||||
server = IrMailServer.create({
|
||||
'name': 'Server',
|
||||
'smtp_host': 'archive-test.smtp.local',
|
||||
})
|
||||
self.mail_template.mail_server_id = server.id
|
||||
with self.assertRaises(UserError, msg='Server cannot be archived because it is used'):
|
||||
server.action_archive()
|
||||
self.assertTrue(server.active)
|
||||
self.mail_template.mail_server_id = IrMailServer
|
||||
server.action_archive() # No more usage -> can be archived
|
||||
self.assertFalse(server.active)
|
||||
|
||||
|
||||
@tagged('mail_template')
|
||||
class TestMailTemplateReset(MailCommon):
|
||||
|
||||
def _load(self, module, *args):
|
||||
convert_file(self.cr, module='mail',
|
||||
filename=get_module_resource(module, *args),
|
||||
idref={}, mode='init', noupdate=False, kind='test')
|
||||
|
||||
def test_mail_template_reset(self):
|
||||
self._load('mail', 'tests', 'test_mail_template.xml')
|
||||
|
||||
mail_template = self.env.ref('mail.mail_template_test').with_context(lang=self.env.user.lang)
|
||||
|
||||
mail_template.write({
|
||||
'body_html': '<div>Hello</div>',
|
||||
'name': 'Mail: Mail Template',
|
||||
'subject': 'Test',
|
||||
'email_from': 'admin@example.com',
|
||||
'email_to': 'user@example.com',
|
||||
'attachment_ids': False,
|
||||
})
|
||||
|
||||
context = {'default_template_ids': mail_template.ids}
|
||||
mail_template_reset = self.env['mail.template.reset'].with_context(context).create({})
|
||||
reset_action = mail_template_reset.reset_template()
|
||||
self.assertTrue(reset_action)
|
||||
|
||||
self.assertEqual(mail_template.body_html.strip(), Markup('<div>Hello Odoo</div>'))
|
||||
self.assertEqual(mail_template.name, 'Mail: Test Mail Template')
|
||||
self.assertEqual(
|
||||
mail_template.email_from,
|
||||
'"{{ object.company_id.name }}" <{{ (object.company_id.email or user.email) }}>'
|
||||
)
|
||||
self.assertEqual(mail_template.email_to, '{{ object.email_formatted }}')
|
||||
self.assertEqual(mail_template.attachment_ids, self.env.ref('mail.mail_template_test_attachment'))
|
||||
|
||||
# subject is not there in the data file template, so it should be set to False
|
||||
self.assertFalse(mail_template.subject, "Subject should be set to False")
|
||||
|
||||
def test_mail_template_reset_translation(self):
|
||||
""" Test if a translated value can be reset correctly when its translation exists/doesn't exist in the po file of the directory """
|
||||
self._load('mail', 'tests', 'test_mail_template.xml')
|
||||
|
||||
self.env['res.lang']._activate_lang('en_UK')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
mail_template = self.env.ref('mail.mail_template_test').with_context(lang='en_US')
|
||||
mail_template.write({
|
||||
'body_html': '<div>Hello</div>',
|
||||
'name': 'Mail: Mail Template',
|
||||
})
|
||||
|
||||
mail_template.with_context(lang='en_UK').write({
|
||||
'body_html': '<div>Hello UK</div>',
|
||||
'name': 'Mail: Mail Template UK',
|
||||
})
|
||||
|
||||
context = {'default_template_ids': mail_template.ids, 'lang': 'fr_FR'}
|
||||
|
||||
def fake_load_file(translation_importer, filepath, lang, xmlids=None):
|
||||
""" a fake load file to mimic the use case when
|
||||
translations for fr_FR exist in the fr.po of the directory and
|
||||
no en.po in the directory
|
||||
"""
|
||||
if lang == 'fr_FR': # fr_FR has translations
|
||||
translation_importer.model_translations['mail.template'] = {
|
||||
'body_html': {'mail.mail_template_test': {'fr_FR': '<div>Hello Odoo FR</div>'}},
|
||||
'name': {'mail.mail_template_test': {'fr_FR': "Mail: Test Mail Template FR"}},
|
||||
}
|
||||
|
||||
with patch('odoo.tools.translate.TranslationImporter.load_file', fake_load_file):
|
||||
mail_template_reset = self.env['mail.template.reset'].with_context(context).create({})
|
||||
reset_action = mail_template_reset.reset_template()
|
||||
self.assertTrue(reset_action)
|
||||
|
||||
self.assertEqual(mail_template.body_html.strip(), Markup('<div>Hello Odoo</div>'))
|
||||
self.assertEqual(mail_template.with_context(lang='en_UK').body_html.strip(), Markup('<div>Hello Odoo</div>'))
|
||||
self.assertEqual(mail_template.with_context(lang='fr_FR').body_html.strip(), Markup('<div>Hello Odoo FR</div>'))
|
||||
|
||||
self.assertEqual(mail_template.name, 'Mail: Test Mail Template')
|
||||
self.assertEqual(mail_template.with_context(lang='en_UK').name, 'Mail: Test Mail Template')
|
||||
self.assertEqual(mail_template.with_context(lang='fr_FR').name, 'Mail: Test Mail Template FR')
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestConfigRestrictEditor(MailCommon):
|
||||
|
||||
def test_switch_icp_value(self):
|
||||
# Sanity check
|
||||
self.assertTrue(self.user_employee.has_group('mail.group_mail_template_editor'))
|
||||
self.assertFalse(self.user_employee.has_group('base.group_system'))
|
||||
|
||||
self.env['ir.config_parameter'].set_param('mail.restrict.template.rendering', True)
|
||||
self.assertFalse(self.user_employee.has_group('mail.group_mail_template_editor'))
|
||||
|
||||
self.env['ir.config_parameter'].set_param('mail.restrict.template.rendering', False)
|
||||
self.assertTrue(self.user_employee.has_group('mail.group_mail_template_editor'))
|
||||
19
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_template.xml
Normal file
19
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_template.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="mail_template_test_attachment" model="ir.attachment">
|
||||
<field name="datas">bWlncmF0aW9uIHRlc3Q=</field>
|
||||
<field name="name">YourCompany2022.doc</field>
|
||||
</record>
|
||||
|
||||
<record id="mail_template_test" model="mail.template">
|
||||
<field name="name">Mail: Test Mail Template</field>
|
||||
<field name="model_id" ref="base.model_res_users"/>
|
||||
<field name="email_from">"{{ object.company_id.name }}" <{{ (object.company_id.email or user.email) }}></field>
|
||||
<field name="email_to">{{ object.email_formatted }}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div>Hello Odoo</div>
|
||||
</field>
|
||||
<field name="lang">{{ object.lang }}</field>
|
||||
<field name="attachment_ids" eval="[(6, 0, [ref('mail_template_test_attachment')])]"/>
|
||||
</record>
|
||||
</odoo>
|
||||
291
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_tools.py
Normal file
291
odoo-bringout-oca-ocb-mail/mail/tests/test_mail_tools.py
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests import tagged, users
|
||||
from odoo import tools
|
||||
|
||||
|
||||
@tagged('mail_tools')
|
||||
class TestMailTools(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailTools, cls).setUpClass()
|
||||
|
||||
cls._test_email = 'alfredoastaire@test.example.com'
|
||||
cls.test_partner = cls.env['res.partner'].create({
|
||||
'country_id': cls.env.ref('base.be').id,
|
||||
'email': cls._test_email,
|
||||
'mobile': '0456001122',
|
||||
'name': 'Alfred Astaire',
|
||||
'phone': '0456334455',
|
||||
})
|
||||
|
||||
cls.sources = [
|
||||
# single email
|
||||
'alfred.astaire@test.example.com',
|
||||
' alfred.astaire@test.example.com ',
|
||||
'Fredo The Great <alfred.astaire@test.example.com>',
|
||||
'"Fredo The Great" <alfred.astaire@test.example.com>',
|
||||
'Fredo "The Great" <alfred.astaire@test.example.com>',
|
||||
# multiple emails
|
||||
'alfred.astaire@test.example.com, evelyne.gargouillis@test.example.com',
|
||||
'Fredo The Great <alfred.astaire@test.example.com>, Evelyne The Goat <evelyne.gargouillis@test.example.com>',
|
||||
'"Fredo The Great" <alfred.astaire@test.example.com>, evelyne.gargouillis@test.example.com',
|
||||
'"Fredo The Great" <alfred.astaire@test.example.com>, <evelyne.gargouillis@test.example.com>',
|
||||
# text containing email
|
||||
'Hello alfred.astaire@test.example.com how are you ?',
|
||||
'<p>Hello alfred.astaire@test.example.com</p>',
|
||||
# text containing emails
|
||||
'Hello "Fredo" <alfred.astaire@test.example.com>, evelyne.gargouillis@test.example.com',
|
||||
'Hello "Fredo" <alfred.astaire@test.example.com> and evelyne.gargouillis@test.example.com',
|
||||
# falsy
|
||||
'<p>Hello Fredo</p>',
|
||||
'j\'adore écrire des @gmail.com ou "@gmail.com" a bit randomly',
|
||||
'',
|
||||
]
|
||||
|
||||
@users('employee')
|
||||
def test_mail_find_partner_from_emails(self):
|
||||
Partner = self.env['res.partner']
|
||||
test_partner = Partner.browse(self.test_partner.ids)
|
||||
self.assertEqual(test_partner.email, self._test_email)
|
||||
|
||||
sources = [
|
||||
self._test_email, # test direct match
|
||||
f'"Norbert Poiluchette" <{self._test_email}>', # encapsulated
|
||||
'fredoastaire@test.example.com', # partial email -> should not match !
|
||||
]
|
||||
expected_partners = [
|
||||
test_partner,
|
||||
test_partner,
|
||||
self.env['res.partner'],
|
||||
]
|
||||
for source, expected_partner in zip(sources, expected_partners):
|
||||
with self.subTest(source=source):
|
||||
found = Partner._mail_find_partner_from_emails([source])
|
||||
self.assertEqual(found, [expected_partner])
|
||||
|
||||
# test with wildcard "_"
|
||||
found = Partner._mail_find_partner_from_emails(['alfred_astaire@test.example.com'])
|
||||
self.assertEqual(found, [self.env['res.partner']])
|
||||
# sub-check: this search does not consider _ as a wildcard
|
||||
found = Partner._mail_search_on_partner(['alfred_astaire@test.example.com'])
|
||||
self.assertEqual(found, self.env['res.partner'])
|
||||
|
||||
# test partners with encapsulated emails
|
||||
# ------------------------------------------------------------
|
||||
test_partner.sudo().write({'email': f'"Alfred Mighty Power Astaire" <{self._test_email}>'})
|
||||
|
||||
sources = [
|
||||
self._test_email, # test direct match
|
||||
f'"Norbert Poiluchette" <{self._test_email}>', # encapsulated
|
||||
]
|
||||
expected_partners = [
|
||||
test_partner,
|
||||
test_partner,
|
||||
]
|
||||
for source, expected_partner in zip(sources, expected_partners):
|
||||
with self.subTest(source=source):
|
||||
found = Partner._mail_find_partner_from_emails([source])
|
||||
self.assertEqual(found, [expected_partner])
|
||||
|
||||
# test with wildcard "_"
|
||||
found = Partner._mail_find_partner_from_emails(['alfred_astaire@test.example.com'])
|
||||
self.assertEqual(found, [self.env['res.partner']])
|
||||
# sub-check: this search does not consider _ as a wildcard
|
||||
found = Partner._mail_search_on_partner(['alfred_astaire@test.example.com'])
|
||||
self.assertEqual(found, self.env['res.partner'])
|
||||
|
||||
@users('admin')
|
||||
def test_mail_find_partner_from_emails_followers(self):
|
||||
""" Test '_mail_find_partner_from_emails' when dealing with records on
|
||||
which followers have to be found based on email. Check multi email
|
||||
and encapsulated email support. """
|
||||
# create partner just for the follow mechanism
|
||||
linked_record = self.env['res.partner'].sudo().create({'name': 'Record for followers'})
|
||||
follower_partner = self.env['res.partner'].sudo().create({
|
||||
'email': self._test_email,
|
||||
'name': 'Duplicated, follower of record',
|
||||
})
|
||||
linked_record.message_subscribe(partner_ids=follower_partner.ids)
|
||||
test_partner = self.test_partner.with_env(self.env)
|
||||
|
||||
# standard test, no multi-email, to assert base behavior
|
||||
sources = [(self._test_email, True), (self._test_email, False),]
|
||||
expected = [follower_partner, test_partner]
|
||||
for (source, follower_check), expected in zip(sources, expected):
|
||||
with self.subTest(source=source, follower_check=follower_check):
|
||||
partner = self.env['res.partner']._mail_find_partner_from_emails(
|
||||
[source], records=linked_record if follower_check else None
|
||||
)[0]
|
||||
self.assertEqual(partner, expected)
|
||||
|
||||
# formatted email
|
||||
encapsulated_test_email = f'"Robert Astaire" <{self._test_email}>'
|
||||
(follower_partner + test_partner).sudo().write({'email': encapsulated_test_email})
|
||||
sources = [
|
||||
(self._test_email, True), # normalized
|
||||
(self._test_email, False), # normalized
|
||||
(encapsulated_test_email, True), # encapsulated, same
|
||||
(encapsulated_test_email, False), # encapsulated, same
|
||||
(f'"AnotherName" <{self._test_email}', True), # same normalized, other name
|
||||
(f'"AnotherName" <{self._test_email}', False), # same normalized, other name
|
||||
]
|
||||
expected = [follower_partner, test_partner,
|
||||
follower_partner, test_partner,
|
||||
follower_partner, test_partner,
|
||||
follower_partner, test_partner]
|
||||
for (source, follower_check), expected in zip(sources, expected):
|
||||
with self.subTest(source=source, follower_check=follower_check):
|
||||
partner = self.env['res.partner']._mail_find_partner_from_emails(
|
||||
[source], records=linked_record if follower_check else None
|
||||
)[0]
|
||||
self.assertEqual(partner, expected,
|
||||
'Mail: formatted email is recognized through usage of normalized email')
|
||||
|
||||
# multi-email
|
||||
_test_email_2 = '"Robert Astaire" <not.alfredoastaire@test.example.com>'
|
||||
(follower_partner + test_partner).sudo().write({'email': f'{self._test_email}, {_test_email_2}'})
|
||||
sources = [
|
||||
(self._test_email, True), # first email
|
||||
(self._test_email, False), # first email
|
||||
(_test_email_2, True), # second email
|
||||
(_test_email_2, False), # second email
|
||||
('not.alfredoastaire@test.example.com', True), # normalized second email in field
|
||||
('not.alfredoastaire@test.example.com', False), # normalized second email in field
|
||||
(f'{self._test_email}, {_test_email_2}', True), # multi-email, both matching, depends on comparison
|
||||
(f'{self._test_email}, {_test_email_2}', False) # multi-email, both matching, depends on comparison
|
||||
]
|
||||
expected = [follower_partner, test_partner,
|
||||
self.env['res.partner'], self.env['res.partner'],
|
||||
self.env['res.partner'], self.env['res.partner'],
|
||||
follower_partner, test_partner]
|
||||
for (source, follower_check), expected in zip(sources, expected):
|
||||
with self.subTest(source=source, follower_check=follower_check):
|
||||
partner = self.env['res.partner']._mail_find_partner_from_emails(
|
||||
[source], records=linked_record if follower_check else None
|
||||
)[0]
|
||||
self.assertEqual(partner, expected,
|
||||
'Mail (FIXME): partial recognition of multi email through email_normalize')
|
||||
|
||||
# test users with same email, priority given to current user
|
||||
# --------------------------------------------------------------
|
||||
self.user_employee.sudo().write({'email': '"Alfred Astaire" <%s>' % self.env.user.partner_id.email_normalized})
|
||||
found = self.env['res.partner']._mail_find_partner_from_emails([self.env.user.partner_id.email_formatted])
|
||||
self.assertEqual(found, [self.env.user.partner_id])
|
||||
|
||||
def test_mail_find_partner_from_emails_multicompany(self):
|
||||
""" Test _mail_find_partner_from_emails when dealing with records in
|
||||
a multicompany environment, returning a partner record with matching
|
||||
company_id. """
|
||||
self._activate_multi_company()
|
||||
Partner = self.env['res.partner']
|
||||
self.test_partner.company_id = self.company_2
|
||||
|
||||
test_partner_no_company = self.test_partner.copy({'company_id': False})
|
||||
test_partner_company_2 = self.test_partner
|
||||
test_partner_company_3 = test_partner_no_company.copy({'company_id': self.company_3.id})
|
||||
records = [
|
||||
None,
|
||||
*Partner.create([
|
||||
{'name': 'Company 2 contact', 'company_id': self.company_2.id},
|
||||
{'name': 'Company 3 contact', 'company_id': self.company_3.id},
|
||||
{'name': 'No restrictions', 'company_id': False},
|
||||
])
|
||||
]
|
||||
expected_partners = [
|
||||
(test_partner_no_company, "W/out reference record, prefer non-specific partner."),
|
||||
(test_partner_company_2, "Prefer same company as reference record."),
|
||||
(test_partner_company_3, "Prefer same company as reference record."),
|
||||
(test_partner_no_company, "Prefer non-specific partner for non-specific records."),
|
||||
]
|
||||
for record, (expected_partner, msg) in zip(records, expected_partners):
|
||||
found = Partner._mail_find_partner_from_emails([self._test_email], records=record)
|
||||
self.assertEqual(found, [expected_partner], msg)
|
||||
|
||||
@users('employee')
|
||||
def test_tools_email_re(self):
|
||||
expected = [
|
||||
# single email
|
||||
['alfred.astaire@test.example.com'],
|
||||
['alfred.astaire@test.example.com'],
|
||||
['alfred.astaire@test.example.com'],
|
||||
['alfred.astaire@test.example.com'],
|
||||
['alfred.astaire@test.example.com'],
|
||||
# multiple emails
|
||||
['alfred.astaire@test.example.com', 'evelyne.gargouillis@test.example.com'],
|
||||
['alfred.astaire@test.example.com', 'evelyne.gargouillis@test.example.com'],
|
||||
['alfred.astaire@test.example.com', 'evelyne.gargouillis@test.example.com'],
|
||||
['alfred.astaire@test.example.com', 'evelyne.gargouillis@test.example.com'],
|
||||
# text containing email
|
||||
['alfred.astaire@test.example.com'],
|
||||
['alfred.astaire@test.example.com'],
|
||||
# text containing emails
|
||||
['alfred.astaire@test.example.com', 'evelyne.gargouillis@test.example.com'],
|
||||
['alfred.astaire@test.example.com', 'evelyne.gargouillis@test.example.com'],
|
||||
# falsy
|
||||
[], [], [],
|
||||
]
|
||||
|
||||
for src, exp in zip(self.sources, expected):
|
||||
res = tools.email_re.findall(src)
|
||||
self.assertEqual(
|
||||
res, exp,
|
||||
'Seems email_re is broken with %s (expected %r, received %r)' % (src, exp, res)
|
||||
)
|
||||
|
||||
@users('employee')
|
||||
def test_tools_email_split_tuples(self):
|
||||
expected = [
|
||||
# single email
|
||||
[('', 'alfred.astaire@test.example.com')],
|
||||
[('', 'alfred.astaire@test.example.com')],
|
||||
[('Fredo The Great', 'alfred.astaire@test.example.com')],
|
||||
[('Fredo The Great', 'alfred.astaire@test.example.com')],
|
||||
[('Fredo The Great', 'alfred.astaire@test.example.com')],
|
||||
# multiple emails
|
||||
[('', 'alfred.astaire@test.example.com'), ('', 'evelyne.gargouillis@test.example.com')],
|
||||
[('Fredo The Great', 'alfred.astaire@test.example.com'), ('Evelyne The Goat', 'evelyne.gargouillis@test.example.com')],
|
||||
[('Fredo The Great', 'alfred.astaire@test.example.com'), ('', 'evelyne.gargouillis@test.example.com')],
|
||||
[('Fredo The Great', 'alfred.astaire@test.example.com'), ('', 'evelyne.gargouillis@test.example.com')],
|
||||
# text containing email -> fallback on parsing to extract text from email
|
||||
[('Hello', 'alfred.astaire@test.example.comhowareyou?')],
|
||||
[('Hello', 'alfred.astaire@test.example.com')],
|
||||
[('Hello Fredo', 'alfred.astaire@test.example.com'), ('', 'evelyne.gargouillis@test.example.com')],
|
||||
[('Hello Fredo', 'alfred.astaire@test.example.com'), ('and', 'evelyne.gargouillis@test.example.com')],
|
||||
# falsy -> probably not designed for that
|
||||
[],
|
||||
[('j\'adore écrire', "des@gmail.comou"), ('', '@gmail.com')], [],
|
||||
]
|
||||
|
||||
for src, exp in zip(self.sources, expected):
|
||||
res = tools.email_split_tuples(src)
|
||||
self.assertEqual(
|
||||
res, exp,
|
||||
'Seems email_split_tuples is broken with %s (expected %r, received %r)' % (src, exp, res)
|
||||
)
|
||||
|
||||
@users('employee')
|
||||
def test_tools_single_email_re(self):
|
||||
expected = [
|
||||
# single email
|
||||
['alfred.astaire@test.example.com'],
|
||||
[], [], [], [], # formatting issue for single email re
|
||||
# multiple emails -> couic
|
||||
[], [], [], [],
|
||||
# text containing email -> couic
|
||||
[], [],
|
||||
# text containing emails -> couic
|
||||
[], [],
|
||||
# falsy
|
||||
[], [], [],
|
||||
]
|
||||
|
||||
for src, exp in zip(self.sources, expected):
|
||||
res = tools.single_email_re.findall(src)
|
||||
self.assertEqual(
|
||||
res, exp,
|
||||
'Seems single_email_re is broken with %s (expected %r, received %r)' % (src, exp, res)
|
||||
)
|
||||
267
odoo-bringout-oca-ocb-mail/mail/tests/test_res_partner.py
Normal file
267
odoo-bringout-oca-ocb-mail/mail/tests/test_res_partner.py
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon, mail_new_test_user
|
||||
from odoo.tests.common import Form, users
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
# samples use effective TLDs from the Mozilla public suffix
|
||||
# list at http://publicsuffix.org
|
||||
SAMPLES = [
|
||||
('"Raoul Grosbedon" <raoul@chirurgiens-dentistes.fr> ', 'Raoul Grosbedon', 'raoul@chirurgiens-dentistes.fr'),
|
||||
('ryu+giga-Sushi@aizubange.fukushima.jp', '', 'ryu+giga-Sushi@aizubange.fukushima.jp'),
|
||||
('Raoul chirurgiens-dentistes.fr', 'Raoul chirurgiens-dentistes.fr', ''),
|
||||
(" Raoul O'hara <!@historicalsociety.museum>", "Raoul O'hara", '!@historicalsociety.museum'),
|
||||
('Raoul Grosbedon <raoul@CHIRURGIENS-dentistes.fr> ', 'Raoul Grosbedon', 'raoul@CHIRURGIENS-dentistes.fr'),
|
||||
('Raoul megaraoul@chirurgiens-dentistes.fr', 'Raoul', 'megaraoul@chirurgiens-dentistes.fr'),
|
||||
('"Patrick Da Beast Poilvache" <PATRICK@example.com>', 'Patrick Poilvache', 'patrick@example.com'),
|
||||
('Patrick Caché <patrick@EXAMPLE.COM>', 'Patrick Poilvache', 'patrick@example.com'),
|
||||
('Patrick Caché <2patrick@EXAMPLE.COM>', 'Patrick Caché', '2patrick@example.com'),
|
||||
|
||||
]
|
||||
|
||||
@tagged('res_partner', 'mail_tools')
|
||||
class TestPartner(MailCommon):
|
||||
|
||||
def _check_find_or_create(self, test_string, expected_name, expected_email, expected_email_normalized=False, check_partner=False, should_create=False):
|
||||
expected_email_normalized = expected_email_normalized or expected_email
|
||||
partner = self.env['res.partner'].find_or_create(test_string)
|
||||
if should_create and check_partner:
|
||||
self.assertTrue(partner.id > check_partner.id, 'find_or_create failed - should have found existing')
|
||||
elif check_partner:
|
||||
self.assertEqual(partner, check_partner, 'find_or_create failed - should have found existing')
|
||||
self.assertEqual(partner.name, expected_name)
|
||||
self.assertEqual(partner.email or '', expected_email)
|
||||
self.assertEqual(partner.email_normalized or '', expected_email_normalized)
|
||||
return partner
|
||||
|
||||
@users('admin')
|
||||
def test_res_partner_find_or_create(self):
|
||||
Partner = self.env['res.partner']
|
||||
|
||||
partner = Partner.browse(Partner.name_create(SAMPLES[0][0])[0])
|
||||
self._check_find_or_create(
|
||||
SAMPLES[0][0], SAMPLES[0][1], SAMPLES[0][2],
|
||||
check_partner=partner, should_create=False
|
||||
)
|
||||
|
||||
partner_2 = Partner.browse(Partner.name_create('sarah.john@connor.com')[0])
|
||||
found_2 = self._check_find_or_create(
|
||||
'john@connor.com', 'john@connor.com', 'john@connor.com',
|
||||
check_partner=partner_2, should_create=True
|
||||
)
|
||||
|
||||
new = self._check_find_or_create(
|
||||
SAMPLES[1][0], SAMPLES[1][2].lower(), SAMPLES[1][2].lower(),
|
||||
check_partner=found_2, should_create=True
|
||||
)
|
||||
|
||||
new2 = self._check_find_or_create(
|
||||
SAMPLES[2][0], SAMPLES[2][1], SAMPLES[2][2],
|
||||
check_partner=new, should_create=True
|
||||
)
|
||||
|
||||
self._check_find_or_create(
|
||||
SAMPLES[3][0], SAMPLES[3][1], SAMPLES[3][2],
|
||||
check_partner=new2, should_create=True
|
||||
)
|
||||
|
||||
new4 = self._check_find_or_create(
|
||||
SAMPLES[4][0], SAMPLES[0][1], SAMPLES[0][2],
|
||||
check_partner=partner, should_create=False
|
||||
)
|
||||
|
||||
self._check_find_or_create(
|
||||
SAMPLES[5][0], SAMPLES[5][1], SAMPLES[5][2],
|
||||
check_partner=new4, should_create=True
|
||||
)
|
||||
|
||||
existing = Partner.create({
|
||||
'name': SAMPLES[6][1],
|
||||
'email': SAMPLES[6][0],
|
||||
})
|
||||
self.assertEqual(existing.name, SAMPLES[6][1])
|
||||
self.assertEqual(existing.email, SAMPLES[6][0])
|
||||
self.assertEqual(existing.email_normalized, SAMPLES[6][2])
|
||||
|
||||
new6 = self._check_find_or_create(
|
||||
SAMPLES[7][0], SAMPLES[6][1], SAMPLES[6][0],
|
||||
expected_email_normalized=SAMPLES[6][2],
|
||||
check_partner=existing, should_create=False
|
||||
)
|
||||
|
||||
self._check_find_or_create(
|
||||
SAMPLES[8][0], SAMPLES[8][1], SAMPLES[8][2],
|
||||
check_partner=new6, should_create=True
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
self.env['res.partner'].find_or_create("Raoul chirurgiens-dentistes.fr", assert_valid_email=True)
|
||||
|
||||
@users('admin')
|
||||
def test_res_partner_find_or_create_email(self):
|
||||
""" Test 'find_or_create' tool used in mail, notably when linking emails
|
||||
found in recipients to partners when sending emails using the mail
|
||||
composer. """
|
||||
partners = self.env['res.partner'].create([
|
||||
{
|
||||
'email': 'classic.format@test.example.com',
|
||||
'name': 'Classic Format',
|
||||
},
|
||||
{
|
||||
'email': '"FindMe Format" <find.me.format@test.example.com>',
|
||||
'name': 'FindMe Format',
|
||||
}, {
|
||||
'email': 'find.me.multi.1@test.example.com, "FindMe Multi" <find.me.multi.2@test.example.com>',
|
||||
'name': 'FindMe Multi',
|
||||
},
|
||||
])
|
||||
# check data used for finding / searching
|
||||
self.assertEqual(
|
||||
partners.mapped('email_formatted'),
|
||||
['"Classic Format" <classic.format@test.example.com>',
|
||||
'"FindMe Format" <find.me.format@test.example.com>',
|
||||
'"FindMe Multi" <find.me.multi.1@test.example.com,find.me.multi.2@test.example.com>']
|
||||
)
|
||||
# when having multi emails, first found one is taken as normalized email
|
||||
self.assertEqual(
|
||||
partners.mapped('email_normalized'),
|
||||
['classic.format@test.example.com', 'find.me.format@test.example.com',
|
||||
'find.me.multi.1@test.example.com']
|
||||
)
|
||||
|
||||
# classic find or create: use normalized email to compare records
|
||||
for email in ('CLASSIC.FORMAT@TEST.EXAMPLE.COM', '"Another Name" <classic.format@test.example.com>'):
|
||||
with self.subTest(email=email):
|
||||
self.assertEqual(self.env['res.partner'].find_or_create(email), partners[0])
|
||||
# find on encapsulated email: comparison of normalized should work
|
||||
for email in ('FIND.ME.FORMAT@TEST.EXAMPLE.COM', '"Different Format" <find.me.format@test.example.com>'):
|
||||
with self.subTest(email=email):
|
||||
self.assertEqual(self.env['res.partner'].find_or_create(email), partners[1])
|
||||
# multi-emails -> no normalized email -> fails each time, create new partner (FIXME)
|
||||
for email_input, match_partner in [
|
||||
('find.me.multi.1@test.example.com', partners[2]),
|
||||
('find.me.multi.2@test.example.com', self.env['res.partner']),
|
||||
]:
|
||||
with self.subTest(email_input=email_input):
|
||||
partner = self.env['res.partner'].find_or_create(email_input)
|
||||
# either matching existing, either new partner
|
||||
if match_partner:
|
||||
self.assertEqual(partner, match_partner)
|
||||
else:
|
||||
self.assertNotIn(partner, partners)
|
||||
self.assertEqual(partner.email, email_input)
|
||||
partner.unlink() # do not mess with subsequent tests
|
||||
|
||||
# now input is multi email -> '_parse_partner_name' used in 'find_or_create'
|
||||
# before trying to normalize is quite tolerant, allowing positive checks
|
||||
for email_input, match_partner, exp_email_partner in [
|
||||
('classic.format@test.example.com,another.email@test.example.com',
|
||||
partners[0], 'classic.format@test.example.com'), # first found email matches existing
|
||||
('another.email@test.example.com,classic.format@test.example.com',
|
||||
self.env['res.partner'], 'another.email@test.example.com'), # first found email does not match
|
||||
('find.me.multi.1@test.example.com,find.me.multi.2@test.example.com',
|
||||
self.env['res.partner'], 'find.me.multi.1@test.example.com'),
|
||||
]:
|
||||
with self.subTest(email_input=email_input):
|
||||
partner = self.env['res.partner'].find_or_create(email_input)
|
||||
# either matching existing, either new partner
|
||||
if match_partner:
|
||||
self.assertEqual(partner, match_partner)
|
||||
else:
|
||||
self.assertNotIn(partner, partners)
|
||||
self.assertEqual(partner.email, exp_email_partner)
|
||||
if partner not in partners:
|
||||
partner.unlink() # do not mess with subsequent tests
|
||||
|
||||
def test_res_partner_get_mention_suggestions_priority(self):
|
||||
name = uuid4() # unique name to avoid conflict with already existing users
|
||||
self.env['res.partner'].create([{'name': f'{name}-{i}-not-user'} for i in range(0, 2)])
|
||||
for i in range(0, 2):
|
||||
mail_new_test_user(self.env, login=f'{name}-{i}-portal-user', groups='base.group_portal')
|
||||
mail_new_test_user(self.env, login=f'{name}-{i}-internal-user', groups='base.group_user')
|
||||
partners_format = self.env['res.partner'].get_mention_suggestions(name, limit=5)
|
||||
self.assertEqual(len(partners_format), 5, "should have found limit (5) partners")
|
||||
# return format for user is either a dict (there is a user and the dict is data) or a list of command (clear)
|
||||
self.assertEqual(list(map(lambda p: isinstance(p['user'], dict) and p['user']['isInternalUser'], partners_format)), [True, True, False, False, False], "should return internal users in priority")
|
||||
self.assertEqual(list(map(lambda p: isinstance(p['user'], dict), partners_format)), [True, True, True, True, False], "should return partners without users last")
|
||||
|
||||
def test_res_partner_log_portal_group(self):
|
||||
Users = self.env['res.users']
|
||||
subtype_note = self.env.ref('mail.mt_note')
|
||||
group_portal, group_user = self.env.ref('base.group_portal'), self.env.ref('base.group_user')
|
||||
|
||||
# check at update
|
||||
new_user = Users.create({
|
||||
'email': 'micheline@test.example.com',
|
||||
'login': 'michmich',
|
||||
'name': 'Micheline Employee',
|
||||
})
|
||||
self.assertEqual(len(new_user.message_ids), 1, 'Should contain Contact created log message')
|
||||
new_msg = new_user.message_ids
|
||||
self.assertNotIn('Portal Access Granted', new_msg.body)
|
||||
self.assertIn('Contact created', new_msg.body)
|
||||
|
||||
new_user.write({'groups_id': [(4, group_portal.id), (3, group_user.id)]})
|
||||
new_msg = new_user.message_ids[0]
|
||||
self.assertIn('Portal Access Granted', new_msg.body)
|
||||
self.assertEqual(new_msg.subtype_id, subtype_note)
|
||||
|
||||
# check at create
|
||||
new_user = Users.create({
|
||||
'email': 'micheline.2@test.example.com',
|
||||
'groups_id': [(4, group_portal.id)],
|
||||
'login': 'michmich.2',
|
||||
'name': 'Micheline Portal',
|
||||
})
|
||||
self.assertEqual(len(new_user.message_ids), 2, 'Should contain Contact created + Portal access log messages')
|
||||
new_msg = new_user.message_ids[0]
|
||||
self.assertIn('Portal Access Granted', new_msg.body)
|
||||
self.assertEqual(new_msg.subtype_id, subtype_note)
|
||||
|
||||
@users('admin')
|
||||
def test_res_partner_merge_wizards(self):
|
||||
Partner = self.env['res.partner']
|
||||
|
||||
p1 = Partner.create({'name': 'Customer1', 'email': 'test1@test.example.com'})
|
||||
p1_msg_ids_init = p1.message_ids
|
||||
p2 = Partner.create({'name': 'Customer2', 'email': 'test2@test.example.com'})
|
||||
p2_msg_ids_init = p2.message_ids
|
||||
p3 = Partner.create({'name': 'Other (dup email)', 'email': 'test1@test.example.com'})
|
||||
|
||||
# add some mail related documents
|
||||
p1.message_subscribe(partner_ids=p3.ids)
|
||||
p1_act1 = p1.activity_schedule(act_type_xmlid='mail.mail_activity_data_todo')
|
||||
p1_msg1 = p1.message_post(
|
||||
body='<p>Log on P1</p>',
|
||||
subtype_id=self.env.ref('mail.mt_comment').id
|
||||
)
|
||||
self.assertEqual(p1.activity_ids, p1_act1)
|
||||
self.assertEqual(p1.message_follower_ids.partner_id, self.partner_admin + p3)
|
||||
self.assertEqual(p1.message_ids, p1_msg_ids_init + p1_msg1)
|
||||
self.assertEqual(p2.activity_ids, self.env['mail.activity'])
|
||||
self.assertEqual(p2.message_follower_ids.partner_id, self.partner_admin)
|
||||
self.assertEqual(p2.message_ids, p2_msg_ids_init)
|
||||
|
||||
MergeForm = Form(self.env['base.partner.merge.automatic.wizard'].with_context(
|
||||
active_model='res.partner',
|
||||
active_ids=(p1 + p2).ids
|
||||
))
|
||||
self.assertEqual(MergeForm.partner_ids[:], p1 + p2)
|
||||
self.assertEqual(MergeForm.dst_partner_id, p2)
|
||||
merge_form = MergeForm.save()
|
||||
merge_form.action_merge()
|
||||
|
||||
# check destination and removal
|
||||
self.assertFalse(p1.exists())
|
||||
self.assertTrue(p2.exists())
|
||||
# check mail documents have been moved
|
||||
self.assertEqual(p2.activity_ids, p1_act1)
|
||||
# TDE note: currently not working as soon as there is a single partner duplicated -> should be improved
|
||||
# self.assertEqual(p2.message_follower_ids.partner_id, self.partner_admin + p3)
|
||||
all_msg = p2_msg_ids_init + p1_msg_ids_init + p1_msg1
|
||||
self.assertEqual(len(p2.message_ids), len(all_msg) + 1, 'Should have original messages + a log')
|
||||
self.assertTrue(all(msg in p2.message_ids for msg in all_msg))
|
||||
74
odoo-bringout-oca-ocb-mail/mail/tests/test_res_users.py
Normal file
74
odoo-bringout-oca-ocb-mail/mail/tests/test_res_users.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.addons.mail.tests.common import MailCommon, mail_new_test_user
|
||||
from odoo.tests import RecordCapturer, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', 'mail_tools', 'res_users')
|
||||
class TestUser(MailCommon):
|
||||
|
||||
@mute_logger('odoo.sql_db')
|
||||
def test_notification_type_constraint(self):
|
||||
with self.assertRaises(IntegrityError, msg='Portal user can not receive notification in Odoo'):
|
||||
mail_new_test_user(
|
||||
self.env,
|
||||
login='user_test_constraint_2',
|
||||
name='Test User 2',
|
||||
email='user_test_constraint_2@test.example.com',
|
||||
notification_type='inbox',
|
||||
groups='base.group_portal',
|
||||
)
|
||||
|
||||
def test_web_create_users(self):
|
||||
src = [
|
||||
'POILUCHETTE@test.example.com',
|
||||
'"Jean Poilvache" <POILVACHE@test.example.com>',
|
||||
]
|
||||
with self.mock_mail_gateway(), \
|
||||
RecordCapturer(self.env['res.users'], []) as capture:
|
||||
self.env['res.users'].web_create_users(src)
|
||||
|
||||
exp_emails = ['poiluchette@test.example.com', 'poilvache@test.example.com']
|
||||
# check reset password are effectively sent
|
||||
for user_email in exp_emails:
|
||||
self.assertMailMailWEmails(
|
||||
[user_email], 'sent',
|
||||
author=self.user_root.partner_id,
|
||||
email_values={
|
||||
'email_from': self.env.company.partner_id.email_formatted,
|
||||
},
|
||||
fields_values={
|
||||
'email_from': self.env.company.partner_id.email_formatted,
|
||||
},
|
||||
)
|
||||
|
||||
# order does not seem guaranteed
|
||||
self.assertEqual(len(capture.records), 2, 'Should create one user / entry')
|
||||
self.assertEqual(
|
||||
sorted(capture.records.mapped('name')),
|
||||
sorted(('poiluchette@test.example.com', 'Jean Poilvache'))
|
||||
)
|
||||
self.assertEqual(
|
||||
sorted(capture.records.mapped('email')),
|
||||
sorted(exp_emails)
|
||||
)
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestUserTours(HttpCaseWithUserDemo):
|
||||
|
||||
def test_user_modify_own_profile(self):
|
||||
"""" A user should be able to modify their own profile.
|
||||
Even if that user does not have access rights to write on the res.users model. """
|
||||
if 'hr.employee' in self.env and not self.user_demo.employee_id:
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'Marc Demo',
|
||||
'user_id': self.user_demo.id,
|
||||
})
|
||||
self.user_demo.tz = "Europe/Brussels"
|
||||
self.start_tour("/web", "mail/static/tests/tours/user_modify_own_profile_tour.js", login="demo")
|
||||
self.assertEqual(self.user_demo.email, "updatedemail@example.com")
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
class TestResUsersSettings(MailCommon):
|
||||
|
||||
@users('employee')
|
||||
def test_find_or_create_for_user_should_create_record_if_not_existing(self):
|
||||
settings = self.user_employee.res_users_settings_ids
|
||||
self.assertFalse(settings, "no records should exist")
|
||||
|
||||
self.env['res.users.settings']._find_or_create_for_user(self.user_employee)
|
||||
settings = self.user_employee.res_users_settings_ids
|
||||
self.assertTrue(settings, "a record should be created after _find_or_create_for_user is called")
|
||||
|
||||
@users('employee')
|
||||
def test_find_or_create_for_user_should_return_correct_res_users_settings(self):
|
||||
settings = self.env['res.users.settings'].create({
|
||||
'user_id': self.user_employee.id,
|
||||
})
|
||||
result = self.env['res.users.settings']._find_or_create_for_user(self.user_employee)
|
||||
self.assertEqual(result, settings, "Correct mail user settings should be returned")
|
||||
|
||||
@users('employee')
|
||||
def test_set_res_users_settings_should_send_notification_on_bus(self):
|
||||
settings = self.env['res.users.settings'].create({
|
||||
'is_discuss_sidebar_category_channel_open': False,
|
||||
'is_discuss_sidebar_category_chat_open': False,
|
||||
'user_id': self.user_employee.id,
|
||||
})
|
||||
|
||||
with self.assertBus(
|
||||
[(self.cr.dbname, 'res.partner', self.partner_employee.id)],
|
||||
[{
|
||||
'type': 'res.users.settings/insert',
|
||||
'payload': {
|
||||
'id': settings.id,
|
||||
'is_discuss_sidebar_category_chat_open': True,
|
||||
},
|
||||
}]):
|
||||
settings.set_res_users_settings({'is_discuss_sidebar_category_chat_open': True})
|
||||
|
||||
@users('employee')
|
||||
def test_set_res_users_settings_should_set_settings_properly(self):
|
||||
settings = self.env['res.users.settings'].create({
|
||||
'is_discuss_sidebar_category_channel_open': False,
|
||||
'is_discuss_sidebar_category_chat_open': False,
|
||||
'user_id': self.user_employee.id,
|
||||
})
|
||||
settings.set_res_users_settings({'is_discuss_sidebar_category_chat_open': True})
|
||||
self.assertEqual(
|
||||
settings.is_discuss_sidebar_category_chat_open,
|
||||
True,
|
||||
"category state should be updated correctly"
|
||||
)
|
||||
873
odoo-bringout-oca-ocb-mail/mail/tests/test_rtc.py
Normal file
873
odoo-bringout-oca-ocb-mail/mail/tests/test_rtc.py
Normal file
|
|
@ -0,0 +1,873 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import fields
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('RTC')
|
||||
class TestChannelInternals(MailCommon):
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_01_join_call(self):
|
||||
"""Join call should remove existing sessions, remove invitation, create a new session, and return data."""
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='Test Channel', group_id=self.env.ref('base.group_user').id)['id'])
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
channel_member._rtc_join_call()
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # end of previous session
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.channel.rtc.session/ended',
|
||||
'payload': {
|
||||
'sessionId': channel_member.rtc_session_ids.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert-and-unlink', [{'id': channel_member.rtc_session_ids.id}])],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert', [{
|
||||
'id': channel_member.rtc_session_ids.id + 1,
|
||||
'channelMember': {
|
||||
"id": channel_member.id,
|
||||
"channel": {"id": channel_member.channel_id.id},
|
||||
"persona": {
|
||||
"partner": {
|
||||
"id": channel_member.partner_id.id,
|
||||
"name": channel_member.partner_id.name,
|
||||
"im_status": channel_member.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
'isCameraOn': False,
|
||||
'isDeaf': False,
|
||||
'isSelfMuted': False,
|
||||
'isScreenSharingOn': False,
|
||||
}])],
|
||||
},
|
||||
},
|
||||
]
|
||||
):
|
||||
res = channel_member._rtc_join_call()
|
||||
self.assertEqual(res, {
|
||||
'iceServers': False,
|
||||
'rtcSessions': [
|
||||
('insert', [{
|
||||
'id': channel_member.rtc_session_ids.id,
|
||||
'channelMember': {
|
||||
"id": channel_member.id,
|
||||
"channel": {"id": channel_member.channel_id.id},
|
||||
"persona": {
|
||||
"partner": {
|
||||
"id": channel_member.partner_id.id,
|
||||
"name": channel_member.partner_id.name,
|
||||
"im_status": channel_member.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
'isCameraOn': False,
|
||||
'isDeaf': False,
|
||||
'isSelfMuted': False,
|
||||
'isScreenSharingOn': False,
|
||||
}]),
|
||||
('insert-and-unlink', [{'id': channel_member.rtc_session_ids.id - 1}]),
|
||||
],
|
||||
'sessionId': channel_member.rtc_session_ids.id,
|
||||
})
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_10_start_call_in_chat_should_invite_all_members_to_call(self):
|
||||
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
channel_member_test_user = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == test_user.partner_id)
|
||||
channel_member._rtc_join_call()
|
||||
last_rtc_session_id = channel_member.rtc_session_ids.id
|
||||
channel_member._rtc_leave_call()
|
||||
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update new session
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # message_post "started a live conference" (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # update of last interest (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update of last interest (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # incoming invitation
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert', [{
|
||||
'id': last_rtc_session_id + 1,
|
||||
'channelMember': {
|
||||
"id": channel_member.id,
|
||||
"channel": {"id": channel_member.channel_id.id},
|
||||
"persona": {
|
||||
"partner": {
|
||||
"id": channel_member.partner_id.id,
|
||||
"name": channel_member.partner_id.name,
|
||||
"im_status": channel_member.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
'isCameraOn': False,
|
||||
'isDeaf': False,
|
||||
'isSelfMuted': False,
|
||||
'isScreenSharingOn': False,
|
||||
}])],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'invitedMembers': [('insert', [{
|
||||
'id': channel_member_test_user.id,
|
||||
'channel': {'id': channel_member_test_user.channel_id.id},
|
||||
'persona': {
|
||||
'partner': {
|
||||
'id': channel_member_test_user.partner_id.id,
|
||||
'name': channel_member_test_user.partner_id.name,
|
||||
'im_status': channel_member_test_user.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
}])],
|
||||
},
|
||||
},
|
||||
]
|
||||
):
|
||||
res = channel_member._rtc_join_call()
|
||||
self.assertIn('invitedMembers', res)
|
||||
self.assertEqual(res['invitedMembers'], [('insert', [{
|
||||
'id': channel_member_test_user.id,
|
||||
'channel': {'id': channel_member_test_user.channel_id.id},
|
||||
'persona': {
|
||||
'partner': {
|
||||
'id': channel_member_test_user.partner_id.id,
|
||||
'name': channel_member_test_user.partner_id.name,
|
||||
'im_status': channel_member_test_user.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
}])])
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_11_start_call_in_group_should_invite_all_members_to_call(self):
|
||||
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
|
||||
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
|
||||
channel.add_members(guest_ids=test_guest.ids)
|
||||
channel_member_test_user = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == test_user.partner_id)
|
||||
channel_member_test_guest = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.guest_id == test_guest)
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
channel_member._rtc_join_call()
|
||||
last_rtc_session_id = channel_member.rtc_session_ids.id
|
||||
channel_member._rtc_leave_call()
|
||||
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update new session
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # message_post "started a live conference" (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # update of last interest (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update of last interest (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # incoming invitation
|
||||
(self.cr.dbname, 'mail.guest', test_guest.id), # incoming invitation
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert', [{
|
||||
'id': last_rtc_session_id + 1,
|
||||
'channelMember': {
|
||||
"id": channel_member.id,
|
||||
"channel": {"id": channel_member.channel_id.id},
|
||||
"persona": {
|
||||
"partner": {
|
||||
"id": channel_member.partner_id.id,
|
||||
"name": channel_member.partner_id.name,
|
||||
"im_status": channel_member.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
'isCameraOn': False,
|
||||
'isDeaf': False,
|
||||
'isSelfMuted': False,
|
||||
'isScreenSharingOn': False,
|
||||
}])],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert', [{
|
||||
'id': last_rtc_session_id + 1,
|
||||
'channelMember': {
|
||||
"id": channel_member.id,
|
||||
"channel": {"id": channel_member.channel_id.id},
|
||||
"persona": {
|
||||
"partner": {
|
||||
"id": channel_member.partner_id.id,
|
||||
"name": channel_member.partner_id.name,
|
||||
"im_status": channel_member.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
'isCameraOn': False,
|
||||
'isDeaf': False,
|
||||
'isSelfMuted': False,
|
||||
'isScreenSharingOn': False,
|
||||
}])],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'invitedMembers': [('insert', [
|
||||
{
|
||||
'id': channel_member_test_user.id,
|
||||
'channel': {'id': channel_member_test_user.channel_id.id},
|
||||
'persona': {
|
||||
'partner': {
|
||||
'id': channel_member_test_user.partner_id.id,
|
||||
'name': channel_member_test_user.partner_id.name,
|
||||
'im_status': channel_member_test_user.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'id': channel_member_test_guest.id,
|
||||
'channel': {'id': channel_member_test_guest.channel_id.id},
|
||||
'persona': {
|
||||
'guest': {
|
||||
'id': channel_member_test_guest.guest_id.id,
|
||||
'name': channel_member_test_guest.guest_id.name,
|
||||
'im_status': channel_member_test_guest.guest_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
])],
|
||||
},
|
||||
},
|
||||
]
|
||||
):
|
||||
res = channel_member._rtc_join_call()
|
||||
self.assertIn('invitedMembers', res)
|
||||
self.assertEqual(res['invitedMembers'], [('insert', [
|
||||
{
|
||||
'id': channel_member_test_user.id,
|
||||
'channel': {'id': channel_member_test_user.channel_id.id},
|
||||
'persona': {
|
||||
'partner': {
|
||||
'id': channel_member_test_user.partner_id.id,
|
||||
'name': channel_member_test_user.partner_id.name,
|
||||
'im_status': channel_member_test_user.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'id': channel_member_test_guest.id,
|
||||
'channel': {'id': channel_member_test_guest.channel_id.id},
|
||||
'persona': {
|
||||
'guest': {
|
||||
'id': channel_member_test_guest.guest_id.id,
|
||||
'name': channel_member_test_guest.guest_id.name,
|
||||
'im_status': channel_member_test_guest.guest_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
])])
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_20_join_call_should_cancel_pending_invitations(self):
|
||||
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
|
||||
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
|
||||
channel.add_members(guest_ids=test_guest.ids)
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
channel_member._rtc_join_call()
|
||||
|
||||
channel_member_test_user = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == test_user.partner_id)
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update invitation
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'rtcInvitingSession': [('unlink',)],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'invitedMembers': [('insert-and-unlink', [{
|
||||
'id': channel_member_test_user.id,
|
||||
'channel': {'id': channel_member_test_user.channel_id.id},
|
||||
'persona': {
|
||||
'partner': {
|
||||
'id': channel_member_test_user.partner_id.id,
|
||||
'name': channel_member_test_user.partner_id.name,
|
||||
'im_status': channel_member_test_user.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
}])],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert', [
|
||||
{
|
||||
'id': channel_member.rtc_session_ids.id + 1,
|
||||
'channelMember': {
|
||||
"id": channel_member_test_user.id,
|
||||
"channel": {"id": channel_member_test_user.channel_id.id},
|
||||
"persona": {
|
||||
"partner": {
|
||||
"id": channel_member_test_user.partner_id.id,
|
||||
"name": channel_member_test_user.partner_id.name,
|
||||
"im_status": channel_member_test_user.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
'isCameraOn': False,
|
||||
'isDeaf': False,
|
||||
'isSelfMuted': False,
|
||||
'isScreenSharingOn': False,
|
||||
},
|
||||
])],
|
||||
},
|
||||
},
|
||||
]
|
||||
):
|
||||
channel_member_test_user._rtc_join_call()
|
||||
|
||||
channel_member_test_guest = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.guest_id == test_guest)
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'mail.guest', test_guest.id), # update invitation
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'rtcInvitingSession': [('unlink',)],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'invitedMembers': [('insert-and-unlink', [{
|
||||
'id': channel_member_test_guest.id,
|
||||
'channel': {'id': channel_member_test_guest.channel_id.id},
|
||||
'persona': {
|
||||
'guest': {
|
||||
'id': channel_member_test_guest.guest_id.id,
|
||||
'name': channel_member_test_guest.guest_id.name,
|
||||
'im_status': channel_member_test_guest.guest_id.im_status,
|
||||
},
|
||||
},
|
||||
}])],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert', [
|
||||
{
|
||||
'id': channel_member.rtc_session_ids.id + 2,
|
||||
'channelMember': {
|
||||
"id": channel_member_test_guest.id,
|
||||
"channel": {"id": channel_member_test_guest.channel_id.id},
|
||||
"persona": {
|
||||
"guest": {
|
||||
"id": channel_member_test_guest.guest_id.id,
|
||||
"name": channel_member_test_guest.guest_id.name,
|
||||
'im_status': channel_member_test_guest.guest_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
'isCameraOn': False,
|
||||
'isDeaf': False,
|
||||
'isSelfMuted': False,
|
||||
'isScreenSharingOn': False,
|
||||
},
|
||||
])],
|
||||
},
|
||||
},
|
||||
]
|
||||
):
|
||||
channel_member_test_guest._rtc_join_call()
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_21_leave_call_should_cancel_pending_invitations(self):
|
||||
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
|
||||
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
|
||||
channel.add_members(guest_ids=test_guest.ids)
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
channel_member._rtc_join_call()
|
||||
|
||||
channel_member_test_user = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == test_user.partner_id)
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update invitation
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'rtcInvitingSession': [('unlink',)],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'invitedMembers': [('insert-and-unlink', [{
|
||||
'id': channel_member_test_user.id,
|
||||
'channel': {'id': channel_member_test_user.channel_id.id},
|
||||
'persona': {
|
||||
'partner': {
|
||||
'id': channel_member_test_user.partner_id.id,
|
||||
'name': channel_member_test_user.partner_id.name,
|
||||
'im_status': channel_member_test_user.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
}])],
|
||||
},
|
||||
},
|
||||
]
|
||||
):
|
||||
channel_member_test_user._rtc_leave_call()
|
||||
|
||||
channel_member_test_guest = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.guest_id == test_guest)
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'mail.guest', test_guest.id), # update invitation
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'rtcInvitingSession': [('unlink',)],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'invitedMembers': [('insert-and-unlink', [{
|
||||
'id': channel_member_test_guest.id,
|
||||
'channel': {'id': channel_member_test_guest.channel_id.id},
|
||||
'persona': {
|
||||
'guest': {
|
||||
'id': channel_member_test_guest.guest_id.id,
|
||||
'name': channel_member_test_guest.guest_id.name,
|
||||
'im_status': channel_member_test_guest.guest_id.im_status,
|
||||
},
|
||||
},
|
||||
}])],
|
||||
},
|
||||
},
|
||||
]
|
||||
):
|
||||
channel_member_test_guest._rtc_leave_call()
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_25_lone_call_participant_leaving_call_should_cancel_pending_invitations(self):
|
||||
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
|
||||
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
|
||||
channel.add_members(guest_ids=test_guest.ids)
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
channel_member_test_user = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == test_user.partner_id)
|
||||
channel_member_test_guest = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.guest_id == test_guest)
|
||||
channel_member._rtc_join_call()
|
||||
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # end session
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update invitation
|
||||
(self.cr.dbname, 'mail.guest', test_guest.id), # update invitation
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.channel.rtc.session/ended',
|
||||
'payload': {
|
||||
'sessionId': channel_member.rtc_session_ids.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'rtcInvitingSession': [('unlink',)],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'rtcInvitingSession': [('unlink',)],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'invitedMembers': [('insert-and-unlink', [
|
||||
{
|
||||
'id': channel_member_test_user.id,
|
||||
'channel': {'id': channel_member_test_user.channel_id.id},
|
||||
'persona': {
|
||||
'partner': {
|
||||
'id': channel_member_test_user.partner_id.id,
|
||||
'name': channel_member_test_user.partner_id.name,
|
||||
'im_status': channel_member_test_user.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'id': channel_member_test_guest.id,
|
||||
'channel': {'id': channel_member_test_guest.channel_id.id},
|
||||
'persona': {
|
||||
'guest': {
|
||||
'id': channel_member_test_guest.guest_id.id,
|
||||
'name': channel_member_test_guest.guest_id.name,
|
||||
'im_status': channel_member_test_guest.guest_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
])],
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert-and-unlink', [{'id': channel_member.rtc_session_ids.id}])],
|
||||
},
|
||||
},
|
||||
]
|
||||
):
|
||||
channel_member._rtc_leave_call()
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_30_add_members_while_in_call_should_invite_new_members_to_call(self):
|
||||
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
|
||||
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda member: member.partner_id == self.user_employee.partner_id)
|
||||
channel_member._rtc_join_call()
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
|
||||
with self.mock_bus():
|
||||
channel.add_members(partner_ids=test_user.partner_id.ids, guest_ids=test_guest.ids, invite_to_rtc_call=True)
|
||||
|
||||
channel_member_test_user = channel.sudo().channel_member_ids.filtered(lambda member: member.partner_id == test_user.partner_id)
|
||||
channel_member_test_guest = channel.sudo().channel_member_ids.filtered(lambda member: member.guest_id == test_guest)
|
||||
found_bus_notifs = self.assertBusNotifications(
|
||||
[
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # channel joined (not asserted below)
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # message_post "invited" (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # update of last interest (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update of last interest (not asserted below)
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # new members (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # incoming invitation
|
||||
(self.cr.dbname, 'mail.guest', test_guest.id), # incoming invitation
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
|
||||
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # update of last interest (not asserted below)
|
||||
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update of last interest (not asserted below)
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # new member (guest) (not asserted below)
|
||||
(self.cr.dbname, 'mail.guest', test_guest.id), # channel joined for guest (not asserted below)
|
||||
],
|
||||
message_items=[
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'rtcInvitingSession': {
|
||||
'id': channel_member.rtc_session_ids.id,
|
||||
'channelMember': {
|
||||
"id": channel_member.id,
|
||||
"channel": {"id": channel_member.channel_id.id},
|
||||
"persona": {
|
||||
"partner": {
|
||||
"id": channel_member.partner_id.id,
|
||||
"name": channel_member.partner_id.name,
|
||||
"im_status": channel_member.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
'isCameraOn': False,
|
||||
'isDeaf': False,
|
||||
'isSelfMuted': False,
|
||||
'isScreenSharingOn': False,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'rtcInvitingSession': {
|
||||
'id': channel_member.rtc_session_ids.id,
|
||||
'channelMember': {
|
||||
"id": channel_member.id,
|
||||
"channel": {"id": channel_member.channel_id.id},
|
||||
"persona": {
|
||||
"partner": {
|
||||
"id": channel_member.partner_id.id,
|
||||
"name": channel_member.partner_id.name,
|
||||
"im_status": channel_member.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
'isCameraOn': False,
|
||||
'isDeaf': False,
|
||||
'isSelfMuted': False,
|
||||
'isScreenSharingOn': False,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.thread/insert',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'model': 'mail.channel',
|
||||
'invitedMembers': [('insert', [
|
||||
{
|
||||
'id': channel_member_test_user.id,
|
||||
'channel': {'id': channel_member_test_user.channel_id.id},
|
||||
'persona': {
|
||||
'partner': {
|
||||
'id': channel_member_test_user.partner_id.id,
|
||||
'name': channel_member_test_user.partner_id.name,
|
||||
'im_status': channel_member_test_user.partner_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'id': channel_member_test_guest.id,
|
||||
'channel': {'id': channel_member_test_guest.channel_id.id},
|
||||
'persona': {
|
||||
'guest': {
|
||||
'id': channel_member_test_guest.guest_id.id,
|
||||
'name': channel_member_test_guest.guest_id.name,
|
||||
'im_status': channel_member_test_guest.guest_id.im_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
])],
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertEqual(self._new_bus_notifs, found_bus_notifs)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_40_leave_call_should_remove_existing_sessions_of_user_in_channel_and_return_data(self):
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
channel_member._rtc_join_call()
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # end session
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of sessions
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.channel.rtc.session/ended',
|
||||
'payload': {
|
||||
'sessionId': channel_member.rtc_session_ids.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert-and-unlink', [{'id': channel_member.rtc_session_ids.id}])],
|
||||
},
|
||||
},
|
||||
],
|
||||
):
|
||||
channel_member._rtc_leave_call()
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_50_garbage_collect_should_remove_old_sessions_and_notify_data(self):
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
channel_member._rtc_join_call()
|
||||
channel_member.rtc_session_ids.flush_model()
|
||||
channel_member.rtc_session_ids._write({'write_date': fields.Datetime.now() - relativedelta(days=2)})
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # session ended
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of sessions
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.channel.rtc.session/ended',
|
||||
'payload': {
|
||||
'sessionId': channel_member.rtc_session_ids.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert-and-unlink', [{'id': channel_member.rtc_session_ids.id}])],
|
||||
},
|
||||
},
|
||||
],
|
||||
):
|
||||
self.env['mail.channel.rtc.session'].sudo()._gc_inactive_sessions()
|
||||
self.assertFalse(channel_member.rtc_session_ids)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_51_action_disconnect_should_remove_selected_session_and_notify_data(self):
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
channel_member._rtc_join_call()
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # session ended
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of sessions
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.channel.rtc.session/ended',
|
||||
'payload': {
|
||||
'sessionId': channel_member.rtc_session_ids.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert-and-unlink', [{'id': channel_member.rtc_session_ids.id}])],
|
||||
},
|
||||
},
|
||||
],
|
||||
):
|
||||
channel_member.rtc_session_ids.action_disconnect()
|
||||
self.assertFalse(channel_member.rtc_session_ids)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_60_rtc_sync_sessions_should_gc_and_return_outdated_and_active_sessions(self):
|
||||
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
|
||||
channel_member = channel.sudo().channel_member_ids.filtered(lambda channel_member: channel_member.partner_id == self.user_employee.partner_id)
|
||||
join_call_values = channel_member._rtc_join_call()
|
||||
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
|
||||
test_channel_member = self.env['mail.channel.member'].create({
|
||||
'guest_id': test_guest.id,
|
||||
'channel_id': channel.id,
|
||||
})
|
||||
test_session = self.env['mail.channel.rtc.session'].sudo().create({'channel_member_id': test_channel_member.id})
|
||||
test_session.flush_model()
|
||||
test_session._write({'write_date': fields.Datetime.now() - relativedelta(days=2)})
|
||||
unused_ids = [9998, 9999]
|
||||
self.env['bus.bus'].sudo().search([]).unlink()
|
||||
with self.assertBus(
|
||||
[
|
||||
(self.cr.dbname, 'mail.guest', test_guest.id), # session ended
|
||||
(self.cr.dbname, 'mail.channel', channel.id), # update list of sessions
|
||||
],
|
||||
[
|
||||
{
|
||||
'type': 'mail.channel.rtc.session/ended',
|
||||
'payload': {
|
||||
'sessionId': test_session.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
'type': 'mail.channel/rtc_sessions_update',
|
||||
'payload': {
|
||||
'id': channel.id,
|
||||
'rtcSessions': [('insert-and-unlink', [{'id': test_session.id}])],
|
||||
},
|
||||
},
|
||||
],
|
||||
):
|
||||
current_rtc_sessions, outdated_rtc_sessions = channel_member._rtc_sync_sessions(check_rtc_session_ids=[join_call_values['sessionId']] + unused_ids)
|
||||
self.assertEqual(channel_member.rtc_session_ids, current_rtc_sessions)
|
||||
self.assertEqual(unused_ids, outdated_rtc_sessions.ids)
|
||||
self.assertFalse(outdated_rtc_sessions.exists())
|
||||
31
odoo-bringout-oca-ocb-mail/mail/tests/test_uninstall.py
Normal file
31
odoo-bringout-oca-ocb-mail/mail/tests/test_uninstall.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged, TransactionCase
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestMailUninstall(TransactionCase):
|
||||
def test_unlink_model(self):
|
||||
model = self.env['ir.model'].create({
|
||||
'name': 'Test Model',
|
||||
'model': 'x_test_model',
|
||||
'state': 'manual',
|
||||
'is_mail_thread': True,
|
||||
})
|
||||
activity_type = self.env['mail.activity.type'].create({
|
||||
'name': 'Test Activity Type',
|
||||
'res_model': model.model,
|
||||
})
|
||||
record = self.env[model.model].create({})
|
||||
|
||||
activity = self.env['mail.activity'].create({
|
||||
'activity_type_id': activity_type.id,
|
||||
'res_model_id': model.id,
|
||||
'res_id': record.id,
|
||||
})
|
||||
|
||||
model.unlink()
|
||||
self.assertFalse(model.exists())
|
||||
self.assertFalse(activity_type.exists())
|
||||
self.assertFalse(activity.exists())
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestUpdateNotification(TransactionCase):
|
||||
def test_user_count(self):
|
||||
ping_msg = self.env['publisher_warranty.contract'].with_context(active_test=False)._get_message()
|
||||
user_count = self.env['res.users'].search_count([('active', '=', True)])
|
||||
self.assertEqual(ping_msg.get('nbr_users'), user_count, 'Update Notification: Users count is badly computed in ping message')
|
||||
share_user_count = self.env['res.users'].search_count([('active', '=', True), ('share', '=', True)])
|
||||
self.assertEqual(ping_msg.get('nbr_share_users'), share_user_count, 'Update Notification: Portal Users count is badly computed in ping message')
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
|
||||
|
||||
class TestUserModifyOwnProfile(HttpCaseWithUserDemo):
|
||||
pass
|
||||
Loading…
Add table
Add a link
Reference in a new issue