mirror of
https://github.com/bringout/oca-ocb-test.git
synced 2026-04-23 09:02:03 +02:00
19.0 vanilla
This commit is contained in:
parent
38c6088dcc
commit
d9452d2060
243 changed files with 30797 additions and 10815 deletions
|
|
@ -13,7 +13,6 @@ pip install odoo-bringout-oca-ocb-test_mail_full
|
|||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- mail
|
||||
- mail_bot
|
||||
- portal
|
||||
|
|
@ -26,34 +25,12 @@ This addon depends on:
|
|||
- test_mail_sms
|
||||
- test_mass_mailing
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Mail Tests (Full)
|
||||
- **Version**: 1.0
|
||||
- **Category**: Hidden
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_mail_full`.
|
||||
- Repository: https://github.com/OCA/OCB
|
||||
- Branch: 19.0
|
||||
- Path: addons/test_mail_full
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
This package preserves the original LGPL-3 license.
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-test_mail_full"
|
||||
version = "16.0.0"
|
||||
description = "Mail Tests (Full) - Mail Tests: performances and tests specific to mail with all sub-modules"
|
||||
description = "Mail Tests (Full) -
|
||||
Mail Tests: performances and tests specific to mail with all sub-modules
|
||||
"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-mail>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mail_bot>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-portal>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-rating>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mass_mailing>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mass_mailing_sms>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-phone_validation>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-sms>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mail>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mail_sms>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mass_mailing>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mail>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-mail_bot>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-portal>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-rating>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-mass_mailing>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-mass_mailing_sms>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-phone_validation>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-sms>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mail>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mail_sms>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-test_mass_mailing>=19.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
|
@ -26,7 +28,7 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import tests
|
||||
|
|
|
|||
|
|
@ -28,15 +28,18 @@ real applications. """,
|
|||
'data/mail_message_subtype_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/ir_rule_data.xml',
|
||||
'views/test_portal_template.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.qunit_suite_tests': [
|
||||
'test_mail_full/static/tests/qunit_suite_tests/*.js',
|
||||
'web.assets_unit_tests': [
|
||||
'test_mail_full/static/tests/**/*',
|
||||
('remove', 'test_mail_full/static/tests/tours/**/*'),
|
||||
],
|
||||
'web.tests_assets': [
|
||||
'test_mail_full/static/tests/helpers/*.js',
|
||||
'web.assets_tests': [
|
||||
'test_mail_full/static/tests/tours/**/*',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'author': 'Odoo S.A.',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import portal
|
||||
|
|
|
|||
|
|
@ -9,6 +9,34 @@ class PortalTest(http.Controller):
|
|||
def test_portal_record_view(self, res_id, access_token=None, **kwargs):
|
||||
return request.make_response(f'Record view of test_portal {res_id} ({access_token}, {kwargs})')
|
||||
|
||||
@http.route("/my/test_portal_records/<int:res_id>", type="http", auth="public", website=True)
|
||||
def test_portal_record_page(self, res_id, **kwargs):
|
||||
record = request.env["mail.test.portal"]._get_thread_with_access(res_id, **kwargs)
|
||||
values = {
|
||||
"object": record,
|
||||
"token": kwargs.get("token"),
|
||||
"hash": kwargs.get("hash", None),
|
||||
"pid": kwargs.get("pid", None),
|
||||
}
|
||||
return request.render("test_mail_full.test_portal_template", values)
|
||||
|
||||
@http.route("/my/test_portal_rating_records/<int:res_id>", type="http", auth="public", website=True)
|
||||
def test_portal_rating_record_page(self, res_id, **kwargs):
|
||||
access_params = {
|
||||
"hash": kwargs.get("hash"),
|
||||
"pid": kwargs.get("pid"),
|
||||
"token": kwargs.get("token"),
|
||||
}
|
||||
record = request.env["mail.test.rating"]._get_thread_with_access(res_id, **access_params)
|
||||
return request.render(
|
||||
"test_mail_full.test_portal_template",
|
||||
{
|
||||
**access_params,
|
||||
"object": record,
|
||||
"display_rating": kwargs.get("display_rating"),
|
||||
},
|
||||
)
|
||||
|
||||
@http.route('/test_portal/public_type/<int:res_id>', type='http', auth='public', methods=['GET'])
|
||||
def test_public_record_view(self, res_id):
|
||||
return request.make_response(f'Testing public controller for {res_id}')
|
||||
|
|
|
|||
|
|
@ -1,224 +0,0 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * test_mail_full
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server saas~12.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-09 10:49+0000\n"
|
||||
"PO-Revision-Date: 2019-09-09 10:49+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_needaction
|
||||
msgid "Action Needed"
|
||||
msgstr "Potrebna akcija"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_attachment_count
|
||||
msgid "Attachment Count"
|
||||
msgstr "Broj priloga"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model,name:test_mail_full.model_mail_test_sms
|
||||
msgid "Chatter Model for SMS Gateway"
|
||||
msgstr "Chatter model za SMS pristupnik"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model,name:test_mail_full.model_mail_test_sms_partner
|
||||
msgid "Chatter Model for SMS Gateway (Partner only)"
|
||||
msgstr "Chatter model za SMS pristupnik (samo partner)"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__create_uid
|
||||
msgid "Created by"
|
||||
msgstr "Kreirao"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__create_date
|
||||
msgid "Created on"
|
||||
msgstr "Kreirano"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__customer_id
|
||||
msgid "Customer"
|
||||
msgstr "Kupac"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Prikazani naziv"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__email_from
|
||||
msgid "Email From"
|
||||
msgstr "Email od"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__phone_sanitized
|
||||
msgid ""
|
||||
"Field used to store sanitized phone number. Helps speeding up searches and "
|
||||
"comparisons."
|
||||
msgstr ""
|
||||
"Polje koje se koristi za pohranu sanitiziranog telefonskog broja. Pomaže "
|
||||
"ubrzati pretraživanja i usporedbe."
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_follower_ids
|
||||
msgid "Followers"
|
||||
msgstr "Pratioci"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_channel_ids
|
||||
msgid "Followers (Channels)"
|
||||
msgstr "Pratioci (Kanali)"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_partner_ids
|
||||
msgid "Followers (Partners)"
|
||||
msgstr "Pratioci (Partneri)"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_needaction
|
||||
msgid "If checked, new messages require your attention."
|
||||
msgstr "Ako je zakačeno, nove poruke će zahtjevati vašu pažnju"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_has_error
|
||||
msgid "If checked, some messages have a delivery error."
|
||||
msgstr "Ako je označeno neke poruke mogu imati grešku u dostavi."
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms_bl__phone_blacklisted
|
||||
msgid ""
|
||||
"If the email address is on the blacklist, the contact won't receive mass "
|
||||
"mailing anymore, from any list"
|
||||
msgstr ""
|
||||
"Ako je adresa e-pošte na crnoj listi, kontakt više neće primati masovnu "
|
||||
"poštu, ni s jedne liste"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_is_follower
|
||||
msgid "Is Follower"
|
||||
msgstr "Pratilac"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Zadnje mijenjano"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "Zadnji ažurirao"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Zadnje ažurirano"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_main_attachment_id
|
||||
msgid "Main Attachment"
|
||||
msgstr "Glavna zakačka"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_has_error
|
||||
msgid "Message Delivery error"
|
||||
msgstr "Greška pri isporuci poruke"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_ids
|
||||
msgid "Messages"
|
||||
msgstr "Poruke"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__mobile_nbr
|
||||
msgid "Mobile Nbr"
|
||||
msgstr "Broj mobilnog"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__name
|
||||
msgid "Name"
|
||||
msgstr "Naziv:"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_needaction_counter
|
||||
msgid "Number of Actions"
|
||||
msgstr "Broj akcija"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_has_error_counter
|
||||
msgid "Number of errors"
|
||||
msgstr "Broj grešaka"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_needaction_counter
|
||||
msgid "Number of messages requiring action"
|
||||
msgstr "Broj poruka koje zahtijevaju aktivnost"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_has_error_counter
|
||||
msgid "Number of messages with delivery error"
|
||||
msgstr "Broj poruka sa greškama pri isporuci"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__message_unread_counter
|
||||
msgid "Number of unread messages"
|
||||
msgstr "Broj nepročitanih poruka"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__phone_blacklisted
|
||||
msgid "Phone Blacklisted"
|
||||
msgstr "Telefon je stavljen na crnu listu"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__phone_nbr
|
||||
msgid "Phone Nbr"
|
||||
msgstr "Broj telefona"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_has_sms_error
|
||||
msgid "SMS Delivery error"
|
||||
msgstr "Greška u slanju SMSa"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms_bl__phone_sanitized
|
||||
msgid "Sanitized Number"
|
||||
msgstr "Sanirani broj"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__subject
|
||||
msgid "Subject"
|
||||
msgstr "Tema"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_unread
|
||||
msgid "Unread Messages"
|
||||
msgstr "Nepročitane poruke"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__message_unread_counter
|
||||
msgid "Unread Messages Counter"
|
||||
msgstr "Brojač nepročitanih poruka"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,field_description:test_mail_full.field_mail_test_sms__website_message_ids
|
||||
msgid "Website Messages"
|
||||
msgstr "Poruke sa website-a"
|
||||
|
||||
#. module: test_mail_full
|
||||
#: model:ir.model.fields,help:test_mail_full.field_mail_test_sms__website_message_ids
|
||||
msgid "Website communication history"
|
||||
msgstr "Povijest komunikacije Web stranice"
|
||||
|
|
@ -5,16 +5,16 @@ from odoo import api, fields, models
|
|||
|
||||
|
||||
class MailTestPortal(models.Model):
|
||||
""" A model intheriting from mail.thread with some fields used for portal
|
||||
sharing, like a partner, ..."""
|
||||
_description = 'Chatter Model for Portal'
|
||||
""" A model inheriting from mail.thread and portal.mixin with some fields
|
||||
used for portal sharing, like a partner, ..."""
|
||||
_name = 'mail.test.portal'
|
||||
_description = 'Chatter Model for Portal'
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'portal.mixin',
|
||||
'mail.thread',
|
||||
]
|
||||
|
||||
name = fields.Char()
|
||||
name = fields.Char('Name')
|
||||
partner_id = fields.Many2one('res.partner', 'Customer')
|
||||
user_id = fields.Many2one(comodel_name='res.users', string="Salesperson")
|
||||
|
||||
|
|
@ -26,8 +26,8 @@ class MailTestPortal(models.Model):
|
|||
|
||||
class MailTestPortalNoPartner(models.Model):
|
||||
""" A model inheriting from portal, but without any partner field """
|
||||
_description = 'Chatter Model for Portal (no partner field)'
|
||||
_name = 'mail.test.portal.no.partner'
|
||||
_description = 'Chatter Model for Portal (no partner field)'
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'portal.mixin',
|
||||
|
|
@ -66,26 +66,24 @@ class MailTestPortalPublicAccessAction(models.Model):
|
|||
|
||||
|
||||
class MailTestRating(models.Model):
|
||||
""" A model inheriting from mail.thread with some fields used for SMS
|
||||
""" A model inheriting from rating.mixin (which inherits from mail.thread) with some fields used for SMS
|
||||
gateway, like a partner, a specific mobile phone, ... """
|
||||
_description = 'Rating Model (ticket-like)'
|
||||
_name = 'mail.test.rating'
|
||||
_description = 'Rating Model (ticket-like)'
|
||||
_inherit = [
|
||||
'mail.thread',
|
||||
'mail.activity.mixin',
|
||||
'rating.mixin',
|
||||
'mail.activity.mixin',
|
||||
'portal.mixin',
|
||||
]
|
||||
_mailing_enabled = True
|
||||
_order = 'name asc, id asc'
|
||||
_order = 'id asc'
|
||||
|
||||
name = fields.Char()
|
||||
subject = fields.Char()
|
||||
name = fields.Char('Name')
|
||||
subject = fields.Char('Subject')
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
customer_id = fields.Many2one('res.partner', 'Customer')
|
||||
email_from = fields.Char(compute='_compute_email_from', precompute=True, readonly=False, store=True)
|
||||
mobile_nbr = fields.Char(compute='_compute_mobile_nbr', precompute=True, readonly=False, store=True)
|
||||
phone_nbr = fields.Char(compute='_compute_phone_nbr', precompute=True, readonly=False, store=True)
|
||||
email_from = fields.Char('From', compute='_compute_email_from', precompute=True, readonly=False, store=True)
|
||||
phone_nbr = fields.Char('Phone Number', compute='_compute_phone_nbr', precompute=True, readonly=False, store=True)
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=1)
|
||||
|
||||
@api.depends('customer_id')
|
||||
|
|
@ -96,14 +94,6 @@ class MailTestRating(models.Model):
|
|||
elif not rating.email_from:
|
||||
rating.email_from = False
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_mobile_nbr(self):
|
||||
for rating in self:
|
||||
if rating.customer_id.mobile:
|
||||
rating.mobile_nbr = rating.customer_id.mobile
|
||||
elif not rating.mobile_nbr:
|
||||
rating.mobile_nbr = False
|
||||
|
||||
@api.depends('customer_id')
|
||||
def _compute_phone_nbr(self):
|
||||
for rating in self:
|
||||
|
|
@ -112,11 +102,50 @@ class MailTestRating(models.Model):
|
|||
elif not rating.phone_nbr:
|
||||
rating.phone_nbr = False
|
||||
|
||||
def _mail_get_partner_fields(self):
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
def _phone_get_number_fields(self):
|
||||
return ['phone_nbr']
|
||||
|
||||
def _rating_apply_get_default_subtype_id(self):
|
||||
return self.env['ir.model.data']._xmlid_to_res_id("test_mail_full.mt_mail_test_rating_rating_done")
|
||||
|
||||
def _rating_get_partner(self):
|
||||
return self.customer_id
|
||||
|
||||
@api.model
|
||||
def _allow_publish_rating_stats(self):
|
||||
return True
|
||||
|
||||
|
||||
class MailTestRatingThread(models.Model):
|
||||
"""A model inheriting from mail.thread with minimal fields for testing
|
||||
rating submission without the rating mixin but with the same test code:
|
||||
|
||||
- partner_id: value returned by the base _rating_get_partner method
|
||||
- user_id: value returned by the base _rating_get_operator method
|
||||
"""
|
||||
_name = 'mail.test.rating.thread'
|
||||
_description = 'Model for testing rating without the rating mixin'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'name asc, id asc'
|
||||
|
||||
name = fields.Char('Name')
|
||||
customer_id = fields.Many2one('res.partner', 'Customer')
|
||||
user_id = fields.Many2one('res.users', 'Responsible', tracking=1)
|
||||
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['customer_id']
|
||||
|
||||
def _rating_get_partner(self):
|
||||
return self.customer_id or super()._rating_get_partner()
|
||||
|
||||
|
||||
class MailTestRatingThreadRead(models.Model):
|
||||
"""Same as MailTestRatingThread but post accessible on read by portal users."""
|
||||
_name = 'mail.test.rating.thread.read'
|
||||
_description = "Read-post rating model"
|
||||
_inherit = ["mail.test.rating.thread"]
|
||||
_order = "name asc, id asc"
|
||||
_mail_post_access = "read"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mail_test_portal_all,mail.test.portal.all,model_mail_test_portal,,0,0,0,0
|
||||
access_mail_test_portal_user,mail.test.portal.user,model_mail_test_portal,base.group_user,1,1,1,1
|
||||
access_mail_test_portal_no_partner_all,mail.test.portal.no.partner.all,model_mail_test_portal_no_partner,,1,0,0,0
|
||||
access_mail_test_portal_no_partner_portal,mail.test.portal.no.partner.all,model_mail_test_portal_no_partner,base.group_portal,1,0,0,0
|
||||
access_mail_test_portal_no_partner_user,mail.test.portal.no.partner.user,model_mail_test_portal_no_partner,base.group_user,1,1,1,1
|
||||
access_mail_test_portal_public_access_action_portal,mail.test.portal.public.access.action.portal,model_mail_test_portal_public_access_action,base.group_portal,1,0,0,0
|
||||
access_mail_test_portal_public_access_action_user,mail.test.portal.public.access.action.user,model_mail_test_portal_public_access_action,base.group_user,1,1,1,1
|
||||
access_mail_test_rating_all,mail.test.rating.all,model_mail_test_rating,,0,0,0,0
|
||||
access_mail_test_rating_portal,mail.test.rating.portal,model_mail_test_rating,base.group_portal,1,0,0,0
|
||||
access_mail_test_rating_user,mail.test.rating.user,model_mail_test_rating,base.group_user,1,1,1,1
|
||||
access_mail_test_rating_thread_all,mail.test.rating.thread.all,model_mail_test_rating_thread,,0,0,0,0
|
||||
access_mail_test_rating_thread_portal,mail.test.rating.thread.portal,model_mail_test_rating_thread,base.group_portal,1,0,0,0
|
||||
access_mail_test_rating_thread_user,mail.test.rating.thread.user,model_mail_test_rating_thread,base.group_user,1,1,1,1
|
||||
access_mail_test_rating_thread_read_portal,mail.test.rating.thread.read.portal,model_mail_test_rating_thread_read,base.group_portal,1,0,0,0
|
||||
access_mail_test_rating_thread_read_user,mail.test.rating.thread.read.user,model_mail_test_rating_thread_read,base.group_user,1,1,1,1
|
||||
|
|
|
|||
|
|
|
@ -1,5 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
|
||||
|
||||
addModelNamesToFetch(['mail.test.rating']);
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { click, contains, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { test } from "@odoo/hoot";
|
||||
import { defineTestMailFullModels } from "@test_mail_full/../tests/test_mail_full_test_helpers";
|
||||
import { serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
defineTestMailFullModels();
|
||||
|
||||
test("rating value displayed on the preview", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
const channelId = pyEnv["discuss.channel"].create({});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
body: "non-empty",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
pyEnv["rating.rating"].create({
|
||||
consumed: true,
|
||||
message_id: messageId,
|
||||
partner_id: partnerId,
|
||||
rating_image_url: "/rating/static/src/img/rating_5.png",
|
||||
rating_text: "top",
|
||||
});
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await contains(".o-mail-NotificationItem-text", { text: "Rating:" });
|
||||
await contains(".o-rating-preview-image[alt='top']");
|
||||
await contains(".o-rating-preview-image[data-src='/rating/static/src/img/rating_5.png']");
|
||||
});
|
||||
|
||||
test("rating value displayed on the needaction preview", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
const ratingId = pyEnv["mail.test.rating"].create({ name: "Test rating" });
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
model: "mail.test.rating",
|
||||
needaction: true,
|
||||
res_id: ratingId,
|
||||
});
|
||||
pyEnv["mail.notification"].create({
|
||||
mail_message_id: messageId,
|
||||
notification_status: "sent",
|
||||
notification_type: "inbox",
|
||||
res_partner_id: serverState.partnerId,
|
||||
});
|
||||
pyEnv["rating.rating"].create([
|
||||
{
|
||||
consumed: true,
|
||||
message_id: messageId,
|
||||
partner_id: partnerId,
|
||||
rating_image_url: "/rating/static/src/img/rating_5.png",
|
||||
rating_text: "top",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await contains(".o-mail-NotificationItem-text", { text: "Rating:" });
|
||||
await contains(".o-rating-preview-image[alt='top']");
|
||||
await contains(".o-rating-preview-image[data-src='/rating/static/src/img/rating_5.png']");
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class MailTestRating extends models.ServerModel {
|
||||
_name = "mail.test.rating";
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { afterNextRender, start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('test_mail_full', {}, function () {
|
||||
QUnit.module('channel_preview_view_tests.js');
|
||||
|
||||
QUnit.test('rating value displayed on the thread preview', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const resPartnerId1 = pyEnv['res.partner'].create({});
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({});
|
||||
const mailMessageId1 = pyEnv['mail.message'].create([
|
||||
{ author_id: resPartnerId1, model: 'mail.channel', res_id: mailChannelId1 },
|
||||
]);
|
||||
pyEnv['rating.rating'].create({
|
||||
consumed: true,
|
||||
message_id: mailMessageId1,
|
||||
partner_id: resPartnerId1,
|
||||
rating_image_url: "/rating/static/src/img/rating_5.png",
|
||||
rating_text: "top",
|
||||
});
|
||||
const { afterEvent, messaging } = await start();
|
||||
await afterNextRender(() => afterEvent({
|
||||
eventName: 'o-thread-cache-loaded-messages',
|
||||
func: () => document.querySelector('.o_MessagingMenu_toggler').click(),
|
||||
message: "should wait until inbox loaded initial needaction messages",
|
||||
predicate: ({ threadCache }) => {
|
||||
return threadCache.thread === messaging.inbox.thread;
|
||||
},
|
||||
}));
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_ChannelPreviewView_ratingText').textContent,
|
||||
"Rating:",
|
||||
"should display the correct content (Rating:)"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ChannelPreviewView_ratingImage',
|
||||
"should have a rating image in the body"
|
||||
);
|
||||
assert.strictEqual(
|
||||
$('.o_ChannelPreviewView_ratingImage').attr('data-src'),
|
||||
"/rating/static/src/img/rating_5.png",
|
||||
"should contain the correct rating image"
|
||||
);
|
||||
assert.strictEqual(
|
||||
$('.o_ChannelPreviewView_ratingImage').attr('data-alt'),
|
||||
"top",
|
||||
"should contain the correct rating text"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { afterNextRender, start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('test_mail_full', {}, function () {
|
||||
QUnit.module('thread_needaction_preview_tests.js');
|
||||
|
||||
QUnit.test('rating value displayed on the thread needaction preview', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const resPartnerId1 = pyEnv['res.partner'].create({});
|
||||
const mailTestRating1 = pyEnv['mail.test.rating'].create({});
|
||||
const mailMessageId1 = pyEnv['mail.message'].create({
|
||||
model: 'mail.test.rating',
|
||||
needaction: true,
|
||||
needaction_partner_ids: [pyEnv.currentPartnerId],
|
||||
res_id: mailTestRating1,
|
||||
});
|
||||
pyEnv['mail.notification'].create({
|
||||
mail_message_id: mailMessageId1,
|
||||
notification_status: 'sent',
|
||||
notification_type: 'inbox',
|
||||
res_partner_id: pyEnv.currentPartnerId,
|
||||
});
|
||||
pyEnv['rating.rating'].create([{
|
||||
consumed: true,
|
||||
message_id: mailMessageId1,
|
||||
partner_id: resPartnerId1,
|
||||
rating_image_url: "/rating/static/src/img/rating_5.png",
|
||||
rating_text: "top",
|
||||
}]);
|
||||
const { afterEvent, messaging } = await start();
|
||||
await afterNextRender(() => afterEvent({
|
||||
eventName: 'o-thread-cache-loaded-messages',
|
||||
func: () => document.querySelector('.o_MessagingMenu_toggler').click(),
|
||||
message: "should wait until inbox loaded initial needaction messages",
|
||||
predicate: ({ threadCache }) => {
|
||||
return threadCache.thread === messaging.inbox.thread;
|
||||
},
|
||||
}));
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_ThreadNeedactionPreview_ratingText').textContent,
|
||||
"Rating:",
|
||||
"should display the correct content (Rating:)"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ThreadNeedactionPreview_ratingImage',
|
||||
"should have a rating image in the body"
|
||||
);
|
||||
assert.strictEqual(
|
||||
$('.o_ThreadNeedactionPreview_ratingImage').attr('data-src'),
|
||||
"/rating/static/src/img/rating_5.png",
|
||||
"should contain the correct rating image"
|
||||
);
|
||||
assert.strictEqual(
|
||||
$('.o_ThreadNeedactionPreview_ratingImage').attr('data-alt'),
|
||||
"top",
|
||||
"should contain the correct rating text"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { ratingModels } from "@rating/../tests/rating_test_helpers";
|
||||
import { MailTestRating } from "@test_mail_full/../tests/mock_server/models/mail_test_rating";
|
||||
import { defineModels } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export const testMailFullModels = { ...ratingModels, MailTestRating };
|
||||
|
||||
export function defineTestMailFullModels() {
|
||||
defineModels(testMailFullModels);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { contains } from "@web/../tests/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("load_more_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Thread .o-mail-Message",
|
||||
run: async function () {
|
||||
await contains(".o-mail-Thread .o-mail-Message", {
|
||||
count: 30,
|
||||
target: document.querySelector("#chatterRoot").shadowRoot,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Thread button:contains(Load More):not(:visible)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { contains } from "@web/../tests/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("star_message_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger:
|
||||
"#chatterRoot:shadow .o-mail-Message:not([data-starred]):contains(Test Message)",
|
||||
run: "hover && click #chatterRoot:shadow [title='Add Star']",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message[data-starred]:contains(Test Message)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("message_actions_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Thread .o-mail-Message",
|
||||
run: async function () {
|
||||
await contains(".o-mail-Thread .o-mail-Message", {
|
||||
count: 1,
|
||||
target: document.querySelector("#chatterRoot").shadowRoot,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Composer-input",
|
||||
run: "edit New message",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Composer button:contains(Send):enabled",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Thread .o-mail-Message",
|
||||
run: async function () {
|
||||
await contains(".o-mail-Thread .o-mail-Message", {
|
||||
count: 2,
|
||||
target: document.querySelector("#chatterRoot").shadowRoot,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message[data-persistent]:contains(New message)",
|
||||
run: "hover && click #chatterRoot:shadow button[title='Add a Reaction']",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-QuickReactionMenu-emoji span:contains(❤️)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
"#chatterRoot:shadow .o-mail-Message:contains(New message) .o-mail-MessageReaction:contains(❤️)",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message:contains(New message)",
|
||||
run: "hover && click #chatterRoot:shadow button[title='Edit']",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message .o-mail-Composer-input",
|
||||
run: "edit Message content changed",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message button:contains(save)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message:contains(Message content changed)",
|
||||
run: "hover && click #chatterRoot:shadow button[title='Delete']",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow button:contains(Delete)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Thread .o-mail-Message",
|
||||
run: async function () {
|
||||
await contains(".o-mail-Thread .o-mail-Message", {
|
||||
count: 1,
|
||||
target: document.querySelector("#chatterRoot").shadowRoot,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
const cannedResponseButtonSelector = "button[title='Insert a Canned response']";
|
||||
|
||||
registry.category("web_tour.tours").add("portal_composer_actions_tour_internal_user", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: `#chatterRoot:shadow .o-mail-Composer ${cannedResponseButtonSelector}`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Composer-input",
|
||||
run() {
|
||||
if (this.anchor.value !== "::") {
|
||||
console.error(
|
||||
"Clicking on the canned response button should insert the '::' into the composer."
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
"#chatterRoot:shadow .o-mail-Composer-suggestion:contains(Hello, how may I help you?)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("portal_composer_actions_tour_portal_user", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: `#chatterRoot:shadow .o-mail-Composer:not(:has(${cannedResponseButtonSelector}))`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { messageActionsRegistry } from "@mail/core/common/message_actions";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
registry.category("web_tour.tours").add("portal_copy_link_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message",
|
||||
run: () => {
|
||||
const copyLinkAction = messageActionsRegistry.get("copy-link");
|
||||
patch(copyLinkAction, { sequence: 1 }); // make sure the action is visible without expanding
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message:contains(Test Message)",
|
||||
run: "hover && click",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message-actions [title='Copy Link']",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { messageActionsRegistry } from "@mail/core/common/message_actions";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
registry.category("web_tour.tours").add("portal_no_copy_link_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message",
|
||||
run: () => {
|
||||
const copyLinkAction = messageActionsRegistry.get("copy-link");
|
||||
patch(copyLinkAction, { sequence: 1 }); // make sure the action is visible without expanding
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message:contains(Test Message)",
|
||||
run: "hover && click",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message-actions",
|
||||
run: async () => {
|
||||
const copyLinkButton = document.querySelector('#chatterRoot').shadowRoot.querySelector("[title='Copy Link']");
|
||||
if (copyLinkButton) {
|
||||
throw new Error("Users without read access should not be able to copy the link to a message");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
const ratingCardSelector = ".o_website_rating_card_container";
|
||||
|
||||
registry.category("web_tour.tours").add("portal_rating_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
// Ensure that the rating data has been fetched before making a negative assertion for rating cards.
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message-body:text(Message without rating)",
|
||||
},
|
||||
{
|
||||
trigger: `#chatterRoot:shadow .o-mail-Chatter-top:not(:has(${ratingCardSelector}))`,
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Composer-input",
|
||||
run: "edit Excellent service!",
|
||||
},
|
||||
{
|
||||
trigger: "#chatterRoot:shadow .o-mail-Composer-send:enabled",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: `#chatterRoot:shadow .o-mail-Chatter-top ${ratingCardSelector} .o_website_rating_table_row[data-star='4']:has(:text(100%))`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("portal_display_rating_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: `#chatterRoot:shadow .o-mail-Chatter-top ${ratingCardSelector}`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("portal_not_display_rating_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
// Ensure that the rating data has been fetched before making a negative assertion for rating cards.
|
||||
trigger: "#chatterRoot:shadow .o-mail-Message-body:text(Message with rating)",
|
||||
},
|
||||
{
|
||||
trigger: `#chatterRoot:shadow .o-mail-Chatter-top:not(:has(${ratingCardSelector}))`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_odoobot
|
||||
from . import test_controller_attachment
|
||||
from . import test_controller_reaction
|
||||
from . import test_controller_update
|
||||
from . import test_controller_thread
|
||||
from . import test_ir_mail_server
|
||||
from . import test_mail_bot
|
||||
from . import test_mail_performance
|
||||
from . import test_mail_thread_internals
|
||||
from . import test_mass_mailing
|
||||
from . import test_portal
|
||||
from . import test_rating
|
||||
from . import test_res_users
|
||||
from . import test_ui
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo
|
||||
from odoo.addons.mail.tests.common_controllers import MailControllerAttachmentCommon
|
||||
|
||||
|
||||
@odoo.tests.tagged("-at_install", "post_install", "mail_controller")
|
||||
class TestPortalAttachmentController(MailControllerAttachmentCommon):
|
||||
|
||||
def test_attachment_upload_portal(self):
|
||||
"""Test access to upload an attachment on portal"""
|
||||
record = self.env["mail.test.portal.no.partner"].create({"name": "Test"})
|
||||
token, bad_token, sign, bad_sign, _ = self._get_sign_token_params(record)
|
||||
self._execute_subtests_upload(
|
||||
record,
|
||||
(
|
||||
(self.user_public, False),
|
||||
(self.user_public, True, token),
|
||||
(self.user_public, True, sign),
|
||||
(self.guest, False),
|
||||
(self.guest, True, token),
|
||||
(self.guest, True, sign),
|
||||
(self.user_portal, False),
|
||||
(self.user_portal, False, bad_token),
|
||||
(self.user_portal, False, bad_sign),
|
||||
(self.user_portal, True, token),
|
||||
(self.user_portal, True, sign),
|
||||
(self.user_employee, True),
|
||||
(self.user_employee, True, bad_token),
|
||||
(self.user_employee, True, bad_sign),
|
||||
(self.user_employee, True, token),
|
||||
(self.user_employee, True, sign),
|
||||
(self.user_admin, True),
|
||||
(self.user_admin, True, bad_token),
|
||||
(self.user_admin, True, bad_sign),
|
||||
(self.user_admin, True, token),
|
||||
(self.user_admin, True, sign),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
from odoo.addons.mail.tests.common_controllers import MailControllerReactionCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged("-at_install", "post_install", "mail_controller")
|
||||
class TestPortalMessageReactionController(MailControllerReactionCommon):
|
||||
|
||||
def test_message_reaction_nomsg(self):
|
||||
"""Test access of message reaction for a non-existing message."""
|
||||
self._execute_subtests(
|
||||
self.fake_message,
|
||||
((user, False) for user in [self.user_public, self.guest, self.user_portal, self.user_employee]),
|
||||
)
|
||||
|
||||
def test_message_reaction_portal_no_partner(self):
|
||||
"""Test access of message reaction for portal without partner."""
|
||||
record = self.env["mail.test.portal.no.partner"].create({"name": "Test"})
|
||||
token, bad_token, sign, bad_sign, partner = self._get_sign_token_params(record)
|
||||
message = record.message_post(body="portal no partner")
|
||||
self._execute_subtests(
|
||||
message,
|
||||
(
|
||||
(self.user_public, False),
|
||||
(self.user_public, False, bad_token),
|
||||
(self.user_public, False, bad_sign),
|
||||
# False because no portal partner, no guest
|
||||
(self.user_public, False, token),
|
||||
(self.user_public, True, sign, {"partner": partner}),
|
||||
(self.guest, False),
|
||||
(self.guest, False, bad_token),
|
||||
(self.guest, False, bad_sign),
|
||||
(self.guest, True, token),
|
||||
(self.guest, True, sign, {"partner": partner}),
|
||||
(self.user_portal, False),
|
||||
(self.user_portal, False, bad_token),
|
||||
(self.user_portal, False, bad_sign),
|
||||
(self.user_portal, True, token),
|
||||
(self.user_portal, True, sign),
|
||||
(self.user_employee, True),
|
||||
(self.user_employee, True, bad_token),
|
||||
(self.user_employee, True, bad_sign),
|
||||
(self.user_employee, True, token),
|
||||
(self.user_employee, True, sign),
|
||||
),
|
||||
)
|
||||
|
||||
def test_message_reaction_portal_assigned_partner(self):
|
||||
"""Test access of message reaction for portal with partner."""
|
||||
rec_partner = self.env["res.partner"].create({"name": "Record Partner"})
|
||||
record = self.env["mail.test.portal"].create({"name": "Test", "partner_id": rec_partner.id})
|
||||
message = record.message_post(body="portal with partner")
|
||||
token, bad_token, sign, bad_sign, partner = self._get_sign_token_params(record)
|
||||
self._execute_subtests(
|
||||
message,
|
||||
(
|
||||
(self.user_public, False),
|
||||
(self.user_public, False, bad_token),
|
||||
(self.user_public, False, bad_sign),
|
||||
(self.user_public, True, token, {"partner": rec_partner}),
|
||||
(self.user_public, True, sign, {"partner": partner}),
|
||||
# sign has priority over token when both are provided
|
||||
(self.user_public, True, token | sign, {"partner": partner}),
|
||||
(self.guest, False),
|
||||
(self.guest, False, bad_token),
|
||||
(self.guest, False, bad_sign),
|
||||
(self.guest, True, token, {"partner": rec_partner}),
|
||||
(self.guest, True, sign, {"partner": partner}),
|
||||
(self.guest, True, token | sign, {"partner": partner}),
|
||||
(self.user_portal, False),
|
||||
(self.user_portal, False, bad_token),
|
||||
(self.user_portal, False, bad_sign),
|
||||
(self.user_portal, True, token),
|
||||
(self.user_portal, True, sign),
|
||||
(self.user_employee, True),
|
||||
(self.user_employee, True, bad_token),
|
||||
(self.user_employee, True, bad_sign),
|
||||
(self.user_employee, True, token),
|
||||
(self.user_employee, True, sign),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
from odoo.addons.mail.tests.common_controllers import MailControllerThreadCommon, MessagePostSubTestData
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged("-at_install", "post_install", "mail_controller")
|
||||
class TestPortalThreadController(MailControllerThreadCommon):
|
||||
|
||||
def test_message_post_portal_no_partner(self):
|
||||
"""Test access of message post for portal without partner."""
|
||||
record = self.env["mail.test.portal.no.partner"].create({"name": "Test"})
|
||||
token, bad_token, sign, bad_sign, partner = self._get_sign_token_params(record)
|
||||
|
||||
def test_access(user, allowed, route_kw=None, exp_author=None):
|
||||
return MessagePostSubTestData(user, allowed, route_kw=route_kw, exp_author=exp_author)
|
||||
|
||||
self._execute_message_post_subtests(
|
||||
record,
|
||||
(
|
||||
test_access(self.user_public, False),
|
||||
test_access(self.user_public, False, route_kw=bad_token),
|
||||
test_access(self.user_public, False, route_kw=bad_sign),
|
||||
test_access(self.user_public, True, route_kw=token),
|
||||
test_access(self.user_public, True, route_kw=sign, exp_author=partner),
|
||||
test_access(self.guest, False),
|
||||
test_access(self.guest, False, route_kw=bad_token),
|
||||
test_access(self.guest, False, route_kw=bad_sign),
|
||||
test_access(self.guest, True, route_kw=token),
|
||||
test_access(self.guest, True, route_kw=sign, exp_author=partner),
|
||||
test_access(self.user_portal, False),
|
||||
test_access(self.user_portal, False, route_kw=bad_token),
|
||||
test_access(self.user_portal, False, route_kw=bad_sign),
|
||||
test_access(self.user_portal, True, route_kw=token),
|
||||
test_access(self.user_portal, True, route_kw=sign),
|
||||
test_access(self.user_employee, True),
|
||||
test_access(self.user_employee, True, route_kw=bad_token),
|
||||
test_access(self.user_employee, True, route_kw=bad_sign),
|
||||
test_access(self.user_employee, True, route_kw=token),
|
||||
test_access(self.user_employee, True, route_kw=sign),
|
||||
),
|
||||
)
|
||||
|
||||
def test_message_post_portal_with_partner(self):
|
||||
"""Test access of message post for portal with partner."""
|
||||
rec_partner = self.env["res.partner"].create({"name": "Record Partner"})
|
||||
record = self.env["mail.test.portal"].create({"name": "Test", "partner_id": rec_partner.id})
|
||||
token, bad_token, sign, bad_sign, partner = self._get_sign_token_params(record)
|
||||
|
||||
def test_access(user, allowed, route_kw=None, exp_author=None):
|
||||
return MessagePostSubTestData(user, allowed, route_kw=route_kw, exp_author=exp_author)
|
||||
|
||||
self._execute_message_post_subtests(
|
||||
record,
|
||||
(
|
||||
test_access(self.user_public, False),
|
||||
test_access(self.user_public, False, route_kw=bad_token),
|
||||
test_access(self.user_public, False, route_kw=bad_sign),
|
||||
test_access(self.user_public, True, route_kw=token, exp_author=rec_partner),
|
||||
test_access(self.user_public, True, route_kw=sign, exp_author=partner),
|
||||
# sign has priority over token when both are provided
|
||||
test_access(self.user_public, True, route_kw=token | sign, exp_author=partner),
|
||||
test_access(self.guest, False),
|
||||
test_access(self.guest, False, route_kw=bad_token),
|
||||
test_access(self.guest, False, route_kw=bad_sign),
|
||||
test_access(self.guest, True, route_kw=token, exp_author=rec_partner),
|
||||
test_access(self.guest, True, route_kw=sign, exp_author=partner),
|
||||
test_access(self.guest, True, route_kw=token | sign, exp_author=partner),
|
||||
test_access(self.user_portal, False),
|
||||
test_access(self.user_portal, False, route_kw=bad_token),
|
||||
test_access(self.user_portal, False, route_kw=bad_sign),
|
||||
test_access(self.user_portal, True, route_kw=token),
|
||||
test_access(self.user_portal, True, route_kw=sign),
|
||||
test_access(self.user_employee, True),
|
||||
test_access(self.user_employee, True, route_kw=bad_token),
|
||||
test_access(self.user_employee, True, route_kw=bad_sign),
|
||||
test_access(self.user_employee, True, route_kw=token),
|
||||
test_access(self.user_employee, True, route_kw=sign),
|
||||
),
|
||||
)
|
||||
|
||||
def test_message_post_partner_ids_mention_token(self):
|
||||
"""Test partner_ids of message_post for portal record without partner.
|
||||
All users are allowed to mention with specific message_mention token."""
|
||||
record = self.env["mail.test.portal.no.partner"].create({"name": "Test"})
|
||||
token, bad_token, sign, bad_sign, partner = self._get_sign_token_params(record)
|
||||
all_partners = (
|
||||
self.user_portal + self.user_employee + self.user_admin
|
||||
).partner_id
|
||||
record.message_subscribe(partner_ids=self.user_employee.partner_id.ids)
|
||||
|
||||
def test_partners(user, allowed, exp_partners, route_kw=None, exp_author=None):
|
||||
return MessagePostSubTestData(
|
||||
user,
|
||||
allowed,
|
||||
partners=all_partners,
|
||||
route_kw=route_kw,
|
||||
exp_author=exp_author,
|
||||
exp_partners=exp_partners,
|
||||
add_mention_token=True,
|
||||
)
|
||||
|
||||
self._execute_message_post_subtests(
|
||||
record,
|
||||
(
|
||||
test_partners(self.user_public, False, all_partners),
|
||||
test_partners(self.user_public, False, all_partners, route_kw=bad_token),
|
||||
test_partners(self.user_public, False, all_partners, route_kw=bad_sign),
|
||||
test_partners(self.user_public, True, all_partners, route_kw=token),
|
||||
test_partners(self.user_public, True, all_partners, route_kw=sign, exp_author=partner),
|
||||
test_partners(self.guest, False, all_partners),
|
||||
test_partners(self.guest, False, all_partners, route_kw=bad_token),
|
||||
test_partners(self.guest, False, all_partners, route_kw=bad_sign),
|
||||
test_partners(self.guest, True, all_partners, route_kw=token),
|
||||
test_partners(self.guest, True, all_partners, route_kw=sign, exp_author=partner),
|
||||
test_partners(self.user_portal, False, all_partners),
|
||||
test_partners(self.user_portal, False, all_partners, route_kw=bad_token),
|
||||
test_partners(self.user_portal, False, all_partners, route_kw=bad_sign),
|
||||
test_partners(self.user_portal, True, all_partners, route_kw=token),
|
||||
test_partners(self.user_portal, True, all_partners, route_kw=sign),
|
||||
test_partners(self.user_employee, True, all_partners),
|
||||
test_partners(self.user_employee, True, all_partners, route_kw=bad_token),
|
||||
test_partners(self.user_employee, True, all_partners, route_kw=bad_sign),
|
||||
test_partners(self.user_employee, True, all_partners, route_kw=token),
|
||||
test_partners(self.user_employee, True, all_partners, route_kw=sign),
|
||||
),
|
||||
)
|
||||
|
||||
def test_message_post_partner_ids_portal(self):
|
||||
"""Test partner_ids of message_post for portal record without partner.
|
||||
Only internal users are allowed to mention without specific message_mention token."""
|
||||
record = self.env["mail.test.portal.no.partner"].create({"name": "Test"})
|
||||
token, bad_token, sign, bad_sign, partner = self._get_sign_token_params(record)
|
||||
all_partners = (
|
||||
self.user_portal + self.user_employee + self.user_admin
|
||||
).partner_id
|
||||
record.message_subscribe(partner_ids=self.user_employee.partner_id.ids)
|
||||
|
||||
def test_partners(user, allowed, exp_partners, route_kw=None, exp_author=None):
|
||||
return MessagePostSubTestData(
|
||||
user,
|
||||
allowed,
|
||||
partners=all_partners,
|
||||
route_kw=route_kw,
|
||||
exp_author=exp_author,
|
||||
exp_partners=exp_partners,
|
||||
)
|
||||
|
||||
self._execute_message_post_subtests(
|
||||
record,
|
||||
(
|
||||
test_partners(self.user_public, False, self.env["res.partner"]),
|
||||
test_partners(self.user_public, False, self.env["res.partner"], route_kw=bad_token),
|
||||
test_partners(self.user_public, False, self.env["res.partner"], route_kw=bad_sign),
|
||||
test_partners(self.user_public, True, self.env["res.partner"], route_kw=token),
|
||||
test_partners(self.user_public, True, self.env["res.partner"], route_kw=sign, exp_author=partner),
|
||||
test_partners(self.guest, False, self.env["res.partner"]),
|
||||
test_partners(self.guest, False, self.env["res.partner"], route_kw=bad_token),
|
||||
test_partners(self.guest, False, self.env["res.partner"], route_kw=bad_sign),
|
||||
test_partners(self.guest, True, self.env["res.partner"], route_kw=token),
|
||||
test_partners(self.guest, True, self.env["res.partner"], route_kw=sign, exp_author=partner),
|
||||
test_partners(self.user_portal, False, self.env["res.partner"]),
|
||||
test_partners(self.user_portal, False, self.env["res.partner"], route_kw=bad_token),
|
||||
test_partners(self.user_portal, False, self.env["res.partner"], route_kw=bad_sign),
|
||||
test_partners(self.user_portal, True, self.env["res.partner"], route_kw=token),
|
||||
test_partners(self.user_portal, True, self.env["res.partner"], route_kw=sign),
|
||||
test_partners(self.user_employee, True, all_partners),
|
||||
test_partners(self.user_employee, True, all_partners, route_kw=bad_token),
|
||||
test_partners(self.user_employee, True, all_partners, route_kw=bad_sign),
|
||||
test_partners(self.user_employee, True, all_partners, route_kw=token),
|
||||
test_partners(self.user_employee, True, all_partners, route_kw=sign),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
from odoo.addons.mail.tests.common_controllers import MailControllerUpdateCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged("-at_install", "post_install", "mail_controller")
|
||||
class TestPortalMessageUpdateController(MailControllerUpdateCommon):
|
||||
|
||||
def test_message_update_no_message(self):
|
||||
"""Test update a non-existing message."""
|
||||
self._execute_subtests(
|
||||
self.fake_message,
|
||||
((user, False) for user in [self.guest, self.user_admin, self.user_employee, self.user_portal, self.user_public]),
|
||||
)
|
||||
|
||||
def test_message_update_portal(self):
|
||||
"""Test only admin and author can modify content of a message, works if
|
||||
author is a portal user. """
|
||||
record = self.env["mail.test.portal.no.partner"].create({"name": "Test"})
|
||||
token, bad_token, sign, bad_sign, _ = self._get_sign_token_params(record)
|
||||
message = record.message_post(
|
||||
body=self.message_body,
|
||||
author_id=self.user_portal.partner_id.id,
|
||||
message_type="comment",
|
||||
)
|
||||
self._execute_subtests(
|
||||
message,
|
||||
(
|
||||
(self.user_public, False),
|
||||
(self.user_public, False, token),
|
||||
(self.user_public, False, sign),
|
||||
(self.guest, False),
|
||||
(self.guest, False, token),
|
||||
(self.guest, False, sign),
|
||||
(self.user_portal, False),
|
||||
(self.user_portal, False, bad_token),
|
||||
(self.user_portal, False, bad_sign),
|
||||
(self.user_portal, True, token),
|
||||
(self.user_portal, True, sign),
|
||||
(self.user_employee, False),
|
||||
(self.user_employee, False, token),
|
||||
(self.user_employee, False, sign),
|
||||
(self.user_admin, True),
|
||||
(self.user_admin, True, bad_token),
|
||||
(self.user_admin, True, bad_sign),
|
||||
(self.user_admin, True, token),
|
||||
(self.user_admin, True, sign),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
from contextlib import contextmanager
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
from odoo.addons.base.models.ir_mail_server import IrMail_Server
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
@tagged('mail_server')
|
||||
class TestIrMailServerPersonal(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env['ir.config_parameter'].sudo().set_param('mail.disable_personal_mail_servers', False)
|
||||
cls.user_admin.email = 'admin@test.lan'
|
||||
cls.user_employee.email = 'employee@test.lan'
|
||||
cls.user_employee.group_ids += cls.env.ref('mass_mailing.group_mass_mailing_user')
|
||||
cls.test_partner = cls.env['res.partner'].create({
|
||||
'name': 'test partner', 'email': 'test.partner@test.lan'
|
||||
})
|
||||
cls.mail_server_user.write({
|
||||
'from_filter': cls.user_employee.email,
|
||||
'owner_user_id': cls.user_employee,
|
||||
'smtp_user': cls.user_employee.email,
|
||||
})
|
||||
cls.user_employee.invalidate_recordset(['outgoing_mail_server_id'])
|
||||
|
||||
@contextmanager
|
||||
def mock_mail_connect(self):
|
||||
original_connect = IrMail_Server._connect__
|
||||
self.connected_server_ids = []
|
||||
|
||||
def patched_connect(mail_server, *args, **kwargs):
|
||||
self.connected_server_ids.append(kwargs.get('mail_server_id'))
|
||||
original_connect(mail_server, *args, **kwargs)
|
||||
|
||||
with patch.object(IrMail_Server, '_connect__', autospec=True, wraps=IrMail_Server, side_effect=patched_connect):
|
||||
yield
|
||||
|
||||
@users('admin', 'employee')
|
||||
def test_personal_mail_server_allowed_post(self):
|
||||
"""Check that only the owner of the mail server can create mails that will be sent from it."""
|
||||
test_record = self.test_partner.with_user(self.env.user)
|
||||
with self.mock_mail_connect():
|
||||
test_record.message_post(
|
||||
body='hello',
|
||||
author_id=self.user_employee.partner_id.id, email_from=self.user_employee.email,
|
||||
partner_ids=test_record.ids,
|
||||
)
|
||||
|
||||
self.assertEqual(len(self.connected_server_ids), 1)
|
||||
if self.env.user == self.mail_server_user.owner_user_id:
|
||||
self.assertEqual(self.connected_server_ids[0], self.mail_server_user.id)
|
||||
else:
|
||||
self.assertNotEqual(self.connected_server_ids[0], self.mail_server_user.id)
|
||||
|
||||
# check disallowed exceptions
|
||||
if self.env.user != self.mail_server_user.owner_user_id:
|
||||
# check raise on invalid server at create
|
||||
with self.assertRaises(ValidationError):
|
||||
test_record.message_post(
|
||||
body='hello',
|
||||
author_id=self.user_employee.partner_id.id, email_from=self.user_employee.email,
|
||||
mail_server_id=self.mail_server_user.id,
|
||||
partner_ids=test_record.ids,
|
||||
)
|
||||
|
||||
# check raise on invalid server at send (should not happen in normal flow)
|
||||
mail = self.env['mail.mail'].sudo().create({
|
||||
'body_html': 'hello',
|
||||
'email_from': self.user_employee.email,
|
||||
'author_id': self.user_employee.partner_id.id,
|
||||
'partner_ids': test_record.ids,
|
||||
})
|
||||
with self.mock_mail_gateway(), self.assertRaisesRegex(UserError, "Unauthorized server for some of the sending mails."):
|
||||
mail._send(self, mail_server=self.mail_server_user)
|
||||
|
||||
def test_personal_mail_server_find_mail_server(self):
|
||||
"""Check that _find_mail_server only finds 'public' servers unless otherwise allowed."""
|
||||
IrMailServer = self.env['ir.mail_server']
|
||||
all_servers = IrMailServer.search([])
|
||||
test_cases = [
|
||||
(None, False),
|
||||
(all_servers, True),
|
||||
]
|
||||
for mail_servers, should_find_personal in test_cases:
|
||||
with self.subTest(mail_servers=mail_servers):
|
||||
found_server, found_email_from = IrMailServer._find_mail_server(self.user_employee.email, mail_servers=mail_servers)
|
||||
if should_find_personal:
|
||||
self.assertEqual(
|
||||
(found_server, found_email_from), (self.mail_server_user, self.user_employee.email),
|
||||
'Passing in a server that is owned should allow finding it.'
|
||||
)
|
||||
else:
|
||||
self.assertNotEqual(
|
||||
found_server, self.mail_server_user,
|
||||
'Finding a server for an email_from without specifying a list of servers should not find owned servers.'
|
||||
)
|
||||
|
||||
@users('employee')
|
||||
def test_immutable_create_uid(self):
|
||||
"""Make sure create_uid is not writable, as it's a security assumption for these tests."""
|
||||
message = self.test_partner.with_user(self.env.user).message_post(
|
||||
body='hello',
|
||||
author_id=self.user_employee.partner_id.id, email_from=self.user_employee.email,
|
||||
partner_ids=self.test_partner.ids,
|
||||
)
|
||||
|
||||
self.assertEqual(message.create_uid, self.user_employee)
|
||||
message.create_uid = self.user_admin
|
||||
self.assertEqual(message.create_uid, self.user_employee)
|
||||
|
||||
def test_personal_mail_server_mail_for_existing_message(self):
|
||||
"""Crons should be able to send a mail from a personal server for an existing message."""
|
||||
message = self.test_partner.with_user(self.user_employee).message_post(body='hello')
|
||||
message.partner_ids += self.test_partner
|
||||
with self.mock_mail_connect():
|
||||
self.test_partner.with_user(self.env.ref('base.user_root'))._notify_thread(message)
|
||||
self.assertEqual(self.connected_server_ids, [self.mail_server_user.id], "Should have used message creator's server.")
|
||||
|
|
@ -1,19 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.addons.test_mail.tests.common import TestRecipients
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged("odoobot")
|
||||
class TestOdoobot(TestMailCommon, TestRecipients):
|
||||
class TestOdoobot(MailCommon, TestRecipients):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestOdoobot, cls).setUpClass()
|
||||
super().setUpClass()
|
||||
cls.test_record = cls.env['mail.test.simple'].with_context(cls._test_context).create({'name': 'Test', 'email_from': 'ignasse@example.com'})
|
||||
|
||||
cls.odoobot = cls.env.ref("base.partner_root")
|
||||
|
|
@ -24,14 +22,14 @@ class TestOdoobot(TestMailCommon, TestRecipients):
|
|||
'partner_ids': [],
|
||||
'subtype_xmlid': 'mail.mt_comment'
|
||||
}
|
||||
cls.odoobot_ping_body = '<a href="http://odoo.com/web#model=res.partner&id=%s" class="o_mail_redirect" data-oe-id="%s" data-oe-model="res.partner" target="_blank">@OdooBot</a>' % (cls.odoobot.id, cls.odoobot.id)
|
||||
cls.odoobot_ping_body = f'<a href="http://odoo.com/odoo/res.partner/{cls.odoobot.id}" class="o_mail_redirect" data-oe-id="{cls.odoobot.id}" data-oe-model="res.partner" target="_blank">@OdooBot</a>'
|
||||
cls.test_record_employe = cls.test_record.with_user(cls.user_employee)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_fetch_listener(self):
|
||||
channel = self.user_employee.with_user(self.user_employee)._init_odoobot()
|
||||
odoobot = self.env.ref("base.partner_root")
|
||||
odoobot_in_fetch_listeners = self.env['mail.channel.member'].search([('channel_id', '=', channel.id), ('partner_id', '=', odoobot.id)])
|
||||
odoobot_in_fetch_listeners = self.env['discuss.channel.member'].search([('channel_id', '=', channel.id), ('partner_id', '=', odoobot.id)])
|
||||
self.assertEqual(len(odoobot_in_fetch_listeners), 1, 'odoobot should appear only once in channel_fetch_listeners')
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
|
|
@ -1,76 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import datetime, timedelta
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mail.tests.test_performance import BaseMailPerformance
|
||||
from odoo import Command
|
||||
from odoo.addons.test_mail.tests.test_performance import BaseMailPostPerformance
|
||||
from odoo.tests.common import users, warmup
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mail_performance', 'post_install', '-at_install')
|
||||
class TestMailPerformance(BaseMailPerformance):
|
||||
class FullBaseMailPerformance(BaseMailPostPerformance):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailPerformance, cls).setUpClass()
|
||||
super().setUpClass()
|
||||
|
||||
# users / followers
|
||||
cls.user_emp_email = mail_new_test_user(
|
||||
cls.env,
|
||||
company_id=cls.user_admin.company_id.id,
|
||||
company_ids=[(4, cls.user_admin.company_id.id)],
|
||||
email='user.emp.email@test.example.com',
|
||||
login='user_emp_email',
|
||||
groups='base.group_user,base.group_partner_manager',
|
||||
name='Emmanuel Email',
|
||||
notification_type='email',
|
||||
signature='--\nEmmanuel',
|
||||
)
|
||||
cls.user_portal = mail_new_test_user(
|
||||
cls.env,
|
||||
company_id=cls.user_admin.company_id.id,
|
||||
company_ids=[(4, cls.user_admin.company_id.id)],
|
||||
email='user.portal@test.example.com',
|
||||
login='user_portal',
|
||||
groups='base.group_portal',
|
||||
name='Paul Portal',
|
||||
)
|
||||
cls.customers = cls.env['res.partner'].create([
|
||||
{'country_id': cls.env.ref('base.be').id,
|
||||
'email': 'customer.full.test.1@example.com',
|
||||
'name': 'Test Full Customer 1',
|
||||
'mobile': '0456112233',
|
||||
'phone': '0456112233',
|
||||
# records
|
||||
cls.record_containers = cls.env['mail.test.container.mc'].create([
|
||||
{
|
||||
'alias_name': 'test-alias-0',
|
||||
'customer_id': cls.customers[0].id,
|
||||
'name': 'Test Container 1',
|
||||
},
|
||||
{'country_id': cls.env.ref('base.be').id,
|
||||
'email': 'customer.full.test.2@example.com',
|
||||
'name': 'Test Full Customer 2',
|
||||
'mobile': '0456223344',
|
||||
'phone': '0456112233',
|
||||
{
|
||||
'alias_name': 'test-alias-1',
|
||||
'customer_id': cls.customers[1].id,
|
||||
'name': 'Test Container 2',
|
||||
},
|
||||
])
|
||||
|
||||
# record
|
||||
cls.record_container = cls.env['mail.test.container.mc'].create({
|
||||
'alias_name': 'test-alias',
|
||||
'customer_id': cls.customer.id,
|
||||
'name': 'Test Container',
|
||||
})
|
||||
cls.record_ticket = cls.env['mail.test.ticket.mc'].create({
|
||||
cls.record_ticket_mc = cls.env['mail.test.ticket.mc'].create({
|
||||
'email_from': 'email.from@test.example.com',
|
||||
'container_id': cls.record_container.id,
|
||||
'container_id': cls.record_containers[0].id,
|
||||
'customer_id': False,
|
||||
'name': 'Test Ticket',
|
||||
'user_id': cls.user_emp_email.id,
|
||||
'user_id': cls.user_follower_emp_email.id,
|
||||
})
|
||||
cls.record_ticket.message_subscribe(cls.customers.ids + cls.user_admin.partner_id.ids + cls.user_portal.partner_id.ids)
|
||||
cls.record_ticket_mc.message_subscribe(
|
||||
cls.customers.ids + cls.user_admin.partner_id.ids + cls.user_follower_portal.partner_id.ids
|
||||
)
|
||||
|
||||
def test_initial_values(self):
|
||||
cls.tracking_values_ids = [
|
||||
(0, 0, {
|
||||
'field_id': cls.env['ir.model.fields']._get(cls.record_ticket._name, 'email_from').id,
|
||||
'new_value_char': 'new_value',
|
||||
'old_value_char': 'old_value',
|
||||
}),
|
||||
(0, 0, {
|
||||
'field_id': cls.env['ir.model.fields']._get(cls.record_ticket._name, 'customer_id').id,
|
||||
'new_value_char': 'New Fake',
|
||||
'new_value_integer': 2,
|
||||
'old_value_char': 'Old Fake',
|
||||
'old_value_integer': 1,
|
||||
}),
|
||||
]
|
||||
|
||||
|
||||
@tagged('mail_performance', 'post_install', '-at_install')
|
||||
class TestMailPerformance(FullBaseMailPerformance):
|
||||
|
||||
def test_assert_initial_values(self):
|
||||
""" Simply ensure some values through all tests """
|
||||
record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket.ids)
|
||||
record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket_mc.ids)
|
||||
self.assertEqual(record_ticket.message_partner_ids,
|
||||
self.user_emp_email.partner_id + self.user_admin.partner_id + self.customers + self.user_portal.partner_id)
|
||||
self.user_follower_emp_email.partner_id + self.user_admin.partner_id + self.customers + self.user_follower_portal.partner_id)
|
||||
self.assertEqual(len(record_ticket.message_ids), 1)
|
||||
|
||||
@mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
|
|
@ -78,19 +70,384 @@ class TestMailPerformance(BaseMailPerformance):
|
|||
@warmup
|
||||
def test_message_post_w_followers(self):
|
||||
""" Aims to cover as much features of message_post as possible """
|
||||
record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket.ids)
|
||||
record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket_mc.ids)
|
||||
attachments = self.env['ir.attachment'].create(self.test_attachments_vals)
|
||||
self.push_to_end_point_mocked.reset_mock() # reset as executed twice
|
||||
self.flush_tracking()
|
||||
|
||||
with self.assertQueryCount(employee=91): # tmf: 60
|
||||
with self.assertQueryCount(employee=108): # test_mail_full: 106
|
||||
new_message = record_ticket.message_post(
|
||||
attachment_ids=attachments.ids,
|
||||
body='<p>Test Content</p>',
|
||||
body=Markup('<p>Test Content</p>'),
|
||||
email_add_signature=True,
|
||||
mail_auto_delete=True,
|
||||
message_type='comment',
|
||||
subject='Test Subject',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
tracking_value_ids=self.tracking_values_ids,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
new_message.notified_partner_ids,
|
||||
self.user_emp_email.partner_id + self.user_admin.partner_id + self.customers + self.user_portal.partner_id
|
||||
self.user_follower_emp_email.partner_id + self.user_admin.partner_id + self.customers + self.user_follower_portal.partner_id
|
||||
)
|
||||
self.assertEqual(self.push_to_end_point_mocked.call_count, 8, "Not sure why 8")
|
||||
|
||||
|
||||
@tagged('mail_performance', 'post_install', '-at_install')
|
||||
class TestPortalFormatPerformance(FullBaseMailPerformance):
|
||||
"""Test performance of `portal_message_format` with multiple messages
|
||||
with multiple attachments, with ratings.
|
||||
|
||||
Those messages might not make sense functionally but they are crafted to
|
||||
cover as much of the code as possible in regard to number of queries.
|
||||
|
||||
Setup :
|
||||
* 5 records (self.containers -> 5 mail.test.rating records, with
|
||||
a different customer_id each)
|
||||
* 2 messages / record
|
||||
* 2 attachments / message
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.test_users = cls.user_employee + cls.user_emp_inbox + cls.user_emp_email + cls.user_follower_emp_email + cls.user_follower_portal
|
||||
|
||||
# rating-enabled test records
|
||||
with cls.mock_push_to_end_point(cls):
|
||||
cls.record_ratings = cls.env['mail.test.rating'].create([
|
||||
{
|
||||
'customer_id': cls.customers[idx].id,
|
||||
'name': f'TestRating_{idx}',
|
||||
'user_id': cls.test_users[idx].id,
|
||||
|
||||
}
|
||||
for idx in range(5)
|
||||
])
|
||||
|
||||
# messages and ratings
|
||||
user_id_field = cls.env['ir.model.fields']._get(cls.record_ratings._name, 'user_id')
|
||||
comment_subtype_id = cls.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
|
||||
cls.link_previews = cls.env["mail.link.preview"].create(
|
||||
[
|
||||
{"source_url": "https://www.odoo.com"},
|
||||
{"source_url": "https://www.example.com"},
|
||||
]
|
||||
)
|
||||
cls.messages_all = cls.env['mail.message'].sudo().create([
|
||||
{
|
||||
'attachment_ids': [
|
||||
(0, 0, {
|
||||
'datas': 'data',
|
||||
'name': f'Test file {att_idx}',
|
||||
'res_id': record.id,
|
||||
'res_model': record._name,
|
||||
})
|
||||
for att_idx in range(2)
|
||||
],
|
||||
'author_id': record.customer_id.id,
|
||||
'body': f'<p>Test {msg_idx}</p>',
|
||||
'date': datetime(2023, 5, 15, 10, 30, 5),
|
||||
'email_from': record.customer_id.email_formatted,
|
||||
"message_link_preview_ids": [
|
||||
Command.create({"link_preview_id": cls.link_previews[0].id}),
|
||||
Command.create({"link_preview_id": cls.link_previews[1].id}),
|
||||
],
|
||||
'notification_ids': [
|
||||
(0, 0, {
|
||||
'is_read': False,
|
||||
'notification_type': 'inbox',
|
||||
'res_partner_id': cls.customers[(msg_idx * 2)].id,
|
||||
}),
|
||||
(0, 0, {
|
||||
'is_read': True,
|
||||
'notification_type': 'email',
|
||||
'notification_status': 'sent',
|
||||
'res_partner_id': cls.customers[(msg_idx * 2) + 1].id,
|
||||
}),
|
||||
],
|
||||
'message_type': 'comment',
|
||||
'model': record._name,
|
||||
'partner_ids': [
|
||||
(4, cls.customers[(msg_idx * 2)].id),
|
||||
(4, cls.customers[record_idx].id),
|
||||
],
|
||||
'reaction_ids': [
|
||||
(0, 0, {
|
||||
'content': 'https://www.odoo.com',
|
||||
'partner_id': cls.customers[(msg_idx * 2) + 1].id
|
||||
}), (0, 0, {
|
||||
'content': 'https://www.example.com',
|
||||
'partner_id': cls.customers[record_idx].id
|
||||
}),
|
||||
],
|
||||
'res_id': record.id,
|
||||
'subject': f'Test Rating {msg_idx}',
|
||||
'subtype_id': comment_subtype_id,
|
||||
'starred_partner_ids': [
|
||||
(4, cls.customers[(msg_idx * 2)].id),
|
||||
(4, cls.customers[(msg_idx * 2) + 1].id),
|
||||
],
|
||||
'tracking_value_ids': [
|
||||
(0, 0, {
|
||||
'field_id': user_id_field.id,
|
||||
'new_value_char': 'new 1',
|
||||
'new_value_integer': record.user_id.id,
|
||||
'old_value_char': 'old 1',
|
||||
'old_value_integer': cls.user_admin.id,
|
||||
}),
|
||||
]
|
||||
}
|
||||
for msg_idx in range(2)
|
||||
for record_idx, record in enumerate(cls.record_ratings)
|
||||
])
|
||||
|
||||
cls.messages_records = [cls.env[message.model].browse(message.res_id) for message in cls.messages_all]
|
||||
# ratings values related to rating-enabled records
|
||||
cls.ratings_all = cls.env['rating.rating'].sudo().create([
|
||||
{
|
||||
'consumed': True,
|
||||
'message_id': message.id,
|
||||
'partner_id': record.customer_id.id,
|
||||
'publisher_comment': 'Comment',
|
||||
'publisher_id': cls.user_admin.partner_id.id,
|
||||
'publisher_datetime': datetime(2023, 5, 15, 10, 30, 5) - timedelta(days=2),
|
||||
'rated_partner_id': record.user_id.partner_id.id,
|
||||
'rating': 4,
|
||||
'res_id': message.res_id,
|
||||
'res_model_id': cls.env['ir.model']._get_id(message.model),
|
||||
}
|
||||
for rating_idx in range(2)
|
||||
for message, record in zip(cls.messages_all, cls.messages_records)
|
||||
])
|
||||
|
||||
def test_assert_initial_values(self):
|
||||
self.assertEqual(len(self.messages_all), 5 * 2)
|
||||
self.assertEqual(len(self.ratings_all), len(self.messages_all) * 2)
|
||||
|
||||
@mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_portal_message_format_norating(self):
|
||||
messages_all = self.messages_all.with_user(self.env.user)
|
||||
|
||||
with self.assertQueryCount(employee=14):
|
||||
# res = messages_all.portal_message_format(options=None)
|
||||
res = messages_all.portal_message_format(options={'rating_include': False})
|
||||
|
||||
comment_subtype = self.env.ref('mail.mt_comment')
|
||||
self.assertEqual(len(res), len(messages_all))
|
||||
for format_res, message, record in zip(res, messages_all, self.messages_records):
|
||||
self.assertEqual(len(format_res['attachment_ids']), 2)
|
||||
self.maxDiff = None
|
||||
self.assertEqual(
|
||||
format_res['attachment_ids'],
|
||||
[
|
||||
{
|
||||
'checksum': message.attachment_ids[0].checksum,
|
||||
'filename': 'Test file 1',
|
||||
'id': message.attachment_ids[0].id,
|
||||
'mimetype': 'text/plain',
|
||||
'name': 'Test file 1',
|
||||
'raw_access_token': message.attachment_ids[0]._get_raw_access_token(),
|
||||
'res_id': record.id,
|
||||
'res_model': record._name,
|
||||
}, {
|
||||
'checksum': message.attachment_ids[1].checksum,
|
||||
'filename': 'Test file 0',
|
||||
'id': message.attachment_ids[1].id,
|
||||
'mimetype': 'text/plain',
|
||||
'name': 'Test file 0',
|
||||
'raw_access_token': message.attachment_ids[1]._get_raw_access_token(),
|
||||
'res_id': record.id,
|
||||
'res_model': record._name,
|
||||
}
|
||||
]
|
||||
)
|
||||
self.assertEqual(format_res["author_id"]["id"], record.customer_id.id)
|
||||
self.assertEqual(format_res["author_id"]["name"], record.customer_id.display_name)
|
||||
self.assertEqual(format_res['author_avatar_url'], f'/web/image/mail.message/{message.id}/author_avatar/50x50')
|
||||
self.assertEqual(format_res['date'], datetime(2023, 5, 15, 10, 30, 5))
|
||||
self.assertEqual(' '.join(format_res['published_date_str'].split()), '05/15/2023 10:30:05 AM')
|
||||
self.assertEqual(format_res['id'], message.id)
|
||||
self.assertFalse(format_res['is_internal'])
|
||||
self.assertFalse(format_res['is_message_subtype_note'])
|
||||
self.assertEqual(format_res['subtype_id'], (comment_subtype.id, comment_subtype.name))
|
||||
# should not be in, not asked
|
||||
self.assertNotIn('rating_id', format_res)
|
||||
self.assertNotIn('rating_stats', format_res)
|
||||
self.assertNotIn('rating_value', format_res)
|
||||
|
||||
@mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_portal_message_format_rating(self):
|
||||
messages_all = self.messages_all.with_user(self.env.user)
|
||||
|
||||
with self.assertQueryCount(employee=28): # sometimes +1
|
||||
res = messages_all.portal_message_format(options={'rating_include': True})
|
||||
|
||||
self.assertEqual(len(res), len(messages_all))
|
||||
for format_res, _message, _record in zip(res, messages_all, self.messages_records):
|
||||
self.assertEqual(format_res['rating_id']['publisher_avatar'], f'/web/image/res.partner/{self.partner_admin.id}/avatar_128/50x50')
|
||||
self.assertEqual(format_res['rating_id']['publisher_comment'], 'Comment')
|
||||
self.assertEqual(format_res['rating_id']['publisher_id'], self.partner_admin.id)
|
||||
self.assertEqual(" ".join(format_res['rating_id']['publisher_datetime'].split()), '05/13/2023 10:30:05 AM')
|
||||
self.assertEqual(format_res['rating_id']['publisher_name'], self.partner_admin.display_name)
|
||||
self.assertDictEqual(
|
||||
format_res['rating_stats'],
|
||||
{'avg': 4.0, 'total': 4, 'percent': {1: 0.0, 2: 0.0, 3: 0.0, 4: 100.0, 5: 0.0}}
|
||||
)
|
||||
self.assertEqual(format_res['rating_value'], 4)
|
||||
|
||||
@mute_logger('odoo.tests', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_portal_message_format_monorecord(self):
|
||||
message = self.messages_all[0].with_user(self.env.user)
|
||||
|
||||
with self.assertQueryCount(employee=19): # randomness: 18+1
|
||||
res = message.portal_message_format(options={'rating_include': True})
|
||||
|
||||
self.assertEqual(len(res), 1)
|
||||
|
||||
@mute_logger("odoo.tests", "odoo.addons.mail.models.mail_mail", "odoo.models.unlink")
|
||||
@users("employee")
|
||||
@warmup
|
||||
def test_portal_attachment_as_author(self):
|
||||
message = self.env["mail.message"].create(
|
||||
{
|
||||
"attachment_ids": [Command.create({"name": "test attachment"})],
|
||||
"author_id": self.user_employee.partner_id.id,
|
||||
}
|
||||
)
|
||||
res = message.portal_message_format()
|
||||
self.assertEqual(
|
||||
res[0]["attachment_ids"][0]["ownership_token"],
|
||||
message.attachment_ids[0]._get_ownership_token(),
|
||||
)
|
||||
|
||||
|
||||
@tagged('rating', 'mail_performance', 'post_install', '-at_install')
|
||||
class TestRatingPerformance(FullBaseMailPerformance):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.RECORD_COUNT = 20
|
||||
|
||||
cls.partners = cls.env['res.partner'].sudo().create([
|
||||
{'name': 'Jean-Luc %s' % (idx), 'email': 'jean-luc-%s@opoo.com' % (idx)}
|
||||
for idx in range(cls.RECORD_COUNT)])
|
||||
|
||||
# create records with 2 ratings to check batch statistics on them
|
||||
responsibles = [cls.user_admin, cls.user_employee, cls.env['res.users']]
|
||||
with cls.mock_push_to_end_point(cls):
|
||||
cls.record_ratings = cls.env['mail.test.rating'].create([{
|
||||
'customer_id': cls.partners[idx].id,
|
||||
'name': f'Test Rating {idx}',
|
||||
'user_id': responsibles[idx % 3].id,
|
||||
} for idx in range(cls.RECORD_COUNT)])
|
||||
rates = [enum % 5 for enum, _rec in enumerate(cls.record_ratings)]
|
||||
# create rating from 1 -> 5 for each record
|
||||
for rate, record in zip(rates, cls.record_ratings, strict=True):
|
||||
record.rating_apply(rate + 1, token=record._rating_get_access_token())
|
||||
# create rating with 4 or 5 (half records)
|
||||
for record in cls.record_ratings[:10]:
|
||||
record.rating_apply(4, token=record._rating_get_access_token())
|
||||
for record in cls.record_ratings[10:]:
|
||||
record.rating_apply(5, token=record._rating_get_access_token())
|
||||
|
||||
def apply_ratings(self, rate):
|
||||
for record in self.record_ratings:
|
||||
access_token = record._rating_get_access_token()
|
||||
record.rating_apply(rate, token=access_token)
|
||||
self.flush_tracking()
|
||||
|
||||
def create_ratings(self, model):
|
||||
self.record_ratings = self.env[model].create([{
|
||||
'customer_id': self.partners[idx].id,
|
||||
'name': 'Test Rating',
|
||||
'user_id': self.user_admin.id,
|
||||
} for idx in range(self.RECORD_COUNT)])
|
||||
self.flush_tracking()
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_api_rating_get_operator(self):
|
||||
user_names = []
|
||||
with self.assertQueryCount(employee=4): # tmf: 4
|
||||
ratings = self.record_ratings.with_env(self.env)
|
||||
for rating in ratings:
|
||||
user_names.append(rating._rating_get_operator().name)
|
||||
expected_names = ['Mitchell Admin', 'Ernest Employee', False] * 6 + ['Mitchell Admin', 'Ernest Employee']
|
||||
for partner_name, expected_name in zip(user_names, expected_names, strict=True):
|
||||
self.assertEqual(partner_name, expected_name)
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_api_rating_get_partner(self):
|
||||
partner_names = []
|
||||
with self.assertQueryCount(employee=3): # tmf: 3
|
||||
ratings = self.record_ratings.with_env(self.env)
|
||||
for rating in ratings:
|
||||
partner_names.append(rating._rating_get_partner().name)
|
||||
for partner_name, expected in zip(partner_names, self.partners, strict=True):
|
||||
self.assertEqual(partner_name, expected.name)
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_get_grades_perfs(self):
|
||||
with self.assertQueryCount(employee=1):
|
||||
ratings = self.record_ratings.with_env(self.env)
|
||||
grades = ratings.rating_get_grades()
|
||||
self.assertDictEqual(grades, {'great': 28, 'okay': 4, 'bad': 8})
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_get_stats_perfs(self):
|
||||
with self.assertQueryCount(employee=1):
|
||||
ratings = self.record_ratings.with_env(self.env)
|
||||
stats = ratings.rating_get_stats()
|
||||
self.assertDictEqual(stats, {'avg': 3.75, 'total': 40, 'percent': {1: 10.0, 2: 10.0, 3: 10.0, 4: 35.0, 5: 35.0}})
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_last_value_perfs(self):
|
||||
with self.assertQueryCount(employee=274): # tmf: 274
|
||||
self.create_ratings('mail.test.rating.thread')
|
||||
|
||||
with self.assertQueryCount(employee=283): # tmf: 283
|
||||
self.apply_ratings(1)
|
||||
|
||||
with self.assertQueryCount(employee=242): # tmf: 242
|
||||
self.apply_ratings(5)
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_last_value_perfs_with_rating_mixin(self):
|
||||
with self.assertQueryCount(employee=317): # tmf: 317
|
||||
self.create_ratings('mail.test.rating')
|
||||
|
||||
with self.assertQueryCount(employee=325): # tmf: 325
|
||||
self.apply_ratings(1)
|
||||
|
||||
with self.assertQueryCount(employee=304): # tmf: 304
|
||||
self.apply_ratings(5)
|
||||
|
||||
with self.assertQueryCount(employee=1):
|
||||
self.record_ratings._compute_rating_last_value()
|
||||
vals = (val == 5 for val in self.record_ratings.mapped('rating_last_value'))
|
||||
self.assertTrue(all(vals), "The last rating is kept.")
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_stat_fields(self):
|
||||
expected_texts = ['ok', 'ok', 'ok', 'top', 'top'] * 2 + ['ok', 'ok', 'top', 'top', 'top'] * 2
|
||||
expected_satis = [50.0, 50.0, 50.0, 100.0, 100.0] * 4
|
||||
with self.assertQueryCount(employee=2):
|
||||
ratings = self.record_ratings.with_env(self.env)
|
||||
for rating, text, satisfaction in zip(ratings, expected_texts, expected_satis, strict=True):
|
||||
self.assertEqual(rating.rating_avg_text, text)
|
||||
self.assertEqual(rating.rating_percentage_satisfaction, satisfaction)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class TestMailThreadInternals(TestMailThreadInternalsCommon):
|
|||
with self.subTest(test_record=test_record):
|
||||
is_portal = test_record._name != 'mail.test.simple'
|
||||
has_customer = test_record._name != 'mail.test.portal.no.partner'
|
||||
partner_fnames = test_record._mail_get_partner_fields()
|
||||
partner_fnames = test_record._mail_get_partner_fields(introspect_fields=False)
|
||||
|
||||
if is_portal:
|
||||
self.assertFalse(
|
||||
|
|
@ -56,7 +56,9 @@ class TestMailThreadInternals(TestMailThreadInternalsCommon):
|
|||
'By default access tokens are False with portal'
|
||||
)
|
||||
|
||||
groups = test_record._notify_get_recipients_groups()
|
||||
groups = test_record._notify_get_recipients_groups(
|
||||
self.env['mail.message'], False,
|
||||
)
|
||||
portal_customer_group = next(
|
||||
(group for group in groups if group[0] == 'portal_customer'),
|
||||
False
|
||||
|
|
|
|||
|
|
@ -23,22 +23,23 @@ class TestMassMailing(TestMailFullCommon):
|
|||
|
||||
# optout records 1 and 2
|
||||
(recipients[1] | recipients[2]).write({'opt_out': True})
|
||||
recipients[1].email_from = f'"Format Me" <{recipients[1].email_from}>'
|
||||
recipients[1].email_from = f'"Format Me" <{recipients[1].email_normalized}>'
|
||||
# blacklist records 3 and 4
|
||||
self.env['mail.blacklist'].create({'email': recipients[3].email_normalized})
|
||||
self.env['mail.blacklist'].create({'email': recipients[4].email_normalized})
|
||||
recipients[3].email_from = f'"Format Me" <{recipients[3].email_from}>'
|
||||
recipients[3].email_from = f'"Format Me" <{recipients[3].email_normalized}>'
|
||||
# have a duplicate email for 9
|
||||
recipients[9].email_from = f'"Format Me" <{recipients[9].email_from}>'
|
||||
recipients[9].email_from = f'"Format Me" <{recipients[9].email_normalized}>'
|
||||
recipient_dup_1 = recipients[9].copy()
|
||||
recipient_dup_1.email_from = f'"Format Me" <{recipient_dup_1.email_from}>'
|
||||
recipient_dup_1.email_from = f'"Format Me" <{recipient_dup_1.email_normalized}>'
|
||||
# have another duplicate for 9, but with multi emails already done
|
||||
recipient_dup_2 = recipients[9].copy()
|
||||
recipient_dup_2.email_from += f'; "TestDupe" <{recipients[8].email_from}>'
|
||||
recipient_dup_2.email_from += f'; "TestDupe" <{recipients[8].email_normalized}>'
|
||||
# have another duplicate for 9, but with multi emails, one is different
|
||||
recipient_dup_3 = recipients[9].copy() # this one will passthrough (best-effort)
|
||||
recipient_dup_3.email_from += '; "TestMulti" <test.multi@test.example.com>'
|
||||
recipient_dup_4 = recipient_dup_2.copy() # this one will be discarded (youpi)
|
||||
|
||||
# have a void mail
|
||||
recipient_void_1 = self.env['mailing.test.optout'].create({'name': 'TestRecord_void_1'})
|
||||
# have a falsy mail
|
||||
|
|
@ -57,71 +58,79 @@ class TestMassMailing(TestMailFullCommon):
|
|||
mailing.action_send_mail()
|
||||
|
||||
for recipient in recipients_all:
|
||||
recipient_info = {
|
||||
'email': recipient.email_normalized,
|
||||
'content': f'Hello {recipient.name}',
|
||||
'mail_values': {
|
||||
'subject': f'Subject {recipient.name}',
|
||||
},
|
||||
}
|
||||
|
||||
# opt-out: cancel (cancel mail)
|
||||
if recipient in recipients[1] | recipients[2]:
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_optout"
|
||||
# blacklisted: cancel (cancel mail)
|
||||
elif recipient in recipients[3] | recipients[4]:
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_bl"
|
||||
# duplicates: cancel (cancel mail)
|
||||
elif recipient in (recipient_dup_1, recipient_dup_2, recipient_dup_4):
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_dup"
|
||||
# void: error (failed mail)
|
||||
elif recipient == recipient_void_1:
|
||||
recipient_info['trace_status'] = 'cancel'
|
||||
recipient_info['failure_type'] = "mail_email_missing"
|
||||
# falsy: error (failed mail)
|
||||
elif recipient == recipient_falsy_1:
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_email_invalid"
|
||||
recipient_info['email'] = recipient.email_from # normalized is False but email should be falsymail
|
||||
else:
|
||||
# multi email -> outgoing email contains all emails
|
||||
with self.subTest(recipient_from=recipient.email_from):
|
||||
recipient_info = {
|
||||
'content': f'Hello {recipient.name}',
|
||||
'email': recipient.email_normalized or '',
|
||||
'email_to_mail': recipient.email_from or '',
|
||||
'email_to_recipients': [[recipient.email_from]],
|
||||
'mail_values': {
|
||||
'subject': f'Subject {recipient.name}',
|
||||
},
|
||||
}
|
||||
# ; transformed into comma
|
||||
if recipient == recipient_dup_2:
|
||||
recipient_info['email_to_mail'] = '"Format Me" <test.record.09@test.example.com>,"TestDupe" <test.record.08@test.example.com>'
|
||||
if recipient == recipient_dup_3:
|
||||
email = self._find_sent_email(self.user_marketing.email_formatted, ['test.record.09@test.example.com', 'test.multi@test.example.com'])
|
||||
recipient_info['email_to_mail'] = '"Format Me" <test.record.09@test.example.com>,"TestMulti" <test.multi@test.example.com>'
|
||||
# multi email -> outgoing email contains all emails
|
||||
recipient_info['email_to_recipients'] = [['"Format Me" <test.record.09@test.example.com>', '"TestMulti" <test.multi@test.example.com>']]
|
||||
if recipient == recipient_dup_4:
|
||||
recipient_info['email_to_mail'] = '"Format Me" <test.record.09@test.example.com>,"TestDupe" <test.record.08@test.example.com>'
|
||||
|
||||
# opt-out: cancel (cancel mail)
|
||||
if recipient in recipients[1] | recipients[2]:
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_optout"
|
||||
# blacklisted: cancel (cancel mail)
|
||||
elif recipient in recipients[3] | recipients[4]:
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_bl"
|
||||
# duplicates: cancel (cancel mail)
|
||||
elif recipient in (recipient_dup_1, recipient_dup_2, recipient_dup_4):
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_dup"
|
||||
# void: cancel (cancel mail)
|
||||
elif recipient == recipient_void_1:
|
||||
recipient_info['trace_status'] = 'cancel'
|
||||
recipient_info['failure_type'] = "mail_email_missing"
|
||||
# falsy: cancel (cancel mail)
|
||||
elif recipient == recipient_falsy_1:
|
||||
recipient_info['trace_status'] = "cancel"
|
||||
recipient_info['failure_type'] = "mail_email_invalid"
|
||||
recipient_info['email'] = recipient.email_from # normalized is False but email should be falsymail
|
||||
else:
|
||||
email = self._find_sent_email(self.user_marketing.email_formatted, [recipient.email_normalized])
|
||||
# preview correctly integrated rendered qweb
|
||||
self.assertIn(
|
||||
'Hi %s :)' % recipient.name,
|
||||
email['body'])
|
||||
# rendered unsubscribe
|
||||
self.assertIn(
|
||||
'%s/mailing/%s/confirm_unsubscribe' % (mailing.get_base_url(), mailing.id),
|
||||
email['body'])
|
||||
unsubscribe_href = self._get_href_from_anchor_id(email['body'], "url6")
|
||||
unsubscribe_url = werkzeug.urls.url_parse(unsubscribe_href)
|
||||
unsubscribe_params = unsubscribe_url.decode_query().to_dict(flat=True)
|
||||
self.assertEqual(int(unsubscribe_params['res_id']), recipient.id)
|
||||
self.assertEqual(unsubscribe_params['email'], recipient.email_normalized)
|
||||
self.assertEqual(
|
||||
mailing._unsubscribe_token(unsubscribe_params['res_id'], (unsubscribe_params['email'])),
|
||||
unsubscribe_params['token']
|
||||
)
|
||||
# rendered view
|
||||
self.assertIn(
|
||||
'%s/mailing/%s/view' % (mailing.get_base_url(), mailing.id),
|
||||
email['body'])
|
||||
view_href = self._get_href_from_anchor_id(email['body'], "url6")
|
||||
view_url = werkzeug.urls.url_parse(view_href)
|
||||
view_params = view_url.decode_query().to_dict(flat=True)
|
||||
self.assertEqual(int(view_params['res_id']), recipient.id)
|
||||
self.assertEqual(view_params['email'], recipient.email_normalized)
|
||||
self.assertEqual(
|
||||
mailing._unsubscribe_token(view_params['res_id'], (view_params['email'])),
|
||||
view_params['token']
|
||||
)
|
||||
email = self._find_sent_email(self.user_marketing.email_formatted, recipient_info['email_to_recipients'][0])
|
||||
# preview correctly integrated rendered qweb
|
||||
self.assertIn(
|
||||
'Hi %s :)' % recipient.name,
|
||||
email['body'])
|
||||
# rendered unsubscribe
|
||||
self.assertIn(
|
||||
'%s/mailing/%s/confirm_unsubscribe' % (mailing.get_base_url(), mailing.id),
|
||||
email['body'])
|
||||
unsubscribe_href = self._get_href_from_anchor_id(email['body'], "url6")
|
||||
unsubscribe_url = werkzeug.urls.url_parse(unsubscribe_href)
|
||||
unsubscribe_params = unsubscribe_url.decode_query().to_dict(flat=True)
|
||||
self.assertEqual(int(unsubscribe_params['document_id']), recipient.id)
|
||||
self.assertEqual(unsubscribe_params['email'], recipient.email_normalized)
|
||||
self.assertEqual(
|
||||
mailing._generate_mailing_recipient_token(unsubscribe_params['document_id'], (unsubscribe_params['email'])),
|
||||
unsubscribe_params['hash_token']
|
||||
)
|
||||
# rendered view
|
||||
self.assertIn(
|
||||
'%s/mailing/%s/view' % (mailing.get_base_url(), mailing.id),
|
||||
email['body'])
|
||||
view_href = self._get_href_from_anchor_id(email['body'], "url6")
|
||||
view_url = werkzeug.urls.url_parse(view_href)
|
||||
view_params = view_url.decode_query().to_dict(flat=True)
|
||||
self.assertEqual(int(view_params['document_id']), recipient.id)
|
||||
self.assertEqual(view_params['email'], recipient.email_normalized)
|
||||
self.assertEqual(
|
||||
mailing._generate_mailing_recipient_token(view_params['document_id'], (view_params['email'])),
|
||||
view_params['hash_token']
|
||||
)
|
||||
|
||||
self.assertMailTraces(
|
||||
[recipient_info], mailing, recipient,
|
||||
|
|
|
|||
|
|
@ -2,28 +2,27 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug.urls import url_parse, url_decode, url_encode
|
||||
import json
|
||||
|
||||
from odoo import http
|
||||
from odoo.addons.auth_signup.models.res_partner import ResPartner
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.addons.test_mail_full.tests.common import TestMailFullCommon
|
||||
from odoo.addons.test_mail_sms.tests.common import TestSMSRecipients
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests import tagged, users
|
||||
from odoo.tests.common import HttpCase
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tools import html_escape, mute_logger
|
||||
|
||||
|
||||
@tagged('portal')
|
||||
class TestPortal(HttpCase, TestMailFullCommon, TestSMSRecipients):
|
||||
class TestPortal(TestMailFullCommon, TestSMSRecipients):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPortal, self).setUp()
|
||||
super().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()
|
||||
|
||||
|
||||
|
|
@ -36,12 +35,22 @@ class TestPortalControllers(TestPortal):
|
|||
'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}')
|
||||
token = self.record_portal.access_token
|
||||
formatted_record = mail_record.portal_message_format(options={"token": token})[0]
|
||||
self.assertEqual(
|
||||
formatted_record.get("author_avatar_url"),
|
||||
f"/mail/avatar/mail.message/{mail_record.id}/author_avatar/50x50?access_token={token}",
|
||||
)
|
||||
response = self.url_open(
|
||||
f"/mail/avatar/mail.message/{mail_record.id}/author_avatar/50x50?access_token={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')
|
||||
self.assertEqual(response.headers.get('Content-Type'), 'image/svg+xml; charset=utf-8')
|
||||
self.assertRegex(response.headers.get('Content-Disposition', ''), r'mail_message-\d+-author_avatar\.svg')
|
||||
|
||||
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
|
||||
placeholder_response = self.url_open(
|
||||
f'/mail/avatar/mail.message/{mail_record.id}/author_avatar/50x50?access_token={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')
|
||||
|
|
@ -53,107 +62,71 @@ class TestPortalControllers(TestPortal):
|
|||
|
||||
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(
|
||||
post_url = f"{self.record_portal.get_base_url()}/mail/message/post"
|
||||
pid = self.partner_2.id
|
||||
_hash = self.record_portal._sign_token(pid)
|
||||
res = self.url_open(
|
||||
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,
|
||||
'thread_model': self.record_portal._name,
|
||||
'thread_id': self.record_portal.id,
|
||||
'post_data': {'body': "Test"},
|
||||
'hash': _hash,
|
||||
'pid': pid,
|
||||
},
|
||||
},
|
||||
)
|
||||
res.raise_for_status()
|
||||
self.assertNotIn("error", res.json())
|
||||
message = self.record_portal.message_ids[0]
|
||||
formatted_message = message.portal_message_format(options={"hash": _hash, "pid": pid})[0]
|
||||
self.assertEqual(
|
||||
formatted_message.get("author_avatar_url"),
|
||||
f"/mail/avatar/mail.message/{message.id}/author_avatar/50x50?_hash={_hash}&pid={pid}",
|
||||
)
|
||||
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}')
|
||||
f"/mail/avatar/mail.message/{message.id}/author_avatar/50x50?_hash={_hash}&pid={pid}"
|
||||
)
|
||||
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')
|
||||
self.assertEqual(response.headers.get('Content-Type'), 'image/svg+xml; charset=utf-8')
|
||||
self.assertRegex(response.headers.get('Content-Disposition', ''), r'mail_message-\d+-author_avatar\.svg')
|
||||
|
||||
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
|
||||
f'/mail/avatar/mail.message/{message.id}/author_avatar/50x50?_hash={_hash + "a"}&pid={pid}'
|
||||
) # 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"
|
||||
post_url = f"{self.record_portal.get_base_url()}/mail/message/post"
|
||||
|
||||
# test as not logged
|
||||
self.opener.post(
|
||||
self.url_open(
|
||||
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,
|
||||
'thread_model': self.record_portal._name,
|
||||
'thread_id': self.record_portal.id,
|
||||
'post_data': {'body': "Test"},
|
||||
'token': self.record_portal.access_token,
|
||||
'hash': self.record_portal._sign_token(self.partner_2.id),
|
||||
'pid': self.partner_2.id,
|
||||
},
|
||||
},
|
||||
)
|
||||
message = self.record_portal.message_ids[0]
|
||||
# Only messages from the current user not OdooBot
|
||||
messages = self.record_portal.message_ids.filtered(lambda msg: msg.author_id == self.partner_2)
|
||||
|
||||
self.assertIn('Test', message.body)
|
||||
self.assertEqual(message.author_id, self.partner_2)
|
||||
self.assertIn('Test', messages[0].body)
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', 'portal', 'mail_controller')
|
||||
class TestPortalFlow(TestMailFullCommon, HttpCase):
|
||||
class TestPortalFlow(MailCommon, HttpCase):
|
||||
""" Test shared links, mail/view links and redirection (backend, customer
|
||||
portal or frontend for specific addons). """
|
||||
|
||||
|
|
@ -164,7 +137,6 @@ class TestPortalFlow(TestMailFullCommon, HttpCase):
|
|||
'country_id': cls.env.ref('base.fr').id,
|
||||
'email': 'mdelvaux34@example.com',
|
||||
'lang': 'en_US',
|
||||
'mobile': '+33639982325',
|
||||
'name': 'Mathias Delvaux',
|
||||
'phone': '+33353011823',
|
||||
})
|
||||
|
|
@ -198,6 +170,16 @@ class TestPortalFlow(TestMailFullCommon, HttpCase):
|
|||
})
|
||||
cls._create_portal_user()
|
||||
|
||||
# The test relies on `record_access_url` to check the validity of mails being sent,
|
||||
# however, when auth_signup is installed, a new token is generated each time the url
|
||||
# is being requested.
|
||||
# By removing the time-based hashing from this function we can ensure the stability of
|
||||
# the url during the tests.
|
||||
def patched_generate_signup_token(self, *_, **__):
|
||||
self.ensure_one()
|
||||
return str([self.id, self._get_login_date(), self.signup_type])
|
||||
cls.classPatch(ResPartner, '_generate_signup_token', patched_generate_signup_token)
|
||||
|
||||
# prepare access URLs on self to ease tests
|
||||
# ------------------------------------------------------------
|
||||
base_url = cls.record_portal.get_base_url()
|
||||
|
|
@ -220,7 +202,9 @@ class TestPortalFlow(TestMailFullCommon, HttpCase):
|
|||
cls.record_url_no_model = f'{cls.record_portal.get_base_url()}/mail/view?model=this.should.not.exists&res_id=1'
|
||||
|
||||
# find portal + auth data url
|
||||
for group_name, group_func, group_data in cls.record_portal.sudo()._notify_get_recipients_groups(False):
|
||||
for group_name, group_func, group_data in cls.record_portal.sudo()._notify_get_recipients_groups(
|
||||
cls.env['mail.message'], False
|
||||
):
|
||||
if group_name == 'portal_customer' and group_func(cls.customer):
|
||||
cls.record_portal_url_auth = group_data['button_access']['url']
|
||||
break
|
||||
|
|
@ -244,25 +228,25 @@ class TestPortalFlow(TestMailFullCommon, HttpCase):
|
|||
cls.portal_web_url = f'{base_url}/my/test_portal/{cls.record_portal.id}'
|
||||
cls.portal_web_url_with_token = f'{base_url}/my/test_portal/{cls.record_portal.id}?{url_encode({"access_token": cls.record_portal.access_token, "pid": cls.customer.id, "hash": cls.record_portal_hash}, sort=True)}'
|
||||
cls.public_act_url_share = f'{base_url}/test_portal/public_type/{cls.record_public_act_url.id}'
|
||||
cls.internal_backend_local_url = f'/web#{url_encode({"model": cls.record_internal._name, "id": cls.record_internal.id, "active_id": cls.record_internal.id, "cids": cls.company_admin.id}, sort=True)}'
|
||||
cls.portal_backend_local_url = f'/web#{url_encode({"model": cls.record_portal._name, "id": cls.record_portal.id, "active_id": cls.record_portal.id, "cids": cls.company_admin.id}, sort=True)}'
|
||||
cls.read_backend_local_url = f'/web#{url_encode({"model": cls.record_read._name, "id": cls.record_read.id, "active_id": cls.record_read.id, "cids": cls.company_admin.id}, sort=True)}'
|
||||
cls.public_act_url_backend_local_url = f'/web#{url_encode({"model": cls.record_public_act_url._name, "id": cls.record_public_act_url.id, "active_id": cls.record_public_act_url.id, "cids": cls.company_admin.id}, sort=True)}'
|
||||
cls.discuss_local_url = '/web#action=mail.action_discuss'
|
||||
cls.internal_backend_local_url = f'/odoo/{cls.record_internal._name}/{cls.record_internal.id}'
|
||||
cls.portal_backend_local_url = f'/odoo/{cls.record_portal._name}/{cls.record_portal.id}'
|
||||
cls.read_backend_local_url = f'/odoo/{cls.record_read._name}/{cls.record_read.id}'
|
||||
cls.public_act_url_backend_local_url = f'/odoo/{cls.record_public_act_url._name}/{cls.record_public_act_url.id}'
|
||||
cls.discuss_local_url = '/odoo/action-mail.action_discuss'
|
||||
|
||||
def test_assert_initial_data(self):
|
||||
""" Test some initial values. Test that record_access_url is a valid URL
|
||||
""" Test some initial values. Test that record_portal_url_auth is a valid URL
|
||||
to view the record_portal and that record_access_url_wrong_token only differs
|
||||
from record_access_url by a different access_token. """
|
||||
self.record_internal.with_user(self.user_employee).check_access_rule('read')
|
||||
self.record_portal.with_user(self.user_employee).check_access_rule('read')
|
||||
self.record_read.with_user(self.user_employee).check_access_rule('read')
|
||||
from record_portal_url_auth by a different access_token. """
|
||||
self.record_internal.with_user(self.user_employee).check_access('read')
|
||||
self.record_portal.with_user(self.user_employee).check_access('read')
|
||||
self.record_read.with_user(self.user_employee).check_access('read')
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.record_internal.with_user(self.user_portal).check_access_rights('read')
|
||||
self.record_internal.with_user(self.user_portal).check_access('read')
|
||||
with self.assertRaises(AccessError):
|
||||
self.record_portal.with_user(self.user_portal).check_access_rights('read')
|
||||
self.record_read.with_user(self.user_portal).check_access_rights('read')
|
||||
self.record_portal.with_user(self.user_portal).check_access('read')
|
||||
self.record_read.with_user(self.user_portal).check_access('read')
|
||||
|
||||
self.assertNotEqual(self.record_portal_url_auth, self.record_portal_url_auth_wrong_token)
|
||||
url_params = []
|
||||
|
|
@ -331,7 +315,7 @@ class TestPortalFlow(TestMailFullCommon, HttpCase):
|
|||
# std url, read record -> redirect to my with parameters being record portal action parameters (???)
|
||||
(
|
||||
'Access record (no customer portal)', self.record_read_url_base,
|
||||
f'{self.test_base_url}/my#{url_encode({"model": self.record_read._name, "id": self.record_read.id, "active_id": self.record_read.id, "cids": self.company_admin.id}, sort=True)}',
|
||||
f'{self.test_base_url}/my?{url_encode({"subpath": f"{self.record_read._name}/{self.record_read.id}"})}',
|
||||
),
|
||||
# std url, no access to record -> redirect to my
|
||||
(
|
||||
|
|
@ -445,6 +429,63 @@ class TestPortalFlow(TestMailFullCommon, HttpCase):
|
|||
'Failed with %s - %s' % (model, res_id)
|
||||
)
|
||||
|
||||
def assert_URL(self, url, expected_path, expected_fragment_params=None, expected_query=None):
|
||||
"""Asserts that the URL has the expected path and if set, the expected fragment parameters and query."""
|
||||
parsed_url = url_parse(url)
|
||||
fragment_params = url_decode(parsed_url.fragment)
|
||||
self.assertEqual(parsed_url.path, expected_path)
|
||||
if expected_fragment_params:
|
||||
for key, expected_value in expected_fragment_params.items():
|
||||
self.assertEqual(fragment_params.get(key), expected_value,
|
||||
f'Expected: "{key}={expected_value}" (for path: {expected_path})')
|
||||
if expected_query:
|
||||
self.assertEqual(expected_query, parsed_url.query,
|
||||
f'Expected: query="{expected_query}" (for path: {expected_path})')
|
||||
|
||||
@users('employee')
|
||||
def test_send_message_to_customer(self):
|
||||
"""Same as test_send_message_to_customer_using_template but without a template."""
|
||||
composer = self.env['mail.compose.message'].with_context(
|
||||
self._get_mail_composer_web_context(
|
||||
self.record_portal,
|
||||
default_email_layout_xmlid='mail.mail_notification_layout_with_responsible_signature',
|
||||
)
|
||||
).create({
|
||||
'body': '<p>Hello Mathias Delvaux, your quotation is ready for review.</p>',
|
||||
'partner_ids': self.customer.ids,
|
||||
'subject': 'Your Quotation "a white table"',
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
composer._action_send_mail()
|
||||
|
||||
self.assertEqual(len(self._mails), 1)
|
||||
self.assertIn(f'"{html_escape(self.record_portal_url_auth)}"', self._mails[0].get('body'))
|
||||
# Check that the template is not used (not the same subject)
|
||||
self.assertEqual('Your Quotation "a white table"', self._mails[0].get('subject'))
|
||||
self.assertIn('Hello Mathias Delvaux', self._mails[0].get('body'))
|
||||
|
||||
@users('employee')
|
||||
def test_send_message_to_customer_using_template(self):
|
||||
"""Send a mail to a customer without an account and check that it contains a link to view the record.
|
||||
|
||||
Other tests below check that that same link has the correct behavior.
|
||||
This test follows the common use case by using a template while the next send the mail without a template."""
|
||||
composer = self.env['mail.compose.message'].with_context(
|
||||
self._get_mail_composer_web_context(
|
||||
self.record_portal,
|
||||
default_email_layout_xmlid='mail.mail_notification_layout_with_responsible_signature',
|
||||
default_template_id=self.mail_template.id,
|
||||
)
|
||||
).create({})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
composer._action_send_mail()
|
||||
|
||||
self.assertEqual(len(self._mails), 1)
|
||||
self.assertIn(f'"{html_escape(self.record_portal_url_auth)}"', self._mails[0].get('body'))
|
||||
self.assertEqual(f'Your quotation "{self.record_portal.name}"', self._mails[0].get('subject')) # Check that the template is used
|
||||
|
||||
|
||||
@tagged('portal')
|
||||
class TestPortalMixin(TestPortal):
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ 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
|
||||
from odoo.tests.common import HttpCase, users, warmup
|
||||
from odoo.tests.common import users, warmup
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestRatingCommon(TestMailFullCommon, TestSMSRecipients):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestRatingCommon, cls).setUpClass()
|
||||
|
|
@ -22,88 +23,104 @@ class TestRatingCommon(TestMailFullCommon, TestSMSRecipients):
|
|||
'name': 'Test Rating',
|
||||
'user_id': cls.user_admin.id,
|
||||
})
|
||||
cls.record_rating_thread = cls.env['mail.test.rating.thread'].create({
|
||||
'customer_id': cls.partner_1.id,
|
||||
'name': 'Test rating without rating mixin',
|
||||
'user_id': cls.user_admin.id,
|
||||
})
|
||||
|
||||
|
||||
@tagged('rating')
|
||||
class TestRatingFlow(TestRatingCommon):
|
||||
|
||||
def test_initial_values(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
self.assertFalse(record_rating.rating_ids)
|
||||
self.assertEqual(record_rating.message_partner_ids, self.partner_admin)
|
||||
self.assertEqual(len(record_rating.message_ids), 1)
|
||||
for record_rating in [self.record_rating, self.record_rating_thread]:
|
||||
record_rating = record_rating.with_env(self.env)
|
||||
self.assertFalse(record_rating.rating_ids)
|
||||
self.assertEqual(record_rating.message_partner_ids, self.partner_admin)
|
||||
self.assertEqual(len(record_rating.message_ids), 1)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_rating_prepare(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
for record_rating, desc in ((self.record_rating, 'With rating mixin'),
|
||||
(self.record_rating_thread, 'Without rating mixin')):
|
||||
with self.subTest(desc):
|
||||
record_rating = record_rating.with_env(self.env)
|
||||
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
|
||||
# check rating creation
|
||||
rating = record_rating.rating_ids
|
||||
self.assertEqual(rating.access_token, access_token)
|
||||
self.assertFalse(rating.consumed)
|
||||
self.assertFalse(rating.is_internal)
|
||||
self.assertEqual(rating.partner_id, self.partner_1)
|
||||
self.assertEqual(rating.rated_partner_id, self.user_admin.partner_id)
|
||||
self.assertFalse(rating.rating)
|
||||
# check rating creation
|
||||
rating = record_rating.rating_ids
|
||||
self.assertEqual(rating.access_token, access_token)
|
||||
self.assertFalse(rating.consumed)
|
||||
self.assertFalse(rating.is_internal)
|
||||
self.assertEqual(rating.partner_id, self.partner_1)
|
||||
self.assertEqual(rating.rated_partner_id, self.user_admin.partner_id)
|
||||
self.assertFalse(rating.rating)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_rating_rating_apply(self):
|
||||
record_rating = self.record_rating.with_env(self.env)
|
||||
record_messages = record_rating.message_ids
|
||||
for record_rating, expected_subtype, is_rating_mixin_test in (
|
||||
(self.record_rating_thread, self.env.ref('mail.mt_comment'), False),
|
||||
(self.record_rating, self.env.ref('test_mail_full.mt_mail_test_rating_rating_done'), True),
|
||||
):
|
||||
with self.subTest('With rating mixin' if is_rating_mixin_test else 'Without rating mixin'):
|
||||
record_rating = record_rating.with_env(self.env)
|
||||
record_messages = record_rating.message_ids
|
||||
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
# prepare rating token
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
|
||||
# simulate an email click: notification should be delayed
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(5, token=access_token, feedback='Top Feedback', notify_delay_send=True)
|
||||
message = record_rating.message_ids[0]
|
||||
rating = record_rating.rating_ids
|
||||
# simulate an email click: notification should be delayed
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(5, token=access_token, feedback='Top Feedback', notify_delay_send=True)
|
||||
message = record_rating.message_ids[0]
|
||||
rating = record_rating.rating_ids
|
||||
|
||||
# check posted message
|
||||
self.assertEqual(record_rating.message_ids, record_messages + message)
|
||||
self.assertIn('Top Feedback', message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_5.png', message.body)
|
||||
self.assertEqual(message.author_id, self.partner_1)
|
||||
self.assertEqual(message.rating_ids, rating)
|
||||
self.assertFalse(message.notified_partner_ids)
|
||||
self.assertEqual(message.subtype_id, self.env.ref('test_mail_full.mt_mail_test_rating_rating_done'))
|
||||
# check posted message
|
||||
self.assertEqual(record_rating.message_ids, record_messages + message)
|
||||
self.assertIn('Top Feedback', message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_5.png', message.body)
|
||||
self.assertEqual(message.author_id, self.partner_1)
|
||||
self.assertEqual(message.rating_ids, rating)
|
||||
self.assertFalse(message.notified_partner_ids)
|
||||
self.assertEqual(message.subtype_id, expected_subtype)
|
||||
|
||||
# check rating update
|
||||
self.assertTrue(rating.consumed)
|
||||
self.assertEqual(rating.feedback, 'Top Feedback')
|
||||
self.assertEqual(rating.message_id, message)
|
||||
self.assertEqual(rating.rating, 5)
|
||||
self.assertEqual(record_rating.rating_last_value, 5)
|
||||
# check rating update
|
||||
self.assertTrue(rating.consumed)
|
||||
self.assertEqual(rating.feedback, 'Top Feedback')
|
||||
self.assertEqual(rating.message_id, message)
|
||||
self.assertEqual(rating.rating, 5)
|
||||
if is_rating_mixin_test:
|
||||
self.assertEqual(record_rating.rating_last_value, 5)
|
||||
|
||||
# give a feedback: send notifications (notify_delay_send set to False)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(1, token=access_token, feedback='Bad Feedback')
|
||||
# give a feedback: send notifications (notify_delay_send set to False)
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
|
||||
record_rating.rating_apply(1, token=access_token, feedback='Bad Feedback')
|
||||
|
||||
# check posted message: message is updated
|
||||
update_message = record_rating.message_ids[0]
|
||||
self.assertEqual(update_message, message, 'Should update first message')
|
||||
self.assertEqual(record_rating.message_ids, record_messages + update_message)
|
||||
self.assertIn('Bad Feedback', update_message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_1.png', update_message.body)
|
||||
self.assertEqual(update_message.author_id, self.partner_1)
|
||||
self.assertEqual(update_message.rating_ids, rating)
|
||||
self.assertEqual(update_message.notified_partner_ids, self.partner_admin)
|
||||
self.assertEqual(update_message.subtype_id, self.env.ref("test_mail_full.mt_mail_test_rating_rating_done"))
|
||||
# check posted message: message is updated
|
||||
update_message = record_rating.message_ids[0]
|
||||
self.assertEqual(update_message, message, 'Should update first message')
|
||||
self.assertEqual(record_rating.message_ids, record_messages + update_message)
|
||||
self.assertIn('Bad Feedback', update_message.body)
|
||||
self.assertIn('/rating/static/src/img/rating_1.png', update_message.body)
|
||||
self.assertEqual(update_message.author_id, self.partner_1)
|
||||
self.assertEqual(update_message.rating_ids, rating)
|
||||
self.assertEqual(update_message.notified_partner_ids, self.partner_admin)
|
||||
self.assertEqual(update_message.subtype_id, expected_subtype)
|
||||
|
||||
# check rating update
|
||||
new_rating = record_rating.rating_ids
|
||||
self.assertEqual(new_rating, rating, 'Should update first rating')
|
||||
self.assertTrue(new_rating.consumed)
|
||||
self.assertEqual(new_rating.feedback, 'Bad Feedback')
|
||||
self.assertEqual(new_rating.message_id, update_message)
|
||||
self.assertEqual(new_rating.rating, 1)
|
||||
self.assertEqual(record_rating.rating_last_value, 1)
|
||||
# check rating update
|
||||
new_rating = record_rating.rating_ids
|
||||
self.assertEqual(new_rating, rating, 'Should update first rating')
|
||||
self.assertTrue(new_rating.consumed)
|
||||
self.assertEqual(new_rating.feedback, 'Bad Feedback')
|
||||
self.assertEqual(new_rating.message_id, update_message)
|
||||
self.assertEqual(new_rating.rating, 1)
|
||||
if is_rating_mixin_test:
|
||||
self.assertEqual(record_rating.rating_last_value, 1)
|
||||
|
||||
|
||||
@tagged('rating')
|
||||
|
|
@ -131,107 +148,141 @@ class TestRatingMixin(TestRatingCommon):
|
|||
self.assertEqual(record_rating.rating_avg, 3, "The average should be equal to 3")
|
||||
|
||||
|
||||
@tagged('rating', 'mail_performance', 'post_install', '-at_install')
|
||||
class TestRatingPerformance(TestRatingCommon):
|
||||
|
||||
@users('employee')
|
||||
@warmup
|
||||
def test_rating_last_value_perfs(self):
|
||||
RECORD_COUNT = 100
|
||||
partners = self.env['res.partner'].sudo().create([
|
||||
{'name': 'Jean-Luc %s' % (idx), 'email': 'jean-luc-%s@opoo.com' % (idx)} for idx in range(RECORD_COUNT)])
|
||||
|
||||
with self.assertQueryCount(employee=1516): # tmf 1516 / com 5510
|
||||
record_ratings = self.env['mail.test.rating'].create([{
|
||||
'customer_id': partners[idx].id,
|
||||
'name': 'Test Rating',
|
||||
'user_id': self.user_admin.id,
|
||||
} for idx in range(RECORD_COUNT)])
|
||||
self.flush_tracking()
|
||||
|
||||
with self.assertQueryCount(employee=2004): # tmf 2004
|
||||
for record in record_ratings:
|
||||
access_token = record._rating_get_access_token()
|
||||
record.rating_apply(1, token=access_token)
|
||||
self.flush_tracking()
|
||||
|
||||
with self.assertQueryCount(employee=2003): # tmf 2003
|
||||
for record in record_ratings:
|
||||
access_token = record._rating_get_access_token()
|
||||
record.rating_apply(5, token=access_token)
|
||||
self.flush_tracking()
|
||||
|
||||
with self.assertQueryCount(employee=1):
|
||||
record_ratings._compute_rating_last_value()
|
||||
vals = [val == 5 for val in record_ratings.mapped('rating_last_value')]
|
||||
self.assertTrue(all(vals), "The last rating is kept.")
|
||||
|
||||
|
||||
@tagged('rating')
|
||||
class TestRatingRoutes(HttpCase, TestRatingCommon):
|
||||
@tagged("rating", "rating_portal")
|
||||
class TestRatingRoutes(TestRatingCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._create_portal_user()
|
||||
|
||||
def test_open_rating_route(self):
|
||||
"""
|
||||
16.0 + expected behavior
|
||||
1) Clicking on the smiley image triggers the /rate/<string:token>/<int:rate>
|
||||
route should not update the rating of the record but simply redirect
|
||||
to the feedback form
|
||||
2) Customer interacts with webpage and submits FORM. Triggers /rate/<string:token>/submit_feedback
|
||||
route. Should update the rating of the record with the data in the POST request
|
||||
"""
|
||||
self.authenticate(None, None) # set up session for public user
|
||||
access_token = self.record_rating._rating_get_access_token()
|
||||
for record_rating, is_rating_mixin_test in ((self.record_rating_thread, False),
|
||||
(self.record_rating, True)):
|
||||
with self.subTest('With rating mixin' if is_rating_mixin_test else 'Without rating mixin'):
|
||||
"""
|
||||
16.0 + expected behavior
|
||||
1) Clicking on the smiley image triggers the /rate/<string:token>/<int:rate>
|
||||
route should not update the rating of the record but simply redirect
|
||||
to the feedback form
|
||||
2) Customer interacts with webpage and submits FORM. Triggers /rate/<string:token>/submit_feedback
|
||||
route. Should update the rating of the record with the data in the POST request
|
||||
"""
|
||||
self.authenticate(None, None) # set up session for public user
|
||||
access_token = record_rating._rating_get_access_token()
|
||||
|
||||
# First round of clicking the URL and then submitting FORM data
|
||||
response_click_one = self.url_open(f"/rate/{access_token}/5")
|
||||
response_click_one.raise_for_status()
|
||||
# First round of clicking the URL and then submitting FORM data
|
||||
response_click_one = self.url_open(f"/rate/{access_token}/5")
|
||||
response_click_one.raise_for_status()
|
||||
|
||||
# there should be a form to post to validate the feedback and avoid one-click anyway
|
||||
forms = lxml.html.fromstring(response_click_one.content).xpath('//form')
|
||||
self.assertEqual(forms[0].get('method'), 'post')
|
||||
self.assertEqual(forms[0].get('action', ''), f'/rate/{access_token}/submit_feedback')
|
||||
# there should be a form to post to validate the feedback and avoid one-click anyway
|
||||
forms = lxml.html.fromstring(response_click_one.content).xpath('//form')
|
||||
matching_rate_form = next((form for form in forms if form.get("action", "").startswith("/rate")), None)
|
||||
self.assertEqual(matching_rate_form.get('method'), 'post')
|
||||
self.assertEqual(matching_rate_form.get('action', ''), f'/rate/{access_token}/submit_feedback')
|
||||
|
||||
# rating should not change, i.e. default values
|
||||
rating = self.record_rating.rating_ids
|
||||
self.assertFalse(rating.consumed)
|
||||
self.assertEqual(rating.rating, 0)
|
||||
self.assertFalse(rating.feedback)
|
||||
self.assertEqual(self.record_rating.rating_last_value, 0)
|
||||
# rating should not change, i.e. default values
|
||||
rating = record_rating.rating_ids
|
||||
self.assertFalse(rating.consumed)
|
||||
self.assertEqual(rating.rating, 0)
|
||||
self.assertFalse(rating.feedback)
|
||||
if is_rating_mixin_test:
|
||||
self.assertEqual(record_rating.rating_last_value, 0)
|
||||
|
||||
response_submit_one = self.url_open(
|
||||
f"/rate/{access_token}/submit_feedback",
|
||||
data={
|
||||
"rate": 5,
|
||||
"csrf_token": http.Request.csrf_token(self),
|
||||
"feedback": "good",
|
||||
response_submit_one = self.url_open(
|
||||
f"/rate/{access_token}/submit_feedback",
|
||||
data={
|
||||
"rate": 5,
|
||||
"csrf_token": http.Request.csrf_token(self),
|
||||
"feedback": "good",
|
||||
}
|
||||
)
|
||||
response_submit_one.raise_for_status()
|
||||
|
||||
rating_post_submit_one = record_rating.rating_ids
|
||||
self.assertTrue(rating_post_submit_one.consumed)
|
||||
self.assertEqual(rating_post_submit_one.rating, 5)
|
||||
self.assertEqual(rating_post_submit_one.feedback, "good")
|
||||
if is_rating_mixin_test:
|
||||
self.assertEqual(record_rating.rating_last_value, 5)
|
||||
|
||||
# Second round of clicking the URL and then submitting FORM data
|
||||
response_click_two = self.url_open(f"/rate/{access_token}/1")
|
||||
response_click_two.raise_for_status()
|
||||
if is_rating_mixin_test:
|
||||
self.assertEqual(record_rating.rating_last_value, 5) # should not be updated to 1
|
||||
|
||||
# check returned form
|
||||
forms = lxml.html.fromstring(response_click_two.content).xpath('//form')
|
||||
matching_rate_form = next((form for form in forms if form.get("action", "").startswith("/rate")), None)
|
||||
self.assertEqual(matching_rate_form.get('method'), 'post')
|
||||
self.assertEqual(matching_rate_form.get('action', ''), f'/rate/{access_token}/submit_feedback')
|
||||
|
||||
response_submit_two = self.url_open(
|
||||
f"/rate/{access_token}/submit_feedback",
|
||||
data={
|
||||
"rate": 1,
|
||||
"csrf_token": http.Request.csrf_token(self),
|
||||
"feedback": "bad job"
|
||||
}
|
||||
)
|
||||
response_submit_two.raise_for_status()
|
||||
|
||||
rating_post_submit_second = record_rating.rating_ids
|
||||
self.assertTrue(rating_post_submit_second.consumed)
|
||||
self.assertEqual(rating_post_submit_second.rating, 1)
|
||||
self.assertEqual(rating_post_submit_second.feedback, "bad job")
|
||||
if is_rating_mixin_test:
|
||||
self.assertEqual(record_rating.rating_last_value, 1)
|
||||
|
||||
def test_portal_user_can_post_message_with_rating(self):
|
||||
"""Test portal user can post a message with a rating on a thread with
|
||||
_mail_post_access as read. In this case, sudo() is not necessary for
|
||||
message_post itself, but it is necessary for adding the rating. This
|
||||
tests covers the rating part is properly allowed."""
|
||||
record_rating = self.env["mail.test.rating.thread.read"].create(
|
||||
{
|
||||
"customer_id": self.partner_1.id,
|
||||
"name": "Test read access post + rating",
|
||||
"user_id": self.user_admin.id,
|
||||
}
|
||||
)
|
||||
response_submit_one.raise_for_status()
|
||||
|
||||
rating_post_submit_one = self.record_rating.rating_ids
|
||||
self.assertTrue(rating_post_submit_one.consumed)
|
||||
self.assertEqual(rating_post_submit_one.rating, 5)
|
||||
self.assertEqual(rating_post_submit_one.feedback, "good")
|
||||
self.assertEqual(self.record_rating.rating_last_value, 5)
|
||||
|
||||
# Second round of clicking the URL and then submitting FORM data
|
||||
response_click_two = self.url_open(f"/rate/{access_token}/1")
|
||||
response_click_two.raise_for_status()
|
||||
self.assertEqual(self.record_rating.rating_last_value, 5) # should not be updated to 1
|
||||
|
||||
# check returned form
|
||||
forms = lxml.html.fromstring(response_click_two.content).xpath('//form')
|
||||
self.assertEqual(forms[0].get('method'), 'post')
|
||||
self.assertEqual(forms[0].get('action', ''), f'/rate/{access_token}/submit_feedback')
|
||||
|
||||
response_submit_two = self.url_open(f"/rate/{access_token}/submit_feedback",
|
||||
data={"rate": 1,
|
||||
"csrf_token": http.Request.csrf_token(self),
|
||||
"feedback": "bad job"})
|
||||
response_submit_two.raise_for_status()
|
||||
|
||||
rating_post_submit_second = self.record_rating.rating_ids
|
||||
self.assertTrue(rating_post_submit_second.consumed)
|
||||
self.assertEqual(rating_post_submit_second.rating, 1)
|
||||
self.assertEqual(rating_post_submit_second.feedback, "bad job")
|
||||
self.assertEqual(self.record_rating.rating_last_value, 1)
|
||||
# from model
|
||||
message = record_rating.with_user(self.user_portal).message_post(
|
||||
body="Not bad",
|
||||
message_type="comment",
|
||||
rating_value=3,
|
||||
subtype_xmlid="mail.mt_comment",
|
||||
)
|
||||
rating = message.sudo().rating_id
|
||||
self.assertEqual(rating.rating, 3, "rating was properly set")
|
||||
# stealing attempt from another user
|
||||
message2 = record_rating.message_post(
|
||||
body="Attempt to steal rating with another user",
|
||||
message_type="comment",
|
||||
rating_id=rating.id,
|
||||
subtype_xmlid="mail.mt_comment",
|
||||
)
|
||||
self.assertEqual(message.sudo().rating_id, rating, "rating was not removed from m1")
|
||||
self.assertFalse(message2.rating_id, "rating was not added to m2")
|
||||
# from controller
|
||||
self.authenticate("portal_test", "portal_test")
|
||||
res = self.make_jsonrpc_request(
|
||||
"/mail/message/post",
|
||||
{
|
||||
"post_data": {
|
||||
"body": "Good service",
|
||||
"message_type": "comment",
|
||||
"rating_value": 5,
|
||||
"subtype_xmlid": "mail.mt_comment",
|
||||
},
|
||||
"thread_id": record_rating.id,
|
||||
"thread_model": "mail.test.rating.thread.read",
|
||||
},
|
||||
)
|
||||
message = next(
|
||||
m for m in res["store_data"]["mail.message"] if m["id"] == res["message_id"]
|
||||
)
|
||||
rating = next(
|
||||
r for r in res["store_data"]["rating.rating"] if r["id"] == message["rating_id"]
|
||||
)
|
||||
self.assertEqual(rating["rating"], 5)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ class TestResUsers(TestMailFullCommon):
|
|||
cls.portal_user = mail_new_test_user(
|
||||
cls.env,
|
||||
login='portal_user',
|
||||
mobile='+32 494 12 34 56',
|
||||
phone='+32 494 12 34 89',
|
||||
password='password',
|
||||
name='Portal User',
|
||||
|
|
@ -24,7 +23,6 @@ class TestResUsers(TestMailFullCommon):
|
|||
cls.portal_user_2 = mail_new_test_user(
|
||||
cls.env,
|
||||
login='portal_user_2',
|
||||
mobile='+32 494 12 34 22',
|
||||
phone='invalid phone',
|
||||
password='password',
|
||||
name='Portal User 2',
|
||||
|
|
@ -32,6 +30,16 @@ class TestResUsers(TestMailFullCommon):
|
|||
groups='base.group_portal',
|
||||
)
|
||||
|
||||
cls.portal_user_3 = mail_new_test_user(
|
||||
cls.env,
|
||||
login='portal_user_3',
|
||||
phone='+32 494 12 34 22',
|
||||
password='password',
|
||||
name='Portal User 3',
|
||||
email='portal_3@test.example.com',
|
||||
groups='base.group_portal',
|
||||
)
|
||||
|
||||
# Remove existing blacklisted email / phone (they will be sanitized, so we avoid to sanitize them here)
|
||||
cls.env['mail.blacklist'].search([]).unlink()
|
||||
cls.env['phone.blacklist'].search([]).unlink()
|
||||
|
|
@ -40,22 +48,24 @@ class TestResUsers(TestMailFullCommon):
|
|||
"""Test that the email and the phone are blacklisted
|
||||
when a portal user deactivate his own account.
|
||||
"""
|
||||
(self.portal_user | self.portal_user_2)._deactivate_portal_user(request_blacklist=True)
|
||||
(self.portal_user | self.portal_user_2 | self.portal_user_3)._deactivate_portal_user(request_blacklist=True)
|
||||
|
||||
self.assertFalse(self.portal_user.active, 'Should have archived the user')
|
||||
self.assertFalse(self.portal_user.partner_id.active, 'Should have archived the partner')
|
||||
self.assertFalse(self.portal_user_2.active, 'Should have archived the user')
|
||||
self.assertFalse(self.portal_user_2.partner_id.active, 'Should have archived the partner')
|
||||
self.assertFalse(self.portal_user_3.active, 'Should have archived the user')
|
||||
self.assertFalse(self.portal_user_3.partner_id.active, 'Should have archived the partner')
|
||||
|
||||
blacklist = self.env['mail.blacklist'].search([
|
||||
('email', 'in', ('portal@test.example.com', 'portal_2@test.example.com')),
|
||||
('email', 'in', ('portal@test.example.com', 'portal_2@test.example.com', 'portal_3@test.example.com')),
|
||||
])
|
||||
self.assertEqual(len(blacklist), 2, 'Should have blacklisted the users email')
|
||||
self.assertEqual(len(blacklist), 3, 'Should have blacklisted the users email')
|
||||
|
||||
blacklists = self.env['phone.blacklist'].search([
|
||||
('number', 'in', ('+32494123489', '+32494123456', '+32494123422')),
|
||||
('number', 'in', ('+32494123489', '+32494123422')),
|
||||
])
|
||||
self.assertEqual(len(blacklists), 3, 'Should have blacklisted the user phone and mobile')
|
||||
self.assertEqual(len(blacklists), 2, 'Should have blacklisted the user phone')
|
||||
|
||||
blacklist = self.env['phone.blacklist'].search([('number', '=', 'invalid phone')])
|
||||
self.assertFalse(blacklist, 'Should have skipped invalid phone')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from odoo import tests
|
||||
from odoo.addons.test_mail_full.tests.test_portal import TestPortal
|
||||
|
||||
|
||||
@tests.common.tagged("post_install", "-at_install")
|
||||
class TestUIPortal(TestPortal):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.env["mail.message"].create(
|
||||
{
|
||||
"author_id": self.user_employee.partner_id.id,
|
||||
"body": "Test Message",
|
||||
"model": self.record_portal._name,
|
||||
"res_id": self.record_portal.id,
|
||||
"subtype_id": self.ref("mail.mt_comment"),
|
||||
}
|
||||
)
|
||||
|
||||
def test_star_message(self):
|
||||
self.start_tour(
|
||||
f"/my/test_portal_records/{self.record_portal.id}",
|
||||
"star_message_tour",
|
||||
login=self.user_employee.login,
|
||||
)
|
||||
|
||||
def test_no_copy_link_for_non_readable_portal_record(self):
|
||||
# mail.test.portal has read access only for base.group_user
|
||||
self.start_tour(
|
||||
f"/my/test_portal_records/{self.record_portal.id}?{urlencode({'token': self.record_portal.access_token})}",
|
||||
"portal_no_copy_link_tour",
|
||||
login=None,
|
||||
)
|
||||
|
||||
def test_copy_link_for_readable_portal_record(self):
|
||||
# mail.test.portal has read access only for base.group_user
|
||||
self.start_tour(
|
||||
f"/my/test_portal_records/{self.record_portal.id}?{urlencode({'token': self.record_portal.access_token})}",
|
||||
"portal_copy_link_tour",
|
||||
login=self.user_employee.login,
|
||||
)
|
||||
|
||||
def test_load_more(self):
|
||||
self.env["mail.message"].create(
|
||||
[
|
||||
{
|
||||
"author_id": self.user_employee.partner_id.id,
|
||||
"body": f"Test Message {i + 1}",
|
||||
"model": self.record_portal._name,
|
||||
"res_id": self.record_portal.id,
|
||||
"subtype_id": self.ref("mail.mt_comment"),
|
||||
}
|
||||
for i in range(30)
|
||||
]
|
||||
)
|
||||
self.start_tour(
|
||||
f"/my/test_portal_records/{self.record_portal.id}",
|
||||
"load_more_tour",
|
||||
login=self.user_employee.login,
|
||||
)
|
||||
|
||||
def test_message_actions_without_login(self):
|
||||
self.start_tour(
|
||||
f"/my/test_portal_records/{self.record_portal.id}?token={self.record_portal._portal_ensure_token()}",
|
||||
"message_actions_tour",
|
||||
)
|
||||
|
||||
def test_rating_record_portal(self):
|
||||
record_rating = self.env["mail.test.rating"].create({"name": "Test rating record"})
|
||||
# To check if there is no message with rating, there is no rating cards feature.
|
||||
record_rating.message_post(
|
||||
body="Message without rating",
|
||||
message_type="comment",
|
||||
subtype_xmlid="mail.mt_comment",
|
||||
)
|
||||
self.start_tour(
|
||||
f"/my/test_portal_rating_records/{record_rating.id}?display_rating=True&token={record_rating._portal_ensure_token()}",
|
||||
"portal_rating_tour"
|
||||
)
|
||||
|
||||
def test_display_rating_portal(self):
|
||||
record_rating = self.env["mail.test.rating"].create({"name": "Test rating record"})
|
||||
record_rating.message_post(
|
||||
body="Message with rating",
|
||||
message_type="comment",
|
||||
rating_value="5",
|
||||
subtype_xmlid="mail.mt_comment",
|
||||
)
|
||||
self.start_tour(
|
||||
f"/my/test_portal_rating_records/{record_rating.id}?display_rating=True&token={record_rating._portal_ensure_token()}",
|
||||
"portal_display_rating_tour",
|
||||
)
|
||||
self.start_tour(
|
||||
f"/my/test_portal_rating_records/{record_rating.id}?display_rating=False&token={record_rating._portal_ensure_token()}",
|
||||
"portal_not_display_rating_tour",
|
||||
)
|
||||
|
||||
def test_composer_actions_portal(self):
|
||||
self.start_tour(
|
||||
f"/my/test_portal_records/{self.record_portal.id}",
|
||||
"portal_composer_actions_tour_internal_user",
|
||||
login=self.user_employee.login,
|
||||
)
|
||||
self.start_tour(
|
||||
f"/my/test_portal_records/{self.record_portal.id}?token={self.record_portal._portal_ensure_token()}",
|
||||
"portal_composer_actions_tour_portal_user",
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="test_portal_template" name="Test Portal" inherit_id="portal.portal_sidebar" primary="True">
|
||||
<xpath expr="//div[hasclass('o_portal_sidebar')]" position="inside">
|
||||
<!-- chatter -->
|
||||
<div>
|
||||
<h3>Communication history</h3>
|
||||
<t t-call="portal.message_thread"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue