Odoo18-Base/addons/test_mail_full/tests/test_portal.py
2025-03-10 11:12:23 +07:00

217 lines
8.8 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from werkzeug.urls import url_parse, url_decode
import json
from odoo import http
from odoo.addons.test_mail_full.tests.common import TestMailFullCommon
from odoo.addons.test_mail_sms.tests.common import TestSMSRecipients
from odoo.tests import tagged, users
from odoo.tests.common import HttpCase
@tagged('portal')
class TestPortal(HttpCase, TestMailFullCommon, TestSMSRecipients):
def setUp(self):
super(TestPortal, self).setUp()
self.record_portal = self.env['mail.test.portal'].create({
'partner_id': self.partner_1.id,
'name': 'Test Portal Record',
})
self.record_portal._portal_ensure_token()
@tagged('-at_install', 'post_install', 'portal')
class TestPortalControllers(TestPortal):
def test_redirect_to_records(self):
""" Test redirection of portal-enabled records """
# Test Case 0: as anonymous, cannot access, redirect to web/login
response = self.url_open('/mail/view?model=%s&res_id=%s' % (
self.record_portal._name,
self.record_portal.id), timeout=15)
path = url_parse(response.url).path
self.assertEqual(path, '/web/login')
# Test Case 1: as admin, can access record
self.authenticate(self.user_admin.login, self.user_admin.login)
response = self.url_open('/mail/view?model=%s&res_id=%s' % (
self.record_portal._name,
self.record_portal.id), timeout=15)
self.assertEqual(response.status_code, 200)
fragment = url_parse(response.url).fragment
params = url_decode(fragment)
self.assertEqual(params['cids'], '%s' % self.user_admin.company_id.id)
self.assertEqual(params['id'], '%s' % self.record_portal.id)
self.assertEqual(params['model'], self.record_portal._name)
def test_redirect_to_records_norecord(self):
""" Check specific use case of missing model, should directly redirect
to login page. """
for model, res_id in [
(False, self.record_portal.id),
('', self.record_portal.id),
(self.record_portal._name, False),
(self.record_portal._name, ''),
(False, False),
('wrong.model', self.record_portal.id),
(self.record_portal._name, -4),
]:
response = self.url_open(
'/mail/view?model=%s&res_id=%s' % (model, res_id),
timeout=15
)
path = url_parse(response.url).path
self.assertEqual(
path, '/web/login',
'Failed with %s - %s' % (model, res_id)
)
def test_portal_avatar_with_access_token(self):
mail_record = self.env['mail.message'].create({
'author_id': self.record_portal.partner_id.id,
'model': self.record_portal._name,
'res_id': self.record_portal.id,
})
response = self.url_open(f'/mail/avatar/mail.message/{mail_record.id}/author_avatar/50x50?access_token={self.record_portal.access_token}')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.headers.get('Content-Type'), 'image/png')
self.assertRegex(response.headers.get('Content-Disposition', ''), r'mail_message-\d+-author_avatar\.png')
placeholder_response = self.url_open(f'/mail/avatar/mail.message/{mail_record.id}/author_avatar/50x50?access_token={self.record_portal.access_token + "a"}') # false token
self.assertEqual(placeholder_response.status_code, 200)
self.assertEqual(placeholder_response.headers.get('Content-Type'), 'image/png')
self.assertRegex(placeholder_response.headers.get('Content-Disposition', ''), r'placeholder\.png')
no_token_response = self.url_open(f'/mail/avatar/mail.message/{mail_record.id}/author_avatar/50x50')
self.assertEqual(no_token_response.status_code, 200)
self.assertEqual(no_token_response.headers.get('Content-Type'), 'image/png')
self.assertRegex(no_token_response.headers.get('Content-Disposition', ''), r'placeholder\.png')
def test_portal_avatar_with_hash_pid(self):
self.authenticate(None, None)
post_url = f"{self.record_portal.get_base_url()}/mail/chatter_post"
res = self.opener.post(
url=post_url,
json={
'params': {
'csrf_token': http.Request.csrf_token(self),
'message': 'Test',
'res_model': self.record_portal._name,
'res_id': self.record_portal.id,
'hash': self.record_portal._sign_token(self.partner_2.id),
'pid': self.partner_2.id,
},
},
)
res.raise_for_status()
self.assertNotIn("error", res.json())
message = self.record_portal.message_ids[0]
response = self.url_open(
f'/mail/avatar/mail.message/{message.id}/author_avatar/50x50?_hash={self.record_portal._sign_token(self.partner_2.id)}&pid={self.partner_2.id}')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.headers.get('Content-Type'), 'image/png')
self.assertRegex(response.headers.get('Content-Disposition', ''), r'mail_message-\d+-author_avatar\.png')
placeholder_response = self.url_open(
f'/mail/avatar/mail.message/{message.id}/author_avatar/50x50?_hash={self.record_portal._sign_token(self.partner_2.id) + "a"}&pid={self.partner_2.id}') # false hash
self.assertEqual(placeholder_response.status_code, 200)
self.assertEqual(placeholder_response.headers.get('Content-Type'), 'image/png')
self.assertRegex(placeholder_response.headers.get('Content-Disposition', ''), r'placeholder\.png')
def test_portal_message_fetch(self):
"""Test retrieving chatter messages through the portal controller"""
self.authenticate(None, None)
message_fetch_url = '/mail/chatter_fetch'
payload = json.dumps({
'jsonrpc': '2.0',
'method': 'call',
'id': 0,
'params': {
'res_model': 'mail.test.portal',
'res_id': self.record_portal.id,
'token': self.record_portal.access_token,
},
})
def get_chatter_message_count():
res = self.url_open(
url=message_fetch_url,
data=payload,
headers={'Content-Type': 'application/json'}
)
return res.json().get('result', {}).get('message_count', 0)
self.assertEqual(get_chatter_message_count(), 0)
for _ in range(8):
self.record_portal.message_post(
body='Test',
author_id=self.partner_1.id,
message_type='comment',
subtype_id=self.env.ref('mail.mt_comment').id,
)
self.assertEqual(get_chatter_message_count(), 8)
# Empty the body of a few messages
for i in (2, 5, 6):
self.record_portal.message_ids[i].body = ""
# Empty messages should be ignored
self.assertEqual(get_chatter_message_count(), 5)
def test_portal_share_comment(self):
""" Test posting through portal controller allowing to use a hash to
post wihtout access rights. """
self.authenticate(None, None)
post_url = f"{self.record_portal.get_base_url()}/mail/chatter_post"
# test as not logged
self.opener.post(
url=post_url,
json={
'params': {
'csrf_token': http.Request.csrf_token(self),
'hash': self.record_portal._sign_token(self.partner_2.id),
'message': 'Test',
'pid': self.partner_2.id,
'redirect': '/',
'res_model': self.record_portal._name,
'res_id': self.record_portal.id,
'token': self.record_portal.access_token,
},
},
)
message = self.record_portal.message_ids[0]
self.assertIn('Test', message.body)
self.assertEqual(message.author_id, self.partner_2)
@tagged('portal')
class TestPortalMixin(TestPortal):
@users('employee')
def test_portal_mixin(self):
""" Test internals of portal mixin """
customer = self.partner_1.with_env(self.env)
record_portal = self.env['mail.test.portal'].create({
'partner_id': customer.id,
'name': 'Test Portal Record',
})
self.assertFalse(record_portal.access_token)
self.assertEqual(record_portal.access_url, '/my/test_portal/%s' % record_portal.id)
record_portal._portal_ensure_token()
self.assertTrue(record_portal.access_token)